@event4u/agent-config 14.21.0 → 14.22.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 (181) 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 +198 -0
  5. package/CREDITS.md +37 -0
  6. package/dist/agent-src/commands/analyze/inbox.md +380 -5
  7. package/dist/agent-src/commands/analyze/repo.md +4 -2
  8. package/dist/agent-src/contexts/communication/rules-auto/token-efficiency-mechanics.md +69 -0
  9. package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +5 -5
  10. package/dist/agent-src/rules/external-code-graph-interop.md +19 -14
  11. package/dist/agent-src/rules/token-efficiency.md +26 -3
  12. package/dist/agent-src/scripts/_cli_entry.ts +51 -0
  13. package/dist/agent-src/scripts/archive_completed_roadmaps.ts +14 -23
  14. package/dist/agent-src/scripts/roadmap_gates.ts +4 -20
  15. package/dist/agent-src/scripts/roadmap_set_step.ts +5 -2
  16. package/dist/agent-src/scripts/stubs_due.ts +4 -18
  17. package/dist/agent-src/scripts/update_roadmap_progress.ts +7 -24
  18. package/dist/agent-src/skills/code-intelligence/SKILL.md +25 -0
  19. package/dist/agent-src/skills/roadmap-writing/SKILL.md +17 -0
  20. package/dist/agent-src/templates/agent-settings.md +0 -1
  21. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  22. package/dist/cli-delegate/_cli_entry.js +8 -0
  23. package/dist/cli-delegate/_lib/pin_resolver.js +170 -0
  24. package/dist/cli-delegate/archival_sweep.js +8 -0
  25. package/dist/cli-delegate/archive_completed_roadmaps.js +494 -0
  26. package/dist/cli-delegate/check_update_banner.js +923 -0
  27. package/dist/cli-delegate/chunk-5EK5WQN6.js +28 -0
  28. package/dist/cli-delegate/chunk-7XGD2OEU.js +96 -0
  29. package/dist/cli-delegate/{chunk-643Z7FIC.js → chunk-BIXEDQPZ.js} +6 -0
  30. package/dist/cli-delegate/chunk-DBH4AHDK.js +155 -0
  31. package/dist/cli-delegate/chunk-JO25G5H7.js +20 -0
  32. package/dist/cli-delegate/chunk-LTYVIY76.js +7961 -0
  33. package/dist/cli-delegate/chunk-QAJS5YWY.js +999 -0
  34. package/dist/cli-delegate/{chunk-ZVBOHSVF.js → chunk-TGIPZBCW.js} +1 -1
  35. package/dist/cli-delegate/{chunk-YS6XTLEK.js → chunk-TYSY33MD.js} +56 -6
  36. package/dist/cli-delegate/chunk-ULET4BYS.js +328 -0
  37. package/dist/cli-delegate/chunk-YDQSHTD7.js +187 -0
  38. package/dist/cli-delegate/chunk-YWVXLR44.js +48 -0
  39. package/dist/cli-delegate/chunk-ZZQBUHFK.js +273 -0
  40. package/dist/cli-delegate/cmd_conformance.js +2 -2
  41. package/dist/cli-delegate/cmd_export.js +1 -1
  42. package/dist/cli-delegate/cmd_handoff.js +26 -2
  43. package/dist/cli-delegate/cmd_prune.js +1 -1
  44. package/dist/cli-delegate/cmd_session_recycle.js +2 -2
  45. package/dist/cli-delegate/cmd_settings_migrate.js +1 -1
  46. package/dist/cli-delegate/cmd_sync.js +1 -1
  47. package/dist/cli-delegate/cmd_uninstall.js +1 -1
  48. package/dist/cli-delegate/cmd_validate.js +1 -1
  49. package/dist/cli-delegate/dashboard_mode.js +16 -0
  50. package/dist/cli-delegate/gate_budget.js +24 -0
  51. package/dist/cli-delegate/gate_execute.js +25 -0
  52. package/dist/cli-delegate/guarded_baseline.js +18 -0
  53. package/dist/cli-delegate/resume_probe.js +22 -0
  54. package/dist/cli-delegate/roadmap_gates.js +638 -0
  55. package/dist/cli-delegate/roadmap_set_step.js +173 -0
  56. package/dist/cli-delegate/stub_queue.js +47 -0
  57. package/dist/cli-delegate/stubs_due.js +38 -0
  58. package/dist/cli-delegate/ticket_bundles.js +59 -0
  59. package/dist/cli-delegate/update_roadmap_progress.js +60 -0
  60. package/dist/discovery/deprecation-report.md +1 -1
  61. package/dist/discovery/discovery-manifest.json +8 -8
  62. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  63. package/dist/discovery/discovery-manifest.summary.md +1 -1
  64. package/dist/discovery/dormancy-report.md +1 -1
  65. package/dist/discovery/orphan-report.md +1 -1
  66. package/dist/discovery/packs.json +2 -2
  67. package/dist/discovery/trust-report.md +1 -1
  68. package/dist/discovery/workspaces.json +2 -2
  69. package/dist/hooks/dispatch.js +1193 -917
  70. package/dist/install/claudeLayerCarriage.js +237 -0
  71. package/dist/install/claudePathsPlan.js +79 -0
  72. package/dist/install/hostLayerFingerprint.js +1 -1
  73. package/dist/install/install.mjs +6 -0
  74. package/dist/install/partitionEligibility.js +231 -117
  75. package/dist/install/ruleLayerPartition.js +10 -7
  76. package/dist/mcp/registry-manifest.json +2 -2
  77. package/dist/mcp/server.json +2 -2
  78. package/dist/mcp/server.mjs +39219 -38995
  79. package/dist/scripts/condense.js +32 -25
  80. package/docs/MIGRATION.md +49 -1
  81. package/docs/contracts/settings-classes.md +1 -1
  82. package/docs/guidelines/agent-infra/minimal-safe-diff-mechanics.md +73 -0
  83. package/package.json +7 -4
  84. package/src/agent-src/scripts/_cli_entry.ts +51 -0
  85. package/src/agent-src/scripts/archive_completed_roadmaps.ts +14 -23
  86. package/src/agent-src/scripts/roadmap_gates.ts +4 -20
  87. package/src/agent-src/scripts/roadmap_set_step.ts +5 -2
  88. package/src/agent-src/scripts/stubs_due.ts +4 -18
  89. package/src/agent-src/scripts/update_roadmap_progress.ts +7 -24
  90. package/src/config/continuity-surface.json +509 -0
  91. package/src/config/estate-count-budget.json +1 -1
  92. package/src/config/gate-coverage.yml +29 -8
  93. package/src/config/gate-violation-baselines.json +8 -8
  94. package/src/config/gitignore-block.txt +19 -0
  95. package/src/config/host-injection-effect.json +73 -0
  96. package/src/config/packed-binary-manifest.json +33 -0
  97. package/src/config/preamble-payload-budget.json +112 -105
  98. package/src/config/publish-surface.json +4 -2
  99. package/src/install/claudeLayerCarriage.ts +273 -0
  100. package/src/install/claudePathsPlan.ts +76 -0
  101. package/src/install/hostLayerFingerprint.ts +1 -1
  102. package/src/install/partitionEligibility.ts +261 -127
  103. package/src/install/ruleLayerPartition.ts +19 -23
  104. package/src/scripts/_dispatch.bash +77 -27
  105. package/src/scripts/_lib/auto_dispatch.ts +2 -2
  106. package/src/scripts/_lib/catalogue_layer_parity.ts +146 -0
  107. package/src/scripts/_lib/continuity_surface.ts +296 -0
  108. package/src/scripts/_lib/corpus_manifest.ts +26 -9
  109. package/src/scripts/_lib/hook_settings.ts +1 -1
  110. package/src/scripts/_lib/injection_effect.ts +151 -0
  111. package/src/scripts/_lib/layer_overlap_notice.ts +50 -6
  112. package/src/scripts/_lib/packed_binary_predicate.ts +225 -0
  113. package/src/scripts/_lib/pin_resolver.ts +13 -0
  114. package/src/scripts/_lib/regression_neighbourhood.ts +1 -1
  115. package/src/scripts/_lib/retired_status.ts +74 -0
  116. package/src/scripts/_lib/skill_catalogue.ts +156 -21
  117. package/src/scripts/_lib/subagent_capsule.ts +117 -8
  118. package/src/scripts/ai_council/api_transport.ts +5 -0
  119. package/src/scripts/ai_council/deliberation_metrics.ts +353 -0
  120. package/src/scripts/ai_council/prompts.ts +9 -0
  121. package/src/scripts/ai_council/synthesis_disagreement.ts +206 -0
  122. package/src/scripts/capture_skill_catalogue.ts +24 -11
  123. package/src/scripts/check_candidate_lines.ts +499 -0
  124. package/src/scripts/check_continuity_surface.ts +475 -0
  125. package/src/scripts/check_dependency_floors.ts +13 -6
  126. package/src/scripts/check_estate_count.ts +40 -79
  127. package/src/scripts/check_pack_size.ts +262 -23
  128. package/src/scripts/check_roadmap_trackable.ts +19 -16
  129. package/src/scripts/check_rule_layer_partition.ts +54 -34
  130. package/src/scripts/check_single_delivery.ts +97 -31
  131. package/src/scripts/check_update_banner.ts +13 -0
  132. package/src/scripts/code_graph/build.ts +138 -15
  133. package/src/scripts/code_graph/cli.ts +76 -8
  134. package/src/scripts/code_graph/extract.ts +11 -0
  135. package/src/scripts/code_graph/grammar_probe.ts +43 -0
  136. package/src/scripts/code_graph/loader.ts +88 -14
  137. package/src/scripts/code_graph/query.ts +121 -11
  138. package/src/scripts/code_graph/resolution_tiers.ts +238 -0
  139. package/src/scripts/code_graph/sqlite_store.ts +247 -6
  140. package/src/scripts/code_graph/types.ts +66 -1
  141. package/src/scripts/code_graph/validate.ts +23 -0
  142. package/src/scripts/condense.ts +34 -22
  143. package/src/scripts/corpus_manifest.ts +5 -5
  144. package/src/scripts/council_cli.ts +11 -12
  145. package/src/scripts/count_intent_disagreement.ts +426 -0
  146. package/src/scripts/handoff_context_hook.ts +79 -0
  147. package/src/scripts/hook_effect_doctor.ts +82 -1
  148. package/src/scripts/hook_manifest.json +1 -1
  149. package/src/scripts/hook_manifest.yaml +64 -12
  150. package/src/scripts/hooks/chain_nudge_hook.ts +282 -0
  151. package/src/scripts/hooks/code_graph_context_hook.ts +212 -0
  152. package/src/scripts/hooks/concern_registry.ts +4 -2
  153. package/src/scripts/hooks/host_semantics.ts +1 -1
  154. package/src/scripts/hooks/orchestration_record_hook.ts +2 -2
  155. package/src/scripts/hooks/rule_inject_hook.ts +1 -1
  156. package/src/scripts/hooks/skill_route_hook.ts +11 -4
  157. package/src/scripts/inbox_source_census.ts +322 -0
  158. package/src/scripts/install-hooks.sh +82 -0
  159. package/src/scripts/{lint_carrier_integrity.ts → lint_deferral_integrity.ts} +60 -214
  160. package/src/scripts/lint_plan_risk_register.ts +13 -17
  161. package/src/scripts/mcp_server/suggest_skill.ts +72 -0
  162. package/src/scripts/mcp_server/tools.ts +2 -56
  163. package/src/scripts/media/lib/adapter-contract.md +7 -2
  164. package/src/scripts/media/lib/fixtures/flux/asset-0001.png +0 -0
  165. package/src/scripts/media/lib/fixtures/gemini-image/asset-0001.png +0 -0
  166. package/src/scripts/media/lib/fixtures/ideogram/asset-0001.png +0 -0
  167. package/src/scripts/rdp_candidates_delta.ts +276 -0
  168. package/src/scripts/rdp_quality_eval.ts +217 -19
  169. package/src/scripts/release_mix_taxonomy.json +3 -2
  170. package/src/scripts/report_host_injection_effect.ts +189 -0
  171. package/src/scripts/report_layer_overlap.ts +41 -0
  172. package/src/scripts/report_skill_menu_census.ts +341 -0
  173. package/src/scripts/skill_tools/score_skill_relevance.ts +70 -6
  174. package/src/scripts/skill_tools/suggest_skill_for_task.ts +37 -3
  175. package/src/vendor/grammars/README.md +61 -0
  176. package/src/vendor/grammars/tree-sitter-javascript.wasm +0 -0
  177. package/src/vendor/grammars/tree-sitter-php.wasm +0 -0
  178. package/src/vendor/grammars/tree-sitter-typescript.wasm +0 -0
  179. package/src/scripts/_lib/carrier_status.ts +0 -118
  180. package/src/scripts/_lib/estate_carrier_cases.ts +0 -72
  181. package/src/scripts/hooks/code_graph_nudge_hook.ts +0 -191
