@event4u/agent-config 14.7.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 (138) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +245 -388
  3. package/README.md +1 -1
  4. package/dist/agent-src/commands/evals/git-pr-merge.json +13 -0
  5. package/dist/agent-src/commands/evals/roadmap.json +17 -1
  6. package/dist/agent-src/commands/pr/create.md +14 -2
  7. package/dist/agent-src/commands/pr/merge.md +379 -0
  8. package/dist/agent-src/commands/roadmap/process-full.md +157 -3
  9. package/dist/agent-src/commands/roadmap.md +1 -1
  10. package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +46 -36
  11. package/dist/agent-src/contexts/execution/autonomy-mechanics.md +37 -2
  12. package/dist/agent-src/contexts/execution/roadmap-execution-contract.md +64 -2
  13. package/dist/agent-src/contexts/execution/roadmap-process-loop.md +154 -2
  14. package/dist/agent-src/rules/artifact-drafting-protocol.md +5 -1
  15. package/dist/agent-src/rules/decision-revisit-gate.md +65 -33
  16. package/dist/agent-src/rules/recurring-criticism.md +1 -1
  17. package/dist/agent-src/rules/roadmap-progress-sync.md +19 -0
  18. package/dist/agent-src/rules/source-confidentiality.md +1 -1
  19. package/dist/agent-src/scripts/archive_completed_roadmaps.ts +7 -4
  20. package/dist/agent-src/scripts/dashboard_mode.ts +212 -0
  21. package/dist/agent-src/scripts/update_roadmap_progress.ts +47 -48
  22. package/dist/agent-src/skills/adr-create/SKILL.md +124 -5
  23. package/dist/agent-src/skills/decision-record/SKILL.md +61 -3
  24. package/dist/agent-src/skills/decision-review/SKILL.md +65 -0
  25. package/dist/agent-src/skills/merge-conflicts/SKILL.md +26 -0
  26. package/dist/agent-src/skills/security/SKILL.md +8 -0
  27. package/dist/agent-src/skills/security-audit/SKILL.md +12 -1
  28. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  29. package/dist/agent-src/templates/roadmaps.md +20 -2
  30. package/dist/cli/registry.js +1 -0
  31. package/dist/cli/registry.js.map +1 -1
  32. package/dist/cli-delegate/{chunk-JZ4NR2TW.js → chunk-7PRE6ZV2.js} +1 -1
  33. package/dist/cli-delegate/{chunk-7F3VYSM2.js → chunk-EEJUMCDZ.js} +14 -14
  34. package/dist/cli-delegate/{chunk-74E3TKWV.js → chunk-H4VIVD4C.js} +5 -5
  35. package/dist/cli-delegate/chunk-I3EIN2YG.js +36 -0
  36. package/dist/cli-delegate/{chunk-4VR4ORQ6.js → chunk-LPPWO5CE.js} +4 -3
  37. package/dist/cli-delegate/{chunk-UEZ7D35A.js → chunk-N7CDDGI3.js} +4 -23
  38. package/dist/cli-delegate/cmd_adr_effective.js +1162 -0
  39. package/dist/cli-delegate/cmd_conformance.js +14 -14
  40. package/dist/cli-delegate/cmd_converge.js +3 -3
  41. package/dist/cli-delegate/cmd_doctor.js +7 -7
  42. package/dist/cli-delegate/cmd_export.js +6 -6
  43. package/dist/cli-delegate/cmd_handoff.js +7 -6
  44. package/dist/cli-delegate/cmd_memory_get.js +2 -1
  45. package/dist/cli-delegate/cmd_migrate.js +2 -2
  46. package/dist/cli-delegate/cmd_preflight.js +5 -5
  47. package/dist/cli-delegate/cmd_prune.js +6 -6
  48. package/dist/cli-delegate/cmd_route_audit.js +6 -5
  49. package/dist/cli-delegate/cmd_route_explain.js +3 -3
  50. package/dist/cli-delegate/cmd_session_recycle.js +3 -3
  51. package/dist/cli-delegate/cmd_settings_get.js +3 -3
  52. package/dist/cli-delegate/cmd_settings_migrate.js +6 -6
  53. package/dist/cli-delegate/cmd_sync.js +6 -6
  54. package/dist/cli-delegate/cmd_uninstall.js +6 -6
  55. package/dist/cli-delegate/cmd_update.js +3 -3
  56. package/dist/cli-delegate/cmd_upgrade.js +3 -3
  57. package/dist/cli-delegate/cmd_validate.js +6 -6
  58. package/dist/cli-delegate/cmd_versions.js +3 -3
  59. package/dist/discovery/deprecation-report.md +1 -1
  60. package/dist/discovery/discovery-manifest.json +53 -22
  61. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  62. package/dist/discovery/discovery-manifest.summary.md +3 -3
  63. package/dist/discovery/dormancy-report.md +1 -1
  64. package/dist/discovery/orphan-report.md +1 -1
  65. package/dist/discovery/packs.json +7 -6
  66. package/dist/discovery/trust-report.md +2 -2
  67. package/dist/discovery/workspaces.json +6 -5
  68. package/dist/hooks/dispatch.js +806 -681
  69. package/dist/install/globalRuleLayers.js +169 -0
  70. package/dist/install/globalRuleLayers.js.map +1 -0
  71. package/dist/install/partitionEligibility.js +156 -0
  72. package/dist/install/partitionEligibility.js.map +1 -1
  73. package/dist/install/ruleLayerPartition.js +157 -0
  74. package/dist/install/ruleLayerPartition.js.map +1 -0
  75. package/dist/mcp/registry-manifest.json +3 -3
  76. package/dist/mcp/server.json +2 -2
  77. package/dist/mcp/server.mjs +654 -578
  78. package/dist/scripts/_lib/prune_empty_dirs.js +85 -0
  79. package/dist/scripts/_lib/prune_empty_dirs.js.map +1 -0
  80. package/dist/scripts/condense.js +52 -89
  81. package/dist/scripts/condense.js.map +1 -1
  82. package/docs/guidelines/agent-infra/artifact-drafting-protocol-mechanics.md +45 -1
  83. package/docs/guidelines/agent-infra/false-green.md +47 -1
  84. package/package.json +1 -1
  85. package/src/agent-src/scripts/archive_completed_roadmaps.ts +7 -4
  86. package/src/agent-src/scripts/dashboard_mode.ts +212 -0
  87. package/src/agent-src/scripts/update_roadmap_progress.ts +47 -48
  88. package/src/config/ci-local-parity.yml +21 -0
  89. package/src/config/estate-count-budget.json +275 -2
  90. package/src/config/evaluator-budgets.json +3 -3
  91. package/src/config/gate-coverage.yml +220 -12
  92. package/src/config/gate-violation-baselines.json +73 -63
  93. package/src/config/gitignore-block.txt +15 -2
  94. package/src/config/hook-latency-budget.json +5 -0
  95. package/src/config/rule-stub-ceilings.json +17 -3
  96. package/src/install/globalRuleLayers.ts +197 -0
  97. package/src/install/partitionEligibility.ts +177 -0
  98. package/src/install/ruleLayerPartition.ts +212 -0
  99. package/src/scripts/_cli/cmd_adr_effective.ts +484 -0
  100. package/src/scripts/_dispatch.bash +11 -0
  101. package/src/scripts/_lib/adr_frontmatter.test.ts +365 -0
  102. package/src/scripts/_lib/adr_frontmatter.ts +459 -0
  103. package/src/scripts/_lib/envelope_grounding.ts +11 -1
  104. package/src/scripts/_lib/layer_overlap_notice.ts +11 -2
  105. package/src/scripts/_lib/prune_empty_dirs.ts +80 -0
  106. package/src/scripts/adr/evidence_census.ts +899 -0
  107. package/src/scripts/adr/regenerate_index.ts +12 -32
  108. package/src/scripts/adr_cite_check.ts +540 -39
  109. package/src/scripts/audit_adr_coverage.ts +103 -17
  110. package/src/scripts/bench_hook_latency.ts +204 -0
  111. package/src/scripts/build_archive_index.ts +34 -0
  112. package/src/scripts/check_adr_frontmatter.ts +574 -39
  113. package/src/scripts/check_hook_bundle_content.ts +219 -0
  114. package/src/scripts/check_new_adr_evidence.ts +529 -0
  115. package/src/scripts/check_references.ts +18 -0
  116. package/src/scripts/check_rule_layer_partition.ts +531 -0
  117. package/src/scripts/check_single_delivery.ts +137 -4
  118. package/src/scripts/check_standing_rule_delivery.ts +8 -2
  119. package/src/scripts/condense.ts +61 -91
  120. package/src/scripts/external_sources_denylist.json +4 -1
  121. package/src/scripts/hooks/block_unauthorized_git.ts +22 -2
  122. package/src/scripts/hooks/dispatch_hook.ts +154 -187
  123. package/src/scripts/hooks/dispatch_issues.ts +38 -19
  124. package/src/scripts/hooks/fallback_yaml.ts +66 -0
  125. package/src/scripts/hooks/host_semantics.ts +21 -0
  126. package/src/scripts/hooks/injection_budget.ts +13 -3
  127. package/src/scripts/hooks/py_json_dumps.ts +103 -0
  128. package/src/scripts/hooks/state_io.ts +88 -1
  129. package/src/scripts/hooks/stdin_failure_policy.ts +96 -0
  130. package/src/scripts/injection_scan_hook.ts +137 -5
  131. package/src/scripts/lint_hook_manifest.ts +49 -0
  132. package/src/scripts/lint_provenance_vocabulary.ts +472 -4
  133. package/src/scripts/routing_doctor.ts +5 -1
  134. package/src/scripts/sync_pr_branch.ts +163 -20
  135. package/dist/cli-delegate/{chunk-PPQO4NGR.js → chunk-6RJA2F5A.js} +3 -3
  136. package/dist/cli-delegate/{chunk-6EOCNQAM.js → chunk-E4XIZDIB.js} +0 -0
  137. package/dist/cli-delegate/{chunk-FBKL6JWT.js → chunk-MMSGVZKV.js} +3 -3
  138. package/dist/cli-delegate/{chunk-GJ5BVHXM.js → chunk-PMEKGDED.js} +3 -3
