@event4u/agent-config 11.0.0 → 12.1.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 (312) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +173 -229
  3. package/agents/templates/.ai-council.yml.example +46 -3
  4. package/dist/agent-src/commands/agent-handoff.md +0 -1
  5. package/dist/agent-src/commands/agent-status.md +0 -1
  6. package/dist/agent-src/commands/agents/audit.md +0 -1
  7. package/dist/agent-src/commands/agents/init.md +0 -1
  8. package/dist/agent-src/commands/agents/optimize.md +0 -1
  9. package/dist/agent-src/commands/agents/user/accept.md +0 -1
  10. package/dist/agent-src/commands/agents/user/delete.md +0 -1
  11. package/dist/agent-src/commands/agents/user/init.md +0 -1
  12. package/dist/agent-src/commands/agents/user/review.md +0 -1
  13. package/dist/agent-src/commands/agents/user/show.md +0 -1
  14. package/dist/agent-src/commands/agents/user/update.md +0 -1
  15. package/dist/agent-src/commands/agents/user.md +0 -1
  16. package/dist/agent-src/commands/agents.md +0 -1
  17. package/dist/agent-src/commands/analytics/prune.md +0 -1
  18. package/dist/agent-src/commands/analytics/show.md +0 -1
  19. package/dist/agent-src/commands/analytics.md +0 -1
  20. package/dist/agent-src/commands/analyze/conformance.md +0 -1
  21. package/dist/agent-src/commands/analyze/decision.md +0 -1
  22. package/dist/agent-src/commands/analyze/inbox.md +0 -1
  23. package/dist/agent-src/commands/analyze/incident.md +0 -1
  24. package/dist/agent-src/commands/analyze/near-miss.md +0 -1
  25. package/dist/agent-src/commands/analyze/postmortem.md +0 -1
  26. package/dist/agent-src/commands/analyze/premortem.md +0 -1
  27. package/dist/agent-src/commands/analyze/reference-repo.md +0 -1
  28. package/dist/agent-src/commands/analyze.md +0 -1
  29. package/dist/agent-src/commands/brand/identity.md +0 -1
  30. package/dist/agent-src/commands/brand/review.md +0 -1
  31. package/dist/agent-src/commands/brand/strategy.md +0 -1
  32. package/dist/agent-src/commands/brand/tokens.md +0 -1
  33. package/dist/agent-src/commands/brand/voice.md +0 -1
  34. package/dist/agent-src/commands/brand.md +0 -1
  35. package/dist/agent-src/commands/bug/fix.md +0 -1
  36. package/dist/agent-src/commands/bug/investigate.md +0 -1
  37. package/dist/agent-src/commands/bug.md +0 -1
  38. package/dist/agent-src/commands/challenge-me/vision.md +0 -1
  39. package/dist/agent-src/commands/challenge-me/with-docs.md +0 -1
  40. package/dist/agent-src/commands/challenge-me.md +0 -1
  41. package/dist/agent-src/commands/chat-history/import.md +0 -1
  42. package/dist/agent-src/commands/chat-history.md +0 -1
  43. package/dist/agent-src/commands/check-current-md.md +0 -1
  44. package/dist/agent-src/commands/commit/in-chunks.md +0 -1
  45. package/dist/agent-src/commands/commit.md +0 -1
  46. package/dist/agent-src/commands/condense.md +0 -1
  47. package/dist/agent-src/commands/context/create.md +0 -1
  48. package/dist/agent-src/commands/context/refactor.md +0 -1
  49. package/dist/agent-src/commands/context.md +0 -1
  50. package/dist/agent-src/commands/contribution-precheck.md +0 -1
  51. package/dist/agent-src/commands/cost/profile.md +0 -1
  52. package/dist/agent-src/commands/cost/report.md +0 -1
  53. package/dist/agent-src/commands/cost.md +0 -1
  54. package/dist/agent-src/commands/council/analysis.md +0 -1
  55. package/dist/agent-src/commands/council/debate.md +0 -1
  56. package/dist/agent-src/commands/council/default.md +0 -1
  57. package/dist/agent-src/commands/council/design.md +0 -1
  58. package/dist/agent-src/commands/council/optimize.md +0 -1
  59. package/dist/agent-src/commands/council/pr.md +0 -1
  60. package/dist/agent-src/commands/council.md +0 -1
  61. package/dist/agent-src/commands/design-system/capture.md +0 -1
  62. package/dist/agent-src/commands/design-system/generate.md +0 -1
  63. package/dist/agent-src/commands/design-system/import.md +0 -1
  64. package/dist/agent-src/commands/design-system.md +0 -1
  65. package/dist/agent-src/commands/estimate-ticket.md +0 -1
  66. package/dist/agent-src/commands/explain-run.md +0 -1
  67. package/dist/agent-src/commands/feature/dev.md +0 -1
  68. package/dist/agent-src/commands/feature/explore.md +0 -1
  69. package/dist/agent-src/commands/feature/plan.md +0 -1
  70. package/dist/agent-src/commands/feature/refactor.md +0 -1
  71. package/dist/agent-src/commands/feature/roadmap.md +0 -1
  72. package/dist/agent-src/commands/feature.md +0 -1
  73. package/dist/agent-src/commands/fix/ci.md +0 -1
  74. package/dist/agent-src/commands/fix/comments.md +0 -1
  75. package/dist/agent-src/commands/fix/portability.md +0 -1
  76. package/dist/agent-src/commands/fix/pr-comments-loop.md +0 -1
  77. package/dist/agent-src/commands/fix/pr-comments.md +0 -1
  78. package/dist/agent-src/commands/fix/quality.md +0 -1
  79. package/dist/agent-src/commands/fix/refs.md +0 -1
  80. package/dist/agent-src/commands/fix/route.md +0 -1
  81. package/dist/agent-src/commands/fix/seeder.md +0 -1
  82. package/dist/agent-src/commands/fix.md +0 -1
  83. package/dist/agent-src/commands/ghostwriter/delete.md +0 -1
  84. package/dist/agent-src/commands/ghostwriter/fetch.md +0 -1
  85. package/dist/agent-src/commands/ghostwriter/list.md +0 -1
  86. package/dist/agent-src/commands/ghostwriter/show.md +0 -1
  87. package/dist/agent-src/commands/ghostwriter/write.md +0 -1
  88. package/dist/agent-src/commands/ghostwriter.md +0 -1
  89. package/dist/agent-src/commands/grill-me.md +0 -1
  90. package/dist/agent-src/commands/humanize.md +0 -1
  91. package/dist/agent-src/commands/image/analyse.md +0 -1
  92. package/dist/agent-src/commands/image/create.md +0 -1
  93. package/dist/agent-src/commands/image/verify.md +0 -1
  94. package/dist/agent-src/commands/image.md +0 -1
  95. package/dist/agent-src/commands/implement-ticket.md +0 -1
  96. package/dist/agent-src/commands/jira-ticket.md +0 -1
  97. package/dist/agent-src/commands/judge/on-diff.md +0 -1
  98. package/dist/agent-src/commands/judge/solo.md +0 -1
  99. package/dist/agent-src/commands/judge/steps.md +0 -1
  100. package/dist/agent-src/commands/judge.md +0 -1
  101. package/dist/agent-src/commands/knowledge/cross-repo.md +0 -1
  102. package/dist/agent-src/commands/knowledge/forget.md +0 -1
  103. package/dist/agent-src/commands/knowledge/ingest.md +0 -1
  104. package/dist/agent-src/commands/knowledge/list.md +0 -1
  105. package/dist/agent-src/commands/knowledge.md +0 -1
  106. package/dist/agent-src/commands/memory/add.md +0 -1
  107. package/dist/agent-src/commands/memory/learn-low-impact.md +0 -1
  108. package/dist/agent-src/commands/memory/load.md +0 -1
  109. package/dist/agent-src/commands/memory/mine-session.md +0 -1
  110. package/dist/agent-src/commands/memory/promote.md +0 -1
  111. package/dist/agent-src/commands/memory/propose.md +0 -1
  112. package/dist/agent-src/commands/memory.md +0 -1
  113. package/dist/agent-src/commands/mission/upgrade.md +0 -1
  114. package/dist/agent-src/commands/mode.md +0 -1
  115. package/dist/agent-src/commands/module/create.md +0 -1
  116. package/dist/agent-src/commands/module/explore.md +0 -1
  117. package/dist/agent-src/commands/module.md +0 -1
  118. package/dist/agent-src/commands/optimize/agents-dir.md +0 -1
  119. package/dist/agent-src/commands/optimize/augmentignore.md +0 -1
  120. package/dist/agent-src/commands/optimize/deep.md +0 -1
  121. package/dist/agent-src/commands/optimize/project.md +0 -1
  122. package/dist/agent-src/commands/optimize/prompt.md +0 -1
  123. package/dist/agent-src/commands/optimize/rtk.md +0 -1
  124. package/dist/agent-src/commands/optimize/skills.md +0 -1
  125. package/dist/agent-src/commands/optimize.md +0 -1
  126. package/dist/agent-src/commands/orchestrate.md +0 -1
  127. package/dist/agent-src/commands/override/create.md +0 -1
  128. package/dist/agent-src/commands/override/manage.md +0 -1
  129. package/dist/agent-src/commands/override.md +0 -1
  130. package/dist/agent-src/commands/package/reset.md +0 -1
  131. package/dist/agent-src/commands/package/test.md +0 -1
  132. package/dist/agent-src/commands/package.md +0 -1
  133. package/dist/agent-src/commands/post-as/ghostwriter.md +0 -1
  134. package/dist/agent-src/commands/post-as/me.md +0 -1
  135. package/dist/agent-src/commands/post-as.md +0 -1
  136. package/dist/agent-src/commands/pr/create/description-only.md +0 -1
  137. package/dist/agent-src/commands/pr/create.md +23 -9
  138. package/dist/agent-src/commands/prediction-pool.md +0 -1
  139. package/dist/agent-src/commands/prepare-for-review.md +0 -1
  140. package/dist/agent-src/commands/profile/activate.md +0 -1
  141. package/dist/agent-src/commands/profile/deactivate.md +0 -1
  142. package/dist/agent-src/commands/profile/show.md +0 -1
  143. package/dist/agent-src/commands/profile.md +0 -1
  144. package/dist/agent-src/commands/project/analyze.md +0 -1
  145. package/dist/agent-src/commands/project/health.md +0 -1
  146. package/dist/agent-src/commands/project.md +0 -1
  147. package/dist/agent-src/commands/refine-ticket.md +0 -1
  148. package/dist/agent-src/commands/research/deep.md +0 -1
  149. package/dist/agent-src/commands/research/report.md +0 -1
  150. package/dist/agent-src/commands/research.md +0 -1
  151. package/dist/agent-src/commands/review/changes.md +0 -1
  152. package/dist/agent-src/commands/review/routing.md +0 -1
  153. package/dist/agent-src/commands/review.md +0 -1
  154. package/dist/agent-src/commands/roadmap/ai-council.md +0 -1
  155. package/dist/agent-src/commands/roadmap/create.md +0 -1
  156. package/dist/agent-src/commands/roadmap/materialize.md +0 -1
  157. package/dist/agent-src/commands/roadmap/next.md +0 -1
  158. package/dist/agent-src/commands/roadmap/process-full.md +0 -1
  159. package/dist/agent-src/commands/roadmap/process-phase.md +0 -1
  160. package/dist/agent-src/commands/roadmap/process-step.md +0 -1
  161. package/dist/agent-src/commands/roadmap.md +0 -1
  162. package/dist/agent-src/commands/rule-compliance-audit.md +0 -1
  163. package/dist/agent-src/commands/security-audit-config.md +0 -1
  164. package/dist/agent-src/commands/skill/preview.md +0 -1
  165. package/dist/agent-src/commands/skill.md +0 -1
  166. package/dist/agent-src/commands/skills/discover.md +0 -1
  167. package/dist/agent-src/commands/skills.md +0 -1
  168. package/dist/agent-src/commands/sync/agent-settings.md +0 -1
  169. package/dist/agent-src/commands/sync/gitignore/fix.md +0 -1
  170. package/dist/agent-src/commands/sync/gitignore.md +0 -1
  171. package/dist/agent-src/commands/sync.md +0 -1
  172. package/dist/agent-src/commands/tdd/green.md +0 -1
  173. package/dist/agent-src/commands/tdd/red.md +0 -1
  174. package/dist/agent-src/commands/tdd/refactor.md +0 -1
  175. package/dist/agent-src/commands/tdd.md +0 -1
  176. package/dist/agent-src/commands/team/adversarial.md +0 -1
  177. package/dist/agent-src/commands/team/delegate.md +0 -1
  178. package/dist/agent-src/commands/team/review.md +0 -1
  179. package/dist/agent-src/commands/team/status.md +0 -1
  180. package/dist/agent-src/commands/team-knowledge/bootstrap.md +0 -1
  181. package/dist/agent-src/commands/team-knowledge/consolidate.md +0 -1
  182. package/dist/agent-src/commands/team-knowledge.md +0 -1
  183. package/dist/agent-src/commands/team.md +0 -1
  184. package/dist/agent-src/commands/tests/create.md +0 -1
  185. package/dist/agent-src/commands/tests/e2e-heal.md +0 -1
  186. package/dist/agent-src/commands/tests/e2e-plan.md +0 -1
  187. package/dist/agent-src/commands/tests/execute.md +0 -1
  188. package/dist/agent-src/commands/tests.md +0 -1
  189. package/dist/agent-src/commands/threat-model.md +0 -1
  190. package/dist/agent-src/commands/update-form-request-messages.md +0 -1
  191. package/dist/agent-src/commands/upstream-contribute.md +0 -1
  192. package/dist/agent-src/commands/video/from-script.md +0 -1
  193. package/dist/agent-src/commands/video/from-song.md +0 -1
  194. package/dist/agent-src/commands/video/scene.md +0 -1
  195. package/dist/agent-src/commands/video/stitch.md +0 -1
  196. package/dist/agent-src/commands/video/storyboard.md +0 -1
  197. package/dist/agent-src/commands/video.md +0 -1
  198. package/dist/agent-src/commands/work.md +0 -1
  199. package/dist/agent-src/commands/worktree/cleanup.md +0 -1
  200. package/dist/agent-src/commands/worktree/create.md +0 -1
  201. package/dist/agent-src/commands/worktree/status.md +0 -1
  202. package/dist/agent-src/commands/worktree/verify.md +0 -1
  203. package/dist/agent-src/commands/worktree.md +0 -1
  204. package/dist/agent-src/contexts/model-recommendations.md +6 -4
  205. package/dist/agent-src/rules/low-impact-corpus-privacy-floor.md +1 -0
  206. package/dist/agent-src/scripts/roadmap_gates.ts +30 -2
  207. package/dist/agent-src/scripts/update_roadmap_progress.ts +32 -1
  208. package/dist/agent-src/skills/docx-authoring/SKILL.md +1 -0
  209. package/dist/agent-src/skills/git-workflow/SKILL.md +9 -1
  210. package/dist/agent-src/skills/pdf-tools/SKILL.md +1 -0
  211. package/dist/agent-src/skills/roadmap-writing/SKILL.md +7 -7
  212. package/dist/agent-src/skills/skill-writing/SKILL.md +35 -0
  213. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  214. package/dist/agent-src/templates/roadmaps.md +41 -3
  215. package/dist/cli/commands/commands.candidates.test.js +1 -1
  216. package/dist/cli/commands/commands.candidates.test.js.map +1 -1
  217. package/dist/cli/commands/commands.js +9 -15
  218. package/dist/cli/commands/commands.js.map +1 -1
  219. package/dist/cli/commands/profiles.test.js +7 -7
  220. package/dist/cli/commands/profiles.test.js.map +1 -1
  221. package/dist/cli/discovery/loadManifest.js.map +1 -1
  222. package/dist/cli/main.js +2 -2
  223. package/dist/cli/main.js.map +1 -1
  224. package/dist/cli/registry.js +1 -0
  225. package/dist/cli/registry.js.map +1 -1
  226. package/dist/cli-delegate/chunk-K34QTSD5.js +3895 -0
  227. package/dist/cli-delegate/{chunk-L565YFXH.js → chunk-LKZ5DOYW.js} +557 -297
  228. package/dist/cli-delegate/{chunk-NN4KW4HZ.js → chunk-O52B4F5S.js} +123 -2338
  229. package/dist/cli-delegate/cmd_conformance.js +18 -6
  230. package/dist/cli-delegate/cmd_doctor.js +2 -2
  231. package/dist/cli-delegate/cmd_export.js +1 -1
  232. package/dist/cli-delegate/cmd_handoff.js +1 -1
  233. package/dist/cli-delegate/cmd_prune.js +1 -1
  234. package/dist/cli-delegate/cmd_settings_migrate.js +1 -1
  235. package/dist/cli-delegate/cmd_sync.js +1 -1
  236. package/dist/cli-delegate/cmd_uninstall.js +1 -1
  237. package/dist/cli-delegate/cmd_validate.js +1 -1
  238. package/dist/discovery/deprecation-report.md +1 -1
  239. package/dist/discovery/discovery-manifest.json +213 -412
  240. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  241. package/dist/discovery/discovery-manifest.summary.md +2 -2
  242. package/dist/discovery/dormancy-report.md +1 -1
  243. package/dist/discovery/orphan-report.md +1 -1
  244. package/dist/discovery/packs.json +3 -3
  245. package/dist/discovery/trust-report.md +1 -1
  246. package/dist/discovery/workspaces.json +3 -3
  247. package/dist/hooks/dispatch.js +2588 -1082
  248. package/dist/install/install.mjs +744 -482
  249. package/dist/mcp/registry-manifest.json +3 -3
  250. package/dist/mcp/server.json +2 -2
  251. package/dist/mcp/server.mjs +159 -15
  252. package/dist/router.json +1 -1
  253. package/dist/scripts/_lib/model_tier.js +9 -0
  254. package/dist/scripts/_lib/model_tier.js.map +1 -1
  255. package/dist/scripts/condense.js +50 -0
  256. package/dist/scripts/condense.js.map +1 -1
  257. package/docs/MIGRATION.md +21 -1
  258. package/docs/contracts/settings-classes.md +41 -0
  259. package/package.json +1 -1
  260. package/src/agent-src/scripts/roadmap_gates.ts +30 -2
  261. package/src/agent-src/scripts/update_roadmap_progress.ts +32 -1
  262. package/src/config/agent-settings.template.yml +34 -0
  263. package/src/config/evaluator-budgets.json +4 -4
  264. package/src/config/gate-coverage.yml +39 -9
  265. package/src/config/gate-violation-baselines.json +9 -3
  266. package/src/config/hook-token-budget.json +7 -1
  267. package/src/config/preamble-payload-budget.json +28 -0
  268. package/src/scripts/_dispatch.bash +52 -5
  269. package/src/scripts/_lib/git_common_dir.ts +173 -0
  270. package/src/scripts/_lib/model_tier.ts +9 -0
  271. package/src/scripts/_lib/release_highlights.ts +108 -7
  272. package/src/scripts/_lib/skill_catalogue.ts +862 -0
  273. package/src/scripts/ai_council/clients.ts +182 -3
  274. package/src/scripts/ai_council/config.ts +16 -1
  275. package/src/scripts/ai_council/low_impact.ts +14 -1
  276. package/src/scripts/ai_council/orchestrator.ts +14 -1
  277. package/src/scripts/ai_council/transport_resolver.ts +32 -2
  278. package/src/scripts/audit_command_surface.ts +21 -15
  279. package/src/scripts/build_catalog_index.ts +1 -1
  280. package/src/scripts/build_discovery_manifest.ts +30 -34
  281. package/src/scripts/capture_skill_catalogue.ts +185 -328
  282. package/src/scripts/check_branch_freshness.ts +217 -62
  283. package/src/scripts/check_release_published.ts +6 -2
  284. package/src/scripts/check_release_trunk_sync.ts +18 -2
  285. package/src/scripts/condense.ts +50 -1
  286. package/src/scripts/dispatch_r2_reviewer.ts +15 -5
  287. package/src/scripts/evidence_report.ts +15 -4
  288. package/src/scripts/gen_discovery_baseline.ts +3 -3
  289. package/src/scripts/hook_manifest.yaml +67 -13
  290. package/src/scripts/hooks/concern_registry.ts +2 -0
  291. package/src/scripts/hooks/orchestration_record_hook.ts +21 -0
  292. package/src/scripts/hooks/subagent_ledger_hook.ts +17 -4
  293. package/src/scripts/hooks/tool_result_bytes_hook.ts +219 -0
  294. package/src/scripts/hot_context_hook.ts +13 -1
  295. package/src/scripts/install.ts +129 -0
  296. package/src/scripts/janitor.ts +77 -2
  297. package/src/scripts/lint_command_tiers.ts +29 -75
  298. package/src/scripts/lint_originality.ts +101 -14
  299. package/src/scripts/lint_plan_risk_register.ts +22 -9
  300. package/src/scripts/lint_roadmap_blockers.ts +109 -6
  301. package/src/scripts/lint_scheduled_deprecations.ts +738 -0
  302. package/src/scripts/migration_status.ts +17 -1
  303. package/src/scripts/probe_review_binding_drift.ts +731 -0
  304. package/src/scripts/release.ts +95 -1
  305. package/src/scripts/schemas/command.schema.json +6 -11
  306. package/src/scripts/schemas/skill.schema.json +12 -2
  307. package/src/scripts/schemas/subagent.schema.json +2 -2
  308. package/src/scripts/session_eol_report.ts +81 -0
  309. package/src/scripts/skill_linter.ts +82 -2
  310. package/src/scripts/workspace_doctor.ts +464 -0
  311. package/src/scripts/worktree_cleanup_check.ts +113 -8
  312. package/dist/cli-delegate/chunk-AYKQGIBG.js +0 -1594