@@ -0,0 +1,225 @@
1
+ /**
2
+ * Binary-payload eligibility — the mechanically verified predicate that
3
+ * reopened the `binary` class.
4
+ *
5
+ * WHAT THIS IS, SAID PLAINLY.
6
+ *
7
+ * `f573d628c` closed `binary` as a hard zero on 2026-09-07. This REOPENS it,
8
+ * 24 hours later, and that IS A WEAKENING OF A CONTROL. An AI council
9
+ * (2026-09-08, 2 seats, unanimous, deep tier) authorised it, and one seat
10
+ * explicitly refused the framing that this is merely "correcting an
11
+ * over-generalisation" — that distinction is cosmetic. The control was closed
12
+ * for the wrong reason, so reopening it is a weakening: justified, and still a
13
+ * weakening. It is written down here rather than softened because a control
14
+ * that gets quietly relaxed is worse than one that never existed.
15
+ *
16
+ * WHY THE CLOSURE WAS WRONG.
17
+ *
18
+ * Decision A over-generalised. Its reasoning was "the three observed binaries
19
+ * are unused, therefore no binary can ever be required" — an invalid
20
+ * generalisation from an observed sample to a universal rule. The vendored
21
+ * tree-sitter grammars are the direct counterexample: bytes the production path
22
+ * loads and the engine requires. A textual placeholder was lossless for three
23
+ * PNGs nothing decoded; for a grammar it does not degrade a fixture, it breaks
24
+ * the engine.
25
+ *
26
+ * WHAT REPLACES THE HARD ZERO — AND WHAT DOES NOT.
27
+ *
28
+ * Not a prose exception. A binary is admitted only when EVERY condition below
29
+ * is verified mechanically, on the bytes that are actually in the tarball:
30
+ *
31
+ * 1. exactly one manifest entry for the path
32
+ * 2. no wildcard or directory grant anywhere in the manifest
33
+ * 3. the packed tarball really contains the path
34
+ * 4. sha256(bytes) equals the manifest hash
35
+ * 5. size equals the manifest size
36
+ * 6. kind is `tree-sitter-wasm-grammar`
37
+ * 7. WASM magic and version are valid
38
+ * 8. the PRODUCTION loader reports the manifest's grammar_id
39
+ * 9. the PRODUCTION loader reports the manifest's ABI
40
+ * 10. a defined invalid-header mutation of the bytes is REJECTED
41
+ * 11. a defined unsupported-ABI expectation is REJECTED
42
+ *
43
+ * 8 and 9 go through `probeGrammarFile`, the engine's own loader, deliberately:
44
+ * a second implementation inside the gate could accept bytes the engine
45
+ * rejects, which is exactly the gap that would let a broken grammar ship while
46
+ * the gate called it valid.
47
+ *
48
+ * 10 and 11 run on every invocation, not in a separate test, because a
49
+ * predicate whose refusals nobody exercises is a predicate nobody has checked.
50
+ *
51
+ * `archive` IS NOT REOPENED.
52
+ *
53
+ * This collision produced evidence about grammars and none whatsoever about
54
+ * archives. `archive` stays a hard zero with no manifest path, and this module
55
+ * offers none.
56
+ *
57
+ * ELIGIBILITY NEVER TOUCHES THE MEASUREMENT.
58
+ *
59
+ * The gate reports `observed` as the true count and byte total on every run.
60
+ * Compliance is `observed <= allowed`, where `allowed` is the number of entries
61
+ * that passed every condition above — never `observed` minus something excused.
62
+ * A file that fails one condition lowers `allowed`; it never disappears from
63
+ * `observed`.
64
+ */
65
+ import { createHash } from 'node:crypto';
66
+ import * as fs from 'node:fs';
67
+ import * as path from 'node:path';
68
+
69
+ /** One manifest row. Path-exact; `why` is documentation, never a condition. */
70
+ export interface PackedBinaryEntry {
71
+ readonly path: string;
72
+ readonly sha256: string;
73
+ readonly size: number;
74
+ readonly kind: string;
75
+ readonly grammar_id: string;
76
+ readonly abi: number;
77
+ readonly why?: string;
78
+ }
79
+
80
+ export interface PackedBinaryManifest {
81
+ readonly schema_version: number;
82
+ readonly entries: readonly PackedBinaryEntry[];
83
+ }
84
+
85
+ /** The one kind this predicate knows how to verify. */
86
+ export const VERIFIABLE_KIND = 'tree-sitter-wasm-grammar';
87
+
88
+ /** What the production loader reported for a file. */
89
+ export interface GrammarProbe {
90
+ readonly abi: number;
91
+ readonly grammar_id: string;
92
+ }
93
+
94
+ /** A per-file verdict, with the failing condition named. */
95
+ export interface EligibilityVerdict {
96
+ readonly path: string;
97
+ readonly eligible: boolean;
98
+ /** Every condition that failed, in predicate order. Empty iff eligible. */
99
+ readonly failures: readonly string[];
100
+ }
101
+
102
+ /** WASM preamble: `\0asm` followed by little-endian version 1. */
103
+ const WASM_MAGIC = [0x00, 0x61, 0x73, 0x6d] as const;
104
+ const WASM_VERSION = [0x01, 0x00, 0x00, 0x00] as const;
105
+
106
+ /**
107
+ * Condition 7, on its own so the header mutation in condition 10 has something
108
+ * specific to defeat.
109
+ */
110
+ export function wasmMagicAndVersionValid(bytes: Uint8Array): boolean {
111
+ if (bytes.length < 8) return false;
112
+ for (let i = 0; i < 4; i += 1) if (bytes[i] !== WASM_MAGIC[i]) return false;
113
+ for (let i = 0; i < 4; i += 1) if (bytes[4 + i] !== WASM_VERSION[i]) return false;
114
+ return true;
115
+ }
116
+
117
+ /**
118
+ * The DEFINED invalid-header mutation for condition 10.
119
+ *
120
+ * One byte of the magic, flipped. Deliberately NOT `corrupt(bytes)`: arbitrary
121
+ * corruption can land in a custom section and leave a still-valid module, or
122
+ * hit padding and change nothing, so a test built on it can pass while proving
123
+ * nothing. This targets a property the predicate actually validates.
124
+ */
125
+ export function definedInvalidHeaderMutation(bytes: Uint8Array): Uint8Array {
126
+ const out = Uint8Array.from(bytes);
127
+ if (out.length > 1) out[1] = (out[1] as number) ^ 0xff;
128
+ return out;
129
+ }
130
+
131
+ /**
132
+ * The DEFINED unsupported-ABI expectation for condition 11.
133
+ *
134
+ * The ABI is not a byte at a known offset — it is what the loader reports after
135
+ * parsing the module — so this mutates the EXPECTATION rather than the bytes:
136
+ * an entry claiming an ABI the grammar does not have must be refused. Same
137
+ * validated property, targeted from the side where it can be stated exactly.
138
+ */
139
+ export function definedUnsupportedAbiEntry(entry: PackedBinaryEntry): PackedBinaryEntry {
140
+ return { ...entry, abi: entry.abi + 1 };
141
+ }
142
+
143
+ /** Condition 2, evaluated over the whole manifest rather than per file. */
144
+ export function wildcardGrants(manifest: PackedBinaryManifest): string[] {
145
+ return manifest.entries
146
+ .filter((e) => e.path.includes('*') || e.path.includes('?') || e.path.endsWith('/'))
147
+ .map((e) => e.path);
148
+ }
149
+
150
+ /** Paths carrying more than one row — condition 1's uniqueness half. */
151
+ export function duplicatePaths(manifest: PackedBinaryManifest): string[] {
152
+ const seen = new Map<string, number>();
153
+ for (const e of manifest.entries) seen.set(e.path, (seen.get(e.path) ?? 0) + 1);
154
+ return [...seen.entries()].filter(([, n]) => n > 1).map(([p]) => p);
155
+ }
156
+
157
+ /**
158
+ * Evaluate every condition for one packed file.
159
+ *
160
+ * `probe` is injected so the caller decides how the production loader is
161
+ * reached — in the gate it is a child process, in a test it can be a stub that
162
+ * returns a wrong ABI. `null` means the loader could not answer, which fails
163
+ * conditions 8 and 9 rather than passing them by default.
164
+ */
165
+ export function evaluateEntry(options: {
166
+ readonly filePath: string;
167
+ readonly packedPaths: ReadonlySet<string>;
168
+ readonly manifest: PackedBinaryManifest;
169
+ readonly bytes: Uint8Array | null;
170
+ readonly probe: GrammarProbe | null;
171
+ }): EligibilityVerdict {
172
+ const { filePath, packedPaths, manifest, bytes, probe } = options;
173
+ const failures: string[] = [];
174
+
175
+ const rows = manifest.entries.filter((e) => e.path === filePath);
176
+ if (rows.length === 0) failures.push('no manifest entry for this path');
177
+ if (rows.length > 1) failures.push('more than one manifest entry for this path');
178
+ const wild = wildcardGrants(manifest);
179
+ if (wild.length > 0) failures.push(`manifest carries wildcard or directory grants: ${wild.join(', ')}`);
180
+ if (!packedPaths.has(filePath)) failures.push('path is not in the packed tarball');
181
+
182
+ const entry = rows[0];
183
+ if (entry === undefined) return { path: filePath, eligible: false, failures };
184
+
185
+ if (bytes === null) {
186
+ failures.push('bytes could not be read');
187
+ return { path: filePath, eligible: false, failures };
188
+ }
189
+ const digest = createHash('sha256').update(bytes).digest('hex');
190
+ if (digest !== entry.sha256) failures.push(`sha256 mismatch: ${digest} != ${entry.sha256}`);
191
+ if (bytes.length !== entry.size) failures.push(`size mismatch: ${String(bytes.length)} != ${String(entry.size)}`);
192
+ if (entry.kind !== VERIFIABLE_KIND) failures.push(`kind is not ${VERIFIABLE_KIND}: ${entry.kind}`);
193
+ if (!wasmMagicAndVersionValid(bytes)) failures.push('WASM magic or version invalid');
194
+
195
+ if (probe === null) {
196
+ failures.push('the production loader could not read this file');
197
+ } else {
198
+ if (probe.grammar_id !== entry.grammar_id) {
199
+ failures.push(`grammar_id mismatch: loader says ${probe.grammar_id}, manifest says ${entry.grammar_id}`);
200
+ }
201
+ if (probe.abi !== entry.abi) {
202
+ failures.push(`ABI mismatch: loader says ${String(probe.abi)}, manifest says ${String(entry.abi)}`);
203
+ }
204
+ }
205
+
206
+ // Conditions 10 and 11 — the predicate refusing what it must refuse. Run
207
+ // here, on every invocation, so a refusal path is never merely asserted in
208
+ // a test somebody can skip.
209
+ if (wasmMagicAndVersionValid(definedInvalidHeaderMutation(bytes))) {
210
+ failures.push('self-check failed: a defined invalid-header mutation was NOT rejected');
211
+ }
212
+ if (probe !== null && probe.abi === definedUnsupportedAbiEntry(entry).abi) {
213
+ failures.push('self-check failed: a defined unsupported-ABI expectation was NOT rejected');
214
+ }
215
+
216
+ return { path: filePath, eligible: failures.length === 0, failures };
217
+ }
218
+
219
+ /** Read and shape-check the manifest. Throws rather than degrading to empty. */
220
+ export function loadManifest(repoRoot: string): PackedBinaryManifest {
221
+ const p = path.join(repoRoot, 'src', 'config', 'packed-binary-manifest.json');
222
+ const raw = JSON.parse(fs.readFileSync(p, 'utf-8')) as PackedBinaryManifest;
223
+ if (!Array.isArray(raw.entries)) throw new Error(`packed-binary-manifest: entries must be an array (${p})`);
224
+ return raw;
225
+ }
@@ -253,10 +253,23 @@ function _parse_cli(argv: string[]): { cwd: string; installed: string; forward:
253
253
  }
