@event4u/agent-config 9.25.0 → 9.27.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 (222) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +58 -143
  3. package/README.md +1 -1
  4. package/dist/agent-src/commands/analyze/conformance.md +21 -2
  5. package/dist/agent-src/commands/review/changes.md +6 -5
  6. package/dist/agent-src/commands/roadmap/next.md +256 -0
  7. package/dist/agent-src/commands/roadmap.md +12 -4
  8. package/dist/agent-src/contexts/execution/subagent-routing.md +4 -1
  9. package/dist/agent-src/rules/active-remediation.md +2 -0
  10. package/dist/agent-src/rules/analysis-skill-routing.md +2 -0
  11. package/dist/agent-src/rules/architecture.md +2 -0
  12. package/dist/agent-src/rules/artifact-drafting-protocol.md +2 -0
  13. package/dist/agent-src/rules/artifact-engagement-recording.md +2 -0
  14. package/dist/agent-src/rules/augment-edit-discipline.md +2 -0
  15. package/dist/agent-src/rules/autonomous-execution.md +2 -0
  16. package/dist/agent-src/rules/brand-consistency.md +2 -0
  17. package/dist/agent-src/rules/brand-source-of-truth.md +2 -0
  18. package/dist/agent-src/rules/broken-access-control.md +2 -0
  19. package/dist/agent-src/rules/cli-output-handling.md +2 -0
  20. package/dist/agent-src/rules/code-comment-discipline.md +2 -0
  21. package/dist/agent-src/rules/code-provenance.md +2 -0
  22. package/dist/agent-src/rules/command-suggestion-policy.md +2 -0
  23. package/dist/agent-src/rules/commit-conventions.md +2 -0
  24. package/dist/agent-src/rules/communication-through-line.md +2 -0
  25. package/dist/agent-src/rules/content-quoting-floor.md +2 -0
  26. package/dist/agent-src/rules/context-hygiene.md +10 -0
  27. package/dist/agent-src/rules/copilot-routing.md +2 -0
  28. package/dist/agent-src/rules/cross-source-consistency.md +2 -0
  29. package/dist/agent-src/rules/decision-revisit-gate.md +2 -0
  30. package/dist/agent-src/rules/delegation-policy.md +5 -2
  31. package/dist/agent-src/rules/design-fidelity.md +19 -1
  32. package/dist/agent-src/rules/design-review-after-ui-write.md +120 -0
  33. package/dist/agent-src/rules/devcontainer-routing.md +2 -0
  34. package/dist/agent-src/rules/doc-screenshot-hygiene.md +2 -0
  35. package/dist/agent-src/rules/docker-commands.md +2 -0
  36. package/dist/agent-src/rules/domain-adoption-policy.md +2 -0
  37. package/dist/agent-src/rules/domain-safety-disclaimer.md +2 -0
  38. package/dist/agent-src/rules/domain-safety-pii.md +2 -0
  39. package/dist/agent-src/rules/domain-safety-retention.md +2 -0
  40. package/dist/agent-src/rules/downstream-changes.md +2 -0
  41. package/dist/agent-src/rules/engineering-safety-floor.md +2 -0
  42. package/dist/agent-src/rules/evaluator-independence.md +19 -3
  43. package/dist/agent-src/rules/external-code-graph-interop.md +2 -0
  44. package/dist/agent-src/rules/external-reference-deep-dive.md +2 -0
  45. package/dist/agent-src/rules/fast-path-marker-visibility.md +2 -0
  46. package/dist/agent-src/rules/finance-safety-floor.md +2 -0
  47. package/dist/agent-src/rules/framework-neutrality-in-generic-skills.md +2 -0
  48. package/dist/agent-src/rules/git-history-discipline.md +3 -1
  49. package/dist/agent-src/rules/guidelines.md +2 -0
  50. package/dist/agent-src/rules/history-discipline.md +2 -0
  51. package/dist/agent-src/rules/icon-consistency.md +2 -0
  52. package/dist/agent-src/rules/image-likeness-and-rights.md +2 -0
  53. package/dist/agent-src/rules/improve-before-implement.md +2 -0
  54. package/dist/agent-src/rules/invite-challenge.md +2 -0
  55. package/dist/agent-src/rules/laravel-routing.md +2 -0
  56. package/dist/agent-src/rules/laravel-translations.md +2 -0
  57. package/dist/agent-src/rules/legal-safety-floor.md +2 -0
  58. package/dist/agent-src/rules/lethal-trifecta-guard.md +2 -0
  59. package/dist/agent-src/rules/linked-projects-onboarding-gate.md +2 -0
  60. package/dist/agent-src/rules/low-impact-corpus-privacy-floor.md +2 -0
  61. package/dist/agent-src/rules/markdown-safe-codeblocks.md +2 -0
  62. package/dist/agent-src/rules/media-governance-routing.md +2 -0
  63. package/dist/agent-src/rules/media-sync-ground-truth.md +2 -0
  64. package/dist/agent-src/rules/minimal-safe-diff.md +2 -0
  65. package/dist/agent-src/rules/missing-tool-handling.md +2 -0
  66. package/dist/agent-src/rules/model-recommendation.md +2 -0
  67. package/dist/agent-src/rules/no-attribution-footers.md +2 -0
  68. package/dist/agent-src/rules/no-decorative-emojis-in-git-surfaces.md +2 -0
  69. package/dist/agent-src/rules/no-pr-progress-comments.md +2 -0
  70. package/dist/agent-src/rules/no-roadmap-references.md +2 -0
  71. package/dist/agent-src/rules/notes-first-reasoning.md +2 -0
  72. package/dist/agent-src/rules/onboarding-gate.md +2 -0
  73. package/dist/agent-src/rules/output-discipline.md +2 -0
  74. package/dist/agent-src/rules/package-ci-checks.md +2 -0
  75. package/dist/agent-src/rules/persona-governance.md +2 -0
  76. package/dist/agent-src/rules/php-coding.md +2 -0
  77. package/dist/agent-src/rules/prefer-enums-over-literals.md +2 -0
  78. package/dist/agent-src/rules/preservation-guard.md +2 -0
  79. package/dist/agent-src/rules/provider-lifecycle-discipline.md +2 -0
  80. package/dist/agent-src/rules/question-not-instruction.md +2 -0
  81. package/dist/agent-src/rules/reviewer-awareness.md +2 -0
  82. package/dist/agent-src/rules/roadmap-ci-steps-policy.md +2 -0
  83. package/dist/agent-src/rules/roadmap-progress-sync.md +2 -0
  84. package/dist/agent-src/rules/role-mode-adherence.md +2 -0
  85. package/dist/agent-src/rules/rule-type-governance.md +2 -0
  86. package/dist/agent-src/rules/runtime-safety.md +2 -0
  87. package/dist/agent-src/rules/scale-discipline.md +2 -0
  88. package/dist/agent-src/rules/secret-vcs-guard.md +2 -0
  89. package/dist/agent-src/rules/security-sensitive-stop.md +2 -0
  90. package/dist/agent-src/rules/senior-engineering-discipline.md +2 -0
  91. package/dist/agent-src/rules/session-canary.md +63 -19
  92. package/dist/agent-src/rules/settings-ask-protocol.md +180 -0
  93. package/dist/agent-src/rules/size-enforcement.md +2 -0
  94. package/dist/agent-src/rules/skill-improvement-trigger.md +2 -0
  95. package/dist/agent-src/rules/skill-quality.md +2 -0
  96. package/dist/agent-src/rules/slash-command-routing-policy.md +2 -0
  97. package/dist/agent-src/rules/source-confidentiality.md +2 -0
  98. package/dist/agent-src/rules/source-discovery-gate.md +2 -0
  99. package/dist/agent-src/rules/source-of-truth.md +2 -0
  100. package/dist/agent-src/rules/spreadsheet-source-quality.md +2 -0
  101. package/dist/agent-src/rules/strategy-safety-floor.md +2 -0
  102. package/dist/agent-src/rules/symfony-routing.md +2 -0
  103. package/dist/agent-src/rules/think-before-action.md +2 -0
  104. package/dist/agent-src/rules/token-budget-discipline.md +8 -3
  105. package/dist/agent-src/rules/token-efficiency.md +2 -0
  106. package/dist/agent-src/rules/token-optimizer-maintenance.md +2 -0
  107. package/dist/agent-src/rules/tool-safety.md +2 -0
  108. package/dist/agent-src/rules/ui-audit-gate.md +7 -0
  109. package/dist/agent-src/rules/untrusted-input-defense.md +2 -0
  110. package/dist/agent-src/rules/upstream-proposal.md +2 -0
  111. package/dist/agent-src/rules/user-interaction.md +5 -2
  112. package/dist/agent-src/rules/user-interrupt-priority.md +2 -0
  113. package/dist/agent-src/skills/recursive-verification/SKILL.md +13 -0
  114. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  115. package/dist/cli/registry.js +2 -0
  116. package/dist/cli/registry.js.map +1 -1
  117. package/dist/cli-delegate/{chunk-SJQMJI7M.js → chunk-3KJIMFTB.js} +1 -1
  118. package/dist/cli-delegate/{chunk-BSLUHV2F.js → chunk-BCPIOAGV.js} +148 -133
  119. package/dist/cli-delegate/cmd_conformance.js +1 -1
  120. package/dist/cli-delegate/cmd_doctor.js +1 -1
  121. package/dist/cli-delegate/cmd_export.js +1 -1
  122. package/dist/cli-delegate/cmd_prune.js +1 -1
  123. package/dist/cli-delegate/cmd_route_audit.js +4 -1
  124. package/dist/cli-delegate/cmd_route_explain.js +4 -1
  125. package/dist/cli-delegate/cmd_settings_migrate.js +1 -1
  126. package/dist/cli-delegate/cmd_sync.js +1 -1
  127. package/dist/cli-delegate/cmd_uninstall.js +1 -1
  128. package/dist/cli-delegate/cmd_validate.js +1 -1
  129. package/dist/discovery/deprecation-report.md +1 -1
  130. package/dist/discovery/discovery-manifest.json +191 -120
  131. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  132. package/dist/discovery/discovery-manifest.summary.md +4 -4
  133. package/dist/discovery/orphan-report.md +1 -1
  134. package/dist/discovery/packs.json +11 -8
  135. package/dist/discovery/trust-report.md +3 -3
  136. package/dist/discovery/workspaces.json +9 -6
  137. package/dist/hooks/dispatch.js +1148 -349
  138. package/dist/install/install.mjs +1 -1
  139. package/dist/mcp/registry-manifest.json +3 -3
  140. package/dist/mcp/server.json +2 -2
  141. package/dist/mcp/server.mjs +571 -417
  142. package/dist/router.json +1 -1
  143. package/dist/scripts/condense.js +33 -2
  144. package/dist/scripts/condense.js.map +1 -1
  145. package/dist/server/io/yamlIO.js +85 -0
  146. package/dist/server/io/yamlIO.js.map +1 -1
  147. package/dist/server/nicknameResolver.js +116 -0
  148. package/dist/server/nicknameResolver.js.map +1 -0
  149. package/dist/server/routes/ping.js +29 -11
  150. package/dist/server/routes/ping.js.map +1 -1
  151. package/dist/server/routes/settings.js +1 -12
  152. package/dist/server/routes/settings.js.map +1 -1
  153. package/dist/server/routes/wizard.js +52 -2
  154. package/dist/server/routes/wizard.js.map +1 -1
  155. package/dist/shared/interactiveContext.js +73 -0
  156. package/dist/shared/interactiveContext.js.map +1 -0
  157. package/dist/shared/nicknamePrefill.js +59 -0
  158. package/dist/shared/nicknamePrefill.js.map +1 -0
  159. package/dist/shared/settingsAsks.js +128 -0
  160. package/dist/shared/settingsAsks.js.map +1 -0
  161. package/dist/shared/settingsCarveOut.js +118 -0
  162. package/dist/shared/settingsCarveOut.js.map +1 -0
  163. package/dist/shared/settingsConsent.js +93 -0
  164. package/dist/shared/settingsConsent.js.map +1 -0
  165. package/dist/shared/settingsSurface.js +7 -1
  166. package/dist/shared/settingsSurface.js.map +1 -1
  167. package/dist/ui/assets/index-DmR0mbk4.js.map +1 -1
  168. package/package.json +1 -1
  169. package/src/config/agents-paths.yml +15 -0
  170. package/src/config/evaluator-budgets.json +4 -4
  171. package/src/config/gate-coverage.yml +29 -1
  172. package/src/config/gate-violation-baselines.json +13 -7
  173. package/src/scripts/_cli/cmd_doctor.ts +6 -21
  174. package/src/scripts/_cli/cmd_route_audit.ts +4 -1
  175. package/src/scripts/_cli/cmd_route_explain.ts +9 -1
  176. package/src/scripts/_dispatch.bash +18 -0
  177. package/src/scripts/_lib/git_common_dir.ts +128 -0
  178. package/src/scripts/_lib/obligation_frequency.ts +391 -0
  179. package/src/scripts/_lib/session_register.ts +361 -0
  180. package/src/scripts/ai-video/stitch.sh +27 -9
  181. package/src/scripts/before_complete_hook.ts +23 -4
  182. package/src/scripts/bench_ab_v2_run.ts +272 -16
  183. package/src/scripts/bench_ab_v2_stats.ts +156 -3
  184. package/src/scripts/check_completion_review.ts +80 -3
  185. package/src/scripts/check_corpus_staleness.ts +692 -0
  186. package/src/scripts/check_enforcement_coverage.ts +192 -7
  187. package/src/scripts/check_gate_coverage.ts +67 -0
  188. package/src/scripts/check_hook_bundle_freshness.ts +183 -0
  189. package/src/scripts/check_rule_projection_integrity.ts +364 -0
  190. package/src/scripts/condense.ts +36 -2
  191. package/src/scripts/conformance_scan.ts +82 -17
  192. package/src/scripts/dispatch_r2_reviewer.ts +31 -2
  193. package/src/scripts/generate_settings_reference.ts +232 -0
  194. package/src/scripts/git_authorization_hook.ts +75 -10
  195. package/src/scripts/hook_manifest.yaml +87 -27
  196. package/src/scripts/hooks/block_no_verify.ts +59 -2
  197. package/src/scripts/hooks/block_unauthorized_git.ts +199 -16
  198. package/src/scripts/hooks/concern_registry.ts +2 -0
  199. package/src/scripts/hooks/design_slop_hook.ts +13 -1
  200. package/src/scripts/hooks/dispatch_hook.ts +97 -2
  201. package/src/scripts/hooks/evidence_independence.ts +40 -6
  202. package/src/scripts/language_mirror_hook.ts +256 -17
  203. package/src/scripts/lint_abstraction_thresholds.ts +41 -8
  204. package/src/scripts/lint_agents_layout.ts +2 -0
  205. package/src/scripts/lint_hook_manifest.ts +29 -0
  206. package/src/scripts/memory_learn_hook.ts +109 -6
  207. package/src/scripts/orchestration_backfill.ts +354 -0
  208. package/src/scripts/pr_url_reminder_hook.ts +17 -2
  209. package/src/scripts/release.ts +180 -37
  210. package/src/scripts/schemas/review-findings.schema.json +99 -0
  211. package/src/scripts/schemas/rule.schema.json +5 -0
  212. package/src/scripts/session_canary_hook.ts +35 -3
  213. package/src/scripts/session_register_hook.ts +288 -0
  214. package/src/scripts/sessions_cli.ts +140 -0
  215. package/src/scripts/validate_frontmatter.ts +68 -0
  216. package/src/server/io/yamlIO.ts +87 -0
  217. package/src/shared/interactiveContext.ts +98 -0
  218. package/src/shared/nicknamePrefill.ts +96 -0
  219. package/src/shared/settingsAsks.ts +176 -0
  220. package/src/shared/settingsCarveOut.ts +143 -0
  221. package/src/shared/settingsConsent.ts +136 -0
  222. package/src/shared/settingsSurface.ts +7 -1
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@event4u/agent-config",
3
3
  "mcpName": "io.github.event4u-app/agent-config",
