@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.
Files changed (85) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +69 -256
  3. package/README.md +1 -1
  4. package/dist/agent-src/commands/optimize/deep.md +13 -0
  5. package/dist/agent-src/contexts/execution/cheap-question-mechanics.md +12 -2
  6. package/dist/agent-src/contexts/execution/mandated-lines.md +180 -0
  7. package/dist/agent-src/rules/context-hygiene.md +17 -0
  8. package/dist/agent-src/rules/downstream-changes.md +26 -0
  9. package/dist/agent-src/rules/think-before-action.md +25 -0
  10. package/dist/agent-src/rules/token-budget-discipline.md +46 -3
  11. package/dist/agent-src/rules/token-efficiency.md +13 -0
  12. package/dist/agent-src/rules/ui-audit-gate.md +33 -2
  13. package/dist/agent-src/skills/adversarial-review/SKILL.md +18 -4
  14. package/dist/agent-src/skills/decision-review/SKILL.md +49 -0
  15. package/dist/agent-src/skills/judge-artifact-completeness/SKILL.md +5 -1
  16. package/dist/agent-src/skills/judge-bug-hunter/SKILL.md +16 -0
  17. package/dist/agent-src/skills/judge-synthesis/SKILL.md +14 -1
  18. package/dist/agent-src/skills/rule-writing/SKILL.md +33 -0
  19. package/dist/agent-src/skills/skill-improvement-pipeline/SKILL.md +31 -0
  20. package/dist/agent-src/skills/skill-reviewer/SKILL.md +1 -1
  21. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  22. package/dist/cli/registry.js +1 -0
  23. package/dist/cli/registry.js.map +1 -1
  24. package/dist/cli-delegate/chunk-DVRS2KVR.js +4602 -0
  25. package/dist/cli-delegate/{chunk-ELMFOQPW.js → chunk-SJQMJI7M.js} +8 -4597
  26. package/dist/cli-delegate/cmd_export.js +2 -1
  27. package/dist/cli-delegate/cmd_prune.js +2 -1
  28. package/dist/cli-delegate/cmd_settings_migrate.js +2 -1
  29. package/dist/cli-delegate/cmd_settings_set.js +3370 -0
  30. package/dist/cli-delegate/cmd_sync.js +2 -1
  31. package/dist/cli-delegate/cmd_uninstall.js +2 -1
  32. package/dist/cli-delegate/cmd_validate.js +2 -1
  33. package/dist/discovery/deprecation-report.md +1 -1
  34. package/dist/discovery/discovery-manifest.json +17 -17
  35. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  36. package/dist/discovery/discovery-manifest.summary.md +1 -1
  37. package/dist/discovery/orphan-report.md +1 -1
  38. package/dist/discovery/packs.json +2 -2
  39. package/dist/discovery/trust-report.md +1 -1
  40. package/dist/discovery/workspaces.json +2 -2
  41. package/dist/hooks/dispatch.js +79 -1
  42. package/dist/mcp/registry-manifest.json +2 -2
  43. package/dist/mcp/server.json +2 -2
  44. package/dist/scripts/condense.js +47 -2
  45. package/dist/scripts/condense.js.map +1 -1
  46. package/dist/server/routes/settings.js +82 -0
  47. package/dist/server/routes/settings.js.map +1 -1
  48. package/dist/shared/settingsClasses.js +191 -0
  49. package/dist/shared/settingsClasses.js.map +1 -0
  50. package/docs/contracts/settings-classes.md +342 -0
  51. package/docs/guidelines/agent-infra/artifact-drafting-protocol-mechanics.md +23 -2
  52. package/docs/guidelines/agent-infra/false-green.md +187 -0
  53. package/docs/guidelines/agent-infra/gate-authoring.md +177 -0
  54. package/package.json +3 -1
  55. package/src/config/ci-local-parity.yml +12 -0
  56. package/src/config/evaluator-budgets.json +4 -4
  57. package/src/config/gate-coverage.yml +54 -12
  58. package/src/config/gate-violation-baselines.json +6 -0
  59. package/src/config/pack-size-budget.json +4 -3
  60. package/src/scripts/_cli/cmd_settings_set.ts +509 -0
  61. package/src/scripts/_dispatch.bash +16 -0
  62. package/src/scripts/_lib/gate_ledger.ts +295 -0
  63. package/src/scripts/_lib/gate_result.ts +101 -0
  64. package/src/scripts/_lib/gate_self_test.ts +129 -0
  65. package/src/scripts/_lib/measured_render.ts +97 -0
  66. package/src/scripts/_lib/ratchet_base_ref.ts +373 -0
  67. package/src/scripts/check_gate_completeness.ts +244 -0
  68. package/src/scripts/check_gate_coverage.ts +35 -6
  69. package/src/scripts/check_references.ts +125 -15
  70. package/src/scripts/check_review_dispositions.ts +14 -1
  71. package/src/scripts/check_suppression_hygiene.ts +503 -0
  72. package/src/scripts/condense.ts +47 -2
  73. package/src/scripts/hook_manifest.yaml +23 -0
  74. package/src/scripts/hooks/dispatch_hook.ts +65 -1
  75. package/src/scripts/hooks/host_semantics.ts +140 -0
  76. package/src/scripts/lint_framework_leakage.ts +147 -15
  77. package/src/scripts/lint_mandated_lines.ts +339 -0
  78. package/src/scripts/lint_roadmap_complexity.ts +102 -11
  79. package/src/scripts/lint_roadmap_family_cap.ts +191 -0
  80. package/src/scripts/lint_settings_classes.ts +397 -0
  81. package/src/scripts/lint_token_budget_discipline.ts +109 -1
  82. package/src/scripts/report_imperative_density.ts +229 -0
  83. package/src/scripts/verify_physical_move.ts +10 -1
  84. package/src/server/io/yamlIO.ts +173 -0
  85. 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.22.0",
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": 84,
62
- "last_measured": 84,
63
- "last_measured_at": "2026-08-04",
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-04)
43
+ # COVERAGE — THE HONEST DENOMINATOR (re-measured 2026-08-05)
44
44
  # ----------------------------------------------------------
45
- # 223 `lint_*` / `check_*` / `audit_*` scripts live under `src/scripts/`
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
- # **22 of them emit the machine-readable `scanned: <N>` line under their
48
- # CI-identical argv, and all 22 are listed below.** The manifest is therefore
49
- # complete with respect to its own contract, and covers ~10 % of the population.
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
- # The previous revision of this header claimed "8 of 211". Both halves had
52
- # drifted: entries were added without the header following, and the population
53
- # grew by 12. Numbers written in prose beside a mechanism that can compute them
54
- # is how a denominator goes stale — the population figure now comes from the
55
- # function, and the honest-denominator test bounds the drift.
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 ~201 are NOT covered, for one reason: rule 1. They print their count
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 (431 at baseline)"
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.4,
11
- "last_measured": 5.903,
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": {