254
254
 
255
255
  // Script entry — mirrors the Python `if __name__ == "__main__"` block.
256
+ // `build:delegate-aux` bundles this file with `--splitting`, which can move the
257
+ // module body into a shared chunk: `import.meta.url` then names the CHUNK, the
258
+ // URL comparison below never matches, and the entry silently no-ops. Inside that
259
+ // bundle the invoked file name is the reliable signal (same pattern as
260
+ // src/scripts/_cli/cmd_migrate.ts). Defined by that build only.
261
+ declare const __AGENT_CONFIG_CLI_DELEGATE__: boolean | undefined;
256
262
  function _isCliEntry(): boolean {
257
263
  if (process.argv[1] === undefined) {
258
264
  return false;
259
265
  }
266
+ if (typeof __AGENT_CONFIG_CLI_DELEGATE__ !== 'undefined' && __AGENT_CONFIG_CLI_DELEGATE__) {
267
+ // A miss falls THROUGH to the URL comparison below, which is the
268
+ // symlinked-invocation case that fallback exists for.
269
+ if (path.basename(process.argv[1], '.js') === 'pin_resolver') {
270
+ return true;
271
+ }
272
+ }
260
273
  const argvUrl = pathToFileURL(path.resolve(process.argv[1])).href;
261
274
  if (import.meta.url === argvUrl) {
262
275
  return true;
@@ -18,7 +18,7 @@
18
18
  * of them resolves here, so the substitution is STATED rather than hidden.
19
19
  *
20
20
  * · `agent-config code-graph` — the native code-graph engine (ADR-124). It
21
- * ships `hooks.code_graph.enabled: false`
21
+ * shipped `hooks.code_graph.enabled: false` (a flag retired 2026-09-07)
22
22
  * (`src/config/agent-settings.template.yml:1373-1374`), and
23
23
  * `agent-config code-graph detect` in this checkout answers
24
24
  * `no code-graph source detected`. There is no index to select against.
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Roadmap `status:` values this repository has RETIRED, and the diagnostic a
3
+ * reader gets when one survives in a file.
4
+ *
5
+ * `status: carrier` was deleted by ADR-262. It had two jobs welded together: a
6
+ * structural one (this roadmap is the live destination of an archived parent's
7
+ * `[~]` carries) and an authority one (an autonomous run may not act on it
8
+ * until a human flips it to `ready`). The second was authored by the repository
9
+ * about its own maintainer, was enforced by nothing, and contradicted the drain
10
+ * command's own FORBIDDEN NON-HALT REASONS. The first is a property of the
11
+ * deferral EDGES, which `lint_deferral_integrity` already verifies from both
12
+ * ends, so nothing was left for a node-level status to carry.
13
+ *
14
+ * Deleting a vocabulary term silently is the failure this module exists to
15
+ * prevent: an old file would keep a value nothing reads, and the next author
16
+ * would infer a meaning from its presence. So a retired value is REJECTED with
17
+ * a migration diagnostic rather than ignored.
18
+ *
19
+ * The read is frontmatter-scoped on purpose, and that is inherited rather than
20
+ * newly decided: a fenced `status: carrier` inside a roadmap documenting the
21
+ * syntax is not a declaration, and a body-wide match turned exactly that into a
22
+ * hard failure before a review caught it.
23
+ */
24
+
25
+ /** Retired `status:` values, mapped to the record that retired each one. */
26
+ export const RETIRED_STATUS_VALUES: ReadonlyMap<string, string> = new Map([
27
+ ['carrier', 'ADR-262'],
28
+ ]);
29
+
30
+ /** The leading `---\n…\n---` block, or null when the text carries none. */
31
+ export function frontmatterBlock(text: string): string | null {
32
+ const m = /^---\n([\s\S]*?)\n---/.exec(text);
33
+ return m === null ? null : (m[1] ?? '');
34
+ }
35
+
36
+ /** The frontmatter `status:` value, lower-cased, or null when absent. */
37
+ export function frontmatterStatus(text: string | null | undefined): string | null {
38
+ if (text === null || text === undefined) {
39
+ return null;
40
+ }
41
+ const fm = frontmatterBlock(text);
42
+ if (fm === null) {
43
+ return null;
44
+ }
45
+ const m = /^status:[ \t]*([A-Za-z-]+)[ \t]*$/m.exec(fm);
46
+ return m === null ? null : (m[1] ?? '').toLowerCase();
47
+ }
48
+
49
+ /**
50
+ * The retired value this text declares, or null when it declares none.
51
+ *
52
+ * Returns the value rather than a boolean so the caller can name it in the
53
+ * diagnostic — "status: carrier is retired" is actionable, "invalid status" is
54
+ * not.
55
+ */
56
+ export function declaresRetiredStatus(text: string | null | undefined): string | null {
57
+ const status = frontmatterStatus(text);
58
+ if (status === null) {
59
+ return null;
60
+ }
61
+ return RETIRED_STATUS_VALUES.has(status) ? status : null;
62
+ }
63
+
64
+ /** The migration diagnostic for a retired value. Names the record and the fix. */
65
+ export function retiredStatusDiagnostic(value: string): string {
66
+ const record = RETIRED_STATUS_VALUES.get(value) ?? 'a decision record';
67
+ return (
68
+ `declares the retired \`status: ${value}\`, deleted by ${record}. ` +
69
+ 'A `status:` value no longer decides whether an autonomous run may act on ' +
70
+ 'a roadmap. Use `status: ready` and put any genuine human-ACTION gate in a ' +
71
+ '`## Blockers` entry, or park the file under `agents/roadmaps/later/` with ' +
72
+ 'an `entry_condition`. Both are read by a gate; a status is not.'
73
+ );
74
+ }
@@ -75,40 +75,146 @@
75
75
  */
76
76
 
77
77
  import * as fs from 'node:fs';
78
+ import * as os from 'node:os';
78
79
  import * as path from 'node:path';
79
80
 
80
- /** Catalogue roots tried in order; the first that exists wins. */
81
- export const DEFAULT_CATALOGUE_ROOTS = ['.claude/skills', 'src/skills'] as const;
81
+ /**
82
+ * Catalogue roots under the WORKSPACE, in precedence order.
83
+ *
84
+ * `src/skills` is the authored tree and exists only in a maintainer checkout.
85
+ * `.claude/skills` is the project-scope projection — a consumer's own skills
86
+ * live there, and so do this repository's flat-command wrappers.
87
+ */
88
+ export const PROJECT_CATALOGUE_ROOTS = ['src/skills', '.claude/skills'] as const;
89
+
90
+ /**
91
+ * The catalogue root under the user's HOME — what the host itself delivers.
92
+ *
93
+ * Home-relative rather than workspace-relative, which is why it cannot live in
94
+ * the list above: `~/.claude/skills` is one directory for every project on the
95
+ * machine, and on a consumer machine it is where `agent-config install` puts the
96
+ * shipped skills.
97
+ */
98
+ export const HOST_CATALOGUE_ROOT = '.claude/skills';
99
+
100
+ /**
101
+ * The PROJECTION root, on its own, for the consumers that need exactly it.
102
+ *
103
+ * `capture_skill_catalogue` compares what a host DELIVERED against what was
104
+ * PROJECTED, so the authored tree is the wrong answer for it even though the
105
+ * ranker wants the authored tree first. Both questions were sharing
106
+ * `DEFAULT_CATALOGUE_ROOTS` and the 2026-09-07 reorder silently changed the
107
+ * second one — the finding a neutral review returned the same day. One constant
108
+ * for two questions is the defect; this is the second question, named.
109
+ */
110
+ export const PROJECTION_CATALOGUE_ROOT = '.claude/skills';
82
111
 
83
112
  /**
84
- * First existing catalogue root under `workspaceRoot`, or `null`.
113
+ * Every root the resolver looks at, as human-readable LABELS.
114
+ *
115
+ * **Labels, never path inputs — this changed on 2026-09-07.**
116
+ *
117
+ * It used to be the resolver's own candidate list and callers joined it onto a
118
+ * root. It now carries a `~/`-prefixed entry, which `path.join(repo, ...)` turns
119
+ * into `<repo>/~/.claude/skills` — a candidate that can never exist. Use
120
+ * {@link resolveSkillCatalogueRoots} to RESOLVE and this list only to REPORT
121
+ * what was searched.
122
+ *
123
+ * For the `searched:` / `tried:` fields of the no-catalogue answers only — a
124
+ * reader needs to know `~` was consulted, and an absolute home path in a
125
+ * machine-readable payload is both noise and a small privacy leak.
126
+ */
127
+ export const DEFAULT_CATALOGUE_ROOTS = [
128
+ ...PROJECT_CATALOGUE_ROOTS,
129
+ `~/${HOST_CATALOGUE_ROOT}`,
130
+ ] as const;
131
+
132
+ /**
133
+ * EVERY readable, non-empty catalogue root, in precedence order.
134
+ *
135
+ * Shared by the `skill-route` concern and the `suggest_skill_for_task` MCP
136
+ * handler on purpose: two resolvers over one catalogue is how a ranker and the
137
+ * tool that exposes it start ranking different trees.
138
+ *
139
+ * **Why a LIST, and not the first hit — owner decision, 2026-09-07.**
140
+ *
141
+ * This returned one root and stopped at the first match, which made the ranker
142
+ * see one tree and the session see another. A consumer project can carry its own
143
+ * skills in `<project>/.claude/skills` while the shipped catalogue sits in
144
+ * `~/.claude/skills`; first-hit-wins ranked the smaller of the two and reported
145
+ * the result as if it were the catalogue. Ranking the UNION ranks what the
146
+ * session can actually reach, which is the only set the answer is about.
147
+ *
148
+ * **The order, and what it is for.**
85
149
  *
86
- * `.claude/skills` before `src/skills` because a CONSUMER install carries the
87
- * former and only a maintainer checkout carries the latter. Shared by the
88
- * `skill-route` concern and the `suggest_skill_for_task` MCP handler on
89
- * purpose: two resolvers over one catalogue is how a ranker and the tool that
90
- * exposes it start ranking different trees.
150
+ * Precedence applies to a NAME COLLISION, never to which roots are read:
151
+ *
152
+ * 1. `src/skills` — the authored tree. In this repository it is the truth, and
153
+ * a maintainer editing a skill must see their edit ranked, not the
154
+ * projection of the last `generate-tools` run.
155
+ * 2. `<project>/.claude/skills` — project scope beats machine scope, the same
156
+ * direction every other per-project override in this suite takes.
157
+ * 3. `~/${HOST_CATALOGUE_ROOT}` — the host-global catalogue.
158
+ *
159
+ * **The two defects this replaced, both real.**
160
+ *
161
+ * It used to try `.claude/skills` FIRST, "because a CONSUMER install carries the
162
+ * former and only a maintainer checkout carries the latter". The premise was
163
+ * right and the conclusion did not follow: a consumer has no `src/skills`, so
164
+ * reading it first costs one `existsSync`.
165
+ *
166
+ * The ADR-236 amendment of 2026-09-07 turned that order into a live defect. The
167
+ * project layer now withholds every skill `~/.claude/skills` carries, so this
168
+ * repository's `.claude/skills` holds only the ~49 flat-command WRAPPERS —
169
+ * non-empty, so the old order resolved it, and each wrapper carries a `SKILL.md`,
170
+ * so nothing downstream could tell it was not the catalogue. Measured the same
171
+ * day: the `skill-route` hook ranked over 49 command wrappers instead of 299
172
+ * skills. Under the union those wrappers are still ranked, which is correct —
173
+ * they are reachable — but they no longer displace the catalogue.
174
+ *
175
+ * The non-empty guard keeps its own separate job: an EMPTY root read as a match
176
+ * made the ranker report an empty catalogue as an empty RESULT, the silent
177
+ * failure `road-to-inbox-harvest-2026-08-f-skill-selection-evidence` is about.
178
+ * Measured in that run: a worktree that had never run `generate-tools` ranked
179
+ * zero skills and exited 0 on a task that scores 47 against `src/skills`.
180
+ *
181
+ * @param home override for the user's home; production callers omit it.
91
182
  */
92
- export function resolveSkillsRoot(workspaceRoot: string): string | null {
93
- for (const candidate of DEFAULT_CATALOGUE_ROOTS) {
94
- const abs = path.join(workspaceRoot, candidate);
183
+ export function resolveSkillCatalogueRoots(workspaceRoot: string, home?: string): string[] {
184
+ const userHome = home ?? process.env['HOME'] ?? os.homedir();
185
+ const candidates = [
186
+ ...PROJECT_CATALOGUE_ROOTS.map((c) => path.join(workspaceRoot, c)),
187
+ path.join(userHome, HOST_CATALOGUE_ROOT),
188
+ ];
189
+ const roots: string[] = [];
190
+ const seen = new Set<string>();
191
+ for (const abs of candidates) {
192
+ // A workspace that IS the home directory would offer `~/.claude/skills`
193
+ // twice and rank every skill in it twice. Cheap to exclude, and the
194
+ // duplicate would be invisible in the output.
195
+ if (seen.has(abs)) continue;
196
+ seen.add(abs);
95
197
  try {
96
198
  if (!fs.existsSync(abs) || !fs.statSync(abs).isDirectory()) continue;
97
- // NON-EMPTY, and that qualifier is the whole fix. `.claude/skills`
98
- // is a gitignored projection, so in a fresh worktree it EXISTS and
99
- // holds nothing — and an empty root resolved as a match makes the
100
- // ranker report an empty catalogue as an empty RESULT, which is the
101
- // silent failure `road-to-inbox-harvest-2026-08-f-skill-selection-evidence`
102
- // is about, arriving through a second door. Measured in that run: a
103
- // worktree that had never run `generate-tools` ranked zero skills
104
- // and exited 0 on a task that scores 47 against `src/skills`.
105
199
  if (fs.readdirSync(abs).length === 0) continue;
106
- return abs;
200
+ roots.push(abs);
107
201
  } catch {
108
202
  // Unreadable candidate is not a match; try the next.
109
203
  }
110
204
  }
111
- return null;
205
+ return roots;
206
+ }
207
+
208
+ /**
209
+ * The highest-precedence root, or `null` when none is readable.
210
+ *
211
+ * Kept for the surfaces that genuinely name ONE directory — a report line, a
212
+ * `skills_root` field. Anything that RANKS should take
213
+ * {@link resolveSkillCatalogueRoots} instead: ranking the first root only is the
214
+ * defect that function exists to remove.
215
+ */
216
+ export function resolveSkillsRoot(workspaceRoot: string, home?: string): string | null {
217
+ return resolveSkillCatalogueRoots(workspaceRoot, home)[0] ?? null;
112
218
  }
113
219
 
114
220
  /** Where observations accumulate. Append-only, one JSON object per line. */
@@ -223,6 +329,35 @@ function descriptionOf(frontmatter: string): string {
223
329
  * the order a host listing follows and therefore the order a positional
224
330
  * hypothesis is measured against.
225
331
  */
332
+ /**
333
+ * The catalogue as the RANKER sees it — every readable root, first name winning.
334
+ *
335
+ * Added 2026-09-07 after a second neutral review. `capture_skill_catalogue`'s D-4
336
+ * join says it reads "the catalogue the runtime ranker reads", and once the ranker
337
+ * moved to the union a single-root read stopped being that. Worse, the root the
338
+ * capture had been pointed at — `.claude/skills` — is the one the ADR-236
339
+ * amendment EMPTIED of skills: the join then read 49 command wrappers as the
340
+ * catalogue and published `pointableBare: 0` against `unpointableBare: 16`, a
341
+ * clean-looking verdict off the wrong tree, with the empty-guard silent because
342
+ * 49 is not 0.
343
+ *
344
+ * Positions are re-derived over the merged list, because a position is a property
345
+ * of the catalogue the host is shown, not of the root a name came from.
346
+ */
347
+ export function readCatalogueAcross(roots: readonly string[]): CatalogueEntry[] {
348
+ const seen = new Set<string>();
349
+ const merged: CatalogueEntry[] = [];
350
+ for (const root of roots) {
351
+ for (const entry of readProjectedCatalogue(root)) {
352
+ if (seen.has(entry.name)) continue;
353
+ seen.add(entry.name);
354
+ merged.push(entry);
355
+ }
356
+ }
357
+ merged.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
358
+ return merged.map((e, i) => ({ ...e, position: i + 1 }));
359
+ }
360
+
226
361
  export function readProjectedCatalogue(root: string): CatalogueEntry[] {
227
362
  // Membership is decided by "does <name>/SKILL.md resolve", never by
228
363
  // `Dirent.isDirectory()`. The host-facing projection is a tree of SYMLINKS