4
- "version": "9.25.0",
4
+ "version": "9.27.0",
5
5
  "description": "Governed skills, rules and commands for AI coding tools (Claude Code, Cursor, Windsurf, Copilot) — every claim machine-checked, including \"zero runtime daemon\".",
6
6
  "license": "MIT",
7
7
  "private": false,
@@ -114,6 +114,21 @@
114
114
  writer: /team-knowledge:bootstrap skill
115
115
  rationale: Deterministic template pages; review-then-commit flow (nothing here is the artifact)
116
116
 
117
+ # ---------------------------------------------------------------------------
118
+ # Self-improvement proposals (TRACKED — listed for classification, not ignored)
119
+ # ---------------------------------------------------------------------------
120
+
121
+ - path: /agents/proposals/
122
+ scope: both
123
+ policy: tracked
124
+ writer: learning-to-rule-or-skill skill (drafts) → upstream-contribute (PR)
125
+ rationale: |
126
+ Proposal drafts must be committed — the proposal-drift workflow reads their
127
+ stage: frontmatter in CI, and upstream-contribute opens a PR from them. Listed
128
+ here because agents-layout.md requires every new top-level agents/ entry to
129
+ appear in this manifest; the entry is inert for the gitignore gate, which reads
130
+ policy: ignored only.
131
+
117
132
  # ---------------------------------------------------------------------------