@@ -0,0 +1,212 @@
1
+ // The `--check` tracked/untracked mode table for the roadmap dashboard.
2
+ //
3
+ // Extracted from `update_roadmap_progress.ts` so the verdict is a pure function
4
+ // over observable state — the generator does the I/O, this decides. It also
5
+ // keeps that script under its per-file size cap.
6
+
7
+ import { spawnSync } from 'node:child_process';
8
+ import * as fs from 'node:fs';
9
+
10
+ /**
11
+ * Is the dashboard expected to be a committed artefact? Explicit, never
12
+ * inferred from git state — an unconditional "absent means pass" cannot tell a
13
+ * correctly-untracked repo from a generator that silently stopped producing
14
+ * output. `tracked` is the default and the historical behaviour.
15
+ */
16
+ export type DashboardMode = 'tracked' | 'untracked';
17
+
18
+ /**
19
+ * Is `abs` in the git index? Used only by `--check --untracked-mode`, to tell
20
+ * "correctly absent" from "still committed". No git at all answers `false`,
21
+ * which is correct: nothing is tracked there.
22
+ */
23
+ export function isTrackedInGit(abs: string, repo_root: string): boolean {
24
+ const r = spawnSync('git', ['ls-files', '--error-unmatch', '--', abs], {
25
+ cwd: repo_root,
26
+ encoding: 'utf-8',
27
+ timeout: 10_000,
28
+ });
29
+ return r.status === 0;
30
+ }
31
+
32
+ export interface DashboardStateInput {
33
+ mode: DashboardMode;
34
+ /** Is the file on disk? */
35
+ present: boolean;
36
+ /** Is the file in the git index? Only consulted in `untracked` mode. */
37
+ trackedInGit: boolean;
38
+ /** Does the on-disk content equal a fresh render? Only meaningful if present. */
39
+ current: boolean;
40
+ /** Repo-relative path, for the messages. */
41
+ rel: string;
42
+ /**
43
+ * How to regenerate, for the stale/missing messages. Defaults to the
44
+ * dashboard's own command. A second caller — `build_archive_index --check`
45
+ * — reuses this table for a different generated artefact, and a message
46
+ * telling that reader to run the dashboard generator would be wrong.
47
+ */
48
+ regen?: string;
49
+ /**
50
+ * What the artefact is called in the "deliberately does not commit the X"
51
+ * hint. Defaults to `dashboard` so every existing message is byte-identical.
52
+ */
53
+ noun?: string;
54
+ }
55
+
56
+ export interface DashboardStateVerdict {
57
+ /** True when the dashboard half of `--check` fails. */
58
+ stale: boolean;
59
+ /** Diagnostic for stderr, or null. */
60
+ error: string | null;
61
+ /** Success line for stdout, or null when `stale`. */
62
+ ok: string | null;
63
+ }
64
+
65
+ // The SOURCE path, not `.augment/scripts/…`: that tree is a gitignored
66
+ // projection built by `task sync`, so the advice failed outright in a fresh
67
+ // worktree and in any CI step ordered before the sync.
68
+ const REGEN =
69
+ 'Run `./scripts-run src/agent-src/scripts/update_roadmap_progress` ' +
70
+ 'to regenerate (or `task roadmap-progress` in Taskfile projects).';
71
+
72
+ /**
73
+ * The mode table:
74
+ *
75
+ * tracked: absent -> fail | present+stale -> fail | present+current -> pass
76
+ * untracked: in-index -> fail | absent -> pass | present+stale -> fail
77
+ * | present+current -> pass
78
+ *
79
+ * Absence is distinguished ONLY in untracked mode; a present file is
80
+ * freshness-checked in both, so untracked never means unchecked.
81
+ */
82
+ export function evaluateDashboardState(inp: DashboardStateInput): DashboardStateVerdict {
83
+ const { mode, present, trackedInGit, current, rel } = inp;
84
+ const regen = inp.regen ?? REGEN;
85
+ const noun = inp.noun ?? 'dashboard';
86
+
87
+ if (mode === 'untracked' && trackedInGit) {
88
+ // Migration incomplete: declared untracked, still carried by git.
89
+ // Print the fix; never run it — git-index ops are the user's.
90
+ return {
91
+ stale: true,
92
+ error:
93
+ `❌ ${rel} is still tracked by git, but this repository runs ` +
94
+ '`--untracked-mode`. Untrack it, keeping the working-tree file:\n' +
95
+ ` git rm --cached ${rel}\n`,
96
+ ok: null,
97
+ };
98
+ }
99
+
100
+ if (!present) {
101
+ if (mode === 'tracked') {
102
+ return {
103
+ stale: true,
104
+ error:
105
+ `❌ ${rel} is missing. ` +
106
+ regen.replace('to regenerate', 'to generate it') +
107
+ ' If this repository deliberately does not commit the ' +
108
+ `${noun}, pass \`--untracked-mode\`.\n`,
109
+ ok: null,
110
+ };
111
+ }
112
+ // untracked + absent: the declared state. "up to date" would mislead.
113
+ return {
114
+ stale: false,
115
+ error: null,
116
+ ok: `✅ ${rel} is not committed here (--untracked-mode) and no stale copy is on disk.\n`,
117
+ };
118
+ }
119
+
120
+ if (!current) {
121
+ return { stale: true, error: `❌ ${rel} is stale. ${regen}\n`, ok: null };
122
+ }
123
+ return { stale: false, error: null, ok: `✅ ${rel} is up to date.\n` };
124
+ }
125
+
126
+ /**
127
+ * `evaluateDashboardState` against the real filesystem and git index. The
128
+ * generator hands over the paths; every read the verdict needs happens here, so
129
+ * the caller's check branch stays three lines.
130
+ */
131
+ export function evaluateDashboardOnDisk(opts: {
132
+ mode: DashboardMode;
133
+ target: string;
134
+ repo_root: string;
135
+ rendered: string;
136
+ rel: string;
137
+ regen?: string;
138
+ noun?: string;
139
+ }): DashboardStateVerdict {
140
+ const present = fs.existsSync(opts.target);
141
+ return evaluateDashboardState({
142
+ mode: opts.mode,
143
+ present,
144
+ trackedInGit: opts.mode === 'untracked' && isTrackedInGit(opts.target, opts.repo_root),
145
+ current: present && fs.readFileSync(opts.target, { encoding: 'utf-8' }) === opts.rendered,
146
+ rel: opts.rel,
147
+ ...(opts.regen === undefined ? {} : { regen: opts.regen }),
148
+ ...(opts.noun === undefined ? {} : { noun: opts.noun }),
149
+ });
150
+ }
151
+
152
+ /** One roadmap row, as far as the estate report below needs it. */
153
+ export interface EstateRow {
154
+ rel: string;
155
+ done: number;
156
+ total_active: number;
157
+ deferred: number;
158
+ }
159
+
160
+ /**
161
+ * The `--check` report: the artefact's own verdict, then the estate-wide
162
+ * conditions — completed-but-unarchived, and Iron Law 3 deferred items.
163
+ *
164
+ * `dashboardOnly` drops the estate half. Two obligations with two blast radii:
165
+ * the artefact verdict is about ONE file and is safe behind a required status
166
+ * check, while the estate conditions are about roadmaps an unrelated change
167
+ * never touched, so requiring them turns any pre-existing estate defect into a
168
+ * merge block on every PR. It did, on 2026-08-22.
169
+ *
170
+ * Extracted from the generator so the report is a pure function of the verdict
171
+ * and two lists — and because the generator sits exactly on its 1500-line
172
+ * source ceiling, where the honest way to add a feature is to take lines out.
173
+ */
174
+ export function reportCheckVerdict(inp: {
175
+ verdict: DashboardStateVerdict;
176
+ complete: readonly EstateRow[];
177
+ pending: readonly EstateRow[];
178
+ dashboardOnly: boolean;
179
+ warnMergeGated: () => void;
180
+ gatedCount: number;
181
+ }): { rc: number; stdout: string; stderr: string } {
182
+ const { verdict, dashboardOnly } = inp;
183
+ const complete = dashboardOnly ? [] : inp.complete;
184
+ const pending = dashboardOnly ? [] : inp.pending;
185
+ let err = verdict.error ?? '';
186
+ if (complete.length) {
187
+ err +=
188
+ '❌ Completed roadmaps are still in `agents/roadmaps/` — ' +
189
+ 'move them to `agents/roadmaps/archive/` (per the ' +
190
+ '`roadmap-progress-sync` rule):\n';
191
+ for (const r of complete) err += ` - ${r.rel} (${r.done}/${r.total_active} done)\n`;
192
+ }
193
+ if (pending.length) {
194
+ err +=
195
+ '❌ Iron Law 3 — roadmaps with unresolved `[~]` deferred ' +
196
+ 'items must NOT auto-archive. Resolve via `roadmap-management § 4b` ' +
197
+ '(spawn follow-up, restore, or cancel):\n';
198
+ for (const r of pending) {
199
+ err += ` - ${r.rel} (${r.done}/${r.total_active} done · ${r.deferred} deferred)\n`;
200
+ }
201
+ }
202
+ if (!dashboardOnly && inp.gatedCount > 0) inp.warnMergeGated();
203
+ const rc = verdict.stale || complete.length > 0 || pending.length > 0 ? 1 : 0;
204
+ return { rc, stdout: rc === 0 ? (verdict.ok ?? '') : '', stderr: err };
205
+ }
206
+
207
+ /** `--tracked-mode` / `--untracked-mode` → the mode, or null for any other token. */
208
+ export function parseModeToken(tok: string): DashboardMode | null {
209
+ if (tok === '--tracked-mode') return 'tracked';
210
+ if (tok === '--untracked-mode') return 'untracked';
211
+ return null;
212
+ }
@@ -64,6 +64,12 @@ import * as fs from 'node:fs';
64
64
  import * as path from 'node:path';