@@ -24,22 +24,82 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
24
24
  const _HERE = fileURLToPath(import.meta.url);
25
25
  const REPO_ROOT = path.join(path.dirname(_HERE), '..', '..');
26
26
 
27
- // TTL configuration per directory (days)
28
- const TTL_CONFIG: ReadonlyArray<{ dir: string; ttlDays: number; description: string }> = [
27
+ // TTL configuration per directory (days).
28
+ //
29
+ // `owner` / `reviewBy` (road-to-inbox-harvest-2026-08-d-context-ledger Step
30
+ // 4.1) put every TTL under the same discipline the two budget config files
31
+ // carry: a retention number nobody owns and nobody revisits is a guess that
32
+ // hardens into a fact. The falsifier is printed per run — see the reclaim
33
+ // total in `main` — so a TTL that reclaims nothing over a review window is
34
+ // removed rather than defended.
35
+ const TTL_CONFIG: ReadonlyArray<{
36
+ dir: string;
37
+ ttlDays: number;
38
+ description: string;
39
+ owner: string;
40
+ reviewBy: string;
41
+ }> = [
29
42
  {
30
43
  dir: 'agents/tmp.old',
31
44
  ttlDays: 30,
32
45
  description: 'Processed inbox archive (consumed agents/tmp/ files)',
46
+ owner: 'maintainer',
47
+ reviewBy: '2026-11-10',
33
48
  },
34
49
  {
35
50
  dir: 'agents/runtime/tmp',
36
51
  ttlDays: 7,
37
52
  description: 'Agent scratch / runtime tmp',
53
+ owner: 'maintainer',
54
+ reviewBy: '2026-11-10',
38
55
  },
39
56
  {
40
57
  dir: 'agents/runtime/council/responses',
41
58
  ttlDays: 7,
42
59
  description: 'Council response cache',
60
+ owner: 'maintainer',
61
+ reviewBy: '2026-11-10',
62
+ },
63
+ // ── Session-scoped runtime state (Step 4.1) ────────────────────────────
64
+ //
65
+ // These four directories accrete ONE entry per session behind a
66
+ // once-per-session latch, and nothing in the tree removed them: writers
67
+ // and readers existed, no reaper did. They are named INDIVIDUALLY rather
68
+ // than sweeping `agents/runtime/state` as a whole, and that is the
69
+ // load-bearing decision in this step.
70
+ //
71
+ // `agents/runtime/state` also holds the CUMULATIVE measurement streams —
72
+ // `audit/`, `injection-census.jsonl`, `tool-result-census.jsonl`,
73
+ // `ai-video-telemetry.jsonl`, the memory index — and a directory-level
74
+ // TTL would delete exactly the corpora this roadmap exists to build. A
75
+ // sweeper that eats the instrument's own data is worse than no sweeper.
76
+ {
77
+ dir: 'agents/runtime/state/session-eol',
78
+ ttlDays: 7,
79
+ description: 'Per-session end-of-life counters (one file per session)',
80
+ owner: 'maintainer',
81
+ reviewBy: '2026-11-10',
82
+ },
83
+ {
84
+ dir: 'agents/runtime/state/reread-guard',
85
+ ttlDays: 7,
86
+ description: 'Per-session re-read latch',
87
+ owner: 'maintainer',
88
+ reviewBy: '2026-11-10',
89
+ },
90
+ {
91
+ dir: 'agents/runtime/state/end-review-nudge',
92
+ ttlDays: 7,
93
+ description: 'Per-session end-review latch',
94
+ owner: 'maintainer',
95
+ reviewBy: '2026-11-10',
96
+ },
97
+ {
98
+ dir: 'agents/runtime/state/probe-throttle',
99
+ ttlDays: 7,
100
+ description: 'Per-session probe throttle latch',
101
+ owner: 'maintainer',
102
+ reviewBy: '2026-11-10',
43
103
  },
44
104
  ];
45
105
 
@@ -227,6 +287,21 @@ function report(apply: boolean): void {
227
287
  );
228
288
  } else if (apply && totalExpired > 0) {
229
289
  process.stdout.write(`Total deleted: ${totalExpired} file(s), ${_humanSize(totalSize)} freed.\n`);
290
+ } else {
291
+ // road-to-inbox-harvest-2026-08-d-context-ledger Step 4.2 — the TTL's
292
+ // own falsifier, and it only works if a ZERO is printed.
293
+ //
294
+ // Until now a run that reclaimed nothing printed no total at all, so
295
+ // "the sweeper ran and found nothing expired" and "the sweeper did not
296
+ // run" produced byte-identical output. That is the difference between
297
+ // evidence that a retention policy is unnecessary and no evidence at
298
+ // all — and the first is what removes a TTL entry rather than letting
299
+ // it harden into an unexamined default.
300
+ process.stdout.write(
301
+ `Total ${apply ? 'deleted' : 'reclaimable'}: 0 file(s), 0 B — nothing was past its TTL.\n` +
302
+ `A reclaim that stays at zero across a review window is the signal to drop the ` +
303
+ `TTL entry, not to keep it: see the owner / reviewBy fields on each entry.\n`,
304
+ );
230
305
  }