118
133
  # Ghostwriter profiles
119
134
  # ---------------------------------------------------------------------------
@@ -58,11 +58,11 @@
58
58
  "deterministic": false
59
59
  },
60
60
  "cli_help_command_count": {
61
- "max": 86,
62
- "last_measured": 86,
63
- "last_measured_at": "2026-08-06",
61
+ "max": 88,
62
+ "last_measured": 88,
63
+ "last_measured_at": "2026-08-07",
64
64
  "method": "registry enumeration — count of `{ name: '...', disposition` entries in src/cli/registry.ts (NOT --help prose parsing; methods historically disagreed 74 vs 76 vs 79)",
65
- "baseline_note": "80 → 81 on 2026-08-03 (release 9.15.0 gate). This RECORDS growth that already shipped through its own reviewed PR; it does not authorize new surface. The +1 is f02db54c2 (fix(cli): register routing:doctor in the CLI registry) — the command existed, only its registry entry was missing, and the registering PR did not move this budget. The gate did its job again: breach WARNS on main and BLOCKS on release, which is the conscious-decision point rather than a silently raised cap. Re-measured 2026-08-03 via the pinned method: 81. Headroom stays zero by design — the next registry addition must move this number in its own PR. 81 → 82 on 2026-08-04: the `handoff` subcommand (road-to-agent-handoff-resume Phase 4) registers in the same PR that adds the surface, per this record's own contract. Re-measured via the pinned method: 82. 82 → 84 on 2026-08-04: the `route:explain` + `route:audit` verbs (routing-correctness validation surface) register in the same PR that adds the surface. Re-measured via the pinned method: 84. 84 → 85 on 2026-08-05: the `settings:set` verb (road-to-zero-ceremony-settings Phase 2 — the one agent-reachable settings writer) registers in the same PR that adds the surface, per this record's own contract. Re-measured via the pinned method: 85. 85 → 86 on 2026-08-06: the `conformance:behavior` verb (road-to-agent-behavior-conformance Phase 5 — replays the local transcript store through the four mechanised conformance checks) registers in the same PR that adds the surface, per this record's own contract. Re-measured via the pinned method: 86."
65
+ "baseline_note": "80 → 81 on 2026-08-03 (release 9.15.0 gate). This RECORDS growth that already shipped through its own reviewed PR; it does not authorize new surface. The +1 is f02db54c2 (fix(cli): register routing:doctor in the CLI registry) — the command existed, only its registry entry was missing, and the registering PR did not move this budget. The gate did its job again: breach WARNS on main and BLOCKS on release, which is the conscious-decision point rather than a silently raised cap. Re-measured 2026-08-03 via the pinned method: 81. Headroom stays zero by design — the next registry addition must move this number in its own PR. 81 → 82 on 2026-08-04: the `handoff` subcommand (road-to-agent-handoff-resume Phase 4) registers in the same PR that adds the surface, per this record's own contract. Re-measured via the pinned method: 82. 82 → 84 on 2026-08-04: the `route:explain` + `route:audit` verbs (routing-correctness validation surface) register in the same PR that adds the surface. Re-measured via the pinned method: 84. 84 → 85 on 2026-08-05: the `settings:set` verb (road-to-zero-ceremony-settings Phase 2 — the one agent-reachable settings writer) registers in the same PR that adds the surface, per this record's own contract. Re-measured via the pinned method: 85. 85 → 86 on 2026-08-06: the `conformance:behavior` verb (road-to-agent-behavior-conformance Phase 5 — replays the local transcript store through the four mechanised conformance checks) registers in the same PR that adds the surface, per this record's own contract. Re-measured via the pinned method: 86. 86 → 88 on 2026-08-07: the `sessions:list` + `sessions:claim` verbs (road-to-parallel-session-coordination — the shell surface of the shared session register, needed because /roadmap:next is command markdown and can only reach the register through a shell call) register in the same PR that adds the surface, per this record's own contract. Re-measured via the pinned method: 88."
66
66
  },
