@event4u/agent-config 14.20.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 (242) 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 +441 -0
  5. package/CREDITS.md +37 -0
  6. package/README.md +1 -1
  7. package/agents/templates/.ai-council.yml.example +10 -0
  8. package/dist/agent-src/commands/analyze/inbox.md +381 -6
  9. package/dist/agent-src/commands/analyze/{reference-repo.md → repo.md} +251 -34
  10. package/dist/agent-src/commands/analyze/roadmap-repos.md +129 -0
  11. package/dist/agent-src/commands/analyze.md +12 -7
  12. package/dist/agent-src/commands/feature/plan.md +55 -16
  13. package/dist/agent-src/commands/feature/refactor.md +8 -3
  14. package/dist/agent-src/commands/humanize.md +48 -4
  15. package/dist/agent-src/contexts/communication/rules-auto/token-efficiency-mechanics.md +69 -0
  16. package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +5 -5
  17. package/dist/agent-src/contexts/execution/contract-decision-sheet.md +22 -1
  18. package/dist/agent-src/contexts/execution/host-capability-manifest.md +4 -1
  19. package/dist/agent-src/contexts/execution/roadmap-process-loop.md +35 -2
  20. package/dist/agent-src/contexts/execution/terminal-states.md +4 -1
  21. package/dist/agent-src/rules/evaluator-independence.md +4 -4
  22. package/dist/agent-src/rules/external-code-graph-interop.md +19 -14
  23. package/dist/agent-src/rules/external-reference-deep-dive.md +2 -2
  24. package/dist/agent-src/rules/token-efficiency.md +26 -3
  25. package/dist/agent-src/scripts/_cli_entry.ts +51 -0
  26. package/dist/agent-src/scripts/archive_completed_roadmaps.ts +14 -23
  27. package/dist/agent-src/scripts/roadmap_gates.ts +4 -20
  28. package/dist/agent-src/scripts/roadmap_set_step.ts +5 -2
  29. package/dist/agent-src/scripts/stubs_due.ts +4 -18
  30. package/dist/agent-src/scripts/update_roadmap_progress.ts +7 -24
  31. package/dist/agent-src/skills/code-intelligence/SKILL.md +25 -0
  32. package/dist/agent-src/skills/competitive-positioning/SKILL.md +2 -2
  33. package/dist/agent-src/skills/humanizer/references/anti-aiisms.md +27 -11
  34. package/dist/agent-src/skills/learning-to-rule-or-skill/SKILL.md +1 -1
  35. package/dist/agent-src/skills/markitdown/SKILL.md +1 -1
  36. package/dist/agent-src/skills/repomix-packer/SKILL.md +1 -1
  37. package/dist/agent-src/skills/roadmap-writing/SKILL.md +17 -0
  38. package/dist/agent-src/skills/technical-specification/SKILL.md +4 -0
  39. package/dist/agent-src/templates/agent-settings.md +0 -1
  40. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  41. package/dist/agent-src/templates/features.md +5 -0
  42. package/dist/agent-src/templates/roadmaps.md +15 -0
  43. package/dist/cli-delegate/_cli_entry.js +8 -0
  44. package/dist/cli-delegate/_lib/pin_resolver.js +170 -0
  45. package/dist/cli-delegate/archival_sweep.js +8 -0
  46. package/dist/cli-delegate/archive_completed_roadmaps.js +494 -0
  47. package/dist/cli-delegate/check_update_banner.js +923 -0
  48. package/dist/cli-delegate/{chunk-KPBWFOPZ.js → chunk-2FTMENJL.js} +194 -37
  49. package/dist/cli-delegate/{chunk-PI2GU3DP.js → chunk-2TAQ2K4V.js} +4 -1
  50. package/dist/cli-delegate/chunk-5EK5WQN6.js +28 -0
  51. package/dist/cli-delegate/chunk-7XGD2OEU.js +96 -0
  52. package/dist/cli-delegate/{chunk-C6IPZRDA.js → chunk-BIXEDQPZ.js} +7 -1
  53. package/dist/cli-delegate/chunk-DBH4AHDK.js +155 -0
  54. package/dist/cli-delegate/{chunk-NUMQHLS5.js → chunk-J3RFNKG4.js} +21 -2
  55. package/dist/cli-delegate/chunk-JO25G5H7.js +20 -0
  56. package/dist/cli-delegate/chunk-LTYVIY76.js +7961 -0
  57. package/dist/cli-delegate/chunk-QAJS5YWY.js +999 -0
  58. package/dist/cli-delegate/{chunk-ZVBOHSVF.js → chunk-TGIPZBCW.js} +1 -1
  59. package/dist/cli-delegate/{chunk-YS6XTLEK.js → chunk-TYSY33MD.js} +56 -6
  60. package/dist/cli-delegate/chunk-ULET4BYS.js +328 -0
  61. package/dist/cli-delegate/chunk-YDQSHTD7.js +187 -0
  62. package/dist/cli-delegate/chunk-YWVXLR44.js +48 -0
  63. package/dist/cli-delegate/chunk-ZZQBUHFK.js +273 -0
  64. package/dist/cli-delegate/cmd_conformance.js +19 -5
  65. package/dist/cli-delegate/cmd_doctor.js +3 -3
  66. package/dist/cli-delegate/cmd_export.js +2 -2
  67. package/dist/cli-delegate/cmd_handoff.js +27 -3
  68. package/dist/cli-delegate/cmd_prune.js +2 -2
  69. package/dist/cli-delegate/cmd_session_recycle.js +2 -2
  70. package/dist/cli-delegate/cmd_settings_migrate.js +2 -2
  71. package/dist/cli-delegate/cmd_sync.js +2 -2
  72. package/dist/cli-delegate/cmd_uninstall.js +2 -2
  73. package/dist/cli-delegate/cmd_validate.js +2 -2
  74. package/dist/cli-delegate/dashboard_mode.js +16 -0
  75. package/dist/cli-delegate/gate_budget.js +24 -0
  76. package/dist/cli-delegate/gate_execute.js +25 -0
  77. package/dist/cli-delegate/guarded_baseline.js +18 -0
  78. package/dist/cli-delegate/resume_probe.js +22 -0
  79. package/dist/cli-delegate/roadmap_gates.js +638 -0
  80. package/dist/cli-delegate/roadmap_set_step.js +173 -0
  81. package/dist/cli-delegate/stub_queue.js +47 -0
  82. package/dist/cli-delegate/stubs_due.js +38 -0
  83. package/dist/cli-delegate/ticket_bundles.js +59 -0
  84. package/dist/cli-delegate/update_roadmap_progress.js +60 -0
  85. package/dist/discovery/deprecation-report.md +1 -1
  86. package/dist/discovery/discovery-manifest.json +60 -29
  87. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  88. package/dist/discovery/discovery-manifest.summary.md +3 -3
  89. package/dist/discovery/dormancy-report.md +1 -1
  90. package/dist/discovery/orphan-report.md +1 -1
  91. package/dist/discovery/packs.json +7 -6
  92. package/dist/discovery/trust-report.md +2 -2
  93. package/dist/discovery/workspaces.json +6 -5
  94. package/dist/hooks/dispatch.js +1522 -1008
  95. package/dist/install/claudeLayerCarriage.js +237 -0
  96. package/dist/install/claudePathsPlan.js +79 -0
  97. package/dist/install/hostLayerFingerprint.js +1 -1
  98. package/dist/install/install.mjs +10 -1
  99. package/dist/install/partitionEligibility.js +231 -117
  100. package/dist/install/ruleLayerPartition.js +10 -7
  101. package/dist/mcp/registry-manifest.json +3 -3
  102. package/dist/mcp/server.json +2 -2
  103. package/dist/mcp/server.mjs +39376 -38902
  104. package/dist/scripts/ai_council/content_ceiling.js +63 -0
  105. package/dist/scripts/ai_council/seat_policy.js +106 -0
  106. package/dist/scripts/ai_council/transport_resolver.js +21 -0
  107. package/dist/scripts/condense.js +32 -25
  108. package/docs/MIGRATION.md +49 -1
  109. package/docs/contracts/settings-classes.md +1 -1
  110. package/docs/guidelines/agent-infra/minimal-safe-diff-mechanics.md +73 -0
  111. package/llms.txt +14 -14
  112. package/package.json +7 -4
  113. package/src/agent-src/scripts/_cli_entry.ts +51 -0
  114. package/src/agent-src/scripts/archive_completed_roadmaps.ts +14 -23
  115. package/src/agent-src/scripts/roadmap_gates.ts +4 -20
  116. package/src/agent-src/scripts/roadmap_set_step.ts +5 -2
  117. package/src/agent-src/scripts/stubs_due.ts +4 -18
  118. package/src/agent-src/scripts/update_roadmap_progress.ts +7 -24
  119. package/src/config/continuity-surface.json +509 -0
  120. package/src/config/estate-count-budget.json +1 -1
  121. package/src/config/gate-coverage.yml +29 -8
  122. package/src/config/gate-violation-baselines.json +8 -8
  123. package/src/config/gitignore-block.txt +19 -0
  124. package/src/config/host-injection-effect.json +73 -0
  125. package/src/config/packed-binary-manifest.json +33 -0
  126. package/src/config/preamble-payload-budget.json +112 -105
  127. package/src/config/publish-surface.json +4 -2
  128. package/src/config/release-gate-locality.yml +4 -3
  129. package/src/install/claudeLayerCarriage.ts +273 -0
  130. package/src/install/claudePathsPlan.ts +76 -0
  131. package/src/install/hostLayerFingerprint.ts +1 -1
  132. package/src/install/partitionEligibility.ts +261 -127
  133. package/src/install/ruleLayerPartition.ts +19 -23
  134. package/src/scripts/_dispatch.bash +77 -27
  135. package/src/scripts/_lib/auto_dispatch.ts +2 -2
  136. package/src/scripts/_lib/blocked_by_marker.ts +189 -0
  137. package/src/scripts/_lib/catalogue_layer_parity.ts +146 -0
  138. package/src/scripts/_lib/continuity_surface.ts +296 -0
  139. package/src/scripts/_lib/corpus_manifest.ts +26 -9
  140. package/src/scripts/_lib/hook_settings.ts +1 -1
  141. package/src/scripts/_lib/host_capability.ts +38 -1
  142. package/src/scripts/_lib/injection_effect.ts +151 -0
  143. package/src/scripts/_lib/layer_overlap_notice.ts +50 -6
  144. package/src/scripts/_lib/packed_binary_predicate.ts +225 -0
  145. package/src/scripts/_lib/pin_resolver.ts +13 -0
  146. package/src/scripts/_lib/regression_neighbourhood.ts +1 -1
  147. package/src/scripts/_lib/release_highlights.ts +19 -211
  148. package/src/scripts/_lib/release_material.ts +27 -77
  149. package/src/scripts/_lib/retired_status.ts +74 -0
  150. package/src/scripts/_lib/skill_catalogue.ts +156 -21
  151. package/src/scripts/_lib/structured_ask.ts +117 -0
  152. package/src/scripts/_lib/subagent_capsule.ts +117 -8
  153. package/src/scripts/_lib/tty_prompt.ts +76 -0
  154. package/src/scripts/ai_council/api_transport.ts +168 -0
  155. package/src/scripts/ai_council/clients.ts +58 -63
  156. package/src/scripts/ai_council/config.ts +13 -0
  157. package/src/scripts/ai_council/content_ceiling.ts +79 -0
  158. package/src/scripts/ai_council/deliberation_metrics.ts +353 -0
  159. package/src/scripts/ai_council/jury_aggregate.ts +178 -0
  160. package/src/scripts/ai_council/prompts.ts +9 -0
  161. package/src/scripts/ai_council/seat_policy.ts +137 -0
  162. package/src/scripts/ai_council/status_surface.ts +79 -0
  163. package/src/scripts/ai_council/synthesis_disagreement.ts +206 -0
  164. package/src/scripts/ai_council/transport_resolver.ts +59 -1
  165. package/src/scripts/ai_tells_rules.ts +446 -5
  166. package/src/scripts/ask_block_census.ts +499 -0
  167. package/src/scripts/bench_humanizer_eval.ts +264 -40
  168. package/src/scripts/bench_prose_tells_fp.ts +198 -0
  169. package/src/scripts/capture_skill_catalogue.ts +24 -11
  170. package/src/scripts/check_branch_work_committed.ts +234 -0
  171. package/src/scripts/check_candidate_lines.ts +499 -0
  172. package/src/scripts/check_continuity_surface.ts +475 -0
  173. package/src/scripts/check_dependency_floors.ts +13 -6
  174. package/src/scripts/check_estate_count.ts +40 -79
  175. package/src/scripts/check_pack_size.ts +263 -24
  176. package/src/scripts/check_release_highlights.ts +0 -157
  177. package/src/scripts/check_roadmap_trackable.ts +19 -16
  178. package/src/scripts/check_rule_layer_partition.ts +54 -34
  179. package/src/scripts/check_single_delivery.ts +97 -31
  180. package/src/scripts/check_update_banner.ts +13 -0
  181. package/src/scripts/code_graph/build.ts +138 -15
  182. package/src/scripts/code_graph/cli.ts +76 -8
  183. package/src/scripts/code_graph/extract.ts +11 -0
  184. package/src/scripts/code_graph/grammar_probe.ts +43 -0
  185. package/src/scripts/code_graph/loader.ts +88 -14
  186. package/src/scripts/code_graph/query.ts +121 -11
  187. package/src/scripts/code_graph/resolution_tiers.ts +238 -0
  188. package/src/scripts/code_graph/sqlite_store.ts +247 -6
  189. package/src/scripts/code_graph/types.ts +66 -1
  190. package/src/scripts/code_graph/validate.ts +23 -0
  191. package/src/scripts/condense.ts +34 -22
  192. package/src/scripts/corpus_manifest.ts +5 -5
  193. package/src/scripts/council_cli.ts +29 -39
  194. package/src/scripts/count_intent_disagreement.ts +426 -0
  195. package/src/scripts/detect_ai_tells.ts +142 -20
  196. package/src/scripts/handoff_context_hook.ts +79 -0
  197. package/src/scripts/harvest_reference_tokens.ts +698 -0
  198. package/src/scripts/hook_effect_doctor.ts +82 -1
  199. package/src/scripts/hook_manifest.json +1 -1
  200. package/src/scripts/hook_manifest.yaml +94 -12
  201. package/src/scripts/hooks/chain_nudge_hook.ts +282 -0
  202. package/src/scripts/hooks/code_graph_context_hook.ts +212 -0
  203. package/src/scripts/hooks/concern_registry.ts +6 -2
  204. package/src/scripts/hooks/delegation_nudge_hook.ts +45 -1
  205. package/src/scripts/hooks/host_semantics.ts +1 -1
  206. package/src/scripts/hooks/one_question_per_ask_hook.ts +191 -0
  207. package/src/scripts/hooks/orchestration_record_hook.ts +2 -2
  208. package/src/scripts/hooks/rule_inject_hook.ts +1 -1
  209. package/src/scripts/hooks/skill_route_hook.ts +11 -4
  210. package/src/scripts/inbox_source_census.ts +322 -0
  211. package/src/scripts/install-hooks.sh +134 -0
  212. package/src/scripts/{lint_carrier_integrity.ts → lint_deferral_integrity.ts} +60 -214
  213. package/src/scripts/lint_plan_risk_register.ts +13 -17
  214. package/src/scripts/lint_roadmap_blockers.ts +51 -1
  215. package/src/scripts/mcp_server/suggest_skill.ts +72 -0
  216. package/src/scripts/mcp_server/tools.ts +2 -56
  217. package/src/scripts/media/lib/adapter-contract.md +7 -2
  218. package/src/scripts/media/lib/fixtures/flux/asset-0001.png +0 -0
  219. package/src/scripts/media/lib/fixtures/gemini-image/asset-0001.png +0 -0
  220. package/src/scripts/media/lib/fixtures/ideogram/asset-0001.png +0 -0
  221. package/src/scripts/migrate_command_suggestions.ts +1 -1
  222. package/src/scripts/probe_unblocked_ask.ts +135 -2
  223. package/src/scripts/rdp_candidates_delta.ts +276 -0
  224. package/src/scripts/rdp_quality_eval.ts +217 -19
  225. package/src/scripts/release.ts +26 -71
  226. package/src/scripts/release_drill.ts +6 -10
  227. package/src/scripts/release_env.ts +1 -0
  228. package/src/scripts/release_mix_taxonomy.json +3 -2
  229. package/src/scripts/release_publication.ts +34 -29
  230. package/src/scripts/report_host_injection_effect.ts +189 -0
  231. package/src/scripts/report_layer_overlap.ts +41 -0
  232. package/src/scripts/report_skill_menu_census.ts +341 -0
  233. package/src/scripts/self_review_gate.ts +409 -15
  234. package/src/scripts/skill_tools/score_skill_relevance.ts +70 -6
  235. package/src/scripts/skill_tools/suggest_skill_for_task.ts +37 -3
  236. package/src/vendor/grammars/README.md +61 -0
  237. package/src/vendor/grammars/tree-sitter-javascript.wasm +0 -0
  238. package/src/vendor/grammars/tree-sitter-php.wasm +0 -0
  239. package/src/vendor/grammars/tree-sitter-typescript.wasm +0 -0
  240. package/src/scripts/_lib/carrier_status.ts +0 -118
  241. package/src/scripts/_lib/estate_carrier_cases.ts +0 -72
  242. package/src/scripts/hooks/code_graph_nudge_hook.ts +0 -191
