@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
@@ -427,13 +427,83 @@ export function sheetQuestion(b: Blocker): { text: string; source: 'question' |
427
427
  * none is recorded it says so and names what the reader must supply, so
428
428
  * regenerating the sheet cannot silently overwrite an answer with a guess.
429
429
  */
430
- function renderSheet(entries: readonly Entry[], now: Date): string {
430
+ /**
431
+ * The recorded answer to the consolidated decision sheet.
432
+ *
433
+ * The sheet is DERIVED, so an answer written into it is lost on the next run —
434
+ * the file says so in its own header. But `road-to-estate-drawdown` blocker
435
+ * `b-consolidated-decision-sheet` resolves only when "the sheet records which
436
+ * option was used", and a derived file cannot carry that by being edited. So the
437
+ * option lives in a NON-derived sibling and the sheet reads it, which keeps the
438
+ * sheet deterministic and still lets it state the answer.
439
+ *
440
+ * Read, never written, and absent is a first-class answer: no marker means the
441
+ * sheet renders exactly as before, so this is additive to every existing caller.
442
+ */
443
+ interface SheetAnswer {
444
+ option: string;
445
+ answered: string;
446
+ authority: string;
447
+ }
448
+
449
+ const SHEET_ANSWER_REL = path.join('agents', 'decisions', 'consolidated-decision-sheet-answer.md');
450
+
451
+ /**
452
+ * One line, three fields, all required — a partial marker is treated as absent
453
+ * rather than rendered half-filled, because a header that named an option with
454
+ * no date or authority would be less honest than one that says nothing.
455
+ */
456
+ const SHEET_ANSWER_RE =
457
+ /<!--\s*sheet-answer:\s*(.+?)\s*\|\s*answered:\s*(\d{4}-\d{2}-\d{2})\s*\|\s*authority:\s*(\S+)\s*-->/;
458
+
459
+ export function readSheetAnswer(repoRoot: string): SheetAnswer | null {
460
+ const file = path.join(repoRoot, SHEET_ANSWER_REL);
461
+ let text: string;
462
+ try {
463
+ text = fs.readFileSync(file, 'utf8');
464
+ } catch {
465
+ return null;
466
+ }
467
+ const m = SHEET_ANSWER_RE.exec(text);
468
+ if (m === null) {
469
+ return null;
470
+ }
471
+ return {
472
+ option: (m[1] as string).trim(),
473
+ answered: (m[2] as string).trim(),
474
+ authority: (m[3] as string).trim(),
475
+ };
476
+ }
477
+
478
+ /** The header block naming the recorded answer, or nothing when none is recorded. */
479
+ function answerBanner(answer: SheetAnswer | null): string[] {
480
+ if (answer === null) {
481
+ return [];
482
+ }
483
+ return [
484
+ '>',
485
+ `> **ANSWERED ${answer.answered} — ${answer.option}.**`,
486
+ `> Recorded in \`${SHEET_ANSWER_REL}\` (not derived);`,
487
+ `> authority \`${answer.authority}\`.`,
488
+ '> The rows below are the ones still OPEN: an answered',
489
+ '> row stays here until the work its own entry names is done, so this is a work',
490
+ '> queue and not a count of unanswered questions.',
491
+ ];
492
+ }
493
+
494
+ function renderSheet(
495
+ entries: readonly Entry[],
496
+ now: Date,
497
+ answer: SheetAnswer | null = null,
498
+ ): string {
431
499
  const mine = entries.filter((e) => needsUser(e.blocker.owner));
432
500
  const stamp = now.toISOString().slice(0, 10);
433
501
  if (mine.length === 0) {
502
+ const banner = answerBanner(answer);
434
503
  return (
435
504
  `# Consolidated decision sheet — nothing owned by you (${stamp})\n\n` +
436
- 'No open blocker in the active roadmap tree carries `Owner: user`. Nothing to answer.\n'
505
+ 'No open blocker in the active roadmap tree carries `Owner: user`. Nothing to answer.\n' +
506
+ (banner.length === 0 ? '' : '\n' + banner.slice(1).join('\n') + '\n')
437
507
  );
438
508
  }
439
509
 
@@ -461,6 +531,7 @@ function renderSheet(entries: readonly Entry[], now: Date): string {
461
531
  '> This file is DERIVED — every line above and below comes from the roadmaps themselves,',
462
532
  '> so regenerating is deterministic and an answer written into this file would be lost.',
463
533
  '> Answers go back into each roadmap at its own blocker; the agent does that.',
534
+ ...answerBanner(answer),
464
535
  '',
465
536
  '| # | Decision | Roadmap | Unblocks | Default source |',
466
537
  '|---:|---|---|---:|---|',
@@ -847,7 +918,7 @@ function main(argv?: readonly string[]): number {
847
918
  const resumed = reply || sheet ? [] : probeLater(roadmapRoot, repoRoot);
848
919
  process.stdout.write(
849
920
  sheet
850
- ? renderSheet(entries, new Date())
921
+ ? renderSheet(entries, new Date(), readSheetAnswer(repoRoot))
851
922
  : reply
852
923
  ? renderReply(entries)
853
924
  : json
@@ -32,8 +32,12 @@
32
32
  *
33
33
  * Invocation (from project root):
34
34
  * node node_modules/.bin/tsx .augment/scripts/update_roadmap_progress.ts # rewrite
35
+ * node node_modules/.bin/tsx .augment/scripts/update_roadmap_progress.ts --archive # rewrite + archive completed
35
36
  * node node_modules/.bin/tsx .augment/scripts/update_roadmap_progress.ts --check # CI: exit 1 if stale
36
37
  *
38
+ * `--archive` is opt-in and `--check` refuses it; the full contract, the
39
+ * council record and the sweep spawner live in `archival_sweep.ts`.
40
+ *
37
41
  * --- Parity notes (ADR-200) ---
38
42
  *
39
43
  * - Python `round()` is banker's rounding (round-half-to-even). Percentages and
@@ -59,6 +63,13 @@ import { spawnSync } from 'node:child_process';
59
63
  import * as fs from 'node:fs';
60
64
  import * as path from 'node:path';
61
65
  import { fileURLToPath, pathToFileURL } from 'node:url';
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';
62
73
  import type * as YamlModule from 'yaml';
63
74
 
64
75
  const _HERE = fileURLToPath(import.meta.url);
@@ -280,12 +291,21 @@ function is_draft(fm: Record<string, string>): boolean {
280
291
  return DRAFT_VALUES.has((fm['status'] ?? '').toLowerCase());
281
292
  }
282
293
 
283
- function is_roadmap_candidate(p: string): boolean {
284
- const name = path.basename(p);
294
+ /**
295
+ * The NAME half of the candidate test. Split out because the parked inventory
296
+ * reads `later/` on purpose and `is_roadmap_candidate` rejects any path carrying
297
+ * an excluded component — `later` included. Sharing this half keeps
298
+ * `later/README.md` excluded from both sides by one rule rather than two.
299
+ */
300
+ function is_roadmap_name(name: string): boolean {
285
301
  if (EXCLUDE_NAMES.has(name)) {
286
302
  return false;
287
303
  }
288
- if (EXCLUDE_PREFIXES.some((pre) => name.startsWith(pre))) {
304
+ return !EXCLUDE_PREFIXES.some((pre) => name.startsWith(pre));
305
+ }
306
+
307
+ function is_roadmap_candidate(p: string): boolean {
308
+ if (!is_roadmap_name(path.basename(p))) {
289
309
  return false;
290
310
  }
291
311
  // path.parts — every component of the path; exclude if any is an excluded dir.
@@ -741,6 +761,140 @@ function collect(roadmap_root: string): RoadmapStats[] {
741
761
  return results;
742
762
  }
743
763
 
764
+ /** One parked roadmap, as the inventory needs it. */
765
+ interface ParkedRoadmap {
766
+ rel: string;
767
+ resume: string | null;
768
+ /** Only the linter's looser marker matched — CI passes, the cell cannot quote. */
769
+ unlabelled: boolean;
770
+ /** Open blockers still carried by the parked file. */
771
+ open_blockers: number;
772
+ /** Of those, the ones whose owner is the user. */
773
+ needs_user: number;
774
+ }
775
+
776
+ /**
777
+ * A line that STATES when the roadmap comes back. Two tiers exist because
778
+ * `lint_roadmap_later_disposition` accepts a bare `trigger`, which is too loose to
779
+ * quote from — measured over the live tree it matched a `Source:` path containing
780
+ * `mixed-trigger-cleanup`, producing cells that were wrong rather than short.
781
+ */
782
+ const RESUME_STATEMENT = /\b(blocked until|resume when|resume-when|blocked-until)\b/i;
783
+
784
+ /**
785
+ * The linter's looser vocabulary. A file matching only this DOES carry a resume
786
+ * condition as far as CI is concerned, so reporting it as missing would make the
787
+ * dashboard contradict a gate that already passed.
788
+ */
789
+ const RESUME_LOOSE = /\btrigger\b/i;
790
+
791
+ /**
792
+ * Parked roadmaps, with the resume condition each one records.
793
+ *
794
+ * A view over existing data, NOT a new governance mechanism: nothing here gates,
795
+ * counts toward a ratchet, or changes what `collect()` scans. It exists because
796
+ * `later/` is excluded from `collect()` and from `/roadmap:process-*` — correct,
797
+ * parked work is not backlog — and that exclusion had no counterweight, so a
798
+ * roadmap moved there left the dashboard entirely.
799
+ */
800
+ function collect_parked(roadmap_root: string): ParkedRoadmap[] {
801
+ const dir = path.join(roadmap_root, 'later');
802
+ let names: string[];
803
+ try {
804
+ names = fs.readdirSync(dir).sort();
805
+ } catch {
806
+ return [];
807
+ }
808
+ const out: ParkedRoadmap[] = [];
809
+ for (const name of names) {
810
+ const full = path.join(dir, name);
811
+ // The NAME half only: the full predicate rejects every path under `later`.
812
+ if (!_isFile(full) || !is_roadmap_name(name)) {
813
+ continue;
814
+ }
815
+ let text: string;
816
+ try {
817
+ text = fs.readFileSync(full, { encoding: 'utf-8' });
818
+ } catch {
819
+ continue;
820
+ }
821
+ if (is_draft(parse_frontmatter(text))) {
822
+ continue;
823
+ }
824
+ const cell = resume_cell(text);
825
+ // Parked blockers are still open blockers — counting them here is what stops
826
+ // this section becoming the surface where a park reads as a resolution.
827
+ const open = parse_blockers(text).filter((b) => !blocker_is_resolved(b));
828
+ out.push({
829
+ rel: name,
830
+ resume: cell.stated,
831
+ unlabelled: cell.unlabelled,
832
+ open_blockers: open.length,
833
+ needs_user: open.filter((b) => blocker_needs_user(b.owner)).length,
834
+ });
835
+ }
836
+ return out;
837
+ }
838
+
839
+ /**
840
+ * What brings a parked roadmap back, as one table cell. Both fields false means the
841
+ * file records nothing — said outright, because an empty cell reads as "nothing to
842
+ * wait for".
843
+ *
844
+ * The PARAGRAPH is joined, not the matched line: roadmap prose wraps at ~80
845
+ * columns, and quoting one line ended 39 of 52 cells mid-clause. A cell stopping at
846
+ * "the pair for" is not a shorter sentence, it is a different claim.
847
+ *
848
+ * Both tiers are tested over the SAME lines. Testing the loose one whole-file while
849
+ * the strict one ran per line made `unlabelled` fire on any file using the word
850
+ * "trigger" anywhere.
851
+ */
852
+ function resume_cell(text: string): { stated: string | null; unlabelled: boolean } {
853
+ const lines = text.split('\n');
854
+ for (let i = 0; i < lines.length; i++) {
855
+ const raw = lines[i] as string;
856
+ if (!RESUME_STATEMENT.test(raw)) {
857
+ continue;
858
+ }
859
+ const parts: string[] = [raw];
860
+ // Continuation lines of the same paragraph: non-blank, and not the start of
861
+ // a new block (list item, heading, fence, table row).
862
+ for (let j = i + 1; j < lines.length; j++) {
863
+ const next = (lines[j] as string).replace(/^\s*>\s?/, '').trim();
864
+ if (next === '' || /^([-*+]\s|#{1,6}\s|```|\||>\s*$)/.test(next)) {
865
+ break;
866
+ }
867
+ parts.push(next);
868
+ }
869
+ const joined = cell_text(parts.join(' '));
870
+ if (joined === '') {
871
+ continue;
872
+ }
873
+ return { stated: joined, unlabelled: false };
874
+ }
875
+ return { stated: null, unlabelled: lines.some((l) => RESUME_LOOSE.test(l)) };
876
+ }
877
+
878
+ /**
879
+ * One roadmap sentence, safe between two pipes. ORDER IS LOAD-BEARING: truncate
880
+ * before escaping, because cutting an escaped string severs a `\|` and leaves a
881
+ * dangling backslash. HTML comments are removed, not escaped — a `<!--` reaching a
882
+ * cell comments out the rest of the document, and one already did.
883
+ */
884
+ function cell_text(raw: string): string {
885
+ let out = raw
886
+ .replace(/^\s*>\s?/, '')
887
+ .replace(/<!--[\s\S]*?-->/g, '')
888
+ .replace(/<!--|-->/g, '')
889
+ .replace(/\*\*/g, '')
890
+ .replace(/\s+/g, ' ')
891
+ .trim();
892
+ if (out.length > 200) {
893
+ out = `${out.slice(0, 197).trimEnd()}...`;
894
+ }
895
+ return out.replace(/\|/g, '\\|');
896
+ }
897
+
744
898
  function unarchived_complete(roadmaps: RoadmapStats[]): RoadmapStats[] {
745
899
  return roadmaps.filter((r) => r.total_active > 0 && r.open_ === 0 && r.deferred === 0);
746
900
  }
@@ -812,7 +966,7 @@ function collect_bundles(repo_root: string): Bundle[] {
812
966
  return out;
813
967
  }
814
968
 
815
- function render(roadmaps: RoadmapStats[], bundles: Bundle[] | null = null): string {
969
+ function render(roadmaps: RoadmapStats[], bundles: Bundle[] | null, roadmap_root: string): string {
816
970
  const total_done = roadmaps.reduce((s, r) => s + r.done, 0);
817
971
  const total_active = roadmaps.reduce((s, r) => s + r.total_active, 0);
818
972
  const overall_pct = total_active ? _pyRound((total_done * 100) / total_active) : 0;
@@ -836,6 +990,7 @@ function render(roadmaps: RoadmapStats[], bundles: Bundle[] | null = null): stri
836
990
  '[skipped/](roadmaps/skipped/) · [later/](roadmaps/later/)' +
837
991
  (total_open_blockers > 0
838
992
  ? ` · **${total_open_blockers}** open blocker${total_open_blockers !== 1 ? 's' : ''}` +
993
+ ' in the active tree' +
839
994
  (user_open_blockers > 0
840
995
  ? `, **${user_open_blockers}** need you → \`agent-config gates\``
841
996
  : '')
@@ -849,8 +1004,9 @@ function render(roadmaps: RoadmapStats[], bundles: Bundle[] | null = null): stri
849
1004
  // script name covers the rest.
850
1005
  '> Auto-generated — do not edit. Regenerate with `task roadmap-progress` ' +
851
1006
  'or by running the `update_roadmap_progress` script for your install; ' +
852
- 'rewritten on every roadmap create / execute / completion change ' +
853
- '(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' +
854
1010
  header_meta,
855
1011
  );
856
1012
  lines.push('## Overall\n');
@@ -939,6 +1095,58 @@ function render(roadmaps: RoadmapStats[], bundles: Bundle[] | null = null): stri
939
1095
  });
940
1096
  lines.push('');
941
1097
  lines.push('---\n');
1098
+
1099
+ // Listed under the active table so the two are read together. Parked work
1100
+ // carries no counts here; what a reader needs is what brings it back.
1101
+ const parked = collect_parked(roadmap_root);
1102
+ if (parked.length > 0) {
1103
+ lines.push(
1104
+ `## Parked — \`later/\` (${parked.length} roadmap` +
1105
+ `${parked.length !== 1 ? 's' : ''}, not active backlog)\n`,
1106
+ );
1107
+ lines.push(
1108
+ '> Excluded from the table above and from `/roadmap:process-*` by ' +
1109
+ 'design. Listed here so a resume condition is visible without ' +
1110
+ 'opening the file.\n',
1111
+ );
1112
+ const parked_open = parked.reduce((n, r) => n + r.open_blockers, 0);
1113
+ const parked_user = parked.reduce((n, r) => n + r.needs_user, 0);
1114
+ if (parked_open > 0) {
1115
+ lines.push(
1116
+ `> Carrying **${parked_open}** open blocker` +
1117
+ `${parked_open !== 1 ? 's' : ''}` +
1118
+ (parked_user > 0 ? `, **${parked_user}** owned by you` : '') +
1119
+ ' — parking resolves nothing, so these are NOT in the ' +
1120
+ 'active-tree count above.\n',
1121
+ );
1122
+ }
1123
+ lines.push('| Roadmap | Open blockers | Resume when |');
1124
+ lines.push('|---|---:|---|');
1125
+ for (const r of parked) {
1126
+ const cell =
1127
+ r.resume ??
1128
+ (r.unlabelled
1129
+ ? '_condition present but unlabelled — see file_'
1130
+ : '_no resume line recorded_');
1131
+ const blockers =
1132
+ r.open_blockers === 0
1133
+ ? '0'
1134
+ : `${r.open_blockers}${r.needs_user > 0 ? ` (${r.needs_user} you)` : ''}`;
1135
+ // `<!-- ref-ignore -->` per row — a correctness fix, not a suppression. A
1136
+ // resume condition NAMES WHAT DOES NOT EXIST YET ("when `x.md` exists" is
1137
+ // the commonest shape in `later/`), so checking these quoted paths as live
1138
+ // references fires the gate on correct content. It fired immediately. The
1139
+ // authoritative copy of each path is the roadmap file; this table quotes
1140
+ // it, and that gate does not walk `later/` either way.
1141
+ lines.push(
1142
+ `| [${r.rel}](roadmaps/later/${r.rel}) | ${blockers} | ${cell} |` +
1143
+ ' <!-- ref-ignore -->',
1144
+ );
1145
+ }
1146
+ lines.push('');
1147
+ lines.push('---\n');
1148
+ }
1149
+
942
1150
  lines.push('## Per-roadmap phase breakdown\n');
943
1151
  for (const r of roadmaps) {
944
1152
  lines.push(`### [${r.rel}](roadmaps/${r.rel})\n`);
@@ -1013,16 +1221,32 @@ class ArgparseExit extends Error {
1013
1221
  const _PROG = 'update_roadmap_progress.py';
1014
1222
 
1015
1223
  function _usage(): string {
1016
- return `usage: ${_PROG} [-h] [--check] [--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
+ );
1017
1228
  }
1018
1229
 
1019
1230
  interface Args {
1020
1231
  check: boolean;
1232
+ mode: DashboardMode;
1233
+ archive: boolean;
1021
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;
1022
1243
  }
1023
1244
 
1024
1245
  function _parseArgs(argv: readonly string[]): Args {
1025
1246
  let check = false;
1247
+ let mode: DashboardMode = 'tracked';
1248
+ let archive = false;
1249
+ let dashboard_only = false;
1026
1250
  let repo_root = process.cwd();
1027
1251
  const emitError = (msg: string): never => {
1028
1252
  process.stderr.write(_usage());
@@ -1038,6 +1262,18 @@ function _parseArgs(argv: readonly string[]): Args {
1038
1262
  } else if (tok === '--check') {
1039
1263
  check = true;
1040
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;
1271
+ } else if (tok === '--archive') {
1272
+ archive = true;
1273
+ i += 1;
1274
+ } else if (tok === '--no-archive') {
1275
+ archive = false;
1276
+ i += 1;
1041
1277
  } else if (tok === '--repo-root') {
1042
1278
  const val = argv[i + 1];
1043
1279
  if (val === undefined) {
@@ -1052,7 +1288,16 @@ function _parseArgs(argv: readonly string[]): Args {
1052
1288
  emitError(`unrecognized arguments: ${tok}`);
1053
1289
  }
1054
1290
  }
1055
- return { check, repo_root };
1291
+ if (check && archive) {
1292
+ // A gate that mutates the tree it is checking cannot be trusted by CI
1293
+ // (council 2026-08-20, both seats). Refuse the combination outright
1294
+ // rather than silently letting one win.
1295
+ emitError('argument --archive: not allowed with --check');
1296
+ }
1297
+ if (dashboard_only && !check) {
1298
+ emitError('argument --dashboard-only: only meaningful with --check');
1299
+ }
1300
+ return { check, mode, archive, repo_root, dashboard_only };
1056
1301
  }
1057
1302
 
1058
1303
  /**
@@ -1094,15 +1339,35 @@ function main(argv?: readonly string[]): number {
1094
1339
  process.stdout.write(`ℹ️ No roadmaps directory at ${roadmap_root} — nothing to do.\n`);
1095
1340
  return 0;
1096
1341
  }
1342
+ // Archive before rendering, so the dashboard describes the tree the sweep
1343
+ // leaves behind rather than the one it found. `--check` never archives: a
1344
+ // gate that mutates the tree it is checking cannot be trusted by CI.
1345
+ let sweep_out = '';
1346
+ if (!args.check && args.archive && unarchived_complete(collect(roadmap_root)).length > 0) {
1347
+ const swept = run_archival_sweep(repo_root);
1348
+ sweep_out = swept.stdout;
1349
+ if (swept.stderr) {
1350
+ process.stderr.write(swept.stderr);
1351
+ }
1352
+ if (!swept.ok) {
1353
+ // A half-finished sweep leaves the tree in a shape the dashboard
1354
+ // would describe as if it were intentional. Report and stop before
1355
+ // writing (council 2026-08-20: "archival failures must prevent
1356
+ // dashboard rendering so partially updated state is not presented
1357
+ // as current").
1358
+ process.stderr.write(
1359
+ '❌ The archival sweep failed — dashboard NOT regenerated. ' +
1360
+ 'Fix the tree, then re-run.\n',
1361
+ );
1362
+ return 1;
1363
+ }
1364
+ }
1097
1365
  const roadmaps = collect(roadmap_root);
1098
- const new_text = render(roadmaps, collect_bundles(repo_root));
1099
- const current = fs.existsSync(target) ? fs.readFileSync(target, { encoding: 'utf-8' }) : '';
1366
+ const new_text = render(roadmaps, collect_bundles(repo_root), roadmap_root);
1100
1367
  const complete = unarchived_complete(roadmaps);
1101
1368
  const pending = pending_iron_law_3(roadmaps);
1102
1369
  const gated = merge_gated_pending(roadmaps);
1103
1370
 
1104
- const _relToRepo = (p: string): string => _relPosix(repo_root, p);
1105
-
1106
1371
  const _warn_merge_gated = (): void => {
1107
1372
  process.stderr.write(
1108
1373
  '⏳ Merge-gated roadmaps (every open item gated on a PR) — ' +
@@ -1119,54 +1384,32 @@ function main(argv?: readonly string[]): number {
1119
1384
  };
1120
1385
 
1121
1386
  if (args.check) {
1122
- const stale = current !== new_text;
1123
- if (stale) {
1124
- process.stderr.write(
1125
- `❌ ${_relToRepo(target)} is stale. ` +
1126
- 'Run `node node_modules/.bin/tsx .augment/scripts/update_roadmap_progress.ts` ' +
1127
- 'to regenerate (or `task roadmap-progress` in Taskfile ' +
1128
- 'projects).\n',
1129
- );
1130
- }
1131
- if (complete.length) {
1132
- process.stderr.write(
1133
- '❌ Completed roadmaps are still in `agents/roadmaps/` — ' +
1134
- 'move them to `agents/roadmaps/archive/` (per the ' +
1135
- '`roadmap-progress-sync` rule):\n',
1136
- );
1137
- for (const r of complete) {
1138
- process.stderr.write(` - ${r.rel} (${r.done}/${r.total_active} done)\n`);
1139
- }
1140
- }
1141
- if (pending.length) {
1142
- process.stderr.write(
1143
- '❌ Iron Law 3 — roadmaps with unresolved `[~]` deferred ' +
1144
- 'items must NOT auto-archive. Resolve via `roadmap-management § 4b` ' +
1145
- '(spawn follow-up, restore, or cancel):\n',
1146
- );
1147
- for (const r of pending) {
1148
- process.stderr.write(
1149
- ` - ${r.rel} (${r.done}/${r.total_active} done · ` +
1150
- `${r.deferred} deferred)\n`,
1151
- );
1152
- }
1153
- }
1154
- if (gated.length) {
1155
- _warn_merge_gated();
1156
- }
1157
- if (stale || complete.length || pending.length) {
1158
- return 1;
1159
- }
1160
- process.stdout.write(`✅ ${_relToRepo(target)} is up to date.\n`);
1161
- 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;
1162
1399
  }
1163
1400
  fs.writeFileSync(target, new_text, { encoding: 'utf-8' });
1164
1401
  process.stdout.write(
1165
- `✅ Wrote ${_relToRepo(target)} · ` +
1402
+ `✅ Wrote ${_relPosix(repo_root, target)} · ` +
1166
1403
  `${roadmaps.length} roadmap(s) · ` +
1167
1404
  `${roadmaps.reduce((s, r) => s + r.done, 0)}/${roadmaps.reduce((s, r) => s + r.total_active, 0)} steps done.\n`,
1168
1405
  );
1406
+ if (sweep_out) {
1407
+ process.stdout.write(sweep_out);
1408
+ }
1169
1409
  if (complete.length) {
1410
+ // Whatever survived the sweep is complete but NOT archivable — the
1411
+ // sweep already said why on stderr (an open blocker outlives its
1412
+ // steps). Without `--archive` this is every complete roadmap.
1170
1413
  process.stderr.write(
1171
1414
  '⚠️ Completed roadmaps not yet archived — move to ' +
1172
1415
  '`agents/roadmaps/archive/`:\n',