@event4u/agent-config 14.6.0 → 14.8.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 (258) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +255 -145
  3. package/README.md +1 -1
  4. package/dist/agent-src/commands/analyze/inbox.md +264 -10
  5. package/dist/agent-src/commands/analyze.md +1 -1
  6. package/dist/agent-src/commands/evals/git-pr-merge.json +13 -0
  7. package/dist/agent-src/commands/evals/roadmap.json +17 -1
  8. package/dist/agent-src/commands/pr/create.md +14 -2
  9. package/dist/agent-src/commands/pr/merge.md +379 -0
  10. package/dist/agent-src/commands/roadmap/process-full.md +272 -32
  11. package/dist/agent-src/commands/roadmap.md +1 -1
  12. package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +46 -36
  13. package/dist/agent-src/contexts/execution/autonomy-mechanics.md +37 -2
  14. package/dist/agent-src/contexts/execution/roadmap-execution-contract.md +95 -2
  15. package/dist/agent-src/contexts/execution/roadmap-process-loop.md +289 -106
  16. package/dist/agent-src/rules/artifact-drafting-protocol.md +5 -1
  17. package/dist/agent-src/rules/context-hygiene.md +2 -0
  18. package/dist/agent-src/rules/decision-revisit-gate.md +67 -32
  19. package/dist/agent-src/rules/fix-what-you-see.md +108 -0
  20. package/dist/agent-src/rules/no-roadmap-references.md +0 -5
  21. package/dist/agent-src/rules/recurring-criticism.md +110 -0
  22. package/dist/agent-src/rules/roadmap-progress-sync.md +20 -1
  23. package/dist/agent-src/rules/rule-type-governance.md +0 -2
  24. package/dist/agent-src/rules/self-repair-loop.md +17 -0
  25. package/dist/agent-src/rules/skill-quality.md +0 -2
  26. package/dist/agent-src/rules/source-confidentiality.md +37 -7
  27. package/dist/agent-src/scripts/archival_sweep.ts +117 -0
  28. package/dist/agent-src/scripts/archive_completed_roadmaps.ts +26 -7
  29. package/dist/agent-src/scripts/dashboard_mode.ts +212 -0
  30. package/dist/agent-src/scripts/gate_budget.ts +282 -0
  31. package/dist/agent-src/scripts/gate_execute.ts +72 -31
  32. package/dist/agent-src/scripts/roadmap_gates.ts +74 -3
  33. package/dist/agent-src/scripts/update_roadmap_progress.ts +296 -53
  34. package/dist/agent-src/skills/adr-create/SKILL.md +124 -5
  35. package/dist/agent-src/skills/ai-council/SKILL.md +48 -807
  36. package/dist/agent-src/skills/ai-council/references/advanced-modes.md +320 -0
  37. package/dist/agent-src/skills/ai-council/references/cost-and-redaction.md +99 -0
  38. package/dist/agent-src/skills/ai-council/references/execution-modes.md +162 -0
  39. package/dist/agent-src/skills/ai-council/references/output-and-synthesis.md +125 -0
  40. package/dist/agent-src/skills/ai-council/references/procedure.md +143 -0
  41. package/dist/agent-src/skills/decision-record/SKILL.md +61 -3
  42. package/dist/agent-src/skills/decision-review/SKILL.md +65 -0
  43. package/dist/agent-src/skills/merge-conflicts/SKILL.md +26 -0
  44. package/dist/agent-src/skills/quality-tools/SKILL.md +34 -326
  45. package/dist/agent-src/skills/quality-tools/references/js-ts-tools.md +113 -0
  46. package/dist/agent-src/skills/quality-tools/references/php-tools.md +222 -0
  47. package/dist/agent-src/skills/roadmap-management/SKILL.md +15 -409
  48. package/dist/agent-src/skills/roadmap-management/references/archival.md +226 -0
  49. package/dist/agent-src/skills/roadmap-management/references/authoring.md +107 -0
  50. package/dist/agent-src/skills/roadmap-management/references/dashboard.md +47 -0
  51. package/dist/agent-src/skills/roadmap-management/references/lifecycle.md +58 -0
  52. package/dist/agent-src/skills/security/SKILL.md +8 -0
  53. package/dist/agent-src/skills/security-audit/SKILL.md +12 -1
  54. package/dist/agent-src/skills/skill-writing/SKILL.md +34 -570
  55. package/dist/agent-src/skills/skill-writing/references/procedure.md +275 -0
  56. package/dist/agent-src/skills/skill-writing/references/read-only-scripts.md +18 -0
  57. package/dist/agent-src/skills/skill-writing/references/section-patterns.md +298 -0
  58. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  59. package/dist/agent-src/templates/roadmaps.md +65 -4
  60. package/dist/agent-src/templates/scripts/telemetry/flush_sender.mjs +163 -0
  61. package/dist/agent-src/templates/scripts/telemetry/remote.ts +160 -7
  62. package/dist/agent-src/templates/scripts/telemetry/transport.ts +169 -0
  63. package/dist/cli/registry.js +2 -1
  64. package/dist/cli/registry.js.map +1 -1
  65. package/dist/cli-delegate/chunk-2GNWGMAL.js +172 -0
  66. package/dist/cli-delegate/{chunk-P6LQDIWK.js → chunk-7PRE6ZV2.js} +338 -0
  67. package/dist/cli-delegate/{chunk-BKO2AZ7Z.js → chunk-B2ERU2VA.js} +1 -1
  68. package/dist/cli-delegate/chunk-E4XIZDIB.js +402 -0
  69. package/dist/cli-delegate/{chunk-3UCJWMMP.js → chunk-EEJUMCDZ.js} +16 -16
  70. package/dist/cli-delegate/{chunk-NXFWFAYV.js → chunk-G23AT6GD.js} +1 -0
  71. package/dist/cli-delegate/{chunk-XURJ4AQC.js → chunk-H4VIVD4C.js} +385 -284
  72. package/dist/cli-delegate/chunk-I3EIN2YG.js +36 -0
  73. package/dist/cli-delegate/{chunk-RLN73PR5.js → chunk-LPPWO5CE.js} +12 -8
  74. package/dist/cli-delegate/chunk-MWRD5YOH.js +100 -0
  75. package/dist/cli-delegate/{chunk-UEZ7D35A.js → chunk-N7CDDGI3.js} +4 -23
  76. package/dist/cli-delegate/{chunk-63WSP6YZ.js → chunk-PMEKGDED.js} +74 -167
  77. package/dist/cli-delegate/{chunk-GRRPEPRN.js → chunk-T7L2QK3K.js} +24 -5
  78. package/dist/cli-delegate/{chunk-EVTY7ISR.js → chunk-YM4GZDJG.js} +9 -1
  79. package/dist/cli-delegate/cmd_adr_effective.js +1162 -0
  80. package/dist/cli-delegate/cmd_conformance.js +466 -716
  81. package/dist/cli-delegate/cmd_converge.js +3 -3
  82. package/dist/cli-delegate/cmd_doctor.js +10 -10
  83. package/dist/cli-delegate/cmd_explain.js +1 -1
  84. package/dist/cli-delegate/cmd_export.js +10 -9
  85. package/dist/cli-delegate/cmd_handoff.js +10 -9
  86. package/dist/cli-delegate/cmd_memory_get.js +2 -1
  87. package/dist/cli-delegate/cmd_migrate.js +2 -2
  88. package/dist/cli-delegate/cmd_packs_active.js +1 -1
  89. package/dist/cli-delegate/cmd_preflight.js +5 -5
  90. package/dist/cli-delegate/cmd_prune.js +11 -10
  91. package/dist/cli-delegate/cmd_route_audit.js +6 -5
  92. package/dist/cli-delegate/cmd_route_explain.js +3 -3
  93. package/dist/cli-delegate/cmd_session_recycle.js +840 -30
  94. package/dist/cli-delegate/cmd_settings_get.js +3 -3
  95. package/dist/cli-delegate/cmd_settings_migrate.js +10 -9
  96. package/dist/cli-delegate/cmd_settings_set.js +1 -1
  97. package/dist/cli-delegate/cmd_sync.js +11 -10
  98. package/dist/cli-delegate/cmd_uninstall.js +13 -11
  99. package/dist/cli-delegate/cmd_update.js +4 -4
  100. package/dist/cli-delegate/cmd_upgrade.js +4 -4
  101. package/dist/cli-delegate/cmd_validate.js +11 -10
  102. package/dist/cli-delegate/cmd_versions.js +3 -3
  103. package/dist/discovery/deprecation-report.md +1 -1
  104. package/dist/discovery/discovery-manifest.json +111 -37
  105. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  106. package/dist/discovery/discovery-manifest.summary.md +5 -5
  107. package/dist/discovery/dormancy-report.md +1 -1
  108. package/dist/discovery/orphan-report.md +1 -1
  109. package/dist/discovery/packs.json +15 -12
  110. package/dist/discovery/trust-report.md +3 -3
  111. package/dist/discovery/workspaces.json +11 -8
  112. package/dist/hooks/dispatch.js +3165 -2192
  113. package/dist/install/claudePathsPlan.js +187 -0
  114. package/dist/install/claudePathsPlan.js.map +1 -0
  115. package/dist/install/globalRuleLayers.js +169 -0
  116. package/dist/install/globalRuleLayers.js.map +1 -0
  117. package/dist/install/hostLayerFingerprint.js +136 -0
  118. package/dist/install/hostLayerFingerprint.js.map +1 -0
  119. package/dist/install/install.mjs +553 -425
  120. package/dist/install/partitionEligibility.js +432 -0
  121. package/dist/install/partitionEligibility.js.map +1 -0
  122. package/dist/install/ruleLayerPartition.js +157 -0
  123. package/dist/install/ruleLayerPartition.js.map +1 -0
  124. package/dist/mcp/registry-manifest.json +3 -3
  125. package/dist/mcp/server.json +2 -2
  126. package/dist/mcp/server.mjs +1863 -1234
  127. package/dist/router.json +1 -1
  128. package/dist/scripts/_lib/agent_src.js +41 -0
  129. package/dist/scripts/_lib/agent_src.js.map +1 -1
  130. package/dist/scripts/_lib/install_layout.js +83 -0
  131. package/dist/scripts/_lib/install_layout.js.map +1 -0
  132. package/dist/scripts/_lib/installed_lock.js +444 -0
  133. package/dist/scripts/_lib/installed_lock.js.map +1 -0
  134. package/dist/scripts/_lib/prune_empty_dirs.js +85 -0
  135. package/dist/scripts/_lib/prune_empty_dirs.js.map +1 -0
  136. package/dist/scripts/condense.js +63 -251
  137. package/dist/scripts/condense.js.map +1 -1
  138. package/dist/server/schemas/settings.js +4 -0
  139. package/dist/server/schemas/settings.js.map +1 -1
  140. package/docs/contracts/settings-classes.md +12 -6
  141. package/docs/guidelines/agent-infra/agent-interaction-and-decision-quality.md +8 -3
  142. package/docs/guidelines/agent-infra/artifact-drafting-protocol-mechanics.md +45 -1
  143. package/docs/guidelines/agent-infra/context-hygiene-mechanics.md +26 -0
  144. package/docs/guidelines/agent-infra/false-green.md +47 -1
  145. package/docs/guidelines/agent-infra/memory-access.md +17 -0
  146. package/docs/guidelines/agent-infra/recurring-criticism-mechanics.md +96 -0
  147. package/package.json +1 -1
  148. package/src/agent-src/scripts/archival_sweep.ts +117 -0
  149. package/src/agent-src/scripts/archive_completed_roadmaps.ts +26 -7
  150. package/src/agent-src/scripts/dashboard_mode.ts +212 -0
  151. package/src/agent-src/scripts/gate_budget.ts +282 -0
  152. package/src/agent-src/scripts/gate_execute.ts +72 -31
  153. package/src/agent-src/scripts/roadmap_gates.ts +74 -3
  154. package/src/agent-src/scripts/update_roadmap_progress.ts +296 -53
  155. package/src/agent-src/templates/scripts/telemetry/flush_sender.mjs +163 -0
  156. package/src/agent-src/templates/scripts/telemetry/remote.ts +160 -7
  157. package/src/agent-src/templates/scripts/telemetry/transport.ts +169 -0
  158. package/src/config/agent-settings.template.yml +27 -2
  159. package/src/config/ci-local-parity.yml +39 -0
  160. package/src/config/estate-count-budget.json +612 -3
  161. package/src/config/evaluator-budgets.json +3 -3
  162. package/src/config/gate-coverage.yml +220 -12
  163. package/src/config/gate-violation-baselines.json +73 -63
  164. package/src/config/gitignore-block.txt +15 -2
  165. package/src/config/hook-latency-budget.json +5 -0
  166. package/src/config/rule-activation-census.json +14 -6
  167. package/src/config/rule-stub-ceilings.json +34 -7
  168. package/src/install/claudePathsPlan.ts +196 -0
  169. package/src/install/globalRuleLayers.ts +197 -0
  170. package/src/install/hostLayerFingerprint.ts +145 -0
  171. package/src/install/partitionEligibility.ts +502 -0
  172. package/src/install/ruleLayerPartition.ts +212 -0
  173. package/src/scripts/_cli/cmd_adr_effective.ts +484 -0
  174. package/src/scripts/_cli/cmd_session_recycle.ts +9 -1
  175. package/src/scripts/_dispatch.bash +17 -1
  176. package/src/scripts/_lib/ac_heading.ts +30 -0
  177. package/src/scripts/_lib/adr_frontmatter.test.ts +365 -0
  178. package/src/scripts/_lib/adr_frontmatter.ts +459 -0
  179. package/src/scripts/_lib/agent_src.ts +42 -0
  180. package/src/scripts/_lib/bench_ab_pinned_repo.ts +162 -0
  181. package/src/scripts/_lib/bench_ab_workspace.ts +10 -1
  182. package/src/scripts/_lib/envelope_grounding.ts +49 -9
  183. package/src/scripts/_lib/installed_lock.ts +50 -5
  184. package/src/scripts/_lib/layer_overlap_notice.ts +11 -2
  185. package/src/scripts/_lib/md_section.ts +129 -0
  186. package/src/scripts/_lib/prune_empty_dirs.ts +80 -0
  187. package/src/scripts/_lib/scope_guard.sh +26 -3
  188. package/src/scripts/_lib/self_repair.ts +19 -0
  189. package/src/scripts/_lib/self_repair_class_b.ts +171 -0
  190. package/src/scripts/adr/evidence_census.ts +899 -0
  191. package/src/scripts/adr/regenerate_index.ts +12 -32
  192. package/src/scripts/adr_cite_check.ts +540 -39
  193. package/src/scripts/ai_council/cli_help.ts +55 -0
  194. package/src/scripts/audit_adr_coverage.ts +103 -17
  195. package/src/scripts/before_complete_hook.ts +153 -28
  196. package/src/scripts/bench_ab_v2_complexity.ts +27 -7
  197. package/src/scripts/bench_hook_latency.ts +204 -0
  198. package/src/scripts/build_archive_index.ts +34 -0
  199. package/src/scripts/check_adr_frontmatter.ts +574 -39
  200. package/src/scripts/check_hook_bundle_content.ts +219 -0
  201. package/src/scripts/check_new_adr_evidence.ts +529 -0
  202. package/src/scripts/check_references.ts +78 -0
  203. package/src/scripts/check_release_published.ts +32 -11
  204. package/src/scripts/check_rule_layer_partition.ts +531 -0
  205. package/src/scripts/check_single_delivery.ts +194 -17
  206. package/src/scripts/check_standing_rule_delivery.ts +8 -2
  207. package/src/scripts/ci_settle.ts +170 -0
  208. package/src/scripts/condense.ts +79 -259
  209. package/src/scripts/council_cli.ts +3 -3
  210. package/src/scripts/dispatch_r2_reviewer.ts +6 -14
  211. package/src/scripts/external_sources_denylist.json +4 -1
  212. package/src/scripts/hook_manifest.json +1 -1
  213. package/src/scripts/hook_manifest.yaml +77 -14
  214. package/src/scripts/hooks/block_no_verify.ts +61 -10
  215. package/src/scripts/hooks/block_unauthorized_git.ts +21 -1
  216. package/src/scripts/hooks/concern_registry.ts +4 -0
  217. package/src/scripts/hooks/dispatch_hook.ts +154 -187
  218. package/src/scripts/hooks/dispatch_issues.ts +38 -19
  219. package/src/scripts/hooks/fallback_yaml.ts +66 -0
  220. package/src/scripts/hooks/host_semantics.ts +21 -0
  221. package/src/scripts/hooks/injection_budget.ts +13 -3
  222. package/src/scripts/hooks/py_json_dumps.ts +103 -0
  223. package/src/scripts/hooks/rtk_wrap_hook.ts +8 -3
  224. package/src/scripts/hooks/run_continuation_hook.ts +8 -5
  225. package/src/scripts/hooks/ship_diff_volume_hook.ts +81 -15
  226. package/src/scripts/hooks/source_first_gate_hook.ts +464 -0
  227. package/src/scripts/hooks/state_io.ts +870 -21
  228. package/src/scripts/hooks/stdin_failure_policy.ts +96 -0
  229. package/src/scripts/hooks/subagent_ledger_hook.ts +44 -15
  230. package/src/scripts/hooks/telemetry_flush_hook.ts +139 -0
  231. package/src/scripts/hooks/telemetry_self_repair.ts +128 -0
  232. package/src/scripts/hooks/telemetry_usage_hook.ts +22 -4
  233. package/src/scripts/hooks/turn_end_gate_hook.ts +81 -15
  234. package/src/scripts/injection_scan_hook.ts +137 -5
  235. package/src/scripts/install.ts +16 -11
  236. package/src/scripts/language_mirror_hook.ts +561 -25
  237. package/src/scripts/lint_framework_leakage_allowlist.json +2 -2
  238. package/src/scripts/lint_hook_manifest.ts +49 -0
  239. package/src/scripts/lint_plan_risk_register.ts +4 -3
  240. package/src/scripts/lint_provenance_vocabulary.ts +472 -4
  241. package/src/scripts/lint_skill_router_head.ts +4 -4
  242. package/src/scripts/mcp_server/tools.ts +2 -2
  243. package/src/scripts/new_roadmap.ts +136 -0
  244. package/src/scripts/pack_mcp_content.ts +20 -5
  245. package/src/scripts/release.ts +37 -38
  246. package/src/scripts/report_layer_overlap.ts +6 -0
  247. package/src/scripts/routing_doctor.ts +5 -1
  248. package/src/scripts/self_repair_hook.ts +37 -5
  249. package/src/scripts/session_register_hook.ts +11 -0
  250. package/src/scripts/skill_usage_collect.ts +11 -1
  251. package/src/scripts/skill_usage_report.ts +278 -8
  252. package/src/scripts/sync_pr_branch.ts +170 -20
  253. package/src/server/schemas/settings.ts +8 -0
  254. package/dist/cli-delegate/chunk-EJITJBJX.js +0 -172
  255. package/dist/cli-delegate/chunk-PZWRPRWP.js +0 -38
  256. package/dist/cli-delegate/{chunk-PPQO4NGR.js → chunk-6RJA2F5A.js} +3 -3
  257. package/dist/cli-delegate/{chunk-RYNADUFG.js → chunk-LWTYTX6F.js} +0 -0
  258. package/dist/cli-delegate/{chunk-FBKL6JWT.js → chunk-MMSGVZKV.js} +3 -3