67
67
  "mcp_public_tool_count": {
68
68
  "max": 19,
@@ -44,7 +44,7 @@
44
44
  # ----------------------------------------------------------
45
45
  # 240 `lint_*` / `check_*` / `audit_*` scripts live under `src/scripts/`
46
46
  # (the count is computed, not asserted — `check_gate_coverage.count_gate_scripts`).
47
- # **31 of them emit the machine-readable `scanned: <N>` line under their
47
+ # **32 of them emit the machine-readable `scanned: <N>` line under their
48
48
  # CI-identical argv, and all 31 are listed below.** The manifest is therefore
49
49
  # complete with respect to its own contract, and covers ~13 % of the population.
50
50
  #
@@ -599,3 +599,31 @@ gates:
599
599
  check_kernel_prefix_stability). The canary is what distinguishes that from
600
600
  a gate scanning nothing — it plants the removal-permitting class with no
601
601
  evidence and the gate reds.
602
+
603
+ - id: check_corpus_staleness
604
+ argv: ["--quiet", "--today", "2026-08-06"]
605
+ min_scanned: 5
606
+ corpus: "src/skills/*/data/manifest.json — grounding-corpus manifests (6 at baseline; brand, database, accessibility-auditor, api-design, design-intelligence, threat-modeling)"
607
+ status: enforced
608
+ canary:
609
+ class: missing-declared-csv
610
+ path: src/skills/zz-canary-corpus/data/manifest.json
611
+ content: |
612
+ {
613
+ "manifest_version": 1,
614
+ "refresh_cadence": "quarterly",
615
+ "upstream": { "last_checked": "2026-07-01" },
616
+ "domains": { "d": { "file": "nope.csv", "search_cols": ["a"], "output_cols": ["b"] } }
617
+ }
618
+ note: >
619
+ `--today` is PINNED in argv on purpose, and it is the whole reason this
620
+ gate is testable at all: the five upstream-vendored corpora share
621
+ `last_checked: 2026-06-07`, so an unpinned reference date makes this row
622
+ flip from green to red on 2026-09-15 with no diff behind it. The pinned
623
+ date keeps the coverage guard measuring COVERAGE; the real calendar check
624
+ is the workflow step, which runs unpinned and is meant to red when the
625
+ cadence is actually missed. Changing the pin is how you re-baseline after a
626
+ re-vendor, and it should be visible in a diff.
627
+ The canary plants a manifest whose declared CSV does not exist — the exact
628
+ gap `corpus-grounding/scripts/schema_validator.ts` leaves open, since it
629
+ computes the path and never opens the file.
@@ -1,36 +1,42 @@
1
1
  {
2
- "$comment": "Violation ratchet for gates whose dead scan root was repaired. A repair REVEALS pre-existing debt; it does not create it. Each entry records the count revealed at repair time \u2014 the gate fails only when its count RISES. Lowering a count is a normal commit; raising one is a defect. Entries expire: a baseline that has not dropped within 56 days of `landed` (or of `reaffirmed.date`) fails its gate, because a number that never moves is suppression with extra steps. Mechanism + rationale: src/scripts/_lib/gate_baseline.ts. Decision: AI council 2026-08-02, roadmap road-to-gates-that-can-fail blocker `dead-gate-finding-triage`.",
2
+ "$comment": "Violation ratchet for gates whose dead scan root was repaired. A repair REVEALS pre-existing debt; it does not create it. Each entry records the count revealed at repair time — the gate fails only when its count RISES. Lowering a count is a normal commit; raising one is a defect. Entries expire: a baseline that has not dropped within 56 days of `landed` (or of `reaffirmed.date`) fails its gate, because a number that never moves is suppression with extra steps. Mechanism + rationale: src/scripts/_lib/gate_baseline.ts. Decision: AI council 2026-08-02, roadmap road-to-gates-that-can-fail blocker `dead-gate-finding-triage`.",
3
3
  "gates": {
4
4
  "check_no_new_legacy_path:hardcoded-scan-roots": {
5
5
  "count": 49,
6
6
  "landed": "2026-08-04",
7
- "note": "Lines under src/scripts/**/*.{ts,mts,mjs} that pass the retired `.agent-src.uncondensed` directory into `path.join(...)` \u2014 a filesystem path CONSTRUCTED against the tree ADR-051 abandoned, which is the exact shape of all 14 dead gates. Deliberately narrow so the number is signal, not noise: 236 raw mentions under src/scripts \u2192 147 string-literal lines \u2192 67 path-constructing lines when the pass was written, after exempting src/scripts/_lib/agent_src.ts (the shared resolver, whose job IS owning the constant \u2014 gates that route through it survived the move). Detectors holding the literal as data to match, strip, or print are not counted, and no prose, docs, tests or fixtures are read: that full-tree shape was measured at 44 files / 213 hits and rejected as the wrong lint (see the note in check_no_new_legacy_path.ts::find_offenders). Recorded at 58 rather than 67 because Phase 1's scan-root repairs were landing on this branch while the pass was being written and had already removed 9 \u2014 a baseline above the truth silently permits new violations. This count goes to ZERO as the remaining repairs land; every repair is a lowering commit. Lowered 56\u219251 by the road-to-renewal-foundation Phase 1 dead-root sweep (check_always_budget, check_portability, check_references, lint_framework_leakage, lint_agents_md). Lowered 51\u219249 by the gate-hardening adoption PR: skill_collision_clusters and skill_overlap were repointed at the live skills tree via the shared resolver \u2014 two gates that had been reading the retired container and scoring 0 of 288 skills."
7
+ "note": "Lines under src/scripts/**/*.{ts,mts,mjs} that pass the retired `.agent-src.uncondensed` directory into `path.join(...)` — a filesystem path CONSTRUCTED against the tree ADR-051 abandoned, which is the exact shape of all 14 dead gates. Deliberately narrow so the number is signal, not noise: 236 raw mentions under src/scripts → 147 string-literal lines → 67 path-constructing lines when the pass was written, after exempting src/scripts/_lib/agent_src.ts (the shared resolver, whose job IS owning the constant — gates that route through it survived the move). Detectors holding the literal as data to match, strip, or print are not counted, and no prose, docs, tests or fixtures are read: that full-tree shape was measured at 44 files / 213 hits and rejected as the wrong lint (see the note in check_no_new_legacy_path.ts::find_offenders). Recorded at 58 rather than 67 because Phase 1's scan-root repairs were landing on this branch while the pass was being written and had already removed 9 — a baseline above the truth silently permits new violations. This count goes to ZERO as the remaining repairs land; every repair is a lowering commit. Lowered 56→51 by the road-to-renewal-foundation Phase 1 dead-root sweep (check_always_budget, check_portability, check_references, lint_framework_leakage, lint_agents_md). Lowered 51→49 by the gate-hardening adoption PR: skill_collision_clusters and skill_overlap were repointed at the live skills tree via the shared resolver — two gates that had been reading the retired container and scoring 0 of 288 skills."
8
8
  },
9
9
  "audit_user_type_axis": {
10
10
  "count": 1,
11
11
  "landed": "2026-08-02",
12
- "note": "One orphan user-type value: `legal`. Five legal-review-prep skills (contract-review, dpa-review, nda-triage, legal-intake-triage, legal-practice-profile) declare `recommended_for_user_types: [legal]`, but there is no `user-types/legal.yml` \u2014 the pack shipped an axis value with no user-type definition behind it, and the dead scan root hid it (the audit reported used=0 orphans=0 while reading zero skills; it now reads 288 and reports used=8). Clearing this entry is a product call, not a copy edit: either author `user-types/legal.yml` (tagline, recommended packs, install-path hint) or drop `legal` from those five skills. Not decided here because both options change shipped pack behaviour."
12
+ "note": "One orphan user-type value: `legal`. Five legal-review-prep skills (contract-review, dpa-review, nda-triage, legal-intake-triage, legal-practice-profile) declare `recommended_for_user_types: [legal]`, but there is no `user-types/legal.yml` — the pack shipped an axis value with no user-type definition behind it, and the dead scan root hid it (the audit reported used=0 orphans=0 while reading zero skills; it now reads 288 and reports used=8). Clearing this entry is a product call, not a copy edit: either author `user-types/legal.yml` (tagline, recommended packs, install-path hint) or drop `legal` from those five skills. Not decided here because both options change shipped pack behaviour."
13
13
  },
14
14
  "lint_handoffs": {
15
15
  "count": 18,
16
16
  "landed": "2026-08-02",
17
- "note": "All 18 are `handoff_tier_mismatch`: a senior-tier skill links to a peer whose frontmatter carries no `tier` (reported as tier='unset') \u2014 e.g. build-buy-partner \u2192 adr-create, customer-research \u2192 po-discovery, gated-reach \u2192 threat-modeling. This is a tier-metadata backfill backlog on the LINKED-TO skills, not a broken link. The one genuinely dangling link revealed by the same repair (competitive-positioning \u2192 a non-existent ../analyze-reference-repo/SKILL.md; it is a command, not a skill) was FIXED in the repairing commit rather than baselined. Clearing this entry means backfilling `tier` on the linked-to skills, or deciding that `tier: unset` on a link target is not a violation and narrowing the rule."
17
+ "note": "All 18 are `handoff_tier_mismatch`: a senior-tier skill links to a peer whose frontmatter carries no `tier` (reported as tier='unset') — e.g. build-buy-partner → adr-create, customer-research → po-discovery, gated-reach → threat-modeling. This is a tier-metadata backfill backlog on the LINKED-TO skills, not a broken link. The one genuinely dangling link revealed by the same repair (competitive-positioning → a non-existent ../analyze-reference-repo/SKILL.md; it is a command, not a skill) was FIXED in the repairing commit rather than baselined. Clearing this entry means backfilling `tier` on the linked-to skills, or deciding that `tier: unset` on a link target is not a violation and narrowing the rule."
18
18
  },
19
19
  "lint_pack_boundaries": {
20
20
  "count": 337,
21
21
  "landed": "2026-08-02",
22
- "note": "Markdown links between source artefacts that cross a pack boundary the source pack's `requires` graph does not reach \u2014 revealed when the scan root moved off the deleted packages/ tree to the live corpus (0 -> 753 artefacts scanned). 82 distinct pack pairs across 131 source files; largest single class is engineering-base -> meta (73), skills in a consumer-installable pack citing this package's own maintenance skills. Each is a link that dangles for a consumer who installs the source pack without the target's. Judged real drift rather than an over-strict gate because it is long-tailed \u2014 top bucket 22%, no single systematic cause \u2014 unlike the lint_artefact_frontmatter case, where one required shape had 0-of-288 adoption. Fix per finding: add the edge to packs.yml requires, retarget the link, or move the artefact."
22
+ "note": "Markdown links between source artefacts that cross a pack boundary the source pack's `requires` graph does not reach — revealed when the scan root moved off the deleted packages/ tree to the live corpus (0 -> 753 artefacts scanned). 82 distinct pack pairs across 131 source files; largest single class is engineering-base -> meta (73), skills in a consumer-installable pack citing this package's own maintenance skills. Each is a link that dangles for a consumer who installs the source pack without the target's. Judged real drift rather than an over-strict gate because it is long-tailed — top bucket 22%, no single systematic cause — unlike the lint_artefact_frontmatter case, where one required shape had 0-of-288 adoption. Fix per finding: add the edge to packs.yml requires, retarget the link, or move the artefact."
23
23
  },
24
24
  "check_gate_completeness": {
25
25
  "count": 217,
26
26
  "landed": "2026-08-05",
27
- "note": "Ledger adoption ratchet. 217 of 221 CI-registered gates do not yet carry per-target completeness accounting (`_lib/gate_ledger.ts`) or a `// ledger-exempt:` marker. This is deliberately NOT a sweep: road-to-skill-ecosystem-gate-integrity Phase 1 adopts exactly three gates (lint_framework_leakage, check_references, lint_roadmap_complexity) plus the checker itself to prove the interface, and the remainder migrates as gates are touched. Shrink-only from here \u2014 the number may not grow, so a NEW gate must adopt or exempt.",
27
+ "note": "Ledger adoption ratchet. 217 of 221 CI-registered gates do not yet carry per-target completeness accounting (`_lib/gate_ledger.ts`) or a `// ledger-exempt:` marker. This is deliberately NOT a sweep: road-to-skill-ecosystem-gate-integrity Phase 1 adopts exactly three gates (lint_framework_leakage, check_references, lint_roadmap_complexity) plus the checker itself to prove the interface, and the remainder migrates as gates are touched. Shrink-only from here — the number may not grow, so a NEW gate must adopt or exempt.",
28
28
  "falsifier": "./scripts-run src/scripts/check_gate_completeness"
29
29
  },
30
30
  "check_depth_budget": {
31
31
  "count": 4,
32
32
  "landed": "2026-08-06",
33
- "note": "Four depth files exceed the 16,000-char per-file ceiling: rule-body-migration-inventory.md (23,782), design-antipatterns.md (20,707), roadmap-process-loop.md (20,360), orchestration-telemetry.md (16,155). Recorded rather than fixed because splitting four large reference documents is unrelated to the change that introduced the ceiling, and a gate that lands as four instant blockers is the shape this repository has already recorded as a failure. Shrink-only from here: a FIFTH over-ceiling file reds. roadmap-process-loop.md is the file that motivated the gate \u2014 it states 'Size budget: <= 4,000 chars' in its own header and is 5x over it, which is what a declared budget with nothing measuring it decays into. The ceiling is a GROWTH RATCHET, not a measured quality threshold: the 7,308-trajectory study behind the 3,500-token skill ceiling (ADR-217) was taken on procedural always-co-loaded skills, and this layer is declarative and loaded one or two files at a time. Replace the number when someone measures comprehension on THIS layer."
33
+ "note": "Four depth files exceed the 16,000-char per-file ceiling: rule-body-migration-inventory.md (23,782), design-antipatterns.md (20,707), roadmap-process-loop.md (20,360), orchestration-telemetry.md (16,155). Recorded rather than fixed because splitting four large reference documents is unrelated to the change that introduced the ceiling, and a gate that lands as four instant blockers is the shape this repository has already recorded as a failure. Shrink-only from here: a FIFTH over-ceiling file reds. roadmap-process-loop.md is the file that motivated the gate — it states 'Size budget: <= 4,000 chars' in its own header and is 5x over it, which is what a declared budget with nothing measuring it decays into. The ceiling is a GROWTH RATCHET, not a measured quality threshold: the 7,308-trajectory study behind the 3,500-token skill ceiling (ADR-217) was taken on procedural always-co-loaded skills, and this layer is declarative and loaded one or two files at a time. Replace the number when someone measures comprehension on THIS layer."
34
+ },
35
+ "gate-self-test:registered-non-adopters": {
36
+ "count": 24,
37
+ "landed": "2026-08-06",
38
+ "note": "Self-test adoption ratchet, expressed as a shrink-only NON-adopter count. 24 of the 32 gates registered enforced in gate-coverage.yml carry neither a `--self-test` (`_lib/gate_self_test.ts`) nor a `// self-test-exempt: <reason>` marker, so nothing proves they DISCRIMINATE — an enforced `scanned:` floor proves a gate read something, only a self-test proves the reading changes the verdict. Deliberately NOT an adoption column ratcheted upward: check_gate_coverage.ts's own report_hardening_ratchet rejects that shape in this same file (\"grades the solution instead of the problem\"), and the sibling adoption ratchet (check_gate_completeness) is already a non-adopter count. Population is the manifest, not every script under src/scripts: padding the manifest to move this number would mean adding a floor per row. Shrink-only from here — a NEW registered gate must adopt or exempt. Migration mechanism, same as the ledger ratchet above: gates adopt as they are touched, not in a sweep — the eight current adopters were each added alongside a real change to that gate. The 56-day non-stagnation clause applies unchanged, so a count parked here fails on its own.",
39
+ "falsifier": "./scripts-run src/scripts/check_gate_coverage"
34
40
  }
35
41
  }
36
42
  }