231
306
  }
232
307
 
@@ -1,20 +1,24 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * Lint slash-command frontmatter for the `tier:` key.
3
+ * Lint slash-command frontmatter for the `visibility:` key.
4
4
  *
5
5
  * Ported from the retired Python `src/scripts/lint_command_tiers.py` (ADR-200, Phase 4 /
6
- * Wave 4b). Mirrors the Python CLI contract exactly: same scan scope, file
7
- * ordering, finding messages, stdout/stderr split, and exit codes
8
- * (return-code bitwise-OR accumulation across the domain + per-root +
9
- * condensed-projection passes).
6
+ * Wave 4b). Keeps the Python CLI contract's shape: same scan scope, file
7
+ * ordering, stdout/stderr split, and exit codes (return-code bitwise-OR
8
+ * accumulation across the domain + per-root + condensed-projection passes).
10
9
  *
11
- * Hard-fails CI if any command lacks a `tier:` declaration or uses an unknown
12
- * tier value. The valid tier set is locked by
10
+ * The integer `tier:` alias this file was named for is GONE
11
+ * (road-to-tier-removal Phase 4): `visibility:` is now the sole command-surface
12
+ * classifier. The filename is retained because the gate id, its Taskfile entry,
13
+ * and the contract doc all key on it; only the checked key changed.
14
+ *
15
+ * Hard-fails CI if any command lacks a `visibility:` declaration or uses an
16
+ * unknown value. The valid set is locked by
13
17
  * docs/contracts/command-surface-tiers.md.
