@event4u/agent-config 14.22.0 → 14.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 (111) hide show
  1. package/.augment-plugin/marketplace.json +3 -3
  2. package/.augment-plugin/plugin.json +1 -1
  3. package/.claude-plugin/marketplace.json +1 -1
  4. package/CHANGELOG.md +159 -380
  5. package/README.md +1 -1
  6. package/dist/agent-src/commands/pr/merge.md +47 -38
  7. package/dist/agent-src/commands/roadmap/next.md +8 -0
  8. package/dist/agent-src/contexts/execution/autonomy-mechanics.md +12 -0
  9. package/dist/agent-src/rules/notes-first-reasoning.md +36 -25
  10. package/dist/agent-src/scripts/archive_completed_roadmaps.ts +106 -4
  11. package/dist/agent-src/skills/code-intelligence/SKILL.md +57 -1
  12. package/dist/agent-src/skills/git-workflow/references/push-closes-its-loop.md +9 -0
  13. package/dist/agent-src/skills/tailwind-engineer/SKILL.md +52 -2
  14. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  15. package/dist/cli/registry.js +1 -1
  16. package/dist/cli-delegate/archive_completed_roadmaps.js +52 -4
  17. package/dist/cli-delegate/{chunk-TGIPZBCW.js → chunk-2KKXJW25.js} +22 -8
  18. package/dist/cli-delegate/{chunk-4FS4WRXP.js → chunk-IAI3XL43.js} +15 -3
  19. package/dist/cli-delegate/{chunk-BIXEDQPZ.js → chunk-KCNTUSPI.js} +1 -1
  20. package/dist/cli-delegate/cmd_export.js +2 -2
  21. package/dist/cli-delegate/cmd_handoff.js +20 -12
  22. package/dist/cli-delegate/cmd_prune.js +2 -2
  23. package/dist/cli-delegate/cmd_session_recycle.js +3 -13
  24. package/dist/cli-delegate/cmd_settings_migrate.js +2 -2
  25. package/dist/cli-delegate/cmd_settings_set.js +1 -1
  26. package/dist/cli-delegate/cmd_sync.js +2 -2
  27. package/dist/cli-delegate/cmd_uninstall.js +2 -2
  28. package/dist/cli-delegate/cmd_validate.js +2 -2
  29. package/dist/discovery/deprecation-report.md +1 -1
  30. package/dist/discovery/discovery-manifest.json +8 -8
  31. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  32. package/dist/discovery/discovery-manifest.summary.md +1 -1
  33. package/dist/discovery/dormancy-report.md +1 -1
  34. package/dist/discovery/orphan-report.md +1 -1
  35. package/dist/discovery/packs.json +2 -2
  36. package/dist/discovery/trust-report.md +1 -1
  37. package/dist/discovery/workspaces.json +2 -2
  38. package/dist/hooks/dispatch.js +880 -289
  39. package/dist/install/install.mjs +15 -3
  40. package/dist/mcp/registry-manifest.json +2 -2
  41. package/dist/mcp/server.json +2 -2
  42. package/dist/mcp/server.mjs +5458 -2453
  43. package/dist/scripts/_lib/lean_projection_mode.js +108 -0
  44. package/dist/scripts/_lib/router_match.js +243 -0
  45. package/dist/scripts/_lib/rule_injection.js +216 -0
  46. package/dist/scripts/condense.js +20 -20
  47. package/dist/scripts/project_thin_rules.js +139 -19
  48. package/dist/server/schemas/settings.js +16 -3
  49. package/docs/MIGRATION.md +56 -0
  50. package/docs/contracts/settings-classes.md +39 -7
  51. package/docs/guidelines/agent-infra/notes-horizon-mechanics.md +90 -0
  52. package/llms.txt +1 -1
  53. package/package.json +1 -1
  54. package/src/agent-src/scripts/archive_completed_roadmaps.ts +106 -4
  55. package/src/config/agent-settings.template.yml +47 -1
  56. package/src/config/continuity-surface.json +7 -0
  57. package/src/config/evaluator-budgets.json +4 -4
  58. package/src/config/gate-violation-baselines.json +2 -2
  59. package/src/config/hook-token-budget.json +7 -6
  60. package/src/config/packed-binary-manifest.json +1 -1
  61. package/src/config/routing-coverage-seed.json +9 -2
  62. package/src/scripts/_cli/cmd_session_recycle.ts +2 -25
  63. package/src/scripts/_cli/handoff_sessions.ts +14 -44
  64. package/src/scripts/_lib/continuity_slot.ts +295 -0
  65. package/src/scripts/_lib/continuity_writer.ts +253 -0
  66. package/src/scripts/_lib/hook_settings.ts +85 -0
  67. package/src/scripts/_lib/lean_projection_mode.ts +127 -0
  68. package/src/scripts/_lib/packed_binary_predicate.ts +37 -0
  69. package/src/scripts/_lib/recycle_envelope_paths.ts +109 -21
  70. package/src/scripts/_lib/regression_neighbourhood.ts +159 -22
  71. package/src/scripts/_lib/session_eol.ts +44 -0
  72. package/src/scripts/_lib/session_index_trust.ts +346 -0
  73. package/src/scripts/_lib/standing_bound_ratchet.ts +154 -0
  74. package/src/scripts/_lib/vendored_grammar_upstream.ts +280 -0
  75. package/src/scripts/audit_mcp_tools.ts +69 -25
  76. package/src/scripts/build_mcp_catalog.ts +24 -1
  77. package/src/scripts/check_host_tree_parity.ts +291 -0
  78. package/src/scripts/check_preamble_payload_budget.ts +136 -7
  79. package/src/scripts/check_rule_projection_integrity.ts +102 -2
  80. package/src/scripts/ci_settle.ts +205 -5
  81. package/src/scripts/code_graph/build.ts +79 -0
  82. package/src/scripts/code_graph/cli.ts +258 -7
  83. package/src/scripts/code_graph/detect.ts +38 -0
  84. package/src/scripts/code_graph/query.ts +25 -2
  85. package/src/scripts/code_graph/sqlite_store.ts +16 -1
  86. package/src/scripts/code_graph/types.ts +52 -3
  87. package/src/scripts/code_graph/validate.ts +3 -1
  88. package/src/scripts/code_graph/verbs.ts +590 -0
  89. package/src/scripts/condense.ts +20 -20
  90. package/src/scripts/handoff_context_hook.ts +39 -1
  91. package/src/scripts/hook_manifest.json +1 -1
  92. package/src/scripts/hook_manifest.yaml +24 -1
  93. package/src/scripts/hooks/block_speaking_inbox_dir.ts +182 -16
  94. package/src/scripts/hooks/code_graph_context_hook.ts +7 -18
  95. package/src/scripts/hooks/push_settle_hook.ts +3 -2
  96. package/src/scripts/hooks/rule_inject_hook.ts +107 -20
  97. package/src/scripts/hooks/session_eol_hook.ts +120 -2
  98. package/src/scripts/hot_context_hook.ts +62 -7
  99. package/src/scripts/lint_roadmap_blockers.ts +164 -1
  100. package/src/scripts/mcp_server/consumer_tool_catalog.json +152 -2
  101. package/src/scripts/mcp_server/graph_tools.ts +294 -0
  102. package/src/scripts/mcp_server/path_util.ts +31 -0
  103. package/src/scripts/mcp_server/tool_catalog_source.ts +7 -1
  104. package/src/scripts/mcp_server/tools.ts +3 -13
  105. package/src/scripts/model_rule_injection.ts +61 -4
  106. package/src/scripts/project_thin_rules.ts +149 -16
  107. package/src/scripts/report_standing_payload_by_host.ts +650 -0
  108. package/src/scripts/schemas/agent-settings.schema.json +7 -1
  109. package/src/scripts/session_memory_index.ts +81 -8
  110. package/src/server/schemas/settings.ts +23 -4
  111. package/src/vendor/grammars/README.md +27 -2
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  [![Smoke](https://github.com/event4u-app/agent-config/actions/workflows/smoke.yml/badge.svg)](https://github.com/event4u-app/agent-config/actions/workflows/smoke.yml) [![Public install smoke (3 OS × 2 Node)](https://github.com/event4u-app/agent-config/actions/workflows/smoke-public-install.yml/badge.svg)](https://github.com/event4u-app/agent-config/actions/workflows/smoke-public-install.yml) [![npm](https://img.shields.io/npm/v/@event4u/agent-config?style=flat-square&label=npm&color=orange)](https://www.npmjs.com/package/@event4u/agent-config) [![agent-config MCP server](https://glama.ai/mcp/servers/event4u-app/agent-config/badges/score.svg)](https://glama.ai/mcp/servers/event4u-app/agent-config) [![MCP Toplist](https://mcptoplist.com/badge/glama%2Fevent4u-app%2Fagent-config.svg)](https://mcptoplist.com/server/glama%2Fevent4u-app%2Fagent-config)
6
6
 
7
- [![Skills](https://img.shields.io/badge/Skills-299-orange?style=flat-square)](dist/agent-src/skills/) [![Rules](https://img.shields.io/badge/Rules-120-orange?style=flat-square)](dist/agent-src/rules/) [![Commands](https://img.shields.io/badge/Commands-204-orange?style=flat-square)](dist/agent-src/commands/) [![Guidelines](https://img.shields.io/badge/Guidelines-118-orange?style=flat-square)](docs/guidelines/) [![Personas](https://img.shields.io/badge/Personas-29-orange?style=flat-square)](dist/agent-src/personas/) [![Advisors](https://img.shields.io/badge/Advisors-5-orange?style=flat-square)](dist/agent-src/personas/advisors/) <!-- claim:published-artifact-counts -->
7
+ [![Skills](https://img.shields.io/badge/Skills-299-orange?style=flat-square)](dist/agent-src/skills/) [![Rules](https://img.shields.io/badge/Rules-120-orange?style=flat-square)](dist/agent-src/rules/) [![Commands](https://img.shields.io/badge/Commands-204-orange?style=flat-square)](dist/agent-src/commands/) [![Guidelines](https://img.shields.io/badge/Guidelines-119-orange?style=flat-square)](docs/guidelines/) [![Personas](https://img.shields.io/badge/Personas-29-orange?style=flat-square)](dist/agent-src/personas/) [![Advisors](https://img.shields.io/badge/Advisors-5-orange?style=flat-square)](dist/agent-src/personas/advisors/) <!-- claim:published-artifact-counts -->
8
8
 
9
9
  <sub>**How these are counted** — one canonical counter, `agent-config` → `update_counts --check`, re-derives all six from the tree and fails CI on a drift of one. Two bases are not what the linked directory shows, so they are stated here rather than left to be inferred: **Commands 202** counts every command file **recursively** (the linked directory holds 61 at its top level), and **Rules 120** counts the **source** rules while the linked projection holds 119 — one rule is dormant and is not projected. **Personas 29** excludes the directory README. Counts are of files and directories: none of them measures quality, activation, or adoption. <!-- claim:published-artifact-counts --></sub>
10
10
 
@@ -4,13 +4,13 @@ name: git-pr-merge
4
4
  disable-model-invocation: true
5
5
  argument-hint: "[all|<pr-number>] [--no-merge]"
6
6
  pack: git
7
- intent: "Prepare an open PR to mergeable — sync the base in, resolve conflicts semantically, drive required checks green — merging itself is gated and inert"
7
+ intent: "Prepare an open PR to mergeable — sync the base in, resolve conflicts semantically, drive required checks green — then merge what the invocation named"
8
8
  routes_to: [git-workflow, github-ci]
9
9
  replaces: []
10
10
  visibility: advanced
11
11
  cluster: git-pr-merge
12
12
  skills: [git-workflow, github-ci]
13
- description: Prepare one open PR to mergeable, or the whole open-PR queue with `all` — merging is specified but gated, so today every invocation stops at mergeable-and-open
13
+ description: Prepare one open PR to mergeable, or the whole open-PR queue with `all`, then merge what the invocation named — `--no-merge` stops at mergeable-and-open
14
14
  suggestion:
15
15
  eligible: false
16
16
  rationale: "Merging is irreversible and gated on the user's own word in the invocation — a suggested merge would manufacture the authorization the Hard Floor requires the user to give."
@@ -28,10 +28,13 @@ green on the head that will actually be merged — and then merge it, if and onl
28
28
  if the invocation authorised that.
29
29
 
30
30
  ```
31
- THE MERGE STEP (§ 9) IS SPECIFIED AND NOT YET ACTIVE. `--no-merge` IS THE
32
- OPERATIVE PATH TODAY, AND A BARE INVOCATION BEHAVES AS IF IT CARRIED IT.
33
- ACTIVATION NEEDS THE OWNER DECISION IN THE `merge-authority` BLOCKER OF
34
- `road-to-drain-commands` — NEVER A COMMAND EDIT, AND NEVER A GUARD EDIT.
31
+ THE MERGE STEP (§ 9) IS ACTIVE. THE OWNER AUTHORISED IT ON 2026-09-08 AND
32
+ ADR-266 RECORDS THE AMENDMENT TO ADR-237 § 4 THAT ACTIVATED IT — THAT RECORD
33
+ IS WHAT ACTIVATED IT, NEVER A COMMAND EDIT AND NEVER A GUARD EDIT.
34
+ AN EXPLICIT `/pr:merge` INVOCATION IS THE THIS-TURN CONFIRMATION, AND IT
35
+ REACHES EXACTLY THE PRs § 1's MANIFEST SNAPSHOTTED FROM IT. IT NEVER REACHES
36
+ A PR THAT ARRIVED LATER, A HEAD THE MANIFEST DID NOT RECORD, OR ANY OTHER
37
+ HARD-FLOOR ACTION. `--no-merge` STILL STOPS BEFORE § 9.
35
38
  ```
36
39
 
37
40
  **The invocation is the authorization, and nothing else is.** `pr-merge` is
@@ -42,11 +45,12 @@ authorized, and this command still reads it as an audit trail; it introduces no
42
45
  second authorization store, and it never writes one. When the work left is
43
46
  outside what the owner authorized, the run stops and reports (§ 7).
44
47
 
45
- Why it ships inert rather than not at all: everything before § 9 — the target
46
- manifest, the four conflict classes, the superseded check, the bounded CI
47
- repair, the cutoff — is the expensive, error-prone half, it is what the live
48
- runs actually proved, and none of it merges anything. `--no-merge` delivers all
49
- of it today.
48
+ Why the preparation half is still the bulk of this command: everything before
49
+ § 9 — the target manifest, the four conflict classes, the superseded check, the
50
+ bounded CI repair, the cutoff — is the expensive, error-prone part, and it is
51
+ what the live runs actually proved. `--no-merge` delivers exactly that half to
52
+ callers that want it, [`/roadmap:process-full`](../../../product-basic/roadmap/process-full/command.md)
53
+ among them.
50
54
 
51
55
  ## Sub-commands
52
56
 
@@ -62,15 +66,16 @@ different lifecycle, not a different count.
62
66
 
63
67
  ## Dispatch
64
68
 
65
- Every row below **prepares** — the merge step is gated (see the banner above),
66
- so today every row ends at mergeable-and-open. The column says what each row
67
- selects, not that it merges.
69
+ Every row below **prepares** first; the last column says whether it then
70
+ merges. A row that merges reaches only the PRs its own manifest snapshotted
71
+ (§ 1), and only once that PR's required checks are green on the head being
72
+ merged (§ 5).
68
73
 
69
74
  | Invocation | Selects | Prepares | Merges |
70
75
  |---|---|---|---|
71
- | `/pr:merge <N>` | exactly PR N | yes | only once the gate opens |
72
- | `/pr:merge` | ONE PR: green first, then infrastructure/tooling before content, then smallest diff (`changedFiles`, then additions+deletions), tiebreak ascending number | yes | only once the gate opens |
73
- | `/pr:merge all` | the whole open-PR list, under § 6's cutoff | yes | only once the gate opens |
76
+ | `/pr:merge <N>` | exactly PR N | yes | yes — PR N |
77
+ | `/pr:merge` | ONE PR: green first, then infrastructure/tooling before content, then smallest diff (`changedFiles`, then additions+deletions), tiebreak ascending number | yes | yes — the one PR it selected |
78
+ | `/pr:merge all` | the whole open-PR list, under § 6's cutoff | yes | yes — the manifest, in queue order |
74
79
  | `… --no-merge` | as above | yes | **never**, explicitly. This is the form [`/roadmap:process-full`](../../../product-basic/roadmap/process-full/command.md) calls for its delivery loop. |
75
80
 
76
81
  **Bare invocation** (`/pr:merge` with no argument) is a **documented default
@@ -81,7 +86,7 @@ to pick would hand back a decision the command can compute. When the open-PR
81
86
  list is empty there is nothing to select and the command reports that and
82
87
  stops.
83
88
 
84
- ## 1. Snapshot the target — an immutable manifest
89
+ ## 1. Snapshot the target — a manifest re-checked before every merge
85
90
 
86
91
  Compute the queue ONCE at invocation and record it as a manifest of
87
92
  `(PR number, head SHA)` pairs:
@@ -102,7 +107,11 @@ other than this run" is undetectable without a base SHA to compare against.
102
107
  cap truncated.
103
108
 
104
109
  The manifest is the authorization's target set and is **never silently
105
- refreshed by a third party's push**. It is, by design, advanced by *this run's
110
+ refreshed by a third party's push**. It lives in this run's own context and in
111
+ no file, so nothing verifies it and no gate would notice if it drifted — what
112
+ actually binds the target is the pre-merge re-read below, a check that fires,
113
+ not a property that holds. Say "re-checked", never "immutable" (ADR-266
114
+ § What this record does not claim, finding 3). It is, by design, advanced by *this run's
106
115
  own* commits — § 2 merges the base in and § 5 pushes, so a prepared PR's head
107
116
  is never the snapshot head, and a naive "refuse when the SHA moved" check would
108
117
  fire on every PR the run touches.
@@ -195,11 +204,12 @@ AND RECORD IT AS `blocked-external`.
195
204
  ```
196
205
 
197
206
  The asymmetry is deliberate and worth stating, because it looks inconsistent:
198
- merging is gated on an owner *decision* recorded once in a blocker, while
199
- closing is gated on a *per-object* confirmation every time. Merging this run's
200
- own PR is an action the run's whole design is about; closing a PR someone else
201
- opened is not, it destroys their work in progress, and no guard in this tree
202
- sees it happen. The cheaper gate goes on the action nothing else watches.
207
+ merging is authorised once by the invocation, while closing needs a *per-object*
208
+ confirmation every time. Merging a PR this queue prepared is the action the
209
+ run's whole design is about, and § 1's manifest already binds it to a named
210
+ number and a recorded head; closing a PR someone else opened is neither, it
211
+ destroys their work in progress, and no guard in this tree sees it happen. The
212
+ stricter gate goes on the action nothing else watches.
203
213
 
204
214
  ## 5. Drive CI green — bounded
205
215
 
@@ -240,13 +250,12 @@ After each merge the base has moved, so the next PR is re-synced against the
240
250
  NEW base — that is the loop, and it is why pre-greening several PRs ahead of
241
251
  their merges is wasted work.
242
252
 
243
- **While the merge step is gated, that loop does not turn**, and the section
244
- below is written for when it does. Nothing merges, so the base does not
245
- advance, no PR leaves the open list, and an `all` run is a **preparation
246
- sweep**: it syncs, classifies, greens and reports each PR once, then stops. It
247
- does not re-prepare a PR its own predecessor invalidated, because it has no
248
- predecessor that landed. The cutoff below still bounds it; the window below
249
- still cannot close it, for the reason § 7 gives.
253
+ **Under `--no-merge` that loop does not turn**, and the paragraph above is
254
+ written for the form that does. Nothing merges, so the base does not advance,
255
+ no PR leaves the open list, and the run is a **preparation sweep**: it syncs,
256
+ classifies, greens and reports each PR once, then stops. It does not re-prepare
257
+ a PR its own predecessor invalidated, because it has no predecessor that
258
+ landed. The cutoff below bounds both forms.
250
259
 
251
260
  **Cutoff.** When the manifest is exhausted, recompute the open-PR list
252
261
  **exactly once**. PRs that appeared during the run are drained as ONE final
@@ -308,10 +317,9 @@ run, and do not proceed to the next PR.
308
317
 
309
318
  ## 8. Kill switches, and what happens after a merge
310
319
 
311
- **Armed during preparation, not only before a merge** — otherwise every switch
312
- below is unreachable while the merge step is gated, and a preparation sweep has
313
- no way to stop at all. Each one aborts the current PR and ends the run on any
314
- of:
320
+ **Armed during preparation, not only before a merge** — otherwise a
321
+ `--no-merge` sweep would have no way to stop at all. Each one aborts the current
322
+ PR and ends the run on any of:
315
323
 
316
324
  - target number or head SHA differs from the manifest;
317
325
  - the base advanced by an actor other than this run;
@@ -365,9 +373,10 @@ conflict resolution. The disposition set is closed:
365
373
 
366
374
  ## Rules
367
375
 
368
- - **Never merge while the gate is closed.** Until the `merge-authority`
369
- blocker resolves, every invocation stops at mergeable-and-open, `--no-merge`
370
- or not. Once it opens, `--no-merge` is still the explicit way to say stop.
376
+ - **Never merge a PR the invocation did not name.** The authorization is the
377
+ owner's own `/pr:merge` sentence and reaches exactly the manifest § 1
378
+ snapshotted from it; a PR that arrived afterwards needs its own go-ahead.
379
+ `--no-merge` is the explicit way to say stop before § 9.
371
380
  - **Never widen, patch, or rebuild-around the git guard.** Verification of the
372
381
  authorization window is read-only.
373
382
  - **Never rebase a pushed branch**; the base is merged in.
@@ -148,6 +148,14 @@ ONE THAT MOVES MORE CHECKBOXES. PICKING BY CHECKBOX COUNT IS THE
148
148
  GOODHART MOVE THIS PACKAGE'S OWN DISCIPLINE FORBIDS.
149
149
  ```
150
150
 
151
+ **An owner-recorded precedence outranks the severity read.** Where an accepted ADR
152
+ states that a named roadmap or set is worked first, that record is the ranking and
153
+ this section does not re-derive one — the owner has already made the call the
154
+ council would otherwise settle. Precedence is not a bypass: a precedent roadmap
155
+ still passes every disqualifier in step 2, and a disqualified precedent roadmap is
156
+ skipped with its disqualifier named like any other. A `proposed` record carries no
157
+ precedence.
158
+
151
159
  - **Exactly one survivor, or one that dominates** → take it, state the
152
160
  one-line reason inline, proceed. Do not ask
153
161
  ([`no-cheap-questions`](../../rules/no-cheap-questions.md)).
@@ -193,6 +193,18 @@ decision. Engage per [`rdp-gate`](rdp-gate.md).
193
193
  The suite states three retry numbers; they are **stages of one escalation
194
194
  ladder on the same failing target**, not competing budgets:
195
195
 
196
+ 0. **Attempt 1 fails → read the candidate list, if one exists, BEFORE retry 1.**
197
+ Where the run recorded a candidate set for this work
198
+ ([`notes-first-reasoning`](../../rules/notes-first-reasoning.md)
199
+ § Reopening), the first failure reads it first: a failure is evidence about
200
+ the CHOSEN FORM, and retrying the same approach spends the budget without
201
+ ever reaching the second-best candidate — which is how the mechanism that
202
+ discovers a form was wrong never routes back to the form. Where no candidate
203
+ list exists the ladder is unchanged and stage 1 runs immediately.
204
+ **This adds a branch; it does not move the budget.** Reading the list is not
205
+ an attempt and is not counted, and the reopen is capped at one per candidate:
206
+ the second contradiction on the same candidate continues down this ladder
207
+ rather than reopening again.
196
208
  1. **Attempt 1 fails → up to 2 retries of the same approach**
197
209
  ([`think-before-action`](../../rules/think-before-action.md) "MAX 2
198
210
  RETRIES PER APPROACH") — the soft, agent-side stage: rethink between
@@ -24,11 +24,8 @@ obligation_frequency: "per-turn"
24
24
  # Notes-First Reasoning
25
25
 
26
26
  Part of the Reasoning Discipline Protocol. Engage per
27
- [`rdp-gate`](../contexts/execution/rdp-gate.md) (settings + task-signal + host
28
- self-assessment) — skip on trivial tasks; apply lightly on a strong-reasoning
29
- host. The notes file is grounded in the documented cross-run lessons memory
30
- (consolidated via [`memory-consolidation`](../skills/memory-consolidation/SKILL.md));
31
- the in-task sections below are a local derivation for within-task scope.
27
+ [`rdp-gate`](../contexts/execution/rdp-gate.md) — skip on trivial tasks, apply
28
+ lightly on a strong-reasoning host.
32
29
 
33
30
  ## The Iron Law
34
31
 
@@ -38,36 +35,50 @@ NOTES FILE — NEVER ECHOED INTO THE RESPONSE.
38
35
  THE RESPONSE CARRIES CONCLUSIONS + EVIDENCE ONLY.
39
36
  ```
40
37
 
41
- Reasoning dumped into the user-facing answer is both noise and a
42
- `reasoning_extraction` refusal risk (see `rdp-gate`). Keep it in the notes file.
43
- This is not "show your work in the reply" — it is the opposite.
38
+ This is not "show your work in the reply" — it is the opposite. Why, and what
39
+ grounds the notes file:
40
+ [`notes-horizon-mechanics`](../docs/guidelines/agent-infra/notes-horizon-mechanics.md).
44
41
 
45
42
  ## Notes file structure (the file, not the response)
46
43
 
47
- Use the sections that apply; the structure carries the enumeration, so there is
48
- no "write N hypotheses" instruction — record what the work actually surfaced.
44
+ Use the sections that apply — record what the work actually surfaced.
49
45
 
50
46
  - `## In-Task Hypothesis Log` — competing explanations under consideration.
51
- - `## Killed beliefs` — each discarded hypothesis + the evidence that killed it.
52
- - `## Predictions` — prediction · confidence · result · lesson (the calibration
53
- loop: hypothesis → prediction → reality → calibration).
54
- - `## Decisions` — decision · alternatives · reason · revisit-if. Tactical,
55
- in-task decisions stay here; **escalate to
56
- [`decision-record`](../skills/decision-record/SKILL.md)/ADR** when the decision
57
- is cross-task or architectural (litmus: would a dev on a different component
58
- next month need this context?).
47
+ - `## Killed beliefs` — each discarded hypothesis + the `killed-if` that killed
48
+ it.
49
+ - `## Predictions` — **chosen form** · prediction · confidence · result · lesson.
50
+ - `## Decisions` — decision · alternatives · reason · revisit-if ·
51
+ **next-commitment**. Tactical decisions stay here; **escalate to
52
+ [`decision-record`](../skills/decision-record/SKILL.md)/ADR** when cross-task
53
+ or architectural.
59
54
  - `## Uncertainty` — per-dimension score (e.g. architecture/implementation/
60
55
  requirements: high/medium/low); feeds the adaptive-effort decision.
61
56
 
57
+ ### The horizon — where `next-commitment` ends
58
+
59
+ ```
60
+ A CHOICE AUTHORISES WORK UP TO THE NEXT EVIDENCE-PRODUCING BOUNDARY.
61
+ NAME THE BOUNDARY FROM THE LIST. NEVER "THE WORK THE EVIDENCE SUPPORTS" —
62
+ THAT IS A JUDGEMENT, AND A JUDGEMENT IS NOT A HORIZON.
63
+ ```
64
+
65
+ ### Reopening — once, on contradiction, never on a schedule
66
+
67
+ ```
68
+ AN OBSERVATION THAT CONTRADICTS THE PREDICTION REOPENS THE CHOICE BEFORE THE
69
+ NEXT STEP — FROM THE CANDIDATE LIST THAT ALREADY EXISTS, NEVER A FRESH
70
+ ENUMERATION. ONE REOPEN PER CANDIDATE. THE SECOND CONTRADICTION HANDS OVER TO
71
+ THE RETRY-BUDGET LADDER RATHER THAN REOPENING AGAIN.
72
+ ```
73
+
62
74
  ## What stays out of notes
63
75
 
64
- User-attribute facts, transient TODOs, and durable cross-run lessons — those go
65
- to the memory system (`memory-consolidation`), not the in-task notes.
76
+ User-attribute facts, transient TODOs, and durable cross-run lessons go to the
77
+ memory system (`memory-consolidation`), not the in-task notes.
66
78
 
67
79
  ## See also
68
80
 
69
- - [`rdp-gate`](../contexts/execution/rdp-gate.md) — the table-free engagement gate.
70
- - [`memory-consolidation`](../skills/memory-consolidation/SKILL.md) — promotes
71
- durable lessons across runs.
72
- - [`verify-before-complete`](verify-before-complete.md) — the evidence the
73
- response carries comes from real tool results.
81
+ [`notes-horizon-mechanics`](../docs/guidelines/agent-infra/notes-horizon-mechanics.md)
82
+ — every mechanic this rule points at. Also
83
+ [`rdp-gate`](../contexts/execution/rdp-gate.md) and
84
+ [`verify-before-complete`](verify-before-complete.md).
@@ -58,7 +58,11 @@ import * as path from 'node:path';
58
58
  import process from 'node:process';
59
59
  import { fileURLToPath } from 'node:url';
60
60
 
61
- import { collect } from './update_roadmap_progress.js';
61
+ import {
62
+ blocker_is_resolved,
63
+ collect,
64
+ parse_blockers,
65
+ } from './update_roadmap_progress.js';
62
66
  import { guardedBaselineProblems, parseGuardedBaselines } from './guarded_baseline.js';
63
67
 
64
68
  import { isCliEntry } from './_cli_entry.js';
@@ -265,6 +269,80 @@ function _git_mv(root: string, src_rel: string, dst_rel: string, dry_run: boolea
265
269
  }
266
270
  }
267
271
 
272
+ // ---------------------------------------------------------------------
273
+ // The index/working-tree blocker divergence check (1b) + staging (1a)
274
+ // ---------------------------------------------------------------------
275
+ //
276
+ // The open-blocker refusal below reads the file with `fs.readFileSync`, i.e.
277
+ // the WORKING TREE. `git mv` then moves the INDEX ENTRY, which still carries
278
+ // whatever was staged. When the two disagree the commit ships the index
279
+ // content — content this gate never read.
280
+ //
281
+ // Measured, once, at full cost (2026-09-08): an edit removing a roadmap's
282
+ // blocker was made and never staged; `git mv` moved the stale index entry; the
283
+ // archived file shipped a blocker declared `Status: open` that had already been
284
+ // relocated to an active roadmap. The same blocker was open in two files at
285
+ // once, one of them archived, and it was caught by a human reading
286
+ // `git status` rather than by any gate.
287
+ //
288
+ // The check is scoped to blocker id → status and deliberately NOT to whole-file
289
+ // equality. An unstaged typo fix in a roadmap title has nothing to do with the
290
+ // defect, and refusing archival for it trains `git add -A`, which destroys the
291
+ // staging discipline this check depends on. AI council 2026-09-08, 2/2 present
292
+ // (anthropic/claude-sonnet-4-5 + openai/gpt-4o), on that scoping specifically:
293
+ // broad scoping is "user-hostile, trains developers to `git add -A`
294
+ // reflexively, defeating staging discipline".
295
+
296
+ /** Blocker id → status, as the archive-transition check compares them. */
297
+ function _blockerStatuses(text: string): Map<string, string> {
298
+ const out = new Map<string, string>();
299
+ for (const b of parse_blockers(text)) {
300
+ out.set(b.id, blocker_is_resolved(b) ? 'resolved' : 'open');
301
+ }
302
+ return out;
303
+ }
304
+
305
+ /**
306
+ * `git show :<rel>` — the STAGED content of a path, or null when the path has
307
+ * no index entry.
308
+ *
309
+ * Null is the untracked / pre-first-commit case, which `_git_mv` already
310
+ * handles by falling back to a plain rename. There is no index entry to
311
+ * disagree with, so the divergence check has nothing to assert and says so by
312
+ * returning null rather than by guessing at an empty string — an empty staged
313
+ * file and an absent one differ, and only the second is legitimate here.
314
+ */
315
+ function _indexContent(root: string, rel: string): string | null {
316
+ const cp = _run(['git', 'show', `:${rel}`], root);
317
+ return cp.returncode === 0 ? cp.stdout : null;
318
+ }
319
+
320
+ /**
321
+ * Human-readable disagreements between the staged and working-tree blocker
322
+ * maps of one path. Empty array = the two agree, or there is no index entry.
323
+ */
324
+ function _blockerDivergence(root: string, rel: string, workTreeText: string): string[] {
325
+ const staged = _indexContent(root, rel);
326
+ if (staged === null) {
327
+ return [];
328
+ }
329
+ const wt = _blockerStatuses(workTreeText);
330
+ const idx = _blockerStatuses(staged);
331
+ const ids = [...new Set([...wt.keys(), ...idx.keys()])].sort();
332
+ const out: string[] = [];
333
+ for (const id of ids) {
334
+ const a = idx.get(id);
335
+ const b = wt.get(id);
336
+ if (a === b) {
337
+ continue;
338
+ }
339
+ out.push(
340
+ `blocker '${id}': staged=${a ?? 'absent'} vs working tree=${b ?? 'absent'}`,
341
+ );
342
+ }
343
+ return out;
344
+ }
345
+
268
346
  /**
269
347
  * Repo-relative paths of files containing `needle`, by walking the filesystem.
270
348
  *
@@ -547,9 +625,8 @@ function archive_completed(
547
625
  // and invisible in a sweep that only reports what it moved; a MALFORMED
548
626
  // one on `[x]` would otherwise archive with an annotation claiming the
549
627
  // opposite. Both are named here instead.
550
- const guarded = parseGuardedBaselines(
551
- fs.readFileSync(path.join(roadmap_root, stats.rel), 'utf-8'),
552
- );
628
+ const roadmapText = fs.readFileSync(path.join(roadmap_root, stats.rel), 'utf-8');
629
+ const guarded = parseGuardedBaselines(roadmapText);
553
630
  if (guarded.length > 0) {
554
631
  process.stderr.write(
555
632
  ` ⚠️ ${stats.rel}: ${guarded.length} guarded-baseline step(s) — ` +
@@ -603,12 +680,37 @@ function archive_completed(
603
680
  if (changed_only && !(touched as Set<string>).has(old_rel)) {
604
681
  continue; // complete, but not this branch's work
605
682
  }
683
+ // The gate above validated the WORKING TREE; `git mv` ships the INDEX.
684
+ // When they disagree about a blocker, the validated content is not the
685
+ // content that gets committed — see § the index/working-tree blocker
686
+ // divergence check above for the measured failure.
687
+ const divergence = _blockerDivergence(root, old_rel, roadmapText);
688
+ if (divergence.length > 0) {
689
+ process.stderr.write(
690
+ ` ⚠️ ${stats.rel}: staged and working-tree blockers disagree ` +
691
+ `(${divergence.length}) — not archived. \`git mv\` would commit ` +
692
+ 'the staged content, which this sweep never validated. Stage or ' +
693
+ 'discard the change, then re-run.\n',
694
+ );
695
+ for (const d of divergence) {
696
+ process.stderr.write(` · ${d}\n`);
697
+ }
698
+ continue;
699
+ }
606
700
  const new_rel = `agents/roadmaps/archive/${stats.rel}`;
607
701
  const mv = _git_mv(root, old_rel, new_rel, dry_run);
608
702
  if (!mv.ok) {
609
703
  process.stderr.write(` ⚠️ could not archive ${old_rel} (mv failed)\n`);
610
704
  continue;
611
705
  }
706
+ // `git mv` moved the index ENTRY, not the working-tree content. Staging
707
+ // the destination makes what gets committed identical to what the checks
708
+ // above read — the divergence check refuses a disagreement that exists
709
+ // BEFORE the move, and this closes one created by an edit between the
710
+ // move and the commit.
711
+ if (!dry_run && mv.tracked) {
712
+ _run(['git', 'add', '--', new_rel], root);
713
+ }
612
714
  const refs = _inbound_ref_rewrite(root, old_rel, new_rel, dry_run, mv.tracked);
613
715
  // Stage rewritten refs only in a tracked repo; an untracked consumer's
614
716
  // files are not in the index, so the filesystem rewrite is the whole job.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  model_tier: inherit
3
3
  name: code-intelligence
4
- description: "Route codebase-structure questions (who calls X, where is this used, what imports, change-impact) to an existing code-graph first: cheaper, never more precise; grep stays routine. Also 'call graph'."
4
+ description: "Route codebase-structure questions (who calls X, where used, change-impact) to an existing code-graph first: impact, tests-for, dead cheaper, never more precise; grep routine. Also 'call graph'."
5
5
  domain: engineering
6
6
  workspaces:
7
7
  - engineering
@@ -204,6 +204,62 @@ refresh` first, or use grep and say so. This is a freshness obligation, not an
204
204
  ordering claim; the rule's query-first ordering still governs which source to
205
205
  reach for.
206
206
 
207
+ ## The four gate verbs — a diff, not a symbol (2026-09-08)
208
+
209
+ `query` / `affected` / `path` / `explain` answer a question about a SYMBOL.
210
+ Four more answer a question about a DIFF, and they are documented here rather
211
+ than in § Procedure for a mechanical reason worth stating: step 4.3 of
212
+ `road-to-a-graph-that-is-shipped` verifies that this file's line 164 — the
213
+ **No class is graph-first** sentence — is unchanged, and inserting anything
214
+ above it moves that line. So the ordering claim K5 protects stays byte-fixed
215
+ and the new material sits below it.
216
+
217
+ Each of the four walks only edges whose resolution mechanism is a stated fact
218
+ (never a repo-wide same-name guess, never an untyped receiver), and each prints
219
+ the mechanisms it accepted, the mechanisms it refused, and the graph's
220
+ staleness — so a narrow answer is distinguishable from a decoupled one:
221
+
222
+ - `agent-config code-graph impact --diff <rev>` — callers, dependents and test
223
+ files reachable from the changed symbols, plus the producing edges and a
224
+ minimal read set.
225
+ - `agent-config code-graph tests-for <symbol>` — the test files that import it.
226
+ An empty list is a finding, not an error.
227
+ - `agent-config code-graph untested --diff <rev>` — changed symbols that no test
228
+ file imports.
229
+ - `agent-config code-graph dead [--entry-points F | --accept-missing-exports]` —
230
+ symbols with no accepted reference that no declared entry point names. It
231
+ **refuses** rather than answering while an entry-point source cannot be read:
232
+ this engine records no exportedness, so without a supplied list an
233
+ exported-but-unimported symbol is indistinguishable from a dead one, and a
234
+ confident false "dead" invites a deletion the graph cannot justify. Nothing it
235
+ lists is a deletion recommendation.
236
+
237
+ These answers reach an agent over MCP without shelling out, and the mapping is
238
+ **not** one tool per verb — corrected after an independent review found this
239
+ paragraph claiming "the same five answers" directly after a list of four:
240
+
241
+ | MCP tool | answers |
242
+ |---|---|
243
+ | `graph_impact` | `impact --diff`, **and** `untested --diff` via `untested_only: true` |
244
+ | `graph_tests_for` | `tests-for` |
245
+ | `graph_dead` | `dead` |
246
+ | `graph_query` | `query` |
247
+ | `graph_path` | `path` |
248
+
249
+ `untested` has no tool of its own; it is a flag on `graph_impact`, because the
250
+ two answer the same question over the same diff and a sixth tool would have cost
251
+ standing context for a boolean. `graph_impact` is the only one that is not
252
+ read-only — it runs `git diff --name-only` to resolve its rev — and
253
+ `graph_dead`'s refusal arrives as `status: "refused"`, never as `status: "ok"`
254
+ with an empty list.
255
+
256
+ **No ordering claim is added here, and no measurement claim either.** These
257
+ verbs are cheaper than reconstructing the same relationship by hand — that is a
258
+ claim about the ALTERNATIVE being a manual reconstruction, not about grep.
259
+ Nothing on this page says they beat grep, none of the four appears in the v2
260
+ benchmark corpus (which measures `callers`, `transitive-impact`, `path-between`
261
+ and `references`), and § Measured twice still governs that question.
262
+
207
263
  ## Do NOT
208
264
 
209
265
  - Do NOT rebuild a **fresh** consumer-shipped index — query it (interop
@@ -68,6 +68,15 @@ returned 0, the turn closed, and the red arrived where the user found it.
68
68
  ending the turn silently on one is what the reminder exists to stop. Whether
69
69
  the reminder changes behavior is unmeasured — it ships as a carrier, not as a
70
70
  claim.
71
+ - **`ci_settle` will not answer for a head that stopped mattering.** Since
72
+ 2026-09-08 it reads the PR's `state` and compares its recorded head against
73
+ `git ls-remote` — before the wait and again at verdict time — and exits 2 with
74
+ a reason when the PR is merged, closed, or record-behind-branch. A green names
75
+ the head it is about. The measured reason: a merged PR's rollup kept answering
76
+ with its merge-time checks, so the tool reported `SETTLED GREEN — 40 check(s)`
77
+ for a commit that had zero check runs. The check does not apply to a fork PR
78
+ whose ref is not under this `origin`, and the line says so instead of claiming
79
+ it passed.
71
80
  - **Neither reaches a host without the slot.** `agent-config hooks:status`
72
81
  answers which slots are bound where you actually are; `post_tool_use` is
73
82
  unbound on windsurf and copilot, and there the settle obligation is
@@ -51,6 +51,42 @@ radius, spacing, shadow, font-size to a configured token. If the design hands yo
51
51
  (`bg-[#3B82F6]`, `mt-[17px]`) are a smell — accept only with a
52
52
  one-line comment naming the design source.
53
53
 
54
+ **That paragraph is written for greenfield, and artifact-bound work inverts
55
+ it.** When a provided finished design is the spec
56
+ ([`design-fidelity`](../../rules/design-fidelity.md)), `#3B82F6` is a decision
57
+ somebody already made and `bg-blue-500` is a guess about it. Three obligations
58
+ follow, and the third is the one this skill used to get wrong:
59
+
60
+ 1. **Do not snap.** Replacing the artifact's value with the nearest configured
61
+ token is the deviation the rule's Iron Law forbids — *"NEVER SWAP … SPACING,
62
+ OR COLOUR"* without explicit confirmation. An artifact-derived exact value is
63
+ not a smell here: it *is* the spec, and the source-naming comment the
64
+ paragraph above asks for is what records it.
65
+ 2. **Translate once, not per call site.** Where the artifact's literal genuinely
66
+ needs to live in the project's system, add **one** named project token
67
+ carrying that exact value and use it everywhere — one token the project owns
68
+ beats N approximations of the same colour. That is a token duty, not a
69
+ deviation: the value does not change.
70
+ 3. **Reconcile as a proposal, never autonomously.** Report each value's
71
+ distance to the nearest project token on the `Reconciled:` line below, and
72
+ let the human decide. Writing a *different* project value because the
73
+ difference looks small is exactly the unconfirmed swap (1) rules out — the
74
+ mode table at `design-fidelity.md:117-121` grants no visibility exemption,
75
+ and `structural` grants only the filling of a spec that is genuinely
76
+ **silent**, which an artifact stating `#3B82F6` is not.
77
+
78
+ **Why not "just approximate within tolerance", which is the eventual intent:**
79
+ that needs a tolerance to exist. There is none — no `reconcile`, `tolerance`,
80
+ `approximat` or `nearest` in the rule or its guideline — and both the threshold
81
+ and whether approximation is autonomous by default are owner decisions, tracked
82
+ as `blocker: approximation-tolerance` and `blocker: fidelity-default-flip` on
83
+ `road-to-design-intent-conformance`. Until they land, a skill granting that
84
+ autonomy would be re-writing the rule from underneath it.
85
+
86
+ Structure, controls, icons, grid and breakpoints are never this skill's to
87
+ adjust; those stay 1:1 with the artifact and belong to the rule, not to a
88
+ utility-class decision.
89
+
54
90
  Token authoring (DTCG 3-layer model, CSS-var/Tailwind generation) lives
55
91
  in [`design-tokens`](../design-tokens/SKILL.md); its
56
92
  `tokens.ts validate --dir <path>` is the **single token-discipline
@@ -103,7 +139,11 @@ When reviewing or proposing styles, return:
103
139
 
104
140
  1. Token map — every colour, spacing, radius, shadow, font-size mapped
105
141
  to its configured token; arbitrary values flagged with the design
106
- source they cite.
142
+ source they cite. Artifact-bound work adds a **Reconciled** block here:
143
+ one line per value, as `<artifact value> → <kept, or the token that
144
+ carries it exactly> (<distance to the nearest project token>)`. This is
145
+ the destination "report the distance" means; without it the obligation
146
+ has nowhere to land and is unobservable in the deliverable.
107
147
  2. Class list — ordered (layout → box-model → typography → colour →
108
148
  state → responsive); inline-style use justified per element.
109
149
  3. Extraction + risk call-out — component / constant / `@apply` / none
@@ -115,6 +155,7 @@ Concrete shape:
115
155
  ```
116
156
  Element: <selector or component name>
117
157
  Token map: <colour/spacing/etc → config token>
158
+ Reconciled: <artifact value → kept or exact-carrying token, + distance>
118
159
  Class list: <ordered classes>
119
160
  Inline style: <only if runtime-computed; else "none">
120
161
  Extraction: <component | constant | @apply | none — reason>
@@ -128,7 +169,16 @@ Risks: <arbitrary values, !important, dark-mode gaps>
128
169
  - `dark:` variants need a token map in both modes; one-sided dark
129
170
  styling is half a feature.
130
171
  - Arbitrary values (`mt-[17px]`) survive Tailwind upgrades but
131
- break the design system; they accumulate silently.
172
+ break the design system; they accumulate silently. **Artifact-bound is the
173
+ exception, not a loophole:** a value the provided design specifies is not
174
+ accumulation, and step 1's three obligations decide it. An arbitrary value
175
+ nobody can trace to a source is still the smell this bullet is about.
176
+ - **The carrier is CSS or a utility class; the value is the design's.** Static
177
+ presentation belongs in CSS / tokens / classes — `style=` is for what only
178
+ the runtime knows (a computed width, a live transform, a measured offset).
179
+ Porting an artifact's inline `style=` into classes is therefore expected and
180
+ is not a deviation, **as long as the resolved value comes out identical**.
181
+ Changing the carrier is free; changing the number is not.
132
182
  - `@apply` inside component CSS interacts with PurgeCSS — keep it
133
183
  in files Tailwind scans, not in vendor CSS.
134
184
  - **Anti-AI-slop catalog.** The bullets below are the Tailwind-specific
@@ -39,7 +39,7 @@ schema_version: 1
39
39
  # CI guard: a release bump of `package.json` must update this value
40
40
  # in lockstep — see scripts/check_template_pin_drift.ts (road-to-
41
41
  # portable-runtime-and-update-check P3.3).
42
- agent_config_version: "14.22.0"
42
+ agent_config_version: "14.23.0"
43
43
 
44
44
  # --- Project identity ---
45
45
  project:
@@ -73,7 +73,7 @@ export const REGISTRY = [
73
73
  { name: 'affected', disposition: 'delegate', synopsis: 'Artefacts related to <artefact> via the discovery relation-graph (BFS).' },
74
74
  { name: 'graph-explain', disposition: 'delegate', synopsis: 'Seed on a <concept>, expand 2 hops over the discovery relation-graph with a node budget.' },
75
75
  { name: 'benchmark', disposition: 'delegate', synopsis: 'Report context-token reduction vs the full always-loaded projection.' },
76
- { name: 'code-graph', disposition: 'delegate', synopsis: 'Deterministic code-graph engine: build|detect|query|affected|path|explain|validate.' },
76
+ { name: 'code-graph', disposition: 'delegate', synopsis: 'Deterministic code-graph engine: build|detect|refresh|validate|query|affected|path|explain, plus the gate verbs impact|tests-for|untested|dead.' },
77
77
  { name: 'hooks:install', disposition: 'delegate', synopsis: 'Install the combined pre-commit hook.' },
78
78
  { name: 'hooks:status', disposition: 'delegate', synopsis: 'Print the runtime hook matrix (per-platform install + bindings).' },
79
79
  { name: 'hooks:doctor', disposition: 'delegate', synopsis: 'Diagnose hook health: concerns, posture, missing trampolines (read-only).' },