@@ -90,6 +90,7 @@ import * as installed_tools from '../_lib/installed_tools.js';
90
90
  import * as user_global_paths from '../_lib/user_global_paths.js';
91
91
  import * as global_deploy_inventory from '../_lib/global_deploy_inventory.js';
92
92
  import { censusDuplicateScope } from '../_lib/duplicate_scope_census.js';
93
+ import { git_common_dir } from '../_lib/git_common_dir.js';
93
94
  import * as sync_gitattributes from '../sync_gitattributes.js';
94
95
  import {
95
96
  PROJECT_ROOT_ENV,
@@ -897,27 +898,11 @@ const _PLACEHOLDER_EMAIL_RE = /^(t@t\.t|.*@example\.(com|org)|.*@localhost)$/i;
897
898
  const _PLACEHOLDER_NAME_RE = /^(t|test|user|nobody|x)$/i;
898
899
 
899
900
  export function _git_config_path(project_root: string): string | null {
900
- const gd = path.join(project_root, '.git');
901
- try {
902
- const st = fs.statSync(gd);
903
- if (st.isDirectory()) {
904
- return path.join(gd, 'config');
905
- }
906
- // Worktree: `.git` is a file "gitdir: <path>".
907
- const m = readText(gd).match(/^gitdir:\s*(.+)\s*$/m);
908
- if (m === null) {
909
- return null;
910
- }
911
- const gdPath = path.resolve(project_root, m[1]!.trim());
912
- const commondirFile = path.join(gdPath, 'commondir');
913
- if (pathExists(commondirFile)) {
914
- const common = path.resolve(gdPath, readText(commondirFile).trim());
915
- return path.join(common, 'config');
916
- }
917
- return path.join(gdPath, 'config');
918
- } catch {
919
- return null;
920
- }
901
+ // The `.git`-file / `commondir` walk this used to inline now lives in
902
+ // `_lib/git_common_dir.ts`, so the session register and this check cannot
903
+ // drift into two different answers to "where is the common dir".
904
+ const common = git_common_dir(project_root);
905
+ return common === null ? null : path.join(common, 'config');
921
906
  }
922
907
 
923
908
  function _parse_git_user(configText: string): { name: string | null; email: string | null } {
@@ -36,8 +36,11 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
36
36
  import { match_prompt, type Router } from '../_lib/router_match.js';
37
37
  import { load_agent_settings } from '../_lib/agent_settings.js';
38
38
  import { read_entries } from '../chat_history.js';
39
+ import { resolvePackageRoot } from '../_lib/package_root.js';
39
40
 
40
- const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..', '..');
41
+ // Marker-anchored, not hop-counted — see the sibling note in cmd_route_explain.ts.
42
+ // Both routing commands were the last two `_cli` entries still counting hops.
43
+ const REPO_ROOT = resolvePackageRoot(import.meta.url);
41
44
  const ROUTER_JSON = path.join(REPO_ROOT, 'dist', 'router.json');
42
45
 
43
46
  // cache-version: v1 — rebuildable audit log: delete-and-rerun rebuilds it from
@@ -31,8 +31,16 @@ import {
31
31
  } from '../_lib/router_match.js';
32
32
  import { measure, method_note } from '../_lib/token_count.js';
33
33
  import { load_agent_settings } from '../_lib/agent_settings.js';
34
+ import { resolvePackageRoot } from '../_lib/package_root.js';
34
35
 
35
- const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..', '..');
36
+ // Marker-anchored, not hop-counted: this module runs from `src/scripts/_cli/`
37
+ // (three levels down) AND from the precompiled `dist/cli-delegate/` (two), and
38
+ // a fixed hop count is right in exactly one of them. Three hops from the bundle
39
+ // landed on `<pkg>/..` — `node_modules/@event4u` for a scoped install — so
40
+ // `route:explain` and `route:audit` reported dist/router.json as missing on
41
+ // every invocation through the shipped binary while both worked under `tsx`.
42
+ // The resolver exists precisely for this and 12 sibling commands already use it.
43
+ const REPO_ROOT = resolvePackageRoot(import.meta.url);
36
44
  const ROUTER_JSON = path.join(REPO_ROOT, 'dist', 'router.json');
37
45
  const RULES_DIST_DIR = path.join(REPO_ROOT, 'dist', 'agent-src', 'rules');
38
46
 
@@ -177,6 +177,10 @@ Tier 2 — maintenance / internal (hooks, MCP, memory, telemetry):
177
177
  projection (from the pinned token baseline). Flags: --format
178
178
  code-graph Deterministic code-graph engine (ADR-124, Class A).
179
179
  Usage: code-graph build|validate|detect|query|explain|affected|path [options]
180
+ sessions:list List live agent sessions on this repository (shared
181
+ session register). Flags: --json
182
+ sessions:claim Claim a roadmap for this session so other sessions skip
183
+ it; --release clears the claim
180
184
  roadmap:progress Regenerate agents/roadmaps-progress.md from open roadmaps
181
185
  roadmap:progress-check Fail if agents/roadmaps-progress.md is stale (for CI)
182
186
  roadmap:archive Archive completed roadmaps (branch-touched by default;
@@ -579,6 +583,18 @@ cmd_mcp_run() {
579
583
  exec_ts "$server_main" "$@"
580
584
  }
581
585
 
586
+ # Shared session register (road-to-parallel-session-coordination). Resolved from
587
+ # PACKAGE_ROOT, not the consumer projection: the CLI imports the register library
588
+ # under src/scripts/_lib/, which is package-internal and never shipped into
589
+ # dist/agent-src/scripts/. Same shape as cmd_settings_set.
590
+ cmd_sessions_list() {
591
+ exec_ts "$PACKAGE_ROOT/src/scripts/sessions_cli.ts" list "$@"
592
+ }
593
+
594
+ cmd_sessions_claim() {
595
+ exec_ts "$PACKAGE_ROOT/src/scripts/sessions_cli.ts" claim "$@"
596
+ }
597
+
582
598
  cmd_roadmap_progress() {
583
599
  local script
584
600
  script="$(resolve_script "dist/agent-src/scripts/update_roadmap_progress.ts" ".augment/scripts/update_roadmap_progress.ts")"
@@ -1252,6 +1268,8 @@ main() {
1252
1268
  benchmark) cmd_benchmark "$@" ;;
1253
1269
  code-graph) cmd_code_graph "$@" ;;
1254
1270
  conformance:behavior) cmd_conformance_behavior "$@" ;;
1271
+ sessions:list) cmd_sessions_list "$@" ;;
1272
+ sessions:claim) cmd_sessions_claim "$@" ;;
1255
1273
  roadmap:progress) cmd_roadmap_progress "$@" ;;