14
18
  *
15
19
  * Exit codes:
16
- * 0 every command declares a valid tier
17
- * 1 one or more commands missing or using an invalid tier
20
+ * 0 every command declares a valid visibility
21
+ * 1 one or more commands missing or using an invalid visibility
18
22
  */
19
23
  import fs from "node:fs";
20
24
  import path from "node:path";
@@ -79,20 +83,13 @@ function COMMANDS_DIRS(): string[] {
79
83
  }
80
84
 
81
85
  const DOMAINS_DIR = path.join(REPO, "src", "domains");
82
- // Consumer-facing projection — must also carry tier so .augment/commands/
83
- // (which symlinks to dist/agent-src/commands/) renders the tier filter.
86
+ // Consumer-facing projection — must also carry visibility so .augment/commands/
87
+ // (which symlinks to dist/agent-src/commands/) renders the surface filter.
84
88
  const COMMANDS_DIR_CONDENSED = path.join(REPO, "dist/agent-src", "commands");
85
89
 
86
- const VALID_TIERS: ReadonlySet<string> = new Set(["0", "1", "2"]);
87
- // ADR-092: `visibility:` is the named source of truth; `tier:` is the
88
- // back-compat integer alias. Both are validated; when both are present they
89
- // MUST agree per this mapping.
90
+ // ADR-092 named `visibility:` the source of truth; road-to-tier-removal Phase 4
91
+ // removed the integer `tier:` alias, so this set is the whole vocabulary.
90
92
  const VALID_VISIBILITIES: ReadonlySet<string> = new Set(["visible", "advanced", "internal"]);
