Skip to content

Commit 1101d40

Browse files
authored
fix(u32): enforce live constraint budget (#173)
1 parent 9a266f8 commit 1101d40

18 files changed

Lines changed: 757 additions & 23 deletions

.claude/commands/vibeguard/gc.md

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,17 +34,23 @@ tags: [vibeguard, gc, cleanup, maintenance]
3434
- Delete worktrees that have been inactive for more than 7 days and have no unmerged changes
3535
- Only warnings about unmerged changes, listing those that need to be handled manually
3636

37-
3. **Code Junk Scanning**
37+
3. **Rule Budget GC**
38+
- Run `bash ${VIBEGUARD_DIR}/scripts/gc/gc-rule-budget.sh <project directory>`
39+
- Count the effective U-32 task constraint budget across global, project, skill, and path-scoped rule sources
40+
- List low-frequency rule candidates that should be downgraded to a skill, hook, or path-scoped child file
41+
42+
4. **Code Junk Scanning**
3843
- Run `bash ${VIBEGUARD_DIR}/guards/universal/check_code_slop.sh <project directory>`
3944
- Detect 5 types of AI garbage patterns: null exception handling, legacy debugging code, expired TODO, dead code marking, overlong files
4045
- Output structured reports
4146

42-
4. **Summary Report**
47+
5. **Summary Report**
4348
```
4449
VibeGuard GC Report
4550
==================
4651
Log: Archive XX items, current XX items
4752
Worktree: Clean X, warn X
53+
Rule budget: X effective constraints, X downgrade candidates
4854
Code garbage: X problems
4955
- Null exception handling: X
5056
- Legacy debug code: X
@@ -53,13 +59,14 @@ tags: [vibeguard, gc, cleanup, maintenance]
5359
- Extra long files: X
5460
```
5561

56-
5. **Recommended fix**
62+
6. **Recommended fix**
5763
- Provide repair suggestions for each type of garbage problem
5864
- Can be repaired item by item after user confirmation
5965
- Run `/vibeguard:check` to verify after fixing
6066

6167
**Reference**
6268
- Log archive: `scripts/gc/gc-logs.sh`
6369
- Worktree cleanup: `scripts/gc/gc-worktrees.sh`
70+
- Rule budget GC: `scripts/gc/gc-rule-budget.sh`
6471
- Code garbage detection: `guards/universal/check_code_slop.sh`
6572
<!-- VIBEGUARD:GC:END -->

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88
## [Unreleased]
99

1010
### Added
11+
- U-32 live constraint budget tooling: `count_active_constraints.sh`, `scripts/constraints/count_active_constraints.py`, and GC downgrade-candidate reporting
1112
- CI doc command path validator (`scripts/ci/validate-doc-command-paths.sh`) to catch stale `~/vibeguard/...` shell examples
1213
- `setup.sh --check` now emits a structured rollup (counts of OK/INFO/WARN/FAIL/BROKEN/MISSING and a final verdict line) so a single broken probe is not lost in 40+ healthy rows
1314
- `setup.sh --check --quiet` filters output to problems-only plus the rollup, for fast triage in long install logs

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ python3 ~/vibeguard/guards/python/check_dead_shims.py /path # dea
189189
| `/vibeguard:learn` | Generate guard rules from errors / extract Skills from discoveries |
190190
| `/vibeguard:interview` | Deep requirements interview → SPEC.md |
191191
| `/vibeguard:exec-plan` | Long-running task execution plan, cross-session resume |
192-
| `/vibeguard:gc` | Garbage collection (log archival + worktree cleanup + code slop scan) |
192+
| `/vibeguard:gc` | Garbage collection (logs + worktrees + rule budget + code slop scan) |
193193
| `/vibeguard:stats` | Hook trigger statistics |
194194

195195
**Routing Contract**

docs/CLAUDE.md.example

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ interview → preflight → coding → check → review → learn → stats
204204
| `/vibeguard:build-fix` | Build error fix |
205205
| `/vibeguard:learn` | Generate new guards from mistakes |
206206
| `/vibeguard:exec-plan` | Long-term task execution plan (cross-session recovery) |
207-
| `/vibeguard:gc` | Garbage collection (log archiving + worktree cleaning + code garbage scanning) |
207+
| `/vibeguard:gc` | Garbage collection (log archiving + worktree cleaning + rule budget + code garbage scanning) |
208208
| `/vibeguard:stats` | Hook trigger statistics |
209209

210210
---

docs/README_CN.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ python3 ~/vibeguard/guards/python/check_dead_shims.py /path
155155
| `/vibeguard:learn` | 从错误中生成 guard/rule,或提炼 Skill |
156156
| `/vibeguard:interview` | 深度需求访谈,输出 SPEC.md |
157157
| `/vibeguard:exec-plan` | 长任务执行计划,支持跨会话恢复 |
158-
| `/vibeguard:gc` | 垃圾回收(日志归档 + worktree 清理 + code slop 扫描) |
158+
| `/vibeguard:gc` | 垃圾回收(日志归档 + worktree 清理 + 规则预算 + code slop 扫描) |
159159
| `/vibeguard:stats` | hook 触发统计 |
160160

161161
快捷别名:`/vg:pf` `/vg:gc` `/vg:ck` `/vg:lrn`

docs/rule-reference.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ Canonical source of truth: `rules/claude-rules/`
5353
| U-29 | Error-driven downgrade paths must be observable at error level | Strict | If an error causes user-visible missing data or incorrect output, you must log it at `error` level or raise it. |
5454
| U-30 | Cross-boundary Pydantic models must use `extra="allow"` | Strict | Any Pydantic model that receives external or cross-boundary data must set `extra="allow"` so `model_validate()` does not silently drop un... |
5555
| U-31 | Cache keys must include code version | Strict | When builder or generation logic changes, old cache entries must invalidate automatically. |
56-
| U-32 | Rule overload threshold + absolute-language detection | Strict | If one rule file contains more than 30 active constraints, raise an overload warning. |
56+
| U-32 | Rule overload threshold + absolute-language detection | Strict | Keep the effective constraint set for a single agent task at 15 or fewer items. |
5757
| U-33 | Code search defaults to glob/grep; vector DB requires written justification | Strict | For agent code retrieval, plain glob/grep driven by the model has empirically beaten vector indexes in production. |
5858

5959
---

hooks/CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ AI coded agent hooks script, automatically triggered before and after the operat
1616
| `post-edit-guard.sh` | PostToolUse(Edit) | Detect quality problems after editing: unwrap, console.log, hard-coded path, Go error discard, oversized diff, repeated editing of the same file (churn), W-15 consecutive same-file edit loop. | unsupported |
1717
| `post-write-guard.sh` | PostToolUse(Write) | Detect duplicate definitions and files with the same name after creating a new file. | unsupported |
1818
| `analysis-paralysis-guard.sh` | PostToolUse(Read|Glob|Grep) | Detect excessive exploration without progress and prompt the agent to act. | unsupported |
19+
| `count_active_constraints.sh` | SessionStart | Count effective task constraints loaded into agent context and enforce the U-32 live-context budget in strict profile. | unsupported |
1920
| `post-build-check.sh` | PostToolUse(Edit/Write) | Automatically run the build check corresponding to the language after editing. | native |
2021
| `skills-loader.sh` | Manual optional | Optional first read prompt script; not registered to hooks by default. | unsupported |
2122
| `stop-guard.sh` | Stop | Verify access control before completion and check for uncommitted source code changes. | native |

hooks/count_active_constraints.sh

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
#!/usr/bin/env bash
2+
# VibeGuard SessionStart/UserPromptSubmit Hook — U-32 live constraint budget
3+
#
4+
# Counts the effective constraints that can enter the current agent context.
5+
# >15 emits an advisory, >30 becomes a hard block when strict mode is enabled.
6+
7+
set -euo pipefail
8+
9+
HOOK_DIR="$(cd "$(dirname "$0")" && pwd)"
10+
source "${HOOK_DIR}/log.sh"
11+
source "${HOOK_DIR}/circuit-breaker.sh"
12+
vg_start_timer
13+
14+
# CI guard: the budget is a human-agent context signal, not a CI failure source.
15+
vg_is_ci && exit 0
16+
17+
INPUT=$(cat 2>/dev/null || true)
18+
19+
REPO_DIR="$(cd "${HOOK_DIR}/.." && pwd)"
20+
COUNTER="${REPO_DIR}/scripts/constraints/count_active_constraints.py"
21+
if [[ ! -f "${COUNTER}" ]]; then
22+
REPO_PATH_FILE="${HOME}/.vibeguard/repo-path"
23+
if [[ -f "${REPO_PATH_FILE}" ]]; then
24+
REPO_DIR=$(<"${REPO_PATH_FILE}")
25+
COUNTER="${REPO_DIR}/scripts/constraints/count_active_constraints.py"
26+
fi
27+
fi
28+
29+
if [[ ! -f "${COUNTER}" ]]; then
30+
vg_log "count-active-constraints" "SessionStart" "warn" "counter script missing" "${COUNTER}"
31+
exit 0
32+
fi
33+
34+
PROJECT_ROOT="${VIBEGUARD_PROJECT_ROOT:-}"
35+
if [[ -z "${PROJECT_ROOT}" ]]; then
36+
PROJECT_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
37+
fi
38+
39+
HOOK_EVENT=$(printf '%s' "${INPUT}" | vg_json_field "hook_event_name" 2>/dev/null || true)
40+
HOOK_EVENT="${HOOK_EVENT:-SessionStart}"
41+
42+
COUNTER_ARGS=(--root "${PROJECT_ROOT}" --home "${HOME}" --json)
43+
44+
if [[ -n "${VIBEGUARD_TASK_PATHS:-}" ]]; then
45+
IFS=',' read -r -a _vg_task_paths <<< "${VIBEGUARD_TASK_PATHS}"
46+
for _vg_task_path in "${_vg_task_paths[@]}"; do
47+
[[ -n "${_vg_task_path}" ]] && COUNTER_ARGS+=(--task-path "${_vg_task_path}")
48+
done
49+
fi
50+
51+
if [[ -n "${VIBEGUARD_ACTIVE_SKILLS:-}" ]]; then
52+
IFS=',' read -r -a _vg_skills <<< "${VIBEGUARD_ACTIVE_SKILLS}"
53+
for _vg_skill in "${_vg_skills[@]}"; do
54+
[[ -n "${_vg_skill}" ]] && COUNTER_ARGS+=(--skill "${_vg_skill}")
55+
done
56+
fi
57+
58+
REPORT_JSON=$(python3 "${COUNTER}" "${COUNTER_ARGS[@]}" 2>/dev/null || true)
59+
if [[ -z "${REPORT_JSON}" ]]; then
60+
vg_log "count-active-constraints" "${HOOK_EVENT}" "warn" "constraint counter failed" "${PROJECT_ROOT}"
61+
exit 0
62+
fi
63+
64+
read -r STATUS TOTAL WARN_THRESHOLD BLOCK_THRESHOLD SUMMARY < <(
65+
python3 -c '
66+
import json, sys
67+
data = json.load(sys.stdin)
68+
status = data.get("status", "ok")
69+
total = data.get("total", 0)
70+
warn = data.get("warn_threshold", 15)
71+
block = data.get("block_threshold", 30)
72+
sources = sorted(data.get("sources", []), key=lambda item: item.get("count", 0), reverse=True)[:3]
73+
summary = "; ".join(
74+
"{count} {kind} {path}".format(
75+
count=item.get("count", 0),
76+
kind=item.get("kind", "?"),
77+
path=item.get("path", ""),
78+
)
79+
for item in sources
80+
)
81+
print(status, total, warn, block, summary)
82+
' <<< "${REPORT_JSON}"
83+
)
84+
85+
if [[ "${STATUS}" == "ok" ]]; then
86+
vg_log "count-active-constraints" "${HOOK_EVENT}" "pass" "constraints=${TOTAL}" "${SUMMARY}"
87+
exit 0
88+
fi
89+
90+
MESSAGE="VIBEGUARD U-32 ${STATUS}: effective task constraints=${TOTAL} (warn>${WARN_THRESHOLD}, block>${BLOCK_THRESHOLD}). Split low-frequency rules into path-scoped files, skills, or hooks before adding more persistent instructions. Top sources: ${SUMMARY}"
91+
92+
if [[ "${STATUS}" == "block" ]]; then
93+
vg_log "count-active-constraints" "${HOOK_EVENT}" "block" "constraints=${TOTAL}" "${SUMMARY}"
94+
if [[ "${VIBEGUARD_U32_STRICT:-1}" == "1" ]]; then
95+
printf '%s\n' "[BLOCKED] ${MESSAGE}" >&2
96+
exit 2
97+
fi
98+
else
99+
vg_log "count-active-constraints" "${HOOK_EVENT}" "warn" "constraints=${TOTAL}" "${SUMMARY}"
100+
fi
101+
102+
VG_HOOK_EVENT="${HOOK_EVENT}" VG_MESSAGE="${MESSAGE}" python3 -c '
103+
import json, os
104+
event = os.environ.get("VG_HOOK_EVENT", "SessionStart")
105+
message = os.environ.get("VG_MESSAGE", "")
106+
print(json.dumps({
107+
"hookSpecificOutput": {
108+
"hookEventName": event,
109+
"additionalContext": message,
110+
}
111+
}, ensure_ascii=False))
112+
'

hooks/manifest.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,21 @@
130130
},
131131
"codex": {"enabled": false, "support": "unsupported", "reason": "Codex runtime scope uses Bash approvals and Stop hooks only"}
132132
},
133+
{
134+
"name": "count-active-constraints",
135+
"script": "count_active_constraints.sh",
136+
"kind": "hook",
137+
"trigger": "SessionStart",
138+
"responsibilities": "Count effective task constraints loaded into agent context and enforce the U-32 live-context budget in strict profile.",
139+
"decision_types": ["pass", "warn", "block"],
140+
"claude": {
141+
"enabled": true,
142+
"event": "SessionStart",
143+
"matchers": [null],
144+
"profiles": ["strict"]
145+
},
146+
"codex": {"enabled": false, "support": "unsupported", "reason": "Codex runtime scope uses Bash approvals and Stop hooks only"}
147+
},
133148
{
134149
"name": "post-build-check",
135150
"script": "post-build-check.sh",

rules/claude-rules/common/coding-style.md

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -97,29 +97,35 @@ When you declare framework components such as configs, traits, persistence layer
9797
- GC receives `project_root` but never propagates it to child tasks, causing functional downgrade.
9898

9999
## U-32: Rule overload threshold + absolute-language detection (strict)
100-
If one rule file contains more than 30 active constraints, raise an overload warning. When a rule uses absolute language such as "always", "never", or "must", it also needs a downgrade path so the system does not create an illusion of control.
100+
Keep the effective constraint set for a single agent task at 15 or fewer items. If the live task context exceeds 15 constraints, warn and split lower-frequency material into path-scoped child files, skills, hooks, or verify scripts. If it exceeds 30 constraints, block and require decomposition before continuing. When a rule uses absolute language such as "always", "never", or "must", it also needs a downgrade path so the system does not create an illusion of control.
101101

102-
**Sources** (three-source convergence, 2026-04-16):
102+
**Sources**:
103+
- arXiv 2605.06445 (Constraint Decay, 2026-05-09 RSS scout): across 80 greenfield and 20 feature tasks over 8 web frameworks, stronger agents lost about 30 assertion-pass-rate points as structured constraints accumulated; data-layer defects were the dominant failure mode.
103104
- Addy Osmani, "How to Write a Good Spec": the curse of instructions. Ten rules can be obeyed less reliably than five.
104105
- Anthropic Claude Code Best Practices: a bloated `CLAUDE.md` causes Claude to ignore the instructions that actually matter.
105106
- Martin Fowler, "Context Engineering": illusion of control is an anti-pattern. LLMs are probabilistic systems, so absolute language creates false guarantees.
106107

107108
**Mechanical checks**:
108-
1. If a rule file contains more than 30 entries, recommend decomposing it into path-scoped child files (for example, only load Rust rules for `*.rs`).
109-
2. If a rule uses absolute phrasing such as "ensure X", "never do Y", or "must be 100%", attach both:
109+
1. Count the actual task-loaded constraint set: global memory files, project `AGENTS.md`/`CLAUDE.md`, active skill files, and path-scoped native rules that match the current task files.
110+
2. If the live task context contains more than 15 effective constraints, emit a U-32 warning and recommend moving lower-frequency material to path-scoped child files, skills, hooks, or verify scripts.
111+
3. If the live task context contains more than 30 effective constraints, block the task until the constraint set is decomposed.
112+
4. If a rule file contains more than 30 entries, recommend decomposing it into path-scoped child files (for example, only load Rust rules for `*.rs`).
113+
5. If a rule uses absolute phrasing such as "ensure X", "never do Y", or "must be 100%", attach both:
110114
- A downgrade path: "If X is not feasible, fall back to Y and mark it stale."
111115
- An observability hook: a verification command or guard script that proves whether the rule is actually being followed.
112-
3. If a global `CLAUDE.md` or `AGENTS.md` file grows beyond 100 lines, move material into skills or path-scoped rule files.
116+
6. If a global `CLAUDE.md` or `AGENTS.md` file grows beyond 100 lines, move material into skills or path-scoped rule files.
117+
7. Run `bash hooks/count_active_constraints.sh` through the strict hook profile, or run `python3 scripts/constraints/count_active_constraints.py --root . --include-canonical-rules --gc-report` manually, to inspect the current budget and downgrade candidates.
113118

114119
**Repair order**:
115-
1. Audit the current rule set and annotate trigger frequency for each rule from access logs.
116-
2. Candidate low-frequency rules (zero hits in the last 30 days) for removal or demotion into a skill.
117-
3. Verify whether high-frequency rules use absolute language without a downgrade path.
118-
4. Split large files by language or domain (`common/`, `rust/`, `python/`, `security/`).
120+
1. Count the current task's effective constraints before adding any new persistent instruction.
121+
2. Audit the current rule set and annotate trigger frequency for each rule from access logs.
122+
3. Candidate low-frequency rules (zero hits in the last 30 days) for removal or demotion into a skill.
123+
4. Verify whether high-frequency rules use absolute language without a downgrade path.
124+
5. Split large files by language or domain (`common/`, `rust/`, `python/`, `security/`) and attach path frontmatter where possible.
119125

120126
**Additional checks**:
121-
4. Before adding a persistent rule, first confirm it is a high-frequency, stable, cross-task constraint; otherwise prefer a skill, hook, or verify script.
122-
5. Long workflow templates, one-off playbooks, and low-frequency knowledge should not live permanently in `CLAUDE.md` or `AGENTS.md`; convert them into an index plus on-demand documents.
127+
1. Before adding a persistent rule, first confirm it is a high-frequency, stable, cross-task constraint; otherwise prefer a skill, hook, or verify script.
128+
2. Long workflow templates, one-off playbooks, and low-frequency knowledge should not live permanently in `CLAUDE.md` or `AGENTS.md`; convert them into an index plus on-demand documents.
123129

124130
**Anti-patterns**:
125131
- A single file accumulates 50+ rules and expects all of them to remain active at once.

0 commit comments

Comments
 (0)