@event4u/agent-config 14.5.0 → 14.7.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 (203) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +384 -3
  3. package/README.md +1 -1
  4. package/dist/agent-src/commands/analyze/decision.md +7 -0
  5. package/dist/agent-src/commands/analyze/inbox.md +264 -10
  6. package/dist/agent-src/commands/analyze.md +1 -1
  7. package/dist/agent-src/commands/optimize/project.md +10 -0
  8. package/dist/agent-src/commands/roadmap/process-full.md +132 -4
  9. package/dist/agent-src/contexts/execution/roadmap-execution-contract.md +31 -0
  10. package/dist/agent-src/contexts/execution/roadmap-process-loop.md +161 -1
  11. package/dist/agent-src/rules/context-hygiene.md +2 -0
  12. package/dist/agent-src/rules/decision-revisit-gate.md +111 -7
  13. package/dist/agent-src/rules/fix-what-you-see.md +108 -0
  14. package/dist/agent-src/rules/no-roadmap-references.md +0 -5
  15. package/dist/agent-src/rules/recurring-criticism.md +110 -0
  16. package/dist/agent-src/rules/roadmap-progress-sync.md +1 -1
  17. package/dist/agent-src/rules/rule-type-governance.md +0 -2
  18. package/dist/agent-src/rules/self-repair-loop.md +17 -0
  19. package/dist/agent-src/rules/skill-quality.md +0 -2
  20. package/dist/agent-src/rules/source-confidentiality.md +37 -7
  21. package/dist/agent-src/scripts/archival_sweep.ts +117 -0
  22. package/dist/agent-src/scripts/archive_completed_roadmaps.ts +19 -3
  23. package/dist/agent-src/scripts/gate_budget.ts +282 -0
  24. package/dist/agent-src/scripts/gate_execute.ts +72 -31
  25. package/dist/agent-src/scripts/roadmap_gates.ts +74 -3
  26. package/dist/agent-src/scripts/update_roadmap_progress.ts +251 -7
  27. package/dist/agent-src/skills/adr-create/SKILL.md +25 -3
  28. package/dist/agent-src/skills/ai-council/SKILL.md +48 -807
  29. package/dist/agent-src/skills/ai-council/references/advanced-modes.md +320 -0
  30. package/dist/agent-src/skills/ai-council/references/cost-and-redaction.md +99 -0
  31. package/dist/agent-src/skills/ai-council/references/execution-modes.md +162 -0
  32. package/dist/agent-src/skills/ai-council/references/output-and-synthesis.md +125 -0
  33. package/dist/agent-src/skills/ai-council/references/procedure.md +143 -0
  34. package/dist/agent-src/skills/decision-review/SKILL.md +18 -0
  35. package/dist/agent-src/skills/quality-tools/SKILL.md +34 -326
  36. package/dist/agent-src/skills/quality-tools/references/js-ts-tools.md +113 -0
  37. package/dist/agent-src/skills/quality-tools/references/php-tools.md +222 -0
  38. package/dist/agent-src/skills/roadmap-management/SKILL.md +15 -409
  39. package/dist/agent-src/skills/roadmap-management/references/archival.md +226 -0
  40. package/dist/agent-src/skills/roadmap-management/references/authoring.md +107 -0
  41. package/dist/agent-src/skills/roadmap-management/references/dashboard.md +47 -0
  42. package/dist/agent-src/skills/roadmap-management/references/lifecycle.md +58 -0
  43. package/dist/agent-src/skills/skill-writing/SKILL.md +34 -570
  44. package/dist/agent-src/skills/skill-writing/references/procedure.md +275 -0
  45. package/dist/agent-src/skills/skill-writing/references/read-only-scripts.md +18 -0
  46. package/dist/agent-src/skills/skill-writing/references/section-patterns.md +298 -0
  47. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  48. package/dist/agent-src/templates/roadmaps.md +45 -2
  49. package/dist/agent-src/templates/scripts/telemetry/flush_sender.mjs +163 -0
  50. package/dist/agent-src/templates/scripts/telemetry/remote.ts +160 -7
  51. package/dist/agent-src/templates/scripts/telemetry/transport.ts +169 -0
  52. package/dist/cli/registry.js +1 -1
  53. package/dist/cli/registry.js.map +1 -1
  54. package/dist/cli-delegate/chunk-2GNWGMAL.js +172 -0
  55. package/dist/cli-delegate/{chunk-RLN73PR5.js → chunk-4VR4ORQ6.js} +10 -7
  56. package/dist/cli-delegate/chunk-6EOCNQAM.js +402 -0
  57. package/dist/cli-delegate/{chunk-XURJ4AQC.js → chunk-74E3TKWV.js} +381 -280
  58. package/dist/cli-delegate/{chunk-3UCJWMMP.js → chunk-7F3VYSM2.js} +4 -4
  59. package/dist/cli-delegate/{chunk-BKO2AZ7Z.js → chunk-B2ERU2VA.js} +1 -1
  60. package/dist/cli-delegate/{chunk-NXFWFAYV.js → chunk-G23AT6GD.js} +1 -0
  61. package/dist/cli-delegate/{chunk-63WSP6YZ.js → chunk-GJ5BVHXM.js} +72 -165
  62. package/dist/cli-delegate/{chunk-ZPSXJTLI.js → chunk-JZ4NR2TW.js} +353 -1
  63. package/dist/cli-delegate/chunk-MWRD5YOH.js +100 -0
  64. package/dist/cli-delegate/{chunk-GRRPEPRN.js → chunk-T7L2QK3K.js} +24 -5
  65. package/dist/cli-delegate/{chunk-EVTY7ISR.js → chunk-YM4GZDJG.js} +9 -1
  66. package/dist/cli-delegate/cmd_conformance.js +459 -709
  67. package/dist/cli-delegate/cmd_doctor.js +6 -6
  68. package/dist/cli-delegate/cmd_explain.js +1 -1
  69. package/dist/cli-delegate/cmd_export.js +7 -6
  70. package/dist/cli-delegate/cmd_handoff.js +6 -6
  71. package/dist/cli-delegate/cmd_packs_active.js +1 -1
  72. package/dist/cli-delegate/cmd_preflight.js +2 -2
  73. package/dist/cli-delegate/cmd_prune.js +7 -6
  74. package/dist/cli-delegate/cmd_session_recycle.js +840 -30
  75. package/dist/cli-delegate/cmd_settings_migrate.js +7 -6
  76. package/dist/cli-delegate/cmd_settings_set.js +1 -1
  77. package/dist/cli-delegate/cmd_sync.js +7 -6
  78. package/dist/cli-delegate/cmd_uninstall.js +9 -7
  79. package/dist/cli-delegate/cmd_update.js +1 -1
  80. package/dist/cli-delegate/cmd_upgrade.js +1 -1
  81. package/dist/cli-delegate/cmd_validate.js +7 -6
  82. package/dist/discovery/deprecation-report.md +1 -1
  83. package/dist/discovery/discovery-manifest.json +72 -29
  84. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  85. package/dist/discovery/discovery-manifest.summary.md +4 -4
  86. package/dist/discovery/dormancy-report.md +1 -1
  87. package/dist/discovery/orphan-report.md +1 -1
  88. package/dist/discovery/packs.json +10 -8
  89. package/dist/discovery/trust-report.md +3 -3
  90. package/dist/discovery/workspaces.json +8 -6
  91. package/dist/hooks/dispatch.js +3127 -2022
  92. package/dist/install/claudePathsPlan.js +187 -0
  93. package/dist/install/claudePathsPlan.js.map +1 -0
  94. package/dist/install/hostLayerFingerprint.js +136 -0
  95. package/dist/install/hostLayerFingerprint.js.map +1 -0
  96. package/dist/install/install.mjs +553 -425
  97. package/dist/install/partitionEligibility.js +276 -0
  98. package/dist/install/partitionEligibility.js.map +1 -0
  99. package/dist/mcp/registry-manifest.json +3 -3
  100. package/dist/mcp/server.json +2 -2
  101. package/dist/mcp/server.mjs +1744 -1191
  102. package/dist/router.json +1 -1
  103. package/dist/scripts/_lib/agent_src.js +41 -0
  104. package/dist/scripts/_lib/agent_src.js.map +1 -1
  105. package/dist/scripts/_lib/install_layout.js +83 -0
  106. package/dist/scripts/_lib/install_layout.js.map +1 -0
  107. package/dist/scripts/_lib/installed_lock.js +444 -0
  108. package/dist/scripts/_lib/installed_lock.js.map +1 -0
  109. package/dist/scripts/condense.js +13 -164
  110. package/dist/scripts/condense.js.map +1 -1
  111. package/dist/server/schemas/settings.js +4 -0
  112. package/dist/server/schemas/settings.js.map +1 -1
  113. package/docs/contracts/settings-classes.md +12 -6
  114. package/docs/guidelines/agent-infra/agent-interaction-and-decision-quality.md +8 -3
  115. package/docs/guidelines/agent-infra/context-hygiene-mechanics.md +26 -0
  116. package/docs/guidelines/agent-infra/engineering-memory-data-format.md +49 -0
  117. package/docs/guidelines/agent-infra/memory-access.md +17 -0
  118. package/docs/guidelines/agent-infra/recurring-criticism-mechanics.md +96 -0
  119. package/package.json +1 -1
  120. package/src/agent-src/scripts/archival_sweep.ts +117 -0
  121. package/src/agent-src/scripts/archive_completed_roadmaps.ts +19 -3
  122. package/src/agent-src/scripts/gate_budget.ts +282 -0
  123. package/src/agent-src/scripts/gate_execute.ts +72 -31
  124. package/src/agent-src/scripts/roadmap_gates.ts +74 -3
  125. package/src/agent-src/scripts/update_roadmap_progress.ts +251 -7
  126. package/src/agent-src/templates/scripts/telemetry/flush_sender.mjs +163 -0
  127. package/src/agent-src/templates/scripts/telemetry/remote.ts +160 -7
  128. package/src/agent-src/templates/scripts/telemetry/transport.ts +169 -0
  129. package/src/config/agent-settings.template.yml +27 -2
  130. package/src/config/ci-local-parity.yml +18 -0
  131. package/src/config/estate-count-budget.json +367 -3
  132. package/src/config/gate-violation-baselines.json +41 -41
  133. package/src/config/pack-size-budget.json +3 -2
  134. package/src/config/rule-activation-census.json +14 -6
  135. package/src/config/rule-stub-ceilings.json +19 -6
  136. package/src/install/claudePathsPlan.ts +196 -0
  137. package/src/install/hostLayerFingerprint.ts +145 -0
  138. package/src/install/partitionEligibility.ts +325 -0
  139. package/src/scripts/_cli/cmd_session_recycle.ts +9 -1
  140. package/src/scripts/_dispatch.bash +6 -1
  141. package/src/scripts/_lib/ac_heading.ts +30 -0
  142. package/src/scripts/_lib/agent_src.ts +42 -0
  143. package/src/scripts/_lib/bench_ab_pinned_repo.ts +162 -0
  144. package/src/scripts/_lib/bench_ab_workspace.ts +10 -1
  145. package/src/scripts/_lib/envelope_grounding.ts +38 -8
  146. package/src/scripts/_lib/installed_lock.ts +50 -5
  147. package/src/scripts/_lib/layer_overlap_notice.ts +85 -0
  148. package/src/scripts/_lib/md_section.ts +129 -0
  149. package/src/scripts/_lib/scope_guard.sh +86 -4
  150. package/src/scripts/_lib/self_repair.ts +19 -0
  151. package/src/scripts/_lib/self_repair_class_b.ts +171 -0
  152. package/src/scripts/_lib/subagent_capsule.ts +81 -1
  153. package/src/scripts/adr/regenerate_index.ts +13 -2
  154. package/src/scripts/adr_cite_check.ts +424 -0
  155. package/src/scripts/ai_council/cli_help.ts +55 -0
  156. package/src/scripts/before_complete_hook.ts +153 -28
  157. package/src/scripts/bench_ab_v2_complexity.ts +27 -7
  158. package/src/scripts/check_adr_frontmatter.ts +187 -1
  159. package/src/scripts/check_references.ts +60 -0
  160. package/src/scripts/check_release_published.ts +32 -11
  161. package/src/scripts/check_single_delivery.ts +517 -0
  162. package/src/scripts/ci_settle.ts +170 -0
  163. package/src/scripts/condense.ts +20 -170
  164. package/src/scripts/council_cli.ts +3 -3
  165. package/src/scripts/dispatch_r2_reviewer.ts +6 -14
  166. package/src/scripts/hook_manifest.json +1 -1
  167. package/src/scripts/hook_manifest.yaml +77 -14
  168. package/src/scripts/hooks/block_no_verify.ts +61 -10
  169. package/src/scripts/hooks/block_unauthorized_git.ts +1 -1
  170. package/src/scripts/hooks/concern_registry.ts +4 -0
  171. package/src/scripts/hooks/rtk_wrap_hook.ts +8 -3
  172. package/src/scripts/hooks/run_continuation_hook.ts +887 -68
  173. package/src/scripts/hooks/ship_diff_volume_hook.ts +81 -15
  174. package/src/scripts/hooks/source_first_gate_hook.ts +464 -0
  175. package/src/scripts/hooks/state_io.ts +782 -20
  176. package/src/scripts/hooks/subagent_ledger_hook.ts +44 -15
  177. package/src/scripts/hooks/telemetry_flush_hook.ts +139 -0
  178. package/src/scripts/hooks/telemetry_self_repair.ts +128 -0
  179. package/src/scripts/hooks/telemetry_usage_hook.ts +22 -4
  180. package/src/scripts/hooks/turn_end_gate_hook.ts +81 -15
  181. package/src/scripts/install.sh +12 -2
  182. package/src/scripts/install.ts +16 -11
  183. package/src/scripts/language_mirror_hook.ts +561 -25
  184. package/src/scripts/lint_framework_leakage_allowlist.json +2 -2
  185. package/src/scripts/lint_plan_risk_register.ts +4 -3
  186. package/src/scripts/lint_skill_router_head.ts +4 -4
  187. package/src/scripts/mcp_server/tools.ts +2 -2
  188. package/src/scripts/memory_consolidation.ts +189 -0
  189. package/src/scripts/memory_eviction.ts +384 -0
  190. package/src/scripts/new_roadmap.ts +136 -0
  191. package/src/scripts/pack_mcp_content.ts +20 -5
  192. package/src/scripts/release.ts +37 -38
  193. package/src/scripts/report_layer_overlap.ts +78 -0
  194. package/src/scripts/report_memory_pointers.ts +622 -0
  195. package/src/scripts/self_repair_hook.ts +37 -5
  196. package/src/scripts/session_register_hook.ts +115 -15
  197. package/src/scripts/skill_usage_collect.ts +11 -1
  198. package/src/scripts/skill_usage_report.ts +278 -8
  199. package/src/scripts/sync_pr_branch.ts +7 -0
  200. package/src/server/schemas/settings.ts +8 -0
  201. package/dist/cli-delegate/chunk-EJITJBJX.js +0 -172
  202. package/dist/cli-delegate/chunk-PZWRPRWP.js +0 -38
  203. /package/dist/cli-delegate/{chunk-RYNADUFG.js → chunk-LWTYTX6F.js} +0 -0
