@event4u/agent-config 9.22.0 → 9.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +69 -256
- package/README.md +1 -1
- package/dist/agent-src/commands/optimize/deep.md +13 -0
- package/dist/agent-src/contexts/execution/cheap-question-mechanics.md +12 -2
- package/dist/agent-src/contexts/execution/mandated-lines.md +180 -0
- package/dist/agent-src/rules/context-hygiene.md +17 -0
- package/dist/agent-src/rules/downstream-changes.md +26 -0
- package/dist/agent-src/rules/think-before-action.md +25 -0
- package/dist/agent-src/rules/token-budget-discipline.md +46 -3
- package/dist/agent-src/rules/token-efficiency.md +13 -0
- package/dist/agent-src/rules/ui-audit-gate.md +33 -2
- package/dist/agent-src/skills/adversarial-review/SKILL.md +18 -4
- package/dist/agent-src/skills/decision-review/SKILL.md +49 -0
- package/dist/agent-src/skills/judge-artifact-completeness/SKILL.md +5 -1
- package/dist/agent-src/skills/judge-bug-hunter/SKILL.md +16 -0
- package/dist/agent-src/skills/judge-synthesis/SKILL.md +14 -1
- package/dist/agent-src/skills/rule-writing/SKILL.md +33 -0
- package/dist/agent-src/skills/skill-improvement-pipeline/SKILL.md +31 -0
- package/dist/agent-src/skills/skill-reviewer/SKILL.md +1 -1
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/cli/registry.js +1 -0
- package/dist/cli/registry.js.map +1 -1
- package/dist/cli-delegate/chunk-DVRS2KVR.js +4602 -0
- package/dist/cli-delegate/{chunk-ELMFOQPW.js → chunk-SJQMJI7M.js} +8 -4597
- package/dist/cli-delegate/cmd_export.js +2 -1
- package/dist/cli-delegate/cmd_prune.js +2 -1
- package/dist/cli-delegate/cmd_settings_migrate.js +2 -1
- package/dist/cli-delegate/cmd_settings_set.js +3370 -0
- package/dist/cli-delegate/cmd_sync.js +2 -1
- package/dist/cli-delegate/cmd_uninstall.js +2 -1
- package/dist/cli-delegate/cmd_validate.js +2 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +17 -17
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +2 -2
- package/dist/discovery/trust-report.md +1 -1
- package/dist/discovery/workspaces.json +2 -2
- package/dist/hooks/dispatch.js +79 -1
- package/dist/mcp/registry-manifest.json +2 -2
- package/dist/mcp/server.json +2 -2
- package/dist/scripts/condense.js +47 -2
- package/dist/scripts/condense.js.map +1 -1
- package/dist/server/routes/settings.js +82 -0
- package/dist/server/routes/settings.js.map +1 -1
- package/dist/shared/settingsClasses.js +191 -0
- package/dist/shared/settingsClasses.js.map +1 -0
- package/docs/contracts/settings-classes.md +342 -0
- package/docs/guidelines/agent-infra/artifact-drafting-protocol-mechanics.md +23 -2
- package/docs/guidelines/agent-infra/false-green.md +187 -0
- package/docs/guidelines/agent-infra/gate-authoring.md +177 -0
- package/package.json +3 -1
- package/src/config/ci-local-parity.yml +12 -0
- package/src/config/evaluator-budgets.json +4 -4
- package/src/config/gate-coverage.yml +54 -12
- package/src/config/gate-violation-baselines.json +6 -0
- package/src/config/pack-size-budget.json +4 -3
- package/src/scripts/_cli/cmd_settings_set.ts +509 -0
- package/src/scripts/_dispatch.bash +16 -0
- package/src/scripts/_lib/gate_ledger.ts +295 -0
- package/src/scripts/_lib/gate_result.ts +101 -0
- package/src/scripts/_lib/gate_self_test.ts +129 -0
- package/src/scripts/_lib/measured_render.ts +97 -0
- package/src/scripts/_lib/ratchet_base_ref.ts +373 -0
- package/src/scripts/check_gate_completeness.ts +244 -0
- package/src/scripts/check_gate_coverage.ts +35 -6
- package/src/scripts/check_references.ts +125 -15
- package/src/scripts/check_review_dispositions.ts +14 -1
- package/src/scripts/check_suppression_hygiene.ts +503 -0
- package/src/scripts/condense.ts +47 -2
- package/src/scripts/hook_manifest.yaml +23 -0
- package/src/scripts/hooks/dispatch_hook.ts +65 -1
- package/src/scripts/hooks/host_semantics.ts +140 -0
- package/src/scripts/lint_framework_leakage.ts +147 -15
- package/src/scripts/lint_mandated_lines.ts +339 -0
- package/src/scripts/lint_roadmap_complexity.ts +102 -11
- package/src/scripts/lint_roadmap_family_cap.ts +191 -0
- package/src/scripts/lint_settings_classes.ts +397 -0
- package/src/scripts/lint_token_budget_discipline.ts +109 -1
- package/src/scripts/report_imperative_density.ts +229 -0
- package/src/scripts/verify_physical_move.ts +10 -1
- package/src/server/io/yamlIO.ts +173 -0
- package/src/shared/settingsClasses.ts +235 -0
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# False Green — the ways a passing result can be wrong here
|
|
2
|
+
|
|
3
|
+
> Nine failure modes, each with the detection command that distinguishes "found nothing" from "looked at nothing". Every one is drawn from this repository's own history, not from a generic checklist.
|
|
4
|
+
|
|
5
|
+
_Origin: `road-to-skill-ecosystem-gate-integrity` Phase 3. The recurring shape is not that a gate reports a wrong finding — it is that a gate reports **no** finding for a reason unrelated to the property it guards, and no output distinguishes the two cases._
|
|
6
|
+
|
|
7
|
+
## The catalogue
|
|
8
|
+
|
|
9
|
+
### 1. Dead scan root
|
|
10
|
+
|
|
11
|
+
The gate walks a directory that moved or was deleted. Zero units, zero findings,
|
|
12
|
+
exit 0, green checkmark. Fourteen gates in this tree were in this state
|
|
13
|
+
simultaneously after a container move; their own output said `0 file(s) scanned`
|
|
14
|
+
and nothing asserted on the number.
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
./scripts-run src/scripts/<gate> | grep -E '^scanned: ' # 0, or absent entirely
|
|
18
|
+
./scripts-run src/scripts/sweep_dead_scan_roots
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
**Structural fix:** `assertScanned` from `_lib/scan_scope.ts`. A gate that read
|
|
22
|
+
nothing has not passed.
|
|
23
|
+
|
|
24
|
+
### 2. Planned-but-never-judged targets
|
|
25
|
+
|
|
26
|
+
The root is alive and the corpus is real, but the loop `continue`s past most of
|
|
27
|
+
it on unwritten conditions. The published count says "enumerated", the reader
|
|
28
|
+
hears "checked". `lint_framework_leakage` published 438 while judging 368.
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
./scripts-run src/scripts/<gate> | grep 'ledger:' # scanned=N planned=M — compare N and M
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**Structural fix:** `_lib/gate_ledger.ts`. Every planned target reaches exactly
|
|
35
|
+
one terminal outcome, and the skip codes are counted per reason.
|
|
36
|
+
|
|
37
|
+
### 3. Allowlist growth
|
|
38
|
+
|
|
39
|
+
The gate is honest, the corpus is real, and the finding was suppressed. Count-
|
|
40
|
+
based ratchets cannot see swap-one-out-add-one-in: remove a real entry, add a
|
|
41
|
+
fresh suppression, the number is identical and the estate moved backwards.
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
./scripts-run src/scripts/check_suppression_hygiene # entry SETS vs the base ref
|
|
45
|
+
git diff origin/main -- 'src/scripts/*allowlist*.json' 'src/config/*baselines*.json'
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**Structural fix:** `_lib/ratchet_base_ref.ts` — compare entry sets against
|
|
49
|
+
`git show <baseRef>:<path>`, the one reading of the baseline the change under
|
|
50
|
+
review cannot rewrite.
|
|
51
|
+
|
|
52
|
+
### 4. Ratchet-entry deletion
|
|
53
|
+
|
|
54
|
+
Subtler than growth: deleting a baseline entry whose `count` is 0 is **weaker**
|
|
55
|
+
than leaving it, because the absence of an entry means "unbaselined" and an
|
|
56
|
+
unbaselined gate's `ok` flips to `actual === 0` with no recorded intent. Harden
|
|
57
|
+
first, then delete, and pin the absence.
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
git diff origin/main -- src/config/gate-violation-baselines.json | grep '^-'
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### 5. Threshold re-anchoring
|
|
64
|
+
|
|
65
|
+
The gate still runs, the corpus is real, and the bar moved. A lowered floor and
|
|
66
|
+
a genuine improvement look identical in the exit code.
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
git diff origin/main -- src/config/gate-coverage.yml | grep -E '^[-+].*min_scanned'
|
|
70
|
+
git diff origin/main -- 'src/config/*budget*.json'
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
**Discriminator:** a legitimate ratchet lowers *severity* while keeping *reach*;
|
|
74
|
+
threshold-lowering reduces reach. Ask which one the diff does.
|
|
75
|
+
|
|
76
|
+
### 6. Suppression sweep
|
|
77
|
+
|
|
78
|
+
Not one entry — twenty. Allowlist growth past ~20 entries in a session is the
|
|
79
|
+
signal that the linter (or the content) is wrong, not that the list is short by
|
|
80
|
+
twenty. The tool-call guard blocks past that count; the honest response is to
|
|
81
|
+
fix the gate's shape.
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
./scripts-run src/scripts/check_suppression_hygiene | grep 'entry(ies)'
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### 7. Crashed gate read as passing gate
|
|
88
|
+
|
|
89
|
+
A gate that throws is not a gate that passed, but an aggregator that only
|
|
90
|
+
inspects exit codes cannot tell an exception from a clean run when the harness
|
|
91
|
+
swallows it. Three of this repository's recorded traps are estate invalidation
|
|
92
|
+
misreported as a per-gate red, or the reverse.
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
./scripts-run src/scripts/<gate>; echo "EXIT=$?" # capture BEFORE any $( )
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
**Structural fix:** capture `EXIT=$?` on the line immediately after the command,
|
|
99
|
+
never after a command substitution; and fail closed when a registered gate
|
|
100
|
+
reports a null or missing verdict.
|
|
101
|
+
|
|
102
|
+
### 8. A derived page reporting an unmeasured dimension
|
|
103
|
+
|
|
104
|
+
The generator has no data for a category and renders a `0`, or worse a computed
|
|
105
|
+
share, which reads as a measurement. The always-budget report printed `60.1%`
|
|
106
|
+
for a dimension it counted **zero** artefacts in.
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
./scripts-run src/scripts/check_derived_page_truthfulness # where present
|
|
110
|
+
grep -rn '0%\|0 of 0' docs/*.md
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
**Structural fix:** an unmeasured category renders as absent or explicitly
|
|
114
|
+
"not measured" — never as a zero and never as a share.
|
|
115
|
+
|
|
116
|
+
### 9. Equality without validity
|
|
117
|
+
|
|
118
|
+
Two things are compared and found equal — because neither exists. `diff a b`
|
|
119
|
+
over two absent files returns equal; two empty lists both serialize to `[]`; an
|
|
120
|
+
empty rendered template equals an absent target file. The gate reports
|
|
121
|
+
"in sync" having compared nothing.
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
./scripts-run src/scripts/<gate> # then delete ONE side and re-run
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
**Structural fix:** assert both sides exist and are well-formed BEFORE
|
|
128
|
+
comparing. Two absences are the absence of a comparison, not the presence of a
|
|
129
|
+
match. The estate-wide audit is below.
|
|
130
|
+
|
|
131
|
+
## The audited sync/parity gates
|
|
132
|
+
|
|
133
|
+
Comparison-shaped gates carry entry #9's failure mode by default: `diff a b`
|
|
134
|
+
where neither side exists returns **equal**. Audited 2026-08-05 across
|
|
135
|
+
`src/scripts/` — **27 comparison gates, 19 guarded, 8 vulnerable, 0 undecided.**
|
|
136
|
+
|
|
137
|
+
A gate is GUARDED when it asserts both sides exist and are well-formed *before*
|
|
138
|
+
comparing — in this tree, almost always via `assertScanned` /
|
|
139
|
+
`assertWatchlistResolves` from `_lib/scan_scope.ts`.
|
|
140
|
+
|
|
141
|
+
### Guarded (19)
|
|
142
|
+
|
|
143
|
+
`check_release_surface_equality` · `check_trunk_drift` · `lint_glama_drift` ·
|
|
144
|
+
`check_ci_strict_superset` · `check_condensation` · `check_artefact_checksums` ·
|
|
145
|
+
`check_bridge_derivation` · `check_discovery_determinism` ·
|
|
146
|
+
`check_cli_registry_budget_sync` · `check_template_pin_drift` ·
|
|
147
|
+
`check_gitignore_freshness` · `check_token_optimizer_freshness` ·
|
|
148
|
+
`lint_eval_freshness` · `lint_behavioural_eval_freshness` ·
|
|
149
|
+
`check_release_trunk_sync` · `sync_gitattributes` · `sync_gitignore` ·
|
|
150
|
+
`iron_law_sha` · `check_test_coverage_diff`
|
|
151
|
+
|
|
152
|
+
Two carry caveats worth knowing: `check_template_pin_drift` has an
|
|
153
|
+
`--allow-empty` opt-in that is a deliberate empty-side pass, and
|
|
154
|
+
`check_test_coverage_diff` is guarded but warn-only, so its guard is currently
|
|
155
|
+
inert.
|
|
156
|
+
|
|
157
|
+
### Vulnerable (8) — shrink-only from here
|
|
158
|
+
|
|
159
|
+
| Gate | The both-absent / both-empty path |
|
|
160
|
+
|---|---|
|
|
161
|
+
| ~~`verify_physical_move`~~ | **FIXED in this change.** `_diff_manifest(null, null)` returned `[]`, and an empty issue list means clean. |
|
|
162
|
+
| `check_ci_local_parity` | a missing workflow dir reads as `''` and a missing taskfile is skipped; two empty corpora ⇒ no delta ⇒ exit 0 |
|
|
163
|
+
| `mcp_parity_smoke` | two empty lists both serialize to `[]`; prints "0 entries match" and returns 0 |
|
|
164
|
+
| `probe_projection_fidelity` | a fixture that loses its `entries:` key yields 0 checks and `fail=0` |
|
|
165
|
+
| `sync_agent_settings` | empty template + absent target ⇒ `'' === ''` ⇒ "already in sync" under `--check` |
|
|
166
|
+
| `sync_github_metadata` | two empty topic lists ⇒ no drift ⇒ exit 0 **even under `--strict`** |
|
|
167
|
+
| `check_kernel_prefix_stability` | deleting the committed snapshot returns `'warmup'` ⇒ exit 0 |
|
|
168
|
+
| `bench_drift_check` | fewer than 2 reports ⇒ "no drift gate yet" ⇒ exit 0 |
|
|
169
|
+
|
|
170
|
+
The seven open rows are recorded rather than swept: fixing release, sync, and
|
|
171
|
+
bench gates in one change would be a large diff across surfaces this change does
|
|
172
|
+
not otherwise touch, which is the unreviewable-batch failure the authoring
|
|
173
|
+
guideline warns about. The fixed row shows the pattern — assert both sides
|
|
174
|
+
exist, and treat two absences as the absence of a comparison.
|
|
175
|
+
|
|
176
|
+
## The ease tripwire
|
|
177
|
+
|
|
178
|
+
A verification that was **far easier than expected** is a signal to check the
|
|
179
|
+
path, not a signal of success. Every entry above felt like a pass at the moment
|
|
180
|
+
it happened. When a gate you expected to fight goes green on the first run, run
|
|
181
|
+
one of the detection commands before believing it.
|
|
182
|
+
|
|
183
|
+
## See also
|
|
184
|
+
|
|
185
|
+
- [`gate-authoring.md`](gate-authoring.md) — the authoring path that prevents these at the source.
|
|
186
|
+
- `docs/contracts/ci-green-floor.md` § CI delta — what a local pass does and does not cover.
|
|
187
|
+
- `src/scripts/_lib/gate_ledger.ts`, `_lib/scan_scope.ts`, `_lib/ratchet_base_ref.ts` — the three structural fixes above.
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# Gate Authoring — the single path for a new gate
|
|
2
|
+
|
|
3
|
+
> Lifecycle, gaming-risk block, paired fixtures, ledger obligation, and finding format. Read this BEFORE writing a new `lint_*` / `check_*` / `audit_*` script.
|
|
4
|
+
|
|
5
|
+
_Origin: `road-to-skill-ecosystem-gate-integrity` Phase 3. It exists because the estate re-learned the same lesson four times at four instances and never wrote the general rule down: a gate that scanned nothing exited green, a budget report printed a share for a dimension it never measured, release gates verified an empty corpus, shape gates ran over frozen corpora. Each was fixed where it was found. This is the authoring surface that stops the fifth._
|
|
6
|
+
|
|
7
|
+
## The five obligations
|
|
8
|
+
|
|
9
|
+
A new gate is not done until all five hold. They are ordered by how expensive
|
|
10
|
+
they are to add later — the first two are nearly free at authoring time and
|
|
11
|
+
nearly impossible to retrofit honestly.
|
|
12
|
+
|
|
13
|
+
### 1. Account for the work — the ledger obligation
|
|
14
|
+
|
|
15
|
+
Use `src/scripts/_lib/gate_ledger.ts`. Plan every target, then give each one
|
|
16
|
+
exactly one terminal outcome: `complete`, `fail`, `skip(reason)`, or
|
|
17
|
+
`outOfScope(reason)`. `finalize()` throws when a planned target reached none.
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
const ledger = new GateLedger('lint_example');
|
|
21
|
+
ledger.plan(files.map(rel));
|
|
22
|
+
for (const f of files) {
|
|
23
|
+
if (isGenerated(f)) { ledger.outOfScope(rel(f), 'generated_artifact'); continue; }
|
|
24
|
+
const finding = check(f);
|
|
25
|
+
if (finding) ledger.fail(rel(f), finding); else ledger.complete(rel(f));
|
|
26
|
+
}
|
|
27
|
+
ledger.report(); // prints `scanned=N planned=N skipped=N`, throws on unaccounted
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Skip reasons come from the closed union in that module. An unlisted reason does
|
|
31
|
+
not typecheck — deliberately, because "excluded dir" / "too big" / "binary?" in
|
|
32
|
+
free prose is not aggregatable, and adding a genuinely new code should be a
|
|
33
|
+
one-line diff a reviewer sees.
|
|
34
|
+
|
|
35
|
+
A gate that genuinely has no per-target structure (a single scalar probe, a
|
|
36
|
+
watch-list guard) carries `// ledger-exempt: <reason>` on its own line instead.
|
|
37
|
+
`check_gate_completeness` ratchets the un-adopted count; it may only shrink.
|
|
38
|
+
|
|
39
|
+
**This is not the scan-root guard.** Keep `assertScanned` /
|
|
40
|
+
`assertWatchlistResolves` from `_lib/scan_scope.ts` as well. One covers the root,
|
|
41
|
+
the other the items under it, and a gate wants both.
|
|
42
|
+
|
|
43
|
+
### 2. Print the denominator on the green path
|
|
44
|
+
|
|
45
|
+
Success output states what was inspected, not only that nothing was found.
|
|
46
|
+
`ledger.report()` does this; if the gate also carries a floor in
|
|
47
|
+
`src/config/gate-coverage.yml`, emit the machine-read `scanned: <N>` line via
|
|
48
|
+
`reportScanned` or directly.
|
|
49
|
+
|
|
50
|
+
Publish the number that was **judged**, not the number that was *enumerated*.
|
|
51
|
+
`lint_framework_leakage` published 438 against 368 actually judged until the
|
|
52
|
+
ledger made the honest number the cheap one to emit — a 19% over-report of its
|
|
53
|
+
own coverage, invisible to every consumer of that line.
|
|
54
|
+
|
|
55
|
+
### 3. Land advisory, promote on empty
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
A NEW GATE LANDS ADVISORY. ITS FINDINGS ARE CLASSIFIED ON THE REAL CORPUS.
|
|
59
|
+
THE BASELINE SHRINKS TO EMPTY. ONLY THEN IS IT PROMOTED TO ERROR.
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
The failure this prevents is recorded: a shape gate wired straight to error over
|
|
63
|
+
a frozen corpus produced 8 hits, all 8 unfixable, and could therefore only ever
|
|
64
|
+
block. Running a new gate against the real corpus and classifying every hit
|
|
65
|
+
BEFORE wiring it is the difference between a ratchet and a wall.
|
|
66
|
+
|
|
67
|
+
Record the promotion condition in the gate's own header comment, in the form
|
|
68
|
+
*"promote to error when `<baseline entry>` reaches 0"*, so the next reader knows
|
|
69
|
+
whether advisory is a stage or a permanent hedge.
|
|
70
|
+
|
|
71
|
+
Use `checkRatchet` from `_lib/gate_baseline.ts` with an entry in
|
|
72
|
+
`src/config/gate-violation-baselines.json`; it returns a verdict rather than
|
|
73
|
+
printing or exiting, so the gate stays in control of its own output.
|
|
74
|
+
|
|
75
|
+
### 4. Name one degenerate pass — the gaming-risk block
|
|
76
|
+
|
|
77
|
+
Every new gate or ratchet carries a `**Gaming risk**` paragraph in its header
|
|
78
|
+
comment naming **at least one** concrete way the metric passes without the
|
|
79
|
+
underlying property holding, plus the mitigation and — honestly — the residual.
|
|
80
|
+
|
|
81
|
+
If one degenerate pass is nameable at authoring time, it will be found in
|
|
82
|
+
practice. Worked examples from this very roadmap:
|
|
83
|
+
|
|
84
|
+
| Gate | Degenerate pass | Mitigation | Residual |
|
|
85
|
+
|---|---|---|---|
|
|
86
|
+
| `check_gate_completeness` | sprinkle `// ledger-exempt:` markers with plausible boilerplate | minimum reason length; the marker lands in the diff | cannot judge whether a reason is *true* |
|
|
87
|
+
| `check_suppression_hygiene` | a `falsifier` holding a command that always exits 0 | literal degenerate forms rejected; minimum length | cannot execute the command to prove it discriminates |
|
|
88
|
+
| `_lib/gate_ledger` | mark every target `outOfScope` to reach a clean tally | the report prints skip counts per code beside the scanned count | a reviewer must still read the ratio |
|
|
89
|
+
|
|
90
|
+
The residual column is the point. A gaming-risk block that claims full coverage
|
|
91
|
+
is itself a false green.
|
|
92
|
+
|
|
93
|
+
### 5. Ship a paired fixture
|
|
94
|
+
|
|
95
|
+
A gate with no negative fixture cannot be shown to discriminate. Minimum set:
|
|
96
|
+
|
|
97
|
+
- the **real repo tree** passes, and the denominator is asserted (not just the
|
|
98
|
+
exit code — a gate that scanned nothing also exits 0);
|
|
99
|
+
- a **planted violation** fails, and the message names the offender;
|
|
100
|
+
- the **boundary** case, where the gate declares one (exactly-at-cap, exactly-
|
|
101
|
+
at-minimum);
|
|
102
|
+
- a **dead scan root** fails rather than passing green;
|
|
103
|
+
- `--quiet` changes the output, not the verdict.
|
|
104
|
+
|
|
105
|
+
Where the gate has a self-test mode (§ Second-order guards below), the
|
|
106
|
+
assertion-count floor is itself asserted: a truncated fixture block must fail
|
|
107
|
+
rather than print success.
|
|
108
|
+
|
|
109
|
+
## Finding format — the suppression key is in the message
|
|
110
|
+
|
|
111
|
+
A finding message carries its own suppression key inline, copy-pasteable:
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
❌ src/skills/example/SKILL.md:42 framework-leakage /artisan/ …
|
|
115
|
+
suppress: {"file":"src/skills/example/SKILL.md","anchor":"php artisan migrate","reason":"<why>","falsifier":"<command>"}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Friction in the suppression path is what drives a maintainer to disable the gate
|
|
119
|
+
instead of narrowing it. Make the honest narrow easier than the blunt off-switch.
|
|
120
|
+
|
|
121
|
+
Prefer a **content anchor** over a line number in any new suppression entry.
|
|
122
|
+
Position-keyed entries re-fire on an insertion nobody made, and this repository
|
|
123
|
+
has already paid for that: 18 of 18 framework-leakage entries are line-keyed,
|
|
124
|
+
and `check_suppression_hygiene` prints that count every run until they migrate.
|
|
125
|
+
|
|
126
|
+
## Derived pages — render only what was measured
|
|
127
|
+
|
|
128
|
+
```
|
|
129
|
+
A CATEGORY WITH NO MEASUREMENT RENDERS AS ABSENT OR EXPLICITLY "NOT MEASURED".
|
|
130
|
+
NEVER AS A ZERO. NEVER AS A COMPUTED SHARE.
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Any generator that writes a derived page uses `_lib/measured_render.ts`:
|
|
134
|
+
`renderShare` returns `not measured` for an empty denominator instead of
|
|
135
|
+
`0.0%`, and `renderCoverage` prints the **gap list** beside the coverage number.
|
|
136
|
+
|
|
137
|
+
The recorded failure: the always-budget report printed a share for a dimension
|
|
138
|
+
it counted zero artefacts in. `0 / 0` became `0.0%`, which reads as a
|
|
139
|
+
measurement that came back low — the most confident possible way to say nothing.
|
|
140
|
+
|
|
141
|
+
Two rules follow from it:
|
|
142
|
+
|
|
143
|
+
- **A coverage number is published with its gaps.** Naming the un-covered
|
|
144
|
+
artefacts costs one line and removes the reader's assumption that the
|
|
145
|
+
remainder is small. When the list is capped for length, the true remainder is
|
|
146
|
+
still stated — a silently truncated gap list reads as "covered everything"
|
|
147
|
+
exactly like the bare number it was meant to qualify.
|
|
148
|
+
- **The paired test asserts the NEGATIVE.** Given an unmeasured dimension, the
|
|
149
|
+
rendered output must contain no `%` at all. Asserting only that it says "not
|
|
150
|
+
measured" still passes a renderer that helpfully appends `(0.0%)` beside it.
|
|
151
|
+
|
|
152
|
+
Adoption is a ratchet, not a sweep: `check_gate_completeness` is the worked
|
|
153
|
+
example, and a generator that neither imports the helper nor carries the
|
|
154
|
+
negative assertion is the gap this section exists to close as generators are
|
|
155
|
+
touched.
|
|
156
|
+
|
|
157
|
+
## Registering the gate
|
|
158
|
+
|
|
159
|
+
1. Task stanza in `taskfiles/ci-fast.yml`, `cmd: ./scripts-run src/scripts/<id> {{.QUIET_FLAG}}`.
|
|
160
|
+
2. `- task: <name>` in the `ci:` list in `Taskfile.yml`.
|
|
161
|
+
3. `src/config/gate-coverage.yml` entry **if** the gate emits `scanned: <N>` — a
|
|
162
|
+
test fails the build on an emitting-but-unregistered gate.
|
|
163
|
+
4. Verify under **both** argv forms — bare and the CI-injected `--quiet`. A
|
|
164
|
+
wrongly-consumed flag has silently become a scan root in this repo before.
|
|
165
|
+
|
|
166
|
+
Set the coverage floor **below** the live count. A floor pinned at today's exact
|
|
167
|
+
number reds on the very change that legitimately retires one unit, while a floor
|
|
168
|
+
well above zero still catches the collapse that matters.
|
|
169
|
+
|
|
170
|
+
## See also
|
|
171
|
+
|
|
172
|
+
- [`false-green.md`](false-green.md) — the catalogue of ways a green result can be false, each with its detection command.
|
|
173
|
+
- `src/scripts/_lib/gate_ledger.ts` — completeness accounting.
|
|
174
|
+
- `src/scripts/_lib/scan_scope.ts` — scan-root and watch-list assertions.
|
|
175
|
+
- `src/scripts/_lib/ratchet_base_ref.ts` — shrink-only against the base ref.
|
|
176
|
+
- `src/scripts/_lib/gate_baseline.ts` — the count ratchet and its staleness expiry.
|
|
177
|
+
- `docs/contracts/ci-green-floor.md` — what CI guarantees, and the local↔remote delta.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@event4u/agent-config",
|
|
3
3
|
"mcpName": "io.github.event4u-app/agent-config",
|
|
4
|
-
"version": "9.
|
|
4
|
+
"version": "9.23.0",
|
|
5
5
|
"description": "Governed skills, rules and commands for AI coding tools (Claude Code, Cursor, Windsurf, Copilot) — every claim machine-checked, including \"zero runtime daemon\".",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"private": false,
|
|
@@ -39,8 +39,10 @@
|
|
|
39
39
|
"docs/guidelines/",
|
|
40
40
|
"docs/contracts/persona-schema.md",
|
|
41
41
|
"docs/contracts/provider-lifecycle.md",
|
|
42
|
+
"docs/contracts/settings-classes.md",
|
|
42
43
|
"src/scripts/",
|
|
43
44
|
"src/server/schemas/",
|
|
45
|
+
"src/server/io/yamlIO.ts",
|
|
44
46
|
"src/shared/",
|
|
45
47
|
"src/install/",
|
|
46
48
|
"src/agent-src/scripts/",
|
|
@@ -69,6 +69,18 @@ pre_push_budget_seconds: 25
|
|
|
69
69
|
# resolver not a gate at all — a helper a workflow step calls to compute
|
|
70
70
|
# something (a diff base, a scope); it decides and blocks nothing
|
|
71
71
|
ci_only:
|
|
72
|
+
- id: check_release_surface_equality
|
|
73
|
+
class: release
|
|
74
|
+
reason: "shells out to `gh pr diff <n>` for a live release PR — there is no PR body to compare the CHANGELOG against on a local checkout"
|
|
75
|
+
- id: check_release_highlights
|
|
76
|
+
class: release
|
|
77
|
+
reason: "requires `--version X.Y.Z` and derives evidence from a release span; exits 2 without one, so a local run gates nothing"
|
|
78
|
+
- id: check_finding_dispositions
|
|
79
|
+
class: release
|
|
80
|
+
reason: "adjudicates the release-PR self-review findings ledger, whose input JSON is produced by the self-review workflow job"
|
|
81
|
+
- id: rule_trigger_eval
|
|
82
|
+
class: network
|
|
83
|
+
reason: "live cross-model LLM trigger evaluation in the secret-gated canary workflow; advisory, paid, and never gating"
|
|
72
84
|
- id: check_evaluator_budgets
|
|
73
85
|
class: benchmark
|
|
74
86
|
reason: "asserts against a measurements JSON that only the nightly evaluator-umbrella run produces — with no artifact there is nothing local to compare against"
|
|
@@ -58,11 +58,11 @@
|
|
|
58
58
|
"deterministic": false
|
|
59
59
|
},
|
|
60
60
|
"cli_help_command_count": {
|
|
61
|
-
"max":
|
|
62
|
-
"last_measured":
|
|
63
|
-
"last_measured_at": "2026-08-
|
|
61
|
+
"max": 85,
|
|
62
|
+
"last_measured": 85,
|
|
63
|
+
"last_measured_at": "2026-08-05",
|
|
64
64
|
"method": "registry enumeration — count of `{ name: '...', disposition` entries in src/cli/registry.ts (NOT --help prose parsing; methods historically disagreed 74 vs 76 vs 79)",
|
|
65
|
-
"baseline_note": "80 → 81 on 2026-08-03 (release 9.15.0 gate). This RECORDS growth that already shipped through its own reviewed PR; it does not authorize new surface. The +1 is f02db54c2 (fix(cli): register routing:doctor in the CLI registry) — the command existed, only its registry entry was missing, and the registering PR did not move this budget. The gate did its job again: breach WARNS on main and BLOCKS on release, which is the conscious-decision point rather than a silently raised cap. Re-measured 2026-08-03 via the pinned method: 81. Headroom stays zero by design — the next registry addition must move this number in its own PR. 81 → 82 on 2026-08-04: the `handoff` subcommand (road-to-agent-handoff-resume Phase 4) registers in the same PR that adds the surface, per this record's own contract. Re-measured via the pinned method: 82. 82 → 84 on 2026-08-04: the `route:explain` + `route:audit` verbs (routing-correctness validation surface) register in the same PR that adds the surface. Re-measured via the pinned method: 84."
|
|
65
|
+
"baseline_note": "80 → 81 on 2026-08-03 (release 9.15.0 gate). This RECORDS growth that already shipped through its own reviewed PR; it does not authorize new surface. The +1 is f02db54c2 (fix(cli): register routing:doctor in the CLI registry) — the command existed, only its registry entry was missing, and the registering PR did not move this budget. The gate did its job again: breach WARNS on main and BLOCKS on release, which is the conscious-decision point rather than a silently raised cap. Re-measured 2026-08-03 via the pinned method: 81. Headroom stays zero by design — the next registry addition must move this number in its own PR. 81 → 82 on 2026-08-04: the `handoff` subcommand (road-to-agent-handoff-resume Phase 4) registers in the same PR that adds the surface, per this record's own contract. Re-measured via the pinned method: 82. 82 → 84 on 2026-08-04: the `route:explain` + `route:audit` verbs (routing-correctness validation surface) register in the same PR that adds the surface. Re-measured via the pinned method: 84. 84 → 85 on 2026-08-05: the `settings:set` verb (road-to-zero-ceremony-settings Phase 2 — the one agent-reachable settings writer) registers in the same PR that adds the surface, per this record's own contract. Re-measured via the pinned method: 85."
|
|
66
66
|
},
|
|
67
67
|
"mcp_public_tool_count": {
|
|
68
68
|
"max": 19,
|
|
@@ -40,21 +40,24 @@
|
|
|
40
40
|
# visible, or it repeats the sin it exists to catch.
|
|
41
41
|
# canary optional mutation recipe (Phase 7) — see CANARY below
|
|
42
42
|
#
|
|
43
|
-
# COVERAGE — THE HONEST DENOMINATOR (re-measured 2026-08-
|
|
43
|
+
# COVERAGE — THE HONEST DENOMINATOR (re-measured 2026-08-05)
|
|
44
44
|
# ----------------------------------------------------------
|
|
45
|
-
#
|
|
45
|
+
# 224 `lint_*` / `check_*` / `audit_*` scripts live under `src/scripts/`
|
|
46
46
|
# (the count is computed, not asserted — `check_gate_coverage.count_gate_scripts`).
|
|
47
|
-
# **
|
|
48
|
-
# CI-identical argv, and all
|
|
49
|
-
# complete with respect to its own contract, and covers ~
|
|
47
|
+
# **27 of them emit the machine-readable `scanned: <N>` line under their
|
|
48
|
+
# CI-identical argv, and all 27 are listed below.** The manifest is therefore
|
|
49
|
+
# complete with respect to its own contract, and covers ~12 % of the population.
|
|
50
50
|
#
|
|
51
|
-
#
|
|
52
|
-
#
|
|
53
|
-
#
|
|
54
|
-
#
|
|
55
|
-
#
|
|
51
|
+
# Two revisions of this header have now gone stale the same way. It claimed
|
|
52
|
+
# "8 of 211", then "22 of 223" while 26 entries were listed. Both halves drift
|
|
53
|
+
# for the same reason: entries are added without the header following. Numbers
|
|
54
|
+
# written in prose beside a mechanism that can compute them is how a denominator
|
|
55
|
+
# goes stale — the population figure comes from the function, the
|
|
56
|
+
# honest-denominator test bounds the drift on that half, and the emitter half
|
|
57
|
+
# stays a manual count until something computes it too. If you add an entry,
|
|
58
|
+
# the two numbers in the paragraph above are part of the diff.
|
|
56
59
|
#
|
|
57
|
-
# The other ~
|
|
60
|
+
# The other ~197 are NOT covered, for one reason: rule 1. They print their count
|
|
58
61
|
# in prose ("111 file(s) scanned", "0 declarer(s)") or not at all, and a guard
|
|
59
62
|
# that parses prose is the fragile thing being replaced. Padding this list with
|
|
60
63
|
# them would manufacture exactly the false green the file exists to prevent.
|
|
@@ -107,6 +110,29 @@
|
|
|
107
110
|
# contract is `dist == rewrite(src)`; `check_ci_local_parity`, whose corpus is
|
|
108
111
|
# workflow files) carries no recipe and is reported UNPROVEN, never green.
|
|
109
112
|
gates:
|
|
113
|
+
- id: check_gate_completeness
|
|
114
|
+
argv: []
|
|
115
|
+
min_scanned: 180
|
|
116
|
+
corpus: "gates reachable from the `ci` / `consistency` task roots (220 at baseline)"
|
|
117
|
+
status: enforced
|
|
118
|
+
note: >
|
|
119
|
+
Floor set below the live count on purpose: the registered set legitimately
|
|
120
|
+
shrinks when a gate is retired, and a floor pinned at today's exact number
|
|
121
|
+
would red on the very change that removes one. It is still high enough
|
|
122
|
+
that a broken closure walk — the dead-scan-root shape one level up, where
|
|
123
|
+
the Taskfile parse degrades and the population silently collapses —
|
|
124
|
+
cannot pass.
|
|
125
|
+
|
|
126
|
+
- id: check_suppression_hygiene
|
|
127
|
+
argv: []
|
|
128
|
+
min_scanned: 8
|
|
129
|
+
corpus: "declared human-authored suppression surfaces (10 at baseline)"
|
|
130
|
+
status: enforced
|
|
131
|
+
note: >
|
|
132
|
+
The denominator is the INVENTORY, not the entry count — the gate's job is
|
|
133
|
+
that every suppression surface is accounted for, and a surface list that
|
|
134
|
+
collapsed to nothing must not read as "no suppressions to check".
|
|
135
|
+
|
|
110
136
|
- id: lint_trigger_collisions
|
|
111
137
|
argv: []
|
|
112
138
|
min_scanned: 90
|
|
@@ -322,7 +348,7 @@ gates:
|
|
|
322
348
|
- id: lint_framework_leakage
|
|
323
349
|
argv: []
|
|
324
350
|
min_scanned: 350
|
|
325
|
-
corpus: "skills / rules / commands .md (
|
|
351
|
+
corpus: "skills / rules / commands .md actually judged (368 of 438 enumerated; the rest carry a declared exemption the ledger names)"
|
|
326
352
|
status: enforced
|
|
327
353
|
note: >
|
|
328
354
|
Its human summary prints "N hits across M files", where M is files WITH
|
|
@@ -459,3 +485,19 @@ gates:
|
|
|
459
485
|
meaningful — it now falls when one of the three inputs is renamed away.
|
|
460
486
|
`argv: []` is the CI-identical invocation: both `taskfiles/ci-fast.yml`
|
|
461
487
|
and `.github/workflows/tests.yml` call it bare.
|
|
488
|
+
|
|
489
|
+
- id: lint_settings_classes
|
|
490
|
+
argv: ["--quiet"]
|
|
491
|
+
min_scanned: 100
|
|
492
|
+
corpus: "leaf keys in src/config/agent-settings.template.yml (140 at baseline)"
|
|
493
|
+
status: enforced
|
|
494
|
+
note: >
|
|
495
|
+
Floor set well below the live 140 so retiring a settings key does not red
|
|
496
|
+
the change that retires it, but high enough that the collapse this file
|
|
497
|
+
exists to catch — the template moving and the gate classifying an empty
|
|
498
|
+
map — cannot pass. No `canary:` recipe: this gate's only possible
|
|
499
|
+
violation is a MODIFICATION of two tracked files (a key added to the
|
|
500
|
+
template, or a row removed from the contract), and the canary harness is
|
|
501
|
+
create-only by design. Discrimination is proven instead by the gate's own
|
|
502
|
+
`--self-test`, which drives the real binary through six rejecting cases
|
|
503
|
+
including the dead-scan-root one.
|
|
@@ -20,6 +20,12 @@
|
|
|
20
20
|
"count": 337,
|
|
21
21
|
"landed": "2026-08-02",
|
|
22
22
|
"note": "Markdown links between source artefacts that cross a pack boundary the source pack's `requires` graph does not reach — revealed when the scan root moved off the deleted packages/ tree to the live corpus (0 -> 753 artefacts scanned). 82 distinct pack pairs across 131 source files; largest single class is engineering-base -> meta (73), skills in a consumer-installable pack citing this package's own maintenance skills. Each is a link that dangles for a consumer who installs the source pack without the target's. Judged real drift rather than an over-strict gate because it is long-tailed — top bucket 22%, no single systematic cause — unlike the lint_artefact_frontmatter case, where one required shape had 0-of-288 adoption. Fix per finding: add the edge to packs.yml requires, retarget the link, or move the artefact."
|
|
23
|
+
},
|
|
24
|
+
"check_gate_completeness": {
|
|
25
|
+
"count": 217,
|
|
26
|
+
"landed": "2026-08-05",
|
|
27
|
+
"note": "Ledger adoption ratchet. 217 of 221 CI-registered gates do not yet carry per-target completeness accounting (`_lib/gate_ledger.ts`) or a `// ledger-exempt:` marker. This is deliberately NOT a sweep: road-to-skill-ecosystem-gate-integrity Phase 1 adopts exactly three gates (lint_framework_leakage, check_references, lint_roadmap_complexity) plus the checker itself to prove the interface, and the remainder migrates as gates are touched. Shrink-only from here — the number may not grow, so a NEW gate must adopt or exempt.",
|
|
28
|
+
"falsifier": "./scripts-run src/scripts/check_gate_completeness"
|
|
23
29
|
}
|
|
24
30
|
}
|
|
25
31
|
}
|
|
@@ -7,9 +7,10 @@
|
|
|
7
7
|
"measurement_conditions": "npm pack --dry-run --json --ignore-scripts, run at the repo root on a clean checkout. `--ignore-scripts` means NO build runs, so dist/cli, dist/ui, dist/mcp and dist/hooks are absent and the figure covers only the TRACKED payload (src/scripts/, dist/agent-src/, dist/install/, docs/guidelines/, templates). That is deliberate: this gate is always-on for every PR, and the tracked content tree is exactly the surface that grows by accretion. The built artifact is ~5.4 MB larger and is gated separately, at release time, by evaluator-budgets.unpacked_size_mb.",
|
|
8
8
|
"budgets": {
|
|
9
9
|
"packed_size_mb": {
|
|
10
|
-
"max": 6.
|
|
11
|
-
"last_measured":
|
|
12
|
-
"method": "npm pack --dry-run --json --ignore-scripts → dist.size / 1e6 (the compressed tarball a consumer actually downloads)"
|
|
10
|
+
"max": 6.9,
|
|
11
|
+
"last_measured": 6.398,
|
|
12
|
+
"method": "npm pack --dry-run --json --ignore-scripts → dist.size / 1e6 (the compressed tarball a consumer actually downloads)",
|
|
13
|
+
"baseline_note": "5.903 → 6.398 re-baselined 2026-08-05. This is a RE-BASELINE, not a carve-out for the change that tripped it: measured on the tracked tree, `origin/main` alone was already at 6.372 of a 6.4 cap, so 28 KB of headroom remained after months of accretion and the next legitimate addition of any size was going to cross it. The addition here is 26 KB — docs/contracts/settings-classes.md (the A/B/C fence, read at RUNTIME by the settings writer, so it must ship) plus src/server/io/yamlIO.ts (imported by that writer to preserve the user's comments). CI measures ~4 KB above the local figure because it regenerates dist/agent-src first. Max set to 6.9 rather than to a number that merely clears: a cap pinned 2 KB above the measurement reds on the next commit and teaches the reader to raise it again, which is how a budget becomes a formality. That restores the ~8% headroom the 5.903/6.4 pair originally carried. The `owner: maintainer` on this file stands — this raise is surfaced in the PR for that decision, not smuggled."
|
|
13
14
|
}
|
|
14
15
|
},
|
|
15
16
|
"per_skill_share": {
|