65
65
  import { fileURLToPath, pathToFileURL } from 'node:url';
66
66
  import { run_archival_sweep } from './archival_sweep.js';
67
+ import {
68
+ evaluateDashboardOnDisk,
69
+ parseModeToken,
70
+ reportCheckVerdict,
71
+ type DashboardMode,
72
+ } from './dashboard_mode.js';
67
73
  import type * as YamlModule from 'yaml';
68
74
 
69
75
  const _HERE = fileURLToPath(import.meta.url);
@@ -998,8 +1004,9 @@ function render(roadmaps: RoadmapStats[], bundles: Bundle[] | null, roadmap_root
998
1004
  // script name covers the rest.
999
1005
  '> Auto-generated — do not edit. Regenerate with `task roadmap-progress` ' +
1000
1006
  'or by running the `update_roadmap_progress` script for your install; ' +
1001
- 'rewritten on every roadmap create / execute / completion change ' +
1002
- '(timestamp lives in git history).\n>\n' +
1007
+ 'rewritten on every roadmap create / execute / completion change. ' +
1008
+ 'A repository that does not commit this file has no git history for ' +
1009
+ 'it — regenerate to see the current state.\n>\n' +
1003
1010
  header_meta,
1004
1011
  );
1005
1012
  lines.push('## Overall\n');