@@ -0,0 +1,136 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * Emit a roadmap skeleton that passes every roadmap gate unedited.
4
+ *
5
+ * WHY THIS EXISTS. Four authoring conventions are enforced by four different
6
+ * gates and documented in prose an author has to already know to look for:
7
+ *
8
+ * 1. `complexity:` is a two-value enum — `lightweight` | `structural`.
9
+ * Measured 2026-08-20 across 88 roadmaps: six DIFFERENT invented values
10
+ * were in the tree (`bounded`, `medium`, `moderate`, `small`, `standard`),
11
+ * one of them on the active side and reddening the trunk, seven more in
12
+ * `later/` where the gate does not scan and which would red on the day
13
+ * they are reactivated.
14
+ * 2. The acceptance heading is matched CASE-INSENSITIVELY and without an end
15
+ * anchor — one predicate, `_lib/ac_heading.ts`, shared by the R1 gate and
16
+ * the R2 dispatcher. It was not always: an end-anchored, case-sensitive
17
+ * copy made 10 of 22 roadmaps writing `Acceptance criteria` invisible to
18
+ * the extractor, and a third copy in `lint_plan_risk_register` kept both
19
+ * faults until 2026-08-20, hashing the empty string for 8 of the 32 ready
20
+ * roadmaps while the gate exited 0. The skeleton still emits the
21
+ * capitalised form: it is the shape every reader already recognises, and
22
+ * nothing now depends on the case.
23
+ * 3. The Risk Register needs its `<!-- risk-review: … -->` marker as the
24
+ * first non-blank line of the section.
25
+ * 4. `Risk type` is a two-value enum — `product` | `implementation`. The
26
+ * natural third word (`process`) hard-fails at pre-push.
27
+ *
28
+ * Every one of those is cheap to satisfy up front and costs a full gate round
29
+ * to discover. This command satisfies them by construction.
30
+ *
31
+ * The skeleton is deliberately MINIMAL: one phase, one step, one risk row. It
32
+ * is a starting point that is green, not a template to fill in blindly — the
33
+ * content is the author's, the shape is not worth rediscovering.
34
+ *
35
+ * Usage: ./scripts-run src/scripts/new_roadmap <slug> [--structural] [--stdout]
36
+ */
37
+
38
+ import * as fs from 'node:fs';
39
+ import * as path from 'node:path';
40
+
41
+ /** Today, as the risk-review marker wants it. */
42
+ function today(): string {
43
+ return new Date().toISOString().slice(0, 10);
44
+ }
45
+
46
+ /** `road to X` from a kebab slug, for the H1. */
47
+ function titleOf(slug: string): string {
48
+ return slug.replace(/-/g, ' ');
49
+ }
50
+
51
+ export function skeleton(slug: string, complexity: 'lightweight' | 'structural', date: string): string {
52
+ return `---
53
+ complexity: ${complexity}
54
+ status: draft
55
+ execution:
56
+ mode: phase-checkpoints
57
+ ---
58
+ # Road to ${titleOf(slug)}
59
+
60
+ > **Source:** replace this with where the finding came from — a measurement, a
61
+ > review, an incident. A roadmap whose origin is not stated cannot be checked
62
+ > against it later.
63
+
64
+ ## Goal
65
+
66
+ One paragraph: what is true when this is finished, phrased so someone else
67
+ could tell whether it happened.
68
+
69
+ ## Phase 1 — <name the first coherent slice>
70
+
71
+ - [ ] **1.1 <the step, as an imperative>.** What changes, and why it is not
72
+ already the case.
73
+ verify: the check that proves it — a command, a test, an observable
74
+ state. Never "looks right".
75
+
76
+ ## Risk Register
77
+ <!-- risk-review: v1 | reviewed: ${date} | reviewer: claude/host -->
78
+
79
+ | Rank | Item | Risk type | Description | Mitigation | Anchored under |
80
+ |------|------|-----------|-------------|------------|----------------|
81
+ | 1 | <the way this roadmap most plausibly fails> | implementation | What goes wrong, concretely | What makes it not happen, or what catches it | Phase 1 — <name the first coherent slice> |
82
+
83
+ ## Acceptance Criteria
84
+
85
+ - [ ] AC-1 — <the residual state, not the activity>. Phrase it on what is true
86
+ afterwards, so a step that ran but achieved nothing cannot satisfy it.
87
+ `;
88
+ }
89
+
90
+ function main(argv: readonly string[]): number {
91
+ const args = argv.filter((a) => !a.startsWith('--'));
92
+ const structural = argv.includes('--structural');
93
+ const toStdout = argv.includes('--stdout');
94
+ const slug = args[0];
95
+
96
+ if (slug === undefined || slug.length === 0) {
97
+ process.stderr.write(
98
+ 'usage: new_roadmap <slug> [--structural] [--stdout]\n' +
99
+ ' <slug> kebab-case, without the road-to- prefix\n' +
100
+ ' --structural tag structural instead of lightweight (no line/phase cap)\n' +
101
+ ' --stdout print instead of writing the file\n',
102
+ );
103
+ return 2;
104
+ }
105
+ if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(slug)) {
106
+ process.stderr.write(`new_roadmap: slug must be kebab-case, got '${slug}'\n`);
107
+ return 2;
108
+ }
109
+
110
+ const body = skeleton(slug, structural ? 'structural' : 'lightweight', today());
111
+ if (toStdout) {
112
+ process.stdout.write(body);
113
+ return 0;
114
+ }
115
+
116
+ const rel = path.join('agents', 'roadmaps', `road-to-${slug}.md`);
117
+ if (fs.existsSync(rel)) {
118
+ process.stderr.write(`new_roadmap: ${rel} already exists — refusing to overwrite\n`);
119
+ return 1;
120
+ }
121
+ fs.mkdirSync(path.dirname(rel), { recursive: true });
122
+ fs.writeFileSync(rel, body, 'utf-8');
123
+ process.stdout.write(
124
+ `✅ wrote ${rel}\n` +
125
+ ' It passes the roadmap gates as emitted. Adding a `### blocker:` entry or\n' +
126
+ ' flipping status to `ready` grows the estate — check_estate_count wants the\n' +
127
+ ' baseline raised with a reason in the same commit.\n',
128
+ );
129
+ return 0;
130
+ }
131
+
132
+ if (process.argv[1] !== undefined && import.meta.url.endsWith(path.basename(process.argv[1]))) {
133
+ process.exitCode = main(process.argv.slice(2));
134
+ }
135
+
136
+ export { main, titleOf, today };
@@ -41,11 +41,26 @@ import { scan_contexts, scan_guidelines, scan_rules } from './mcp_server/resourc
41
41
 