1256
1274
  roadmap:progress-check) cmd_roadmap_progress_check "$@" ;;
1257
1275
  roadmap:archive) cmd_roadmap_archive "$@" ;;
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Resolve a repository's **common git directory** — the one directory every
3
+ * worktree of a repo shares.
4
+ *
5
+ * Extracted from `_cli/cmd_doctor.ts::_git_config_path`, which has resolved the
6
+ * `commondir` file this way since the git-identity check shipped. It is
7
+ * extracted rather than re-implemented on purpose: a repo with two different
8
+ * answers to "where is the common dir" is a bug waiting for a symlinked parent,
9
+ * and the worktree-cleanup work already hit that class once.
10
+ *
11
+ * ## Why this reads files instead of shelling out to git
12
+ *
13
+ * `git rev-parse --git-common-dir` is the obvious implementation and is wrong
14
+ * for the callers here, twice over:
15
+ *
16
+ * 1. **Inherited `GIT_DIR` overrides discovery.** Git hooks export `GIT_DIR`,
17
+ * and every child inherits it, so `execFileSync('git', …, { cwd })` silently
18
+ * resolves against the *hook's* repository rather than `cwd` — in a linked
19
+ * worktree those differ, and the symptom is wrong data, not an error. That
20
+ * is exactly what `_lib/git_env.ts` exists to strip. A file-based resolution
21
+ * has nothing to override and needs no such guard.
22
+ * 2. **The raw output is not comparable across checkouts.** Measured
23
+ * 2026-08-07: from a main checkout `git rev-parse --git-common-dir` prints
24
+ * the **relative** `.git`; from a linked worktree it prints an absolute
25
+ * path. A consumer comparing the raw strings concludes the two checkouts
26
+ * have different common dirs. Only the resolved realpath is a repo identity.
27
+ *
28
+ * ## Symlinked parents converge
29
+ *
30
+ * Measured on a throwaway repo reached both directly and through a symlinked
31
+ * ancestor: all four accesses (main / worktree × real / symlinked) resolve to
32
+ * the same directory, because the returned path is realpath-normalised here.
33
+ * This is the opposite of the worktree-cleanup finding that motivated the
34
+ * check — there realpath reporting caused a mis-classification; here it is the
35
+ * property that makes one shared register possible.
36
+ *
37
+ * Never throws: every caller treats "no common dir" as a reason to degrade,
38
+ * not as an error worth failing a hook over.
39
+ */
40
+
41
+ import fs from 'node:fs';
42
+ import path from 'node:path';
43
+
44
+ /** `fs.realpathSync` that falls back to the input when the path is unreadable. */
45
+ function realpath_or_self(p: string): string {
46
+ try {
47
+ return fs.realpathSync(p);
48
+ } catch {
49
+ return p;
50
+ }
51
+ }
52
+
53
+ /**
54
+ * The **per-worktree** git directory for `project_root` — `.git` in a plain
55
+ * checkout, the linked gitdir in a worktree. This is where `HEAD` lives, so it
56
+ * is the right anchor for "which branch is THIS checkout on", as opposed to
57
+ * `git_common_dir` which is deliberately shared across worktrees.
58
+ */
59
+ export function git_dir(project_root: string): string | null {
60
+ const dot_git = path.join(project_root, '.git');
61
+ try {
62
+ const st = fs.statSync(dot_git);
63
+ if (st.isDirectory()) {
64
+ return realpath_or_self(dot_git);
65
+ }
66
+ const m = fs.readFileSync(dot_git, 'utf-8').match(/^gitdir:\s*(.+)\s*$/m);
67
+ if (m === null) {
68
+ return null;
69
+ }
70
+ return realpath_or_self(path.resolve(project_root, m[1]!.trim()));
71
+ } catch {
72
+ return null;
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Current branch name for `project_root`, or `null` on a detached HEAD or
78
+ * outside a repo.
79
+ *
80
+ * Reads `<git-dir>/HEAD` directly rather than shelling out, for the same reason
81
+ * as everything else in this module: hooks export `GIT_DIR`, and an inherited
82
+ * one silently redirects `git` to the wrong repository.
83
+ */
84
+ export function current_branch(project_root: string): string | null {
85
+ const gd = git_dir(project_root);
86
+ if (gd === null) return null;
87
+ try {
88
+ const head = fs.readFileSync(path.join(gd, 'HEAD'), 'utf-8').trim();
89
+ const m = head.match(/^ref:\s*refs\/heads\/(.+)$/);
90
+ return m === null ? null : m[1]!.trim();
91
+ } catch {
92
+ return null;
93
+ }
94
+ }
95
+
96
+ /**
97
+ * The common git directory for `project_root`, realpath-normalised, or `null`
98
+ * when `project_root` is not inside a git repository.
99
+ *
100
+ * - Plain checkout → `<project_root>/.git`.
101
+ * - Linked worktree → the `commondir` target of the worktree's gitdir, i.e. the
102
+ * main checkout's `.git`, which is identical from every worktree of the repo.
103
+ * - Worktree gitdir without a `commondir` file → the gitdir itself (degenerate,
104
+ * but the historical behaviour and not this change's to alter).
105
+ */
106
+ export function git_common_dir(project_root: string): string | null {
107
+ const dot_git = path.join(project_root, '.git');
108
+ try {
109
+ const st = fs.statSync(dot_git);
110
+ if (st.isDirectory()) {
111
+ return realpath_or_self(dot_git);
112
+ }
113
+ // Linked worktree: `.git` is a FILE containing "gitdir: <path>".
114
+ const m = fs.readFileSync(dot_git, 'utf-8').match(/^gitdir:\s*(.+)\s*$/m);
115
+ if (m === null) {
116
+ return null;
117
+ }
118
+ const gitdir = path.resolve(project_root, m[1]!.trim());
119
+ const commondir_file = path.join(gitdir, 'commondir');
120
+ if (fs.existsSync(commondir_file)) {
121
+ const common = path.resolve(gitdir, fs.readFileSync(commondir_file, 'utf-8').trim());
122
+ return realpath_or_self(common);
123
+ }
124
+ return realpath_or_self(gitdir);
125
+ } catch {
126
+ return null;
127
+ }
128
+ }