@@ -71,18 +71,12 @@ import {
71
71
  collect_span_commits,
72
72
  derive_categories,
73
73
  parse_git_log,
74
- mix_response_blockers,
75
- previous_promise,
76
74
  previous_release_tag,
77
- promise_readback_blockers,
78
75
  stale_draft_labels,
79
76
  } from './_lib/release_highlights.js';
80
- import { loadTaxonomy, measureRange } from './measure_release_mix.js';
81
77
  import {
82
78
  CURATED_HEAD_INSTRUCTION,
83
- PROMISE_READBACK_MARKER,
84
79
  extract_changelog_section,
85
- previous_changelog_version,
86
80
  } from './_lib/release_material.js';
87
81
  import { assertScanned, assertWatchlistResolves, DeadScopeError } from './_lib/scan_scope.js';
88
82
 
@@ -244,23 +238,6 @@ export function main(argv: readonly string[]): number {
244
238
  );
245
239
  return 1;
246
240
  }
247
- // Hoisted above the curated-head branches on purpose. It used to sit inside
248
- // `contradictions.length === 0`, so CI reached it only for a section that
249
- // HAS a head, carries no unrewritten draft and contradicts nothing — while
250
- // the local guards call the same predicate unconditionally. A section
251
- // without a curated head was therefore refused locally and waved through by
252
- // CI: one predicate, two different gatekeepers, which is the drift this
253
- // change exists to remove.
254
- const mix = check_governance_mix_response(section.body, from, to, version);
255
- if (mix !== 0) {
256
- return mix;
257
- }
258
-
259
- const readback = check_previous_promise_readback(changelogText, section.body, version);
260
- if (readback !== 0) {
261
- return readback;
262
- }
263
-
264
241
  const curated = parse_curated_head(section.body);