42
42
  const SCHEMA_VERSION = 1;
43
43
  const PACKER_VERSION = '1.0.0';
44
- // Worker bundle is the compact JSON; gzipped copy lives in R2. Cloudflare's
45
- // condensed-bundle limit is 3 MB (free) / 10 MB (paid); 778 KB gz today
46
- // (438 entries) leaves ample headroom. Hard-fail at 5 MB uncondensed so
47
- // the build dies before the Worker upload does.
48
- const MAX_UNCONDENSED_BYTES = 5 * 1024 * 1024;
44
+ // Worker bundle is the compact JSON; gzipped copy lives in R2. This constant is
45
+ // a PROXY for Cloudflare's condensed-bundle limit (3 MB free / 10 MB paid) — it
46
+ // hard-fails early so the build dies before the Worker upload does. Because it is
47
+ // a proxy, the number that matters is the gz one, and both are restated here as
48
+ // MEASURED values rather than remembered ones.
49
+ //
50
+ // Measured 2026-08-20 on origin/main (770 entries): uncondensed 5,230,537 B,
51
+ // gz 1,793,879 B. So the real constraint sits at 57 % of the free tier with
52
+ // ~1.2 MB of room, while the old 5 MiB proxy had 12,343 B of headroom — a 0.24 %
53
+ // margin on trunk, which made ANY addition above ~12 KB to rules, guidelines or
54
+ // contexts fail here regardless of merit. Raised to 6 MiB, deliberately well
55
+ // above the measurement: a cap pinned just over the current figure reds on the
56
+ // next commit and teaches people to raise it again.
57
+ //
58
+ // The prior comment claimed "778 KB gz today (438 entries)". That was stale by
59
+ // 2.3x on size and 332 entries — the reason the proxy drifted into a blocker
60
+ // without anyone noticing was that its own justification was never re-measured.
61
+ // If this needs raising again, re-measure BOTH numbers and replace them here;
62
+ // the gz figure against 3 MB is the one that decides whether there is a problem.
63
+ const MAX_UNCONDENSED_BYTES = 6 * 1024 * 1024;
49
64
 