@@ -1214,18 +1221,32 @@ class ArgparseExit extends Error {
1214
1221
  const _PROG = 'update_roadmap_progress.py';
1215
1222
 
1216
1223
  function _usage(): string {
1217
- return `usage: ${_PROG} [-h] [--check] [--archive | --no-archive] [--repo-root REPO_ROOT]\n`;
1224
+ return (
1225
+ `usage: ${_PROG} [-h] [--check] [--tracked-mode | --untracked-mode]\n` +
1226
+ ` [--archive | --no-archive] [--repo-root REPO_ROOT] [--dashboard-only]\n`
1227
+ );
1218
1228
  }
1219
1229
 
1220
1230
  interface Args {
1221
1231
  check: boolean;
1232
+ mode: DashboardMode;
1222
1233
  archive: boolean;
1223
1234
  repo_root: string;
1235
+ /**
1236
+ * Assert ONLY this artefact's own state — tracked / absent / stale — not
1237
+ * the estate-wide conditions `--check` also carries. Two obligations, two
1238
+ * blast radii: why that split exists, and what it cost when it did not,
1239
+ * is on the `roadmap-dashboard-untracked-check` task in
1240
+ * `taskfiles/content.yml`.
1241
+ */
1242
+ dashboard_only: boolean;
1224
1243
  }
1225
1244
 
1226
1245
  function _parseArgs(argv: readonly string[]): Args {
1227
1246
  let check = false;
1247
+ let mode: DashboardMode = 'tracked';
1228
1248
  let archive = false;
1249
+ let dashboard_only = false;
1229
1250
  let repo_root = process.cwd();
1230
1251
  const emitError = (msg: string): never => {
1231
1252
  process.stderr.write(_usage());
@@ -1241,6 +1262,12 @@ function _parseArgs(argv: readonly string[]): Args {
1241
1262
  } else if (tok === '--check') {
1242
1263
  check = true;
1243
1264
  i += 1;
1265
+ } else if (parseModeToken(tok) !== null) {
1266
+ mode = parseModeToken(tok) as DashboardMode;
1267
+ i += 1;
1268
+ } else if (tok === '--dashboard-only') {
1269
+ dashboard_only = true;
1270
+ i += 1;
1244
1271
  } else if (tok === '--archive') {
1245
1272
  archive = true;
1246
1273
  i += 1;
@@ -1267,7 +1294,10 @@ function _parseArgs(argv: readonly string[]): Args {
1267
1294
  // rather than silently letting one win.
1268
1295
  emitError('argument --archive: not allowed with --check');
1269
1296
  }
1270
- return { check, archive, repo_root };
1297
+ if (dashboard_only && !check) {
1298
+ emitError('argument --dashboard-only: only meaningful with --check');
1299
+ }
1300
+ return { check, mode, archive, repo_root, dashboard_only };
1271
1301
  }
1272
1302
 
1273
1303
  /**
@@ -1334,13 +1364,10 @@ function main(argv?: readonly string[]): number {
1334
1364
  }
1335
1365
  const roadmaps = collect(roadmap_root);
1336
1366
  const new_text = render(roadmaps, collect_bundles(repo_root), roadmap_root);
1337
- const current = fs.existsSync(target) ? fs.readFileSync(target, { encoding: 'utf-8' }) : '';
1338
1367
  const complete = unarchived_complete(roadmaps);
1339
1368
  const pending = pending_iron_law_3(roadmaps);
1340
1369
  const gated = merge_gated_pending(roadmaps);
1341
1370
 
1342
- const _relToRepo = (p: string): string => _relPosix(repo_root, p);
1343
-
1344
1371
  const _warn_merge_gated = (): void => {
1345
1372
  process.stderr.write(
1346
1373
  '⏳ Merge-gated roadmaps (every open item gated on a PR) — ' +
@@ -1357,50 +1384,22 @@ function main(argv?: readonly string[]): number {
1357
1384
  };
1358
1385
 
1359
1386
  if (args.check) {
1360
- const stale = current !== new_text;
1361
- if (stale) {
1362
- process.stderr.write(
1363
- `❌ ${_relToRepo(target)} is stale. ` +
1364
- 'Run `node node_modules/.bin/tsx .augment/scripts/update_roadmap_progress.ts` ' +
1365
- 'to regenerate (or `task roadmap-progress` in Taskfile ' +
1366
- 'projects).\n',
1367
- );
1368
- }
1369
- if (complete.length) {
1370
- process.stderr.write(
1371
- '❌ Completed roadmaps are still in `agents/roadmaps/` — ' +
1372
- 'move them to `agents/roadmaps/archive/` (per the ' +
1373
- '`roadmap-progress-sync` rule):\n',
1374
- );
1375
- for (const r of complete) {
1376
- process.stderr.write(` - ${r.rel} (${r.done}/${r.total_active} done)\n`);
1377
- }
1378
- }
1379
- if (pending.length) {
1380
- process.stderr.write(
1381
- '❌ Iron Law 3 — roadmaps with unresolved `[~]` deferred ' +
1382
- 'items must NOT auto-archive. Resolve via `roadmap-management § 4b` ' +
1383
- '(spawn follow-up, restore, or cancel):\n',
1384
- );
1385
- for (const r of pending) {
1386
- process.stderr.write(
1387
- ` - ${r.rel} (${r.done}/${r.total_active} done · ` +
1388
- `${r.deferred} deferred)\n`,
1389
- );
1390
- }
1391
- }
1392
- if (gated.length) {
1393
- _warn_merge_gated();
1394
- }
1395
- if (stale || complete.length || pending.length) {
1396
- return 1;
1397
- }
1398
- process.stdout.write(`✅ ${_relToRepo(target)} is up to date.\n`);
1399
- return 0;
1387
+ const rel = _relPosix(repo_root, target);
1388
+ const r = reportCheckVerdict({
1389
+ verdict: evaluateDashboardOnDisk({ mode: args.mode, target, repo_root, rendered: new_text, rel }),
1390
+ complete,
1391
+ pending,
1392
+ dashboardOnly: args.dashboard_only,
1393
+ warnMergeGated: _warn_merge_gated,
1394
+ gatedCount: gated.length,
1395
+ });
1396
+ if (r.stderr) process.stderr.write(r.stderr);
1397
+ if (r.stdout) process.stdout.write(r.stdout);
1398
+ return r.rc;
1400
1399
  }
1401
1400
  fs.writeFileSync(target, new_text, { encoding: 'utf-8' });
1402
1401
  process.stdout.write(
1403
- `✅ Wrote ${_relToRepo(target)} · ` +
1402
+ `✅ Wrote ${_relPosix(repo_root, target)} · ` +
1404
1403
  `${roadmaps.length} roadmap(s) · ` +
1405
1404
  `${roadmaps.reduce((s, r) => s + r.done, 0)}/${roadmaps.reduce((s, r) => s + r.total_active, 0)} steps done.\n`,
1406
1405
  );
@@ -184,6 +184,27 @@ ci_only:
184
184
  # worth remote enforcement, OR it should simply be added to a workflow. Prefer
185
185
  # adding it: a local-only gate is the direction that let real defects merge.
186
186
  local_only:
187
+ - id: check_hook_bundle_content
188
+ class: developer-machine-scoped
189
+ reason: >
190
+ Compares `dist/hooks/dispatch.js` — the bundle that ACTUALLY EXECUTES the
191
+ hooks — against a rebuild from this checkout's sources. `git ls-files
192
+ dist/hooks/` returns ZERO: the bundle is an untracked local build
193
+ artefact, so a fresh CI checkout has none and the gate can only report
194
+ "nothing to compare". Wiring it into a workflow would measure an artefact
195
+ no CI run has, which is the empty-scope false green this repo's
196
+ gate-coverage manifest exists to refuse. Declared rather than wired for
197
+ the same reason `check_single_delivery` below is. The blind spot it
198
+ closes is real and was measured on 2026-08-21: an edit to the git guard's
199
+ authorization window was never rebuilt into the bundle and the guard kept
200
+ enforcing the old value for a full hour. The sibling
201
+ `check_hook_bundle_freshness` did not miss it — that gate is pre-push and
202
+ was never run, and an ordinary edit moves the source mtime, so it would
203
+ have reddened. What it structurally cannot see is the narrower set this
204
+ gate covers: an mtime-preserving write, a `touch` on the bundle, a `cp -p`
205
+ restore. Ordering is not equivalence. That asymmetry is the finding: a green
206
+ CI cannot speak for the bundle a maintainer is running, and only a local
207
+ gate can. Cost measured, like its neighbours in the pre-push set: 0.27s.
187
208
  - id: check_single_delivery
188
209
  class: developer-machine-scoped
189
210
  reason: >