@@ -0,0 +1,219 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * Content-equivalence gate for the self-hosted hook bundle.
4
+ *
5
+ * WHY THIS EXISTS — measured, not hypothetical.
6
+ *
7
+ * `check_hook_bundle_freshness` compares **mtimes**: every bundled hook source
8
+ * must be older than `dist/hooks/dispatch.js`. That establishes ordering, not
9
+ * equivalence. An mtime-preserving edit, a `touch` on the bundle, a restore
10
+ * from an archive, or a filesystem with coarse timestamp resolution all make
11
+ * the inference false while the check stays green.
12
+ *
13
+ * On 2026-08-21 a live run edited `LEDGER_MAX_AGE_MS` in
14
+ * `block_unauthorized_git.ts`, did not rebuild, and the guard kept enforcing
15
+ * the old value; all three copies (source, repo bundle, global install) were
16
+ * inspected by hand before anyone noticed. **The freshness gate did not miss
17
+ * that one — it is pre-push and was never run.** Saying it "stayed green" would
18
+ * be a plausible mechanism standing in for the observed one, which is the
19
+ * failure this repository's own evidence discipline names.
20
+ *
21
+ * What the mtime check genuinely cannot see is narrower and is the reason this
22
+ * gate exists: the mtime check cannot see an edit that does not move the mtime — an
23
+ mtime-preserving write, a `touch` on the bundle, an archive or `cp -p` restore,
24
+ or a filesystem whose timestamp resolution is coarser than the edit interval.
25
+ Demonstrated on this tree rather than argued: an mtime-preserving edit to one
26
+ constant left the bundle at 1 139 302 bytes with an untouched mtime, the
27
+ freshness gate passed, and the digest differed.
28
+ *
29
+ * This gate answers the question directly — are the bytes that execute the
30
+ * bytes this source produces?
31
+ *
32
+ * WHAT IT CANNOT DO, STATED PLAINLY.
33
+ *
34
+ * `dist/hooks/` is **untracked** — `git ls-files dist/hooks/` returns zero.
35
+ * There is no committed artefact to compare against, so this gate can only
36
+ * compare the bundle **present on this machine** against a fresh rebuild from
37
+ * this checkout's sources. In CI a fresh checkout has no bundle at all, so
38
+ * this gate declares a loud no-op there and a green CI says nothing about the
39
+ * bundle a maintainer is actually running. That asymmetry is the finding, not
40
+ * a gap to paper over: the bundle is a local execution artefact and only a
41
+ * local gate can see it.
42
+ *
43
+ * The esbuild invocation is NOT duplicated here — it is read from
44
+ * `package.json`'s `build:hooks` script with only `--outfile=` rewritten, via
45
+ * the same `rewriteOutfile` helper `rebuild_hook_bundle` uses, so the flag set
46
+ * has exactly one home.
47
+ *
48
+ * DETERMINISM — measured, not assumed. Two consecutive rebuilds of an
49
+ * unchanged tree produced the identical sha256 (`616561fb…`, 2026-08-21), and
50
+ * a one-constant edit that left the output byte-count unchanged
51
+ * (1 139 302 bytes both times) still produced a different digest. So the
52
+ * comparison is byte-exact with no canonicalisation, and it is sensitive to
53
+ * the smallest semantic change. Had the rebuild NOT been reproducible, that
54
+ * would have been a release-integrity defect to fix rather than a reason to
55
+ * fall back to timestamps.
56
+ *
57
+ * Exit codes: 0 = equivalent, or no bundle to check · 1 = the executing bundle
58
+ * is not what this source produces · 2 = the rebuild itself failed.
59
+ */
60
+ // ledger-exempt: single-artefact transaction — one rebuild compared against one
61
+ // bundle, yielding one aggregate verdict. The per-source accounting lives in
62
+ // check_hook_bundle_freshness, which carries the ledger.
63
+ import { spawnSync } from "node:child_process";
64
+ import * as crypto from "node:crypto";
65
+ import * as fs from "node:fs";
66
+ import * as path from "node:path";
67
+ import { fileURLToPath } from "node:url";
68
+
69
+ import { reportScanned } from "./_lib/scan_scope.js";
70
+ import { rewriteOutfile } from "./rebuild_hook_bundle.js";
71
+
72
+ const HERE = path.dirname(fileURLToPath(import.meta.url));
73
+ const REPO_ROOT = path.resolve(HERE, "..", "..");
74
+ const LIVE_REL = path.join("dist", "hooks", "dispatch.js");
75
+ // Suffixed with the pid: two concurrent invocations in one checkout (the
76
+ // pre-push gate plus a manual run) otherwise share one path, the first
77
+ // `finally` deletes the other's output, and the survivor reports "rebuild
78
+ // failed" off a missing file — a false red on a pre-push gate, which is the
79
+ // failure mode the PATH injection below exists to avoid.
80
+ const PROBE_REL = path.join("dist", "hooks", `dispatch.content-check.${String(process.pid)}.js`);
81
+
82
+ /** sha256 of a file's bytes, hex. */
83
+ export function digestOf(file: string): string {
84
+ return crypto.createHash("sha256").update(fs.readFileSync(file)).digest("hex");
85
+ }
86
+
87
+ /**
88
+ * Read `build:hooks` out of package.json. Returns null when the script is
89
+ * absent — a renamed build script must fail loudly, never be guessed at.
90
+ */
91
+ export function buildScript(repoRoot: string): string | null {
92
+ const pkgPath = path.join(repoRoot, "package.json");
93
+ if (!fs.existsSync(pkgPath)) return null;
94
+ const pkg: unknown = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
95
+ const scripts =
96
+ typeof pkg === "object" && pkg !== null ? (pkg as Record<string, unknown>)["scripts"] : null;
97
+ if (typeof scripts !== "object" || scripts === null) return null;
98
+ const s = (scripts as Record<string, unknown>)["build:hooks"];
99
+ return typeof s === "string" ? s : null;
100
+ }
101
+
102
+ export function main(root: string = REPO_ROOT): number {
103
+ const live = path.join(root, LIVE_REL);
104
+ if (!fs.existsSync(live)) {
105
+ // Loud no-op, matching the sibling gates: a fresh checkout or CI has no
106
+ // bundle, and materialising one here would change what other gates see.
107
+ process.stdout.write(
108
+ `check_hook_bundle_content: no self-hosted ${LIVE_REL} — nothing executes locally, nothing to compare\n`,
109
+ );
110
+ reportScanned({
111
+ gate: "check_hook_bundle_content",
112
+ scanned: 0,
113
+ units: "bundle(s)",
114
+ roots: [LIVE_REL],
115
+ allowEmpty:
116
+ "OPTIONAL_INPUT: dist/hooks/ is UNTRACKED (`git ls-files dist/hooks/` returns zero), so a fresh " +
117
+ "checkout or a CI runner has no bundle by construction. An empty scope here is the " +
118
+ "correct reading of the environment, not a dead scan root — the artefact this gate " +
119
+ "guards is a local build output and exists only where the hooks actually run.",
120
+ });
121
+ return 0;
122
+ }
123
+
124
+ const script = buildScript(root);
125
+ if (script === null) {
126
+ process.stderr.write(
127
+ "check_hook_bundle_content: package.json has no `build:hooks` script — refusing to guess the flag set\n",
128
+ );
129
+ return 2;
130
+ }
131
+ const rewritten = rewriteOutfile(script, PROBE_REL);
132
+ if (rewritten === null) {
133
+ process.stderr.write(
134
+ `check_hook_bundle_content: \`build:hooks\` no longer writes ${LIVE_REL} — refusing to build to a guessed path\n`,
135
+ );
136
+ return 2;
137
+ }
138
+
139
+ const probe = path.join(root, PROBE_REL);
140
+ try {
141
+ // PATH is injected exactly as `rebuild_hook_bundle` does it, and for the
142
+ // same reason: `build:hooks` starts with a bare `esbuild`, so inheriting an
143
+ // ambient PATH without `node_modules/.bin` makes this gate report "rebuild
144
+ // failed" on a bundle that is in fact correct — a false red on a pre-push
145
+ // gate, which is how a pre-push gate teaches people to skip it.
146
+ const built = spawnSync("sh", ["-c", rewritten], {
147
+ cwd: root,
148
+ encoding: "utf8",
149
+ env: {
150
+ ...process.env,
151
+ PATH: `${path.join(root, "node_modules", ".bin")}:${process.env["PATH"] ?? ""}`,
152
+ },
153
+ });
154
+ if (built.status !== 0 || !fs.existsSync(probe)) {
155
+ process.stderr.write(
156
+ `check_hook_bundle_content: rebuild failed (exit ${String(built.status)})\n${built.stderr ?? ""}\n`,
157
+ );
158
+ return 2;
159
+ }
160
+
161
+ const liveDigest = digestOf(live);
162
+ const probeDigest = digestOf(probe);
163
+ if (liveDigest === probeDigest) {
164
+ process.stdout.write(
165
+ `✅ ${LIVE_REL} is byte-identical to a rebuild from this source (sha256 ${liveDigest.slice(0, 12)})\n`,
166
+ );
167
+ reportScanned({
168
+ gate: "check_hook_bundle_content",
169
+ scanned: 1,
170
+ units: "bundle(s)",
171
+ roots: [LIVE_REL],
172
+ });
173
+ return 0;
174
+ }
175
+
176
+ process.stderr.write(
177
+ `❌ ${LIVE_REL} is NOT what this source produces.\n` +
178
+ ` executing: sha256 ${liveDigest.slice(0, 12)} (${String(fs.statSync(live).size)} bytes)\n` +
179
+ ` rebuilt: sha256 ${probeDigest.slice(0, 12)} (${String(fs.statSync(probe).size)} bytes)\n` +
180
+ ` The mtime check cannot see this — it compares ordering, not bytes.\n` +
181
+ ` Heal with: ./scripts-run src/scripts/rebuild_hook_bundle\n`,
182
+ );
183
+ reportScanned({
184
+ gate: "check_hook_bundle_content",
185
+ scanned: 1,
186
+ units: "bundle(s)",
187
+ roots: [LIVE_REL],
188
+ });
189
+ return 1;
190
+ } finally {
191
+ try {
192
+ fs.rmSync(probe, { force: true });
193
+ } catch {
194
+ // Best effort — a leftover probe file is untracked build output.
195
+ }
196
+ }
197
+ }
198
+
199
+ // Resolved on BOTH sides with `realpathSync`, mirroring `rebuild_hook_bundle`.
200
+ // A string compare against `import.meta.url` is false under a symlinked
201
+ // checkout, and under any repo path containing a space or a non-ASCII
202
+ // character, which `import.meta.url` percent-encodes and `path.resolve` does
203
+ // not. The failure is silent: `main()` never runs, the process exits 0, and
204
+ // the gate reports nothing — a green on an invariant it never evaluated,
205
+ // which is the exact class this gate exists to remove.
206
+ const invokedDirectly = ((): boolean => {
207
+ const entry = process.argv[1];
208
+ if (entry === undefined) return false;
209
+ try {
210
+ return fs.realpathSync(path.resolve(entry)) === fs.realpathSync(fileURLToPath(import.meta.url));
211
+ } catch {
212
+ return false;
213
+ }
214
+ })();
215
+
216
+ if (invokedDirectly) {
217
+ process.exit(main());
218
+ }
219
+