265
242
  if (!curated) {
266
243
  process.stdout.write(`ℹ️ no curated head in the ${version} section — nothing to check\n`);
@@ -356,140 +333,6 @@ export function main(argv: readonly string[]): number {
356
333
  return 1;
357
334
  }
358
335
 
359
- /**
360
- * The governance-versus-product response, checked for PRESENCE, never for the
361
- * ratio (ADR-253).
362
- *
363
- * `measure_release_mix` publishes a level. This is the one place that level has
364
- * a consequence, and the consequence is deliberately about completeness of the
365
- * written answer: when governance-only commits strictly outnumber consumer-only
366
- * commits over the release span, the section under release must carry a written
367
- * response naming either the next cycle's consumer work or a maintainer
368
- * justification. No number is enforced, and no number can be — both council
369
- * seats refused a threshold on fewer than two readings, and a gate with no
370
- * threshold that blocks on the ratio is a smuggled threshold.
371
- *
372
- * The response lives OUTSIDE the curated head, as its own `> **Governance
373
- * mix:**` line, so it does not consume the head's ten-line cap and does not
374
- * become a sixth label every historical section would suddenly lack.
375
- *
376
- * Scope matches every other check here: only the section under release is read.
377
- *
378
- * A measurement failure DEGRADES to a printed warning rather than to a refusal.
379
- * This is a governance signal, not a correctness or security control, and a
380
- * shallow clone or a missing tag on the release path would otherwise turn an
381
- * unrelated environment fact into a blocked release.
382
- */
383
- export function check_governance_mix_response(
384
- body: string,
385
- from: string,
386
- to: string,
387
- version: string,
388
- ): number {
389
- let reading;
390
- try {
391
- reading = measureRange(from, to, loadTaxonomy(), version, REPO_ROOT);
392
- } catch (err) {
393
- process.stdout.write(
394
- `⚠️ governance mix not measured for ${version}: ${(err as Error).message}\n`,
395
- );
396
- return 0;
397
- }
398
- const o = reading.response_obligation;
399
- const level = `governance-only ${o.governance_only} vs consumer-only ${o.consumer_only} (taxonomy ${reading.taxonomy_version})`;
400
- if (!o.triggered) {
401
- process.stdout.write(`✅ governance mix for ${version}: ${level} — no response owed\n`);
402
- return 0;
403
- }
404
- // ONE predicate, shared with `guard_release_branch_push` — see
405
- // `mix_response_blockers`. This side prints and exits; the local side dies
406
- // before anything leaves the machine. Neither owns the rule.
407
- const blockers = mix_response_blockers(body, version, `\`release/${version}\``, {
408
- triggered: true,
409
- level,
410
- });
411
- if (blockers.length === 0) {
412
- process.stdout.write(`✅ governance mix for ${version}: ${level} — response present\n`);
413
- return 0;
414
- }
415
- process.stderr.write(
416
- `❌ the ${version} section owes a governance-versus-product response: ${level}.\n` +
417
- blockers.map((b) => ` - ${b}\n`).join('') +
418
- ' Governance-only commits outnumber consumer-only commits over the release span, so\n' +
419
- ' the section carries a written response naming either the next cycle\'s consumer work\n' +
420
- ' or a maintainer justification (docs/contracts/CHANGELOG-conventions.md § Governance-\n' +
421
- ' versus-product response; the decline this replaces is ADR-253).\n' +
422
- ' Only that section is read; historical sections are out of scope. No ratio is\n' +
423
- ' enforced — this blocks a MISSING answer, never a particular number.\n' +
424
- ' Reproduce locally, before any push:\n' +
425
- ` ./scripts-run src/scripts/check_release_highlights --version ${version}\n`,
426
- );
427
- return 1;
428
- }
429
-
430
- /**
431
- * The previous release's promise, read back at the next release.
432
- *
433
- * `_lib/release_material.ts` generates `Next cycle ships …` and, until this
434
- * check existed, nothing ever asked what became of it: 14.18.0 promised the MCP
435
- * bridge repair, and no gate in the tree would have noticed the same promise
436
- * being reprinted in 14.19.0 and 14.20.0. A commitment nothing reads back is
437
- * free to restate, and one that is free to restate is not a commitment.
438
- *
439
- * Only the section under release is read, and only the previous section's
440
- * governance-response block — the same scope every other check here uses, so a
441
- * historical section cannot red a future release.
442
- *
443
- * The check is about ANSWERING, never about having kept the promise. A gate
444
- * that refused an unkept promise would price honesty above silence and teach
445
- * the next author to promise nothing.
446
- */
447
- export function check_previous_promise_readback(
448
- changelogText: string,
449
- body: string,
450
- version: string,
451
- ): number {
452
- const previousVersion = previous_changelog_version(changelogText, version);
453
- if (previousVersion === null) {
454
- process.stdout.write(`✅ no previous CHANGELOG section before ${version} — no promise to read back\n`);
455
- return 0;
456
- }
457
- const promise = previous_promise(changelogText, previousVersion);
458
- if (promise === null) {
459
- process.stdout.write(
460
- `✅ the ${previousVersion} section made no next-cycle promise — nothing owed by ${version}\n`,
461
- );
462
- return 0;
463
- }
464
- const blockers = promise_readback_blockers(
465
- body,
466
- version,
467
- previousVersion,
468
- promise,
469
- `\`release/${version}\``,
470
- );
471
- if (blockers.length === 0) {
472
- process.stdout.write(`✅ ${version} answers the ${previousVersion} next-cycle promise\n`);
473
- return 0;
474
- }
475
- process.stderr.write(
476
- `❌ the ${version} section owes an answer to the ${previousVersion} next-cycle promise.\n` +
477
- blockers.map((b) => ` - ${b}\n`).join('') +
478
- ` The ${previousVersion} promise reads:\n` +
479
- promise
480
- .split('\n')
481
- .map((l) => ` ${l}\n`)
482
- .join('') +
483
- ` Answering it is one blockquote line under the curated head, e.g.\n` +
484
- ` > ${PROMISE_READBACK_MARKER} the <promise> did not ship; <what happened>.\n` +
485
- ' Shipped, did not ship, or withdrawn with a reason — the check refuses a MISSING\n' +
486
- ' answer, never a particular outcome. An unkept promise answered honestly passes.\n' +
487
- ' Reproduce locally, before any push:\n' +
488
- ` ./scripts-run src/scripts/check_release_highlights --version ${version}\n`,
489
- );
490
- return 1;
491
- }
492
-
493
336
  const _isMain = (() => {
494
337
  const entry = process.argv[1];
495
338
  if (!entry) return false;
@@ -19,6 +19,16 @@
19
19
  * `skipped/`, template/README/open-questions) MUST:
20
20
  * 1. Be parseable by the dashboard's `PHASE_RE` (>= one `## Phase <id>`).
21
21
  * 2. Have at least one trackable checkbox under every parsed phase.
22
+ * 3. Not declare a RETIRED `status:` value.
23
+ *
24
+ * `status: draft` is the one exemption. It used to have a second, `carrier`,
25
+ * which ADR-262 deleted: a `status:` value never again decides whether an
26
+ * autonomous run may act on a roadmap, so a receiver of deferred work is now
27
+ * ordinary estate and passes normal trackability. A surviving `status: carrier`
28
+ * is REJECTED here with the migration diagnostic rather than silently ignored —
29
+ * deleting a vocabulary term quietly would leave old files wearing a value
30
+ * nothing reads, and the next author would infer a meaning from its presence.
31
+ * The retired set and its diagnostic live in `_lib/retired_status.ts`.
22
32
  *
23
33
  * Exit codes:
24
34
  * 0 — every active roadmap has parseable phases with >= 1 checkbox per phase.
@@ -31,6 +41,7 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
31
41
 
32
42
  import { checkRatchet } from './_lib/gate_baseline.js';
33
43
  import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
44
+ import { declaresRetiredStatus, retiredStatusDiagnostic } from './_lib/retired_status.js';
34
45
 
35
46
  const QUIET = process.argv.slice(2).includes('--quiet');
36
47
 
@@ -49,15 +60,6 @@ const PHASE_RE =
49
60
  // FRONTMATTER_RE = re.compile(r"\A---\n(.*?)\n---\s*\n", re.DOTALL)
50
61
  const FRONTMATTER_RE = /^---\n([\s\S]*?)\n---\s*\n/;
51
62
  const DRAFT_VALUES: ReadonlySet<string> = new Set(['draft']);
52
- /**
53
- * A carrier holds obligations deferred out of an archived parent. It is not
54
- * schedulable work — every item in it has an unmet resumption trigger — and it
55
- * has no `## Phase` headings by construction, so it is skipped exactly where a
56
- * draft is. It differs from a draft in what protects it: a draft may be deleted
57
- * freely, a carrier may not, and `lint_carrier_integrity` reds when one loses
58
- * its receiver.
59
- */
60
- const CARRIER_VALUES: ReadonlySet<string> = new Set(['carrier']);
61
63
 
62
64
  const EXCLUDE_NAMES: ReadonlySet<string> = new Set([
63
65
  'template.md',
@@ -101,11 +103,6 @@ function is_draft(fm: Map<string, string>): boolean {
101
103
  return DRAFT_VALUES.has((fm.get('status') ?? '').toLowerCase());
102
104
  }
103
105
 
104
- /** True for a `status: carrier` roadmap — see CARRIER_VALUES. */
105
- function is_carrier(fm: Map<string, string>): boolean {
106
- return CARRIER_VALUES.has((fm.get('status') ?? '').toLowerCase());
107
- }
108
-
109
106
  /** Mirror update_roadmap_progress.is_roadmap_candidate (POSIX path parts). */
110
107
  function is_roadmap_candidate(p: string): boolean {
111
108
  const name = path.basename(p);
@@ -182,7 +179,7 @@ function find_active_roadmaps(root: string): string[] {
182
179
  continue;
183
180
  }
184
181
  const text = fs.readFileSync(p, 'utf-8');
185
- if (is_draft(parse_frontmatter(text)) || is_carrier(parse_frontmatter(text))) {
182
+ if (is_draft(parse_frontmatter(text))) {
186
183
  continue;
187
184
  }
188
185
  out.push(p);
@@ -193,6 +190,13 @@ function find_active_roadmaps(root: string): string[] {
193
190
  /** Return human-readable violation strings for a single roadmap. */
194
191
  function violations_for(p: string): string[] {
195
192
  const text = fs.readFileSync(p, 'utf-8');
193
+ const retired = declaresRetiredStatus(text);
194
+ if (retired !== null) {
195
+ // Returned alone: a file wearing a deleted status has a migration to do
196
+ // before its phase structure is worth reporting on, and stacking phase
197
+ // findings on top would bury the one finding that has to be read first.
198
+ return [`${p}: ${retiredStatusDiagnostic(retired)}`];
199
+ }
196
200
  PHASE_RE.lastIndex = 0;
197
201
  const matches: Array<{ start: number; end: number; id: string; name: string | undefined }> =
198
202
  [];
@@ -359,7 +363,6 @@ export {
359
363
  CHECKBOX_RE,
360
364
  PHASE_RE,
361
365
  is_draft,
362
- is_carrier,
363
366
  is_roadmap_candidate,
364
367
  parse_frontmatter,
365
368
  find_active_roadmaps,
@@ -8,7 +8,7 @@
8
8
  * ADR-236 decided that a rule is delivered from exactly one layer: package-only
9
9
  * rules stay in the project tree, everything else arrives from the host's global
10
10
  * directory and is withheld from the project tree. Measured in a freshly
11
- * generated worktree with `partitionActive: true`, it holds for one host in five:
11
+ * generated worktree with a verified host layer, it holds for one host in five:
12
12
  *
13
13
  * ```
14
14
  * .claude/rules 13 files, 13 package-only, 0 global-only
@@ -51,7 +51,7 @@ import * as fs from 'node:fs';
51
51
  import * as os from 'node:os';
52
52
  import * as path from 'node:path';
53
53
 
54
- import { isExclusivelyPackageOnly, resolvePartitionVerdict } from '../install/partitionEligibility.js';
54
+ import { isExclusivelyPackageOnly } from '../install/partitionEligibility.js';
55
55
  import { PROJECT_RULE_DIRS, globalRuleLayerNames, globalRuleLayerPath } from '../install/globalRuleLayers.js';
56
56
  import { GateLedger, UnaccountedTargetsError } from './_lib/gate_ledger.js';
57
57
  import { reportScanned } from './_lib/scan_scope.js';
@@ -333,7 +333,7 @@ function selfTest(): number {
333
333
  /**
334
334
  * `--prune`: delete exactly the files this gate reports as duplicated.
335
335
  *
336
- * ## Why a gate ships a repair at all
336
+ * **Why a gate ships a repair at all.**
337
337
  *
338
338
  * The documented repair is `task generate-tools`, and on this repository's own main
339
339
  * checkout that command is **structurally inert**: `agents/.agent-tools.yml` is
@@ -381,17 +381,39 @@ function prune(audit: PartitionAudit, root: string): number {
381
381
  }
382
382
 
383
383
  /**
384
- * Does a duplication in this projection mode mean an emitter FAILED, or that the
385
- * partition deliberately did not run?
384
+ * Does THIS directory's duplication mean an emitter failed, or that the
385
+ * per-directory fail-safe legitimately kept the full projection?
386
386
  *
387
- * Split out of `main` so both directions are testable without a lockfile on disk:
388
- * the un-injected path can only assert whatever the machine happens to be, and this
389
- * one bit differs between a maintainer checkout mid-release and a partitioned one —
390
- * the same environment-dependence `ruleLayerPartition.ts` records for its own
391
- * `active` override.
387
+ * **The discriminator, corrected 2026-09-07 after a neutral review.**
388
+ *
389
+ * It used to be the repo-wide host-layer verdict — first a `PartitionMode`
390
+ * string, then the `verified` boolean the 2026-09-07 change reduced it to. Both
391
+ * were wrong in BOTH directions once the withhold stopped reading
392
+ * `installed.lock`:
393
+ *
394
+ * - FALSE NEGATIVE: an install one release behind makes `verified === false`
395
+ * for the whole release window, so a genuine emitter failure — the defect
396
+ * this gate exists to catch — printed and returned 0.
397
+ * - FALSE POSITIVE: a verified layer that legitimately lacks ONE rule makes
398
+ * `partitionRulesForDir` keep all ~104 by design (rules are all-or-nothing,
399
+ * `ruleLayerPartition.ts`), and the gate exited 1 on a correct tree.
400
+ * `rule_partition_per_host.test.ts` pins that state as legal.
401
+ *
402
+ * The right signal was already computed and never asked: `soleCarrier` — the
403
+ * global-scope rules this directory projects that its host layer does NOT hold.
404
+ * Non-empty means the fail-safe fired for a reason a reader can check; empty
405
+ * means the layer carries every withheld name and the emitter simply did not
406
+ * withhold. Per directory, from data on hand, and independent of any lockfile —
407
+ * so the verdict no longer differs between a maintainer machine mid-release and
408
+ * a CI checkout.
409
+ *
410
+ * Also note what a stale layer can NOT do under per-name withholding: if the
411
+ * layer holds the name, the project copy is withheld whatever its CONTENT. So
412
+ * staleness never produces a duplicate, and the old message's second
413
+ * justification was keyed on the wrong fact.
392
414
  */
393
- export function partitionEnforces(mode: string): boolean {
394
- return mode === 'dual-layer/partitioned';
415
+ export function partitionEnforces(dir: Pick<DirAudit, 'soleCarrier'>): boolean {
416
+ return dir.soleCarrier.length === 0;
395
417
  }
396
418
 
397
419
  export function main(argv: readonly string[] = process.argv.slice(2)): number {
@@ -485,34 +507,32 @@ export function main(argv: readonly string[] = process.argv.slice(2)): number {
485
507
  // partition gate and `check_bridge_derivation` deadlock, each red in the state
486
508
  // the other requires.
487
509
  //
488
- // The block is structural rather than incidental: during ANY release the
489
- // building version is ahead of the installed one, so `resolvePartitionVerdict`
490
- // returns `standalone/full` on every maintainer machine for the whole release
491
- // window. A gate that cannot be green while a release is in progress is not
492
- // measuring the release.
510
+ // Split PER DIRECTORY, not on a repo-wide verdict — see `partitionEnforces` for
511
+ // why the verdict was wrong in both directions. A directory that is the sole
512
+ // carrier of at least one global-scope rule kept the full projection by design;
513
+ // one that carries none is an emitter that did not withhold.
493
514
  //
494
- // Teeth are unchanged where the partition IS active: there a duplicate means an
495
- // emitter failed to withhold what the partition selected, which is the defect
496
- // this gate exists to catch. In CI both layers are absent, `offenders` is empty,
497
- // and this branch is never reached — so nothing here relaxes the CI reading.
498
- const verdict = resolvePartitionVerdict(root);
499
- if (!partitionEnforces(verdict.mode)) {
500
- for (const d of offenders) {
501
- process.stdout.write(
502
- `⚠️ ${d.dir}: ${String(d.duplicated.length)} rule(s) also present in ` +
503
- `${d.globalLayer ?? '(unknown)'}\n`,
504
- );
505
- }
515
+ // In CI both layers are absent, `offenders` is empty, and neither branch is
516
+ // reached — so nothing here changes the CI reading.
517
+ const failing = offenders.filter((d) => partitionEnforces(d));
518
+ for (const d of offenders.filter((d) => !partitionEnforces(d))) {
519
+ process.stdout.write(
520
+ `⚠️ ${d.dir}: ${String(d.duplicated.length)} rule(s) also present in ` +
521
+ `${d.globalLayer ?? '(unknown)'} — kept by design: this directory is the sole ` +
522
+ `carrier of ${String(d.soleCarrier.length)} global-scope rule(s) ` +
523
+ `(e.g. ${d.soleCarrier.slice(0, 3).join(', ')}), and the rule partition is ` +
524
+ `all-or-nothing per directory.\n`,
525
+ );
526
+ }
527
+ if (failing.length === 0) {
506
528
  process.stdout.write(
507
- `⚠️ check_rule_layer_partition: partition INACTIVE (${verdict.reason}), so the ` +
508
- `generators emit the full projection by design and this overlap is the fail-safe ` +
509
- `working. Reported, not enforced — run \`agent-config install\` to activate the ` +
510
- `partition here, then this gate has teeth again.\n`,
529
+ `✅ check_rule_layer_partition: every duplication is the per-directory fail-safe ` +
530
+ `working — no directory duplicates a rule its host layer fully carries.\n`,
511
531
  );
512
532
  return 0;
513
533
  }
514
534
 
515
- for (const d of offenders) {
535
+ for (const d of failing) {
516
536
  process.stderr.write(
517
537
  `${report ? '⚠️ ' : '❌ '} ${d.dir}: ${String(d.duplicated.length)} rule(s) also present in ` +
518
538
  `${d.globalLayer ?? '(unknown)'} — e.g. ${d.duplicated.slice(0, 5).join(', ')}\n`,
@@ -34,15 +34,31 @@
34
34
  * fix, which teaches readers to ignore it — the exact failure this estate has
35
35
  * recorded twice already.
36
36
  *
37
- * The reason used to be that `road-to-single-delivery` Phase 2 was HALTED on
38
- * blocker `partition-current-layer-undecidable`. That is no longer true and the
39
- * sentence is corrected rather than left standing: the blocker reads
40
- * `**Status:** resolved`, its roadmap is archived, and the partition shipped
41
- * under ADR-236 Phase 2. What replaced the halt is a per-machine activation —
42
- * the partition withholds artefacts only where the host layer is verified
43
- * against `installed.lock`, so a machine whose install predates the fingerprint
44
- * keeps the full projection BY DESIGN and the overlap this gate reports there is
45
- * the fail-safe working, not a regression.
37
+ * The reason has been corrected twice rather than left standing. It was first
38
+ * that `road-to-single-delivery` Phase 2 was HALTED on blocker
39
+ * `partition-current-layer-undecidable` — no longer true; that blocker reads
40
+ * `**Status:** resolved` and the partition shipped under ADR-236 Phase 2. It was
41
+ * then a per-machine activation: the partition withheld only where the host layer
42
+ * verified against `installed.lock`, so an install one release behind kept the
43
+ * FULL projection by design and the overlap here was the fail-safe working.
44
+ *
45
+ * That second reason is void as of the ADR-236 amendment of 2026-09-07. The
46
+ * withhold no longer reads `installed.lock` at all — it reads each host
47
+ * directory's own contents, per NAME. An overlap this gate reports is therefore
48
+ * one of exactly two things now, and neither is a repo-wide fail-safe:
49
+ *
50
+ * - an artefact the host layer genuinely does NOT hold, kept on its own merits
51
+ * (the per-name fail-safe, and the right answer);
52
+ * - an emitter that failed to withhold what the evidence said to withhold
53
+ * (a defect).
54
+ *
55
+ * It still reports rather than enforcing, for the unchanged reason above. The
56
+ * second reason an earlier draft of this note gave — that the `commands` row's
57
+ * cluster-DIRECTORY key could read a correct tree as a collision — was true when
58
+ * written and was fixed in the same change: that row keys on the posix subpath
59
+ * now (see TYPES). Left recorded rather than deleted, because the argument is
60
+ * what justified `--enforce` staying opt-in and a reader should see that it no
61
+ * longer applies.
46
62
  *
47
63
  * WHERE THIS CHECK IS MEANINGFUL — read this before quoting one of its numbers
48
64
  * (road-to-session-closeout 3.3). It is a DEVELOPER-MACHINE check. It reads two
@@ -100,13 +116,23 @@ const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
100
116
  * (`commands/<cluster>/<sub>.md`), so this gate's `commands` row compares cluster
101
117
  * DIRECTORY names — a coarser key than the thing the host registers.
102
118
  *
103
- * That gap is recorded rather than closed, and the reason is a measurement: since
104
- * `commandsWithheld` gates the colon-form generator, a machine with a verified
105
- * host layer projects ZERO commands, so there is no project-side name for the
106
- * coarse key to be wrong about. On a machine WITHOUT one, the project layer is
107
- * the only layer and there is nothing to compare. The key is therefore imprecise
108
- * exactly where it has no work to do. Closing it would mean reading each command
109
- * file's own slug — real work for a row whose overlap is 0 in both topologies.
119
+ * That gap USED to be recorded rather than closed, on a measurement: while the
120
+ * colon-form generator was gated by one repo-wide verdict, a machine with a
121
+ * verified host layer projected ZERO commands and a machine without one had
122
+ * nothing to compare — so the coarse key was imprecise exactly where it had no
123
+ * work to do.
124
+ *
125
+ * **The ADR-236 amendment of 2026-09-07 falsified that, and it was OBSERVED, not
126
+ * predicted.** Withholding per name leaves the project layer holding exactly the
127
+ * commands the host lacks, and those can share a cluster with commands the host
128
+ * has: measured the same day, `.claude/commands/analyze/` held `repo.md` and
129
+ * `roadmap-repos.md` while `~/.claude/commands/analyze/` held `inbox.md` and
130
+ * seven others. Zero duplication, and the directory key reported a collision.
131
+ *
132
+ * So the `commands` row now keys on the POSIX SUBPATH (`<cluster>/<sub>.md`),
133
+ * which is the host's own unit. `readLayer` takes a `recursive` flag for it;
134
+ * `shape` stays a top-level reading so the shape-mismatch line keeps comparing
135
+ * the same thing it always did.
110
136
  *
111
137
  * `personas` and `user-types` were added 2026-08-21 (closure correction). See
112
138
  * `unknownProjectFamilies` for what now prevents the next omission.
@@ -182,7 +208,7 @@ interface LayerReading {
182
208
  shape: LayerShape;
183
209
  }
184
210
 
185
- function readLayer(dir: string): LayerReading | null {
211
+ function readLayer(dir: string, recursive = false): LayerReading | null {
186
212
  let entries: fs.Dirent[];
187
213
  try {
188
214
  if (!fs.statSync(dir).isDirectory()) return null;
@@ -191,7 +217,11 @@ function readLayer(dir: string): LayerReading | null {
191
217
  return null;
192
218
  }
193
219
  const shape: LayerShape = { symlinks: 0, dirs: 0, files: 0 };
194
- for (const e of entries) {
220
+ // README.md is excluded from BOTH the names and the shape. Counting it in the
221
+ // shape while filtering it from the names printed a self-contradicting line
222
+ // (`personas global 31 · shape (l0/d2/f30)` — 32 entries, 31 names), caught by
223
+ // a second neutral review.
224
+ for (const e of entries.filter((x) => x.name !== 'README.md')) {
195
225
  // isSymbolicLink() is checked FIRST and deliberately: readdir with
196
226
  // withFileTypes does not follow links, but a reader who tested isDirectory
197
227
  // first would classify a symlink-to-directory as a directory and lose the
@@ -200,7 +230,41 @@ function readLayer(dir: string): LayerReading | null {
200
230
  else if (e.isDirectory()) shape.dirs += 1;
201
231
  else shape.files += 1;
202
232
  }
203
- return { names: entries.map((e) => e.name).sort(), shape };
233
+ if (!recursive) {
234
+ // README.md excluded for EVERY family, not only commands. It was excluded
235
+ // on the recursive path from the start and nowhere else, so a README in
236
+ // both layers would have counted as one duplicated artefact for rules,
237
+ // skills, personas, user-types and agents and not for commands — a latent
238
+ // asymmetry a neutral review caught on 2026-09-07. It names no artefact in
239
+ // any family.
240
+ return { names: entries.map((e) => e.name).filter((n) => n !== 'README.md').sort(), shape };
241
+ }
242
+ return { names: _mdSubpaths(dir).sort(), shape };
243
+ }
244
+
245
+ /**
246
+ * Every `*.md` under `dir`, as a posix path relative to it.
247
+ *
248
+ * The identity key for the `commands` row — see the note on TYPES. `README.md` is
249
+ * excluded because both layers carry one and it names no command.
250
+ */
251
+ function _mdSubpaths(dir: string): string[] {
252
+ const out: string[] = [];
253
+ const walk = (abs: string, rel: string): void => {
254
+ let kids: fs.Dirent[];
255
+ try {
256
+ kids = fs.readdirSync(abs, { withFileTypes: true });
257
+ } catch {
258
+ return;
259
+ }
260
+ for (const k of kids) {
261
+ const childRel = rel === '' ? k.name : `${rel}/${k.name}`;
262
+ if (k.isDirectory()) walk(path.join(abs, k.name), childRel);
263
+ else if (k.name.endsWith('.md') && k.name !== 'README.md') out.push(childRel);
264
+ }
265
+ };
266
+ walk(dir, '');
267
+ return out;
204
268
  }
205
269
 
206
270
  /**
@@ -230,8 +294,9 @@ export function declaresPaths(file: string): 'yes' | 'no' | 'unreadable' {
230
294
  export function readType(type: ArtefactType, globalRoot: string, projectRoot: string): TypeReading {
231
295
  const gDir = path.join(globalRoot, type);
232
296
  const pDir = path.join(projectRoot, type);
233
- const gLayer = readLayer(gDir);
234
- const pLayer = readLayer(pDir);
297
+ const recursive = type === 'commands';
298
+ const gLayer = readLayer(gDir, recursive);
299
+ const pLayer = readLayer(pDir, recursive);
235
300
  const globalNames = gLayer?.names ?? null;
236
301
  const projectNames = pLayer?.names ?? null;
237
302
  const globalShape = gLayer?.shape ?? null;
@@ -500,11 +565,12 @@ export function main(argv?: readonly string[]): number {
500
565
  process.stdout.write(
501
566
  'usage: check_single_delivery [--global DIR] [--project DIR] [--enforce] [--quiet]\n' +
502
567
  '\n' +
503
- 'Reports by default and exits 0: the partition SHIPPED (ADR-236, Phase 2)\n' +
504
- 'but activates per machine — only where a verified host layer exists, so a\n' +
505
- 'checkout without one keeps the full projection BY DESIGN and reports overlap.\n' +
506
- '--enforce exits 1 on any overlap; registering it would fail every machine\n' +
507
- 'that has not re-run `agent-config install`, which is why it stays opt-in.\n',
568
+ 'Reports by default and exits 0: since the ADR-236 amendment of\n' +
569
+ '2026-09-07 the project layer withholds per ARTEFACT NAME on each host\n' +
570
+ "directory's own contents, so an artefact the host layer does not hold is\n" +
571
+ 'kept BY DESIGN and reads as overlap here. --enforce exits 1 on any\n' +
572
+ 'overlap. It stays opt-in because a machine whose global layer is stale\n' +
573
+ 'cannot fix the overlap by regenerating — only by `agent-config install`.\n',
508
574
  );
509
575
  return 0;
510
576
  } else if (a !== undefined) {
@@ -626,11 +692,11 @@ export function main(argv?: readonly string[]): number {
626
692
  }
627
693
  process.stdout.write(
628
694
  `⚠️ check_single_delivery: ${detail}. Reported, not enforced — ` +
629
- 'the partition shipped (ADR-236 Phase 2) but activates per machine: it withholds ' +
630
- 'artefacts only where the host layer is verified against `installed.lock`, so a ' +
631
- 'machine whose install predates the fingerprint keeps the full projection BY DESIGN ' +
632
- 'and this overlap is the fail-safe working. Run `agent-config install` to enable it ' +
633
- 'here; use --enforce where every machine is known to be verified.\n',
695
+ 'since the ADR-236 amendment of 2026-09-07 the project layer withholds per ' +
696
+ "ARTEFACT NAME on each host directory's own contents, never on a version in " +
697
+ '`installed.lock`. So an artefact the host layer does not hold is kept BY DESIGN ' +
698
+ 'and appears here; a stale global layer is fixed by `agent-config install`. Use ' +
699
+ '--enforce where every layer is known current.\n',
634
700
  );
635
701
  return 0;
636
702
  }
@@ -157,10 +157,23 @@ async function main(argv: readonly string[] = process.argv.slice(2)): Promise<nu
157
157
  return 0;
158
158
  }
159
159
 
160
+ // `build:delegate-aux` bundles this file with `--splitting`, which can move the
161
+ // module body into a shared chunk: `import.meta.url` then names the CHUNK, the
162
+ // URL comparison below never matches, and the entry silently no-ops. Inside that
163
+ // bundle the invoked file name is the reliable signal (same pattern as
164
+ // src/scripts/_cli/cmd_migrate.ts). Defined by that build only.
165
+ declare const __AGENT_CONFIG_CLI_DELEGATE__: boolean | undefined;
160
166
  function _isCliEntry(): boolean {
161
167
  if (process.argv[1] === undefined) {
162
168
  return false;
163
169
  }
170
+ if (typeof __AGENT_CONFIG_CLI_DELEGATE__ !== 'undefined' && __AGENT_CONFIG_CLI_DELEGATE__) {
171
+ // A miss falls THROUGH to the URL comparison below, which is the
172
+ // symlinked-invocation case that fallback exists for.
173
+ if (path.basename(process.argv[1], '.js') === 'check_update_banner') {
174
+ return true;
175
+ }
176
+ }
164
177
  const argvUrl = pathToFileURL(path.resolve(process.argv[1])).href;
165
178
  if (import.meta.url === argvUrl) {
166
179
  return true;