50
65
  /** Sentinel for the early-exit paths (`raise SystemExit(n)` in Python). */
51
66
  class SystemExit extends Error {
@@ -2475,55 +2475,54 @@ export function nothing_to_release_ci(
2475
2475
 
2476
2476
  const _RELEASE_BRANCH_RE = /^release\/(\d+\.\d+\.\d+)$/;
2477
2477
 
2478
+ /** `package.json`'s `version`, or null when unreadable, not JSON, or key-less. */
2479
+ function _package_version_or_null(): string | null {
2480
+ try {
2481
+ const data = JSON.parse(fs.readFileSync(PACKAGE_JSON, 'utf-8')) as Record<string, unknown>;
2482
+ return 'version' in data ? (data['version'] as string) : null;
2483
+ } catch {
2484
+ return null;
2485
+ }
2486
+ }
2487
+
2488
+ /** Observations `_detect_in_flight_target` reads; injected by its tests. */
2489
+ export interface InFlightProbes {
2490
+ head_branch?: () => string;
2491
+ package_version?: () => string | null;
2492
+ /** True when the tag is ON THE REMOTE — never merely local. */
2493
+ tag_published?: (tag: string) => boolean;
2494
+ }
2495
+
2478
2496
  /**
2479
- * Find the in-flight release target — the SOURCE OF TRUTH is package.json.
2480
- *
2481
- * An "in-flight" release is one whose version was already bumped into `main`'s
2482
- * `package.json` (and possibly merged) but whose tag has not yet been pushed —
2483
- * i.e. the publish step never completed. The canonical anchor is therefore
2484
- * `package.json` version `V` **with no matching tag `V`**, NOT the set of
2485
- * `release/X.Y.Z` branches.
2486
- *
2487
- * Why not the branch set: merged release branches are frequently left
2488
- * undeleted on the remote, so "highest existing release/* branch" can resolve
2489
- * to an OLD, already-published version (e.g. picking 5.4.0 while 5.8.0 is the
2490
- * real in-flight target) and tag a downgrade. The package.json version cannot
2491
- * lie that way — it is the version main currently claims to be, and an
2492
- * untagged claim is exactly an incomplete release.
2493
- *
2494
- * Resolution order:
2495
- * 1. If HEAD is on a `release/X.Y.Z` branch, that explicit checkout wins.
2496
- * 2. Else: read `package.json` version `V`. If tag `V` does not exist
2497
- * (local or remote), `V` is the in-flight target. If it is already
2498
- * tagged, the release is complete → return null (regular bump path).
2499
- *
2500
- * Stale `release/*` branches are never used for version detection.
2497
+ * In-flight release target: `package.json` claims `V` while no PUBLISHED tag
2498
+ * `V` exists, so the publish step never completed. HEAD on `release/X.Y.Z`
2499
+ * wins first; stale `release/*` branches are never consulted (a merged one
2500
+ * left undeleted can tag a downgrade). PUBLISHED means on the REMOTE, because
2501
+ * publish-npm.yml triggers on `push: tags:`. Why not a local tag, and the
2502
+ * 14.6.0 release that stranded on the old form: see
2503
+ * `tests/scripts/release.test.ts` § _detect_in_flight_target.
2501
2504
  */
2502
- function _detect_in_flight_target(): string | null {
2503
- const head = git(['rev-parse', '--abbrev-ref', 'HEAD'], { capture: true });
2504
- const m = _RELEASE_BRANCH_RE.exec(head);
2505
+ function _detect_in_flight_target(probes: InFlightProbes = {}): string | null {
2506
+ const head_branch =
2507
+ probes.head_branch ?? (() => git(['rev-parse', '--abbrev-ref', 'HEAD'], { capture: true }));
2508
+ const package_version = probes.package_version ?? _package_version_or_null;
2509
+ const tag_published = probes.tag_published ?? _tag_exists_remote;
2510
+
2511
+ const m = _RELEASE_BRANCH_RE.exec(head_branch());
2505
2512
  if (m) {
2506
2513
  return m[1] as string;
2507
2514
  }
2508
-
2509
- let version: string;
2510
- try {
2511
- const data = JSON.parse(fs.readFileSync(PACKAGE_JSON, 'utf-8')) as Record<string, unknown>;
2512
- if (!('version' in data)) {
2513
- return null; // KeyError analogue.
2514
- }
2515
- version = data['version'] as string;
2516
- } catch {
2517
- return null; // OSError / JSONDecodeError analogue.
2515
+ const version = package_version();
2516
+ if (version === null) {
2517
+ return null;
2518
2518
  }
2519
2519
  try {
2520
2520
  parse_version(version);
2521
2521
  } catch {
2522
2522
  return null;
2523
2523
  }
2524
-
2525
- // An already-tagged version is a completed release, not in-flight.
2526
- if (_tag_exists_local(version) || _tag_exists_remote(version)) {
2524
+ // A PUBLISHED tag is a completed release; a local-only tag is not.
2525
+ if (tag_published(version)) {
2527
2526
  return null;
2528
2527
  }
2529
2528
  return version;
@@ -0,0 +1,78 @@
1
+ /**
2
+ * report_layer_overlap — print what writing the project layer costs, if anything.
3
+ *
4
+ * Runs as its own step of `task generate-tools` rather than as a call inside
5
+ * `condense.ts`, and the reason is a gate rather than taste: `condense.ts` is
6
+ * already over `check_source_size_budget`'s 1500-line ceiling, and that ratchet is
7
+ * shrink-only — an import plus a call there is +2 lines it refuses, and re-pinning
8
+ * the baseline is named a defect in the gate's own message. So the notice lives
9
+ * beside the task it belongs to.
10
+ *
11
+ * WHY THE NOTICE EXISTS. `generate-tools` writes ONE of the two layers Claude Code
12
+ * loads and was silent about the other existing, while the installer's overlap gate
13
+ * runs at install time and cannot see a layer written afterwards. The overlap is
14
+ * therefore created by whichever producer runs LAST, and neither said so.
15
+ *
16
+ * ADVISORY BY CONSTRUCTION: always exits 0. The build must not fail on a topology
17
+ * the operator may not be able to change today — that would make the build unusable
18
+ * rather than the duplication visible. The check that can refuse is
19
+ * `check_single_delivery --enforce`. Invariant: ADR-236.
20
+ */
21
+
22
+ import * as fs from 'node:fs';
23
+ import * as path from 'node:path';
24
+ import process from 'node:process';
25
+ import { fileURLToPath, pathToFileURL } from 'node:url';
26
+
27
+ import { warnLayerOverlap } from './_lib/layer_overlap_notice.js';
28
+
29
+ // ledger-exempt: this is a REPORTER, not a gate — it always exits 0 by construction
30
+ // (see the docstring above), so there is no verdict for a per-target ledger to
31
+ // account for. `_lib/gate_ledger` records why a gate passed over a target; a script
32
+ // that cannot fail passes over nothing. The advisory-only property is asserted by the
33
+ // task step that calls it, not by this marker.
34
+
35
+ const _HERE = fileURLToPath(import.meta.url);
36
+ const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
37
+
38
+ export function main(argv?: readonly string[]): number {
39
+ const args = argv ?? process.argv.slice(2);
40
+ let projectRoot = REPO_ROOT;
41
+ for (let i = 0; i < args.length; i += 1) {
42
+ const a = args[i];
43
+ if (a === '--project') {
44
+ const v = args[i + 1];
45
+ if (v === undefined || v.startsWith('-')) {
46
+ process.stderr.write('report_layer_overlap: --project needs a directory\n');
47
+ return 1;
48
+ }
49
+ projectRoot = path.resolve(v);
50
+ i += 1;
51
+ } else if (a === '--help' || a === '-h') {
52
+ process.stdout.write('usage: report_layer_overlap [--project DIR]\n');
53
+ return 0;
54
+ } else if (a !== undefined) {
55
+ // Unrecognised arguments are an error even here, where the exit code is
56
+ // otherwise always 0: a silently ignored typo in a build step is how a
57
+ // notice stops firing without anyone noticing.
58
+ process.stderr.write(`report_layer_overlap: unexpected argument ${a}\n`);
59
+ return 1;
60
+ }
61
+ }
62
+ warnLayerOverlap(projectRoot, (m) => process.stdout.write(`${m}\n`));
63
+ return 0;
64
+ }
65
+
66
+ function _isCliEntry(): boolean {
67
+ if (process.argv[1] === undefined) return false;
68
+ if (import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href) return true;
69
+ try {
70
+ return fs.realpathSync(_HERE) === fs.realpathSync(path.resolve(process.argv[1]));
71
+ } catch {
72
+ return false;
73
+ }
74
+ }
75
+
76
+ if (_isCliEntry()) {
77
+ process.exit(main());
78
+ }