91
- const TIER_TO_VISIBILITY: Readonly<Record<string, string>> = {
92
- "0": "visible",
93
- "1": "advanced",
94
- "2": "internal",
95
- };
96
93
 
97
94
  export function parse_field(text: string, key: string): string | null {
98
95
  if (!text.startsWith("---\n")) {
@@ -116,15 +113,13 @@ export function parse_field(text: string, key: string): string | null {
116
113
  return null;
117
114
  }
118
115
 
119
- export function parse_tier(text: string): string | null {
120
- return parse_field(text, "tier");
121
- }
122
-
123
116
  /**
124
117
  * Validate the visibility field (ADR-092). Returns an error string or null.
125
118
  *
126
- * Requires a present + valid visibility, and consistency with the tier alias
127
- * whenever both are declared.
119
+ * Requires a present + valid visibility. The tier↔visibility consistency clause
120
+ * is gone with the alias itself (road-to-tier-removal Phase 4) — a stray `tier:`
121
+ * key is now rejected by `command.schema.json` (`additionalProperties: false`),
122
+ * not reconciled here.
128
123
  */
129
124
  export function visibility_error(text: string): string | null {
130
125
  const vis = parse_field(text, "visibility");
@@ -134,10 +129,6 @@ export function visibility_error(text: string): string | null {
134
129
  if (!VALID_VISIBILITIES.has(vis)) {
135
130
  return `invalid visibility '${vis}'`;
136
131
  }
137
- const tier = parse_field(text, "tier");
138
- if (tier !== null && tier in TIER_TO_VISIBILITY && TIER_TO_VISIBILITY[tier] !== vis) {
139
- return `visibility '${vis}' disagrees with tier '${tier}' (expected '${TIER_TO_VISIBILITY[tier]}')`;
140
- }
141
132
  return null;
142
133
  }
143
134
 
@@ -153,12 +144,6 @@ function stripChar(s: string, ch: string): string {
153
144
  return s.slice(start, end);
154
145
  }
155
146
 
156
- // Mirror Python's `print(sorted(set))` list repr: ['0', '1', ...].
157
- function sortedTiersRepr(): string {
158
- const items = [...VALID_TIERS].sort();
159
- return `[${items.map((s) => `'${s}'`).join(", ")}]`;
160
- }
161
-
162
147
  // Mirror Python's `print(sorted(VALID_VISIBILITIES))` list repr.
163
148
  function sortedVisibilitiesRepr(): string {
164
149
  const items = [...VALID_VISIBILITIES].sort();
@@ -184,43 +169,27 @@ export function lint(commands_dir: string, quiet = false): number {
184
169
  return 1;
185
170
  }
186
171
 
187
- const missing: string[] = [];
188
- const invalid: Array<[string, string]> = [];
189
172
  const vis_errors: Array<[string, string]> = [];
190
173
 
191
174
  for (const cmd of commands) {
192
175
  const rel = relPosix(cmd, commands_dir);
193
176
  const text = fs.readFileSync(cmd, "utf-8");
194
- const tier = parse_tier(text);
195
- if (tier === null) {
196
- missing.push(rel);
197
- } else if (!VALID_TIERS.has(tier)) {
198
- invalid.push([rel, tier]);
199
- }
200
177
  const ve = visibility_error(text);
201
178
  if (ve !== null) {
202
179
  vis_errors.push([rel, ve]);
203
180
  }
204
181
  }
205
182
 
206
- if (missing.length > 0 || invalid.length > 0 || vis_errors.length > 0) {
183
+ if (vis_errors.length > 0) {
207
184
  process.stderr.write(
208
- `❌ lint_command_tiers: ${missing.length} missing tier, ` +
209
- `${invalid.length} invalid tier, ${vis_errors.length} visibility ` +
185
+ `❌ lint_command_tiers: ${vis_errors.length} visibility ` +
210
186
  `(of ${commands.length} commands)\n`,
211
187
  );
212
- for (const name of missing) {
213
- process.stderr.write(` missing tier: ${name}\n`);
214
- }
215
- for (const [name, tier] of invalid) {
216
- process.stderr.write(` invalid tier '${tier}': ${name}\n`);
217
- }
218
188
  for (const [name, err] of vis_errors) {
219
189
  process.stderr.write(` ${err}: ${name}\n`);
220
190
  }
221
191
  process.stderr.write(
222
- ` valid tiers: ${sortedTiersRepr()}; ` +
223
- `valid visibility: ${sortedVisibilitiesRepr()}\n`,
192
+ ` valid visibility: ${sortedVisibilitiesRepr()}\n`,
224
193
  );
225
194
  process.stderr.write(
226
195
  " contract: docs/contracts/command-surface-tiers.md (ADR-092)\n",
@@ -231,7 +200,7 @@ export function lint(commands_dir: string, quiet = false): number {
231
200
  if (!quiet) {
232
201
  process.stdout.write(
233
202
  `✅ lint_command_tiers: ${commands.length} commands, ` +
234
- "all tier + visibility values valid\n",
203
+ "all visibility values valid\n",
235
204
  );
236
205
  }
237
206
  return 0;
@@ -246,35 +215,20 @@ export function lint_domain_sources(quiet = false): number {
246
215
  );
247
216
  return 1;
248
217
  }
249
- const missing: string[] = [];
250
- const invalid: Array<[string, string]> = [];
251
218
  const vis_errors: Array<[string, string]> = [];
252
219
  for (const c of commands) {
253
220
  const rel = relPosix(c, REPO);
254
221
  const text = fs.readFileSync(c, "utf-8");
255
- const t = parse_tier(text);
256
- if (t === null) {
257
- missing.push(rel);
258
- } else if (!VALID_TIERS.has(t)) {
259
- invalid.push([rel, t]);
260
- }
261
222
  const ve = visibility_error(text);
262
223
  if (ve !== null) {
263
224
  vis_errors.push([rel, ve]);
264
225
  }
265
226
  }
266
- if (missing.length > 0 || invalid.length > 0 || vis_errors.length > 0) {
227
+ if (vis_errors.length > 0) {
267
228
  process.stderr.write(
268
- `❌ lint_command_tiers: ${missing.length} missing tier, ` +
269
- `${invalid.length} invalid tier, ${vis_errors.length} visibility ` +
229
+ `❌ lint_command_tiers: ${vis_errors.length} visibility ` +
270
230
  `(of ${commands.length} domain commands)\n`,
271
231
  );
272
- for (const name of missing) {
273
- process.stderr.write(` missing tier: ${name}\n`);
274
- }
275
- for (const [name, tier] of invalid) {
276
- process.stderr.write(` invalid tier '${tier}': ${name}\n`);
277
- }
278
232
  for (const [name, err] of vis_errors) {
279
233
  process.stderr.write(` ${err}: ${name}\n`);
280
234
  }
@@ -283,7 +237,7 @@ export function lint_domain_sources(quiet = false): number {
283
237
  if (!quiet) {
284
238
  process.stdout.write(
285
239
  `✅ lint_command_tiers: ${commands.length} domain commands, ` +
286
- "all tier + visibility values valid\n",
240
+ "all visibility values valid\n",
287
241
  );
288
242
  }
289
243
  return 0;
@@ -354,7 +308,7 @@ export function main(): number {
354
308
  }
355
309
  // The condensed projection is the consumer-facing tree (via the
356
310
  // .augment/commands → dist/agent-src/commands symlink). It must also
357
- // carry tier so the surface stays uniform.
311
+ // carry visibility so the surface stays uniform.
358
312
  if (_isDir(COMMANDS_DIR_CONDENSED)) {
359
313
  rc |= lint(COMMANDS_DIR_CONDENSED, QUIET);
360
314
  }
@@ -20,6 +20,8 @@
20
20
  *
21
21
  * Modes:
22
22
  * (no args) full pairwise audit → agents/reports/originality.{json,md}
23
+ * --base <ref> the revision the change set is measured against; defines
24
+ * what counts as ESTABLISHED scaffold (see below)
23
25
  * --changed <f...> CI path: each changed file vs the whole same-class corpus
24
26
  * AND vs the other changed files of its class
25
27
  *
@@ -41,7 +43,7 @@
41
43
  * it drifts as commands/skills are added or removed. Re-run the full audit
42
44
  * after any large corpus change; do not treat 40 % as invariant.
43
45
  * 2. Adversarial batch masking. In `--changed` mode the boilerplate set is
44
- * computed from the corpus MINUS the change set, so a batch of ≥ _dfFloor
46
+ * derived from the ESTABLISHED corpus, so a batch of ≥ _dfFloor
45
47
  * near-identical re-skins submitted together cannot classify its own shared
46
48
  * shingles as boilerplate (see `_changed`). The full-audit sweep (no
47
49
  * "changed" notion) does NOT have this guard — a batch of ≥ floor identical
@@ -51,8 +53,35 @@
51
53
  * computes the PR's changed corpus files and runs `--changed` on them
52
54
  * (empty changed set reports INCONCLUSIVE, never a pass).
53
55
  *
56
+ * HOW "established" is resolved, and why it changed (2026-08-14):
57
+ * With `--base <ref>` the established corpus is the class corpus AS IT
58
+ * EXISTS AT THAT REVISION — content read from git, not from the worktree.
59
+ * Without it, the older approximation applies: on-disk corpus MINUS the
60
+ * change set.
61
+ *
62
+ * That approximation has a degeneracy the base-ref form does not. When a
63
+ * change set covers (nearly) the whole class — a mechanical edit such as
64
+ * dropping one frontmatter key from every command — "corpus minus change
65
+ * set" is EMPTY, no shingle clears the DF floor, nothing is scaffold, and
66
+ * the class's shared skeleton is scored as authored overlap. Measured on
67
+ * road-to-tier-removal Phase 4: the full audit reported worst 40 % / 0
68
+ * failures, while `--changed` over the same 200 unmodified-in-substance
69
+ * files reported 67.9 % and 3 failures — byte-identical on the base
70
+ * revision's own content, so the delta was entirely the masking collapse.
71
+ *
72
+ * The base-ref form does NOT weaken the guard, and it is worth being
73
+ * precise about why rather than claiming more. Both forms close the
74
+ * re-skin-batch axis, for different reasons: the old one excludes the
75
+ * change set outright, the new one relies on those files' shared shingles
76
+ * being absent from the base content (a NEW file contributes nothing at
77
+ * all; a REWRITTEN file contributes only its OLD text). Either way a
78
+ * batch cannot promote its own new shingles to scaffold. What changes is
79
+ * only the degenerate case: an established corpus that is empty because
80
+ * the change set swallowed it.
81
+ *
54
82
  * Exit 0 clean/warn · 1 fail · 2 usage.
55
83
  */
84
+ import { spawnSync } from 'node:child_process';
56
85
  import * as fs from 'node:fs';
57
86
  import * as path from 'node:path';
58
87
  import { fileURLToPath, pathToFileURL } from 'node:url';
@@ -176,7 +205,11 @@ function _templateShingles(spec: ClassSpec): Set<string> {
176
205
  }
177
206
 
178
207
  function _load(abs: string, cls: ArtifactClass, tmpl: Set<string>): Artifact {
179
- const text = fs.readFileSync(abs, 'utf-8');
208
+ return _loadText(fs.readFileSync(abs, 'utf-8'), abs, cls, tmpl);
209
+ }
210
+
211
+ /** `_load` over supplied text — used to load a file's content at a base ref. */
212
+ function _loadText(text: string, abs: string, cls: ArtifactClass, tmpl: Set<string>): Artifact {
180
213
  const raw = shingles(text, K);
181
214
  // Subtract shared scaffold — a shingle also present in the class template
182
215
  // is boilerplate, not authored content.
@@ -332,14 +365,41 @@ function _writeReport(byClass: Map<ArtifactClass, Artifact[]>, pairs: Pair[], di
332
365
 
333
366
  // --- changed mode ------------------------------------------------------------
334
367
 
335
- function _changed(files: string[]): { pairs: Pair[]; fails: number } {
336
- // The boilerplate DF set is derived from the ESTABLISHED corpus only — the
337
- // change set under review is excluded. Without this, a batch of ≥ _dfFloor
338
- // near-identical re-skins submitted together would lift its OWN shared
339
- // shingles over the floor, be reclassified as boilerplate, subtracted from
340
- // every copy, and score 0 against each other — the gate blinds itself on
341
- // exactly the attack it exists to catch. "What is scaffold" must be defined
342
- // by the corpus as it stands, never by the diff proposing to change it.
368
+ /**
369
+ * A corpus file's content at `ref`, or null when the file does not exist there.
370
+ *
371
+ * Null is the load-bearing case: a file absent at the base revision is NEW in
372
+ * this change set, so it contributes nothing to the scaffold definition. That is
373
+ * what keeps a batch of re-skinned newcomers from masking itself.
374
+ *
375
+ * A git failure is indistinguishable from "absent" at this layer, and both
376
+ * resolve the same conservative way — the file is excluded from scaffold, which
377
+ * can only make the gate stricter, never blinder.
378
+ */
379
+ function _textAtRef(ref: string, relpath: string): string | null {
380
+ const r = spawnSync('git', ['show', `${ref}:${relpath}`], {
381
+ cwd: ROOT,
382
+ encoding: 'utf-8',
383
+ maxBuffer: 32 * 1024 * 1024,
384
+ });
385
+ if (r.status !== 0 || typeof r.stdout !== 'string') return null;
386
+ return r.stdout;
387
+ }
388
+
389
+ function _changed(files: string[], base: string | null = null): { pairs: Pair[]; fails: number } {
390
+ // The boilerplate DF set is derived from the ESTABLISHED corpus only.
391
+ // Without this, a batch of ≥ _dfFloor near-identical re-skins submitted
392
+ // together would lift its OWN shared shingles over the floor, be
393
+ // reclassified as boilerplate, subtracted from every copy, and score 0
394
+ // against each other — the gate blinds itself on exactly the attack it
395
+ // exists to catch. "What is scaffold" must be defined by the corpus as it
396
+ // stands, never by the diff proposing to change it.
397
+ //
398
+ // `--base` makes "as it stands" literal: scaffold comes from the class
399
+ // corpus AT THAT REVISION. Without it we fall back to the older
400
+ // approximation (on-disk minus the change set), which is equivalent for a
401
+ // small change set but degenerates to an EMPTY established corpus when the
402
+ // change set covers the class — see the § 2 note in the module header.
343
403
  const changedRel = new Set(files.map((f) => _rel(path.resolve(ROOT, f))));
344
404
 
345
405
  // Build the corpus in RAW (template-subtracted, pre-DF) form per class so we
@@ -351,8 +411,18 @@ function _changed(files: string[]): { pairs: Pair[]; fails: number } {
351
411
  const tmpl = _templateShingles(spec);
352
412
  templateCache.set(spec.name, tmpl);
353
413
  const arts = spec.files().map((f) => _load(f, spec.name, tmpl));
354
- // Established corpus = on-disk arts minus the change set.
355
- const boiler = _boilerplateSet(arts.filter((a) => !changedRel.has(a.relpath)));
414
+ // Established corpus: at `base` when given (a file absent there is NEW
415
+ // and contributes no scaffold), else on-disk arts minus the change set.
416
+ const established =
417
+ base === null
418
+ ? arts.filter((a) => !changedRel.has(a.relpath))
419
+ : arts
420
+ .map((a) => {
421
+ const text = _textAtRef(base, a.relpath);
422
+ return text === null ? null : _loadText(text, path.resolve(ROOT, a.relpath), spec.name, tmpl);
423
+ })
424
+ .filter((a): a is Artifact => a !== null);
425
+ const boiler = _boilerplateSet(established);
356
426
  for (const a of arts) _subtract(a, boiler);
357
427
  byClass.set(spec.name, arts);
358
428
  boilerByClass.set(spec.name, boiler);
@@ -404,13 +474,30 @@ export function main(argv?: string[]): number {
404
474
  }
405
475
  let quiet = false;
406
476
  let changedFiles: string[] | null = null;
477
+ let base: string | null = null;
407
478
  for (let i = 0; i < args.length; i++) {
408
479
  const a = args[i] as string;
409
480
  if (a === '--quiet') { quiet = true; }
481
+ else if (a === '--base') {
482
+ // `--changed` swallows the rest of argv, so `--base` must precede it.
483
+ const v = args[i + 1];
484
+ if (v === undefined || v.startsWith('-')) {
485
+ process.stderr.write(`--base needs a git ref\n${_usage()}`);
486
+ return 2;
487
+ }
488
+ base = v;
489
+ i += 1;
490
+ }
410
491
  else if (a === '--changed') { changedFiles = args.slice(i + 1).filter((x) => !x.startsWith('-')); break; }
411
492
  else if (a.startsWith('-')) { process.stderr.write(`unrecognized argument: ${a}\n${_usage()}`); return 2; }
412
493
  else { process.stderr.write(`unexpected argument: ${a} (did you mean --changed ${a}?)\n${_usage()}`); return 2; }
413
494
  }
495
+ if (base !== null && changedFiles === null) {
496
+ // Silently ignoring it would let a workflow think the base-anchored
497
+ // scaffold was in effect when the full audit does not use one at all.
498
+ process.stderr.write(`--base is only meaningful with --changed\n${_usage()}`);
499
+ return 2;
500
+ }
414
501
 
415
502
  // Asserted for BOTH modes, and before either: `--changed` compares each
416
503
  // candidate against the same corpus, so a dead root there reports "no
@@ -434,7 +521,7 @@ export function main(argv?: string[]): number {
434
521
  }
435
522
 
436
523
  if (changedFiles !== null) {
437
- const { pairs, fails } = _changed(changedFiles);
524
+ const { pairs, fails } = _changed(changedFiles, base);
438
525
  if (pairs.length > 0) {
439
526
  const stream = fails > 0 ? process.stderr : process.stdout;
440
527
  stream.write(`lint_originality: ${pairs.length} overlap(s) ≥ WARN ${WARN}% (${fails} ≥ FAIL ${FAIL}%):\n`);
@@ -466,7 +553,7 @@ export function main(argv?: string[]): number {
466
553
  }
467
554
 
468
555
  function _usage(): string {
469
- return 'usage: lint_originality [--quiet] [--changed <file...>]\n';
556
+ return 'usage: lint_originality [--quiet] [--base <ref>] [--changed <file...>]\n';
470
557
  }
471
558
 
472
559
  function _isCliEntry(): boolean {
@@ -39,6 +39,7 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
39
39
 
40
40
  import { parse as parseYaml } from 'yaml';
41
41
 
42
+ import { workspaceIdentity } from './_lib/git_common_dir.js';
42
43
  import { gitEnv } from './_lib/git_env.js';
43
44
  import { splitMarkdownRow } from './_lib/md_table.js';
44
45
  import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
@@ -505,12 +506,26 @@ interface HistoryEntry {
505
506
  date: string; // %cs — YYYY-MM-DD
506
507
  }
507
508
 
509
+ /**
510
+ * Repo root containing `dir`, or `null` outside a repository.
511
+ *
512
+ * Census rows R5–R7. The three call sites below each spawned
513
+ * `rev-parse --show-toplevel` through `_git` — already `GIT_DIR`-safe via
514
+ * `gitEnv()`, so this migration is behaviour-identical by construction and
515
+ * exists to remove the third, fourth and fifth re-derivation of one question,
516
+ * not to fix a defect. The resolver reads files instead of spawning, so the
517
+ * three checks also stop costing three subprocesses.
518
+ */
519
+ function _repoRootOf(dir: string): string | null {
520
+ const root = workspaceIdentity(dir).repoRoot;
521
+ return root.resolved ? root.value : null;
522
+ }
523
+
508
524
  /** Newest-first commit history of a file; empty for untracked / non-repo paths. */
509
525
  export function fileHistory(absPath: string): HistoryEntry[] {
510
526
  const dir = path.dirname(absPath);
511
- const top = _git(dir, ['rev-parse', '--show-toplevel']);
512
- if (top === null) return [];
513
- const root = top.trim();
527
+ const root = _repoRootOf(dir);
528
+ if (root === null) return [];
514
529
  // Normalize separators: git pathspecs use `/` on every platform, so a raw
515
530
  // Windows `path.relative()` result matches nothing and would silently yield
516
531
  // an empty history — which the grandfather clause reads as "no baseline".
@@ -540,9 +555,8 @@ export function fileHistory(absPath: string): HistoryEntry[] {
540
555
  */
541
556
  export function hasUncommittedChanges(absPath: string): boolean {
542
557
  const dir = path.dirname(absPath);
543
- const top = _git(dir, ['rev-parse', '--show-toplevel']);
544
- if (top === null) return false;
545
- const root = top.trim();
558
+ const root = _repoRootOf(dir);
559
+ if (root === null) return false;
546
560
  const rel = path.relative(root, absPath).split(path.sep).join('/');
547
561
  const out = _git(root, ['status', '--porcelain', '--', rel]);
548
562
  if (out === null) return false;
@@ -551,9 +565,8 @@ export function hasUncommittedChanges(absPath: string): boolean {
551
565
 
552
566
  function _blobAt(absPath: string, sha: string): string | null {
553
567
  const dir = path.dirname(absPath);
554
- const top = _git(dir, ['rev-parse', '--show-toplevel']);
555
- if (top === null) return null;
556
- const root = top.trim();
568
+ const root = _repoRootOf(dir);
569
+ if (root === null) return null;
557
570
  const rel = path.relative(root, absPath).split(path.sep).join('/');
558
571
  return _git(root, ['show', `${sha}:${rel}`]);
559
572
  }