@event4u/agent-config 9.26.0 → 9.28.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 (252) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/AGENTS.md +12 -11
  3. package/CHANGELOG.md +134 -207
  4. package/README.md +1 -1
  5. package/dist/agent-src/commands/analyze/conformance.md +43 -0
  6. package/dist/agent-src/commands/roadmap/next.md +65 -3
  7. package/dist/agent-src/contexts/execution/orchestration-telemetry.md +10 -3
  8. package/dist/agent-src/rules/active-remediation.md +2 -0
  9. package/dist/agent-src/rules/analysis-skill-routing.md +2 -0
  10. package/dist/agent-src/rules/architecture.md +2 -0
  11. package/dist/agent-src/rules/artifact-drafting-protocol.md +2 -0
  12. package/dist/agent-src/rules/artifact-engagement-recording.md +6 -1
  13. package/dist/agent-src/rules/augment-edit-discipline.md +2 -0
  14. package/dist/agent-src/rules/autonomous-execution.md +3 -1
  15. package/dist/agent-src/rules/brand-consistency.md +2 -0
  16. package/dist/agent-src/rules/brand-source-of-truth.md +35 -6
  17. package/dist/agent-src/rules/broken-access-control.md +2 -0
  18. package/dist/agent-src/rules/cli-output-handling.md +2 -0
  19. package/dist/agent-src/rules/code-comment-discipline.md +2 -0
  20. package/dist/agent-src/rules/code-provenance.md +2 -0
  21. package/dist/agent-src/rules/command-suggestion-policy.md +2 -0
  22. package/dist/agent-src/rules/commit-conventions.md +2 -0
  23. package/dist/agent-src/rules/communication-through-line.md +2 -0
  24. package/dist/agent-src/rules/content-quoting-floor.md +2 -0
  25. package/dist/agent-src/rules/context-hygiene.md +13 -1
  26. package/dist/agent-src/rules/copilot-routing.md +2 -0
  27. package/dist/agent-src/rules/council-availability.md +93 -0
  28. package/dist/agent-src/rules/cross-source-consistency.md +5 -0
  29. package/dist/agent-src/rules/decision-revisit-gate.md +2 -0
  30. package/dist/agent-src/rules/delegation-policy.md +31 -5
  31. package/dist/agent-src/rules/design-fidelity.md +5 -0
  32. package/dist/agent-src/rules/design-review-after-ui-write.md +4 -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 +14 -3
  43. package/dist/agent-src/rules/external-code-graph-interop.md +4 -1
  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 +3 -1
  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 +5 -1
  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 +3 -1
  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 +5 -1
  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 +5 -1
  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 +6 -1
  83. package/dist/agent-src/rules/roadmap-progress-sync.md +2 -0
  84. package/dist/agent-src/rules/role-mode-adherence.md +3 -1
  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 +5 -1
  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/self-repair-loop.md +103 -0
  91. package/dist/agent-src/rules/senior-engineering-discipline.md +2 -0
  92. package/dist/agent-src/rules/session-canary.md +62 -19
  93. package/dist/agent-src/rules/settings-ask-protocol.md +4 -0
  94. package/dist/agent-src/rules/size-enforcement.md +2 -0
  95. package/dist/agent-src/rules/skill-improvement-trigger.md +2 -0
  96. package/dist/agent-src/rules/skill-quality.md +2 -0
  97. package/dist/agent-src/rules/slash-command-routing-policy.md +2 -0
  98. package/dist/agent-src/rules/source-confidentiality.md +2 -0
  99. package/dist/agent-src/rules/source-discovery-gate.md +2 -0
  100. package/dist/agent-src/rules/source-of-truth.md +2 -0
  101. package/dist/agent-src/rules/spreadsheet-source-quality.md +2 -0
  102. package/dist/agent-src/rules/strategy-safety-floor.md +3 -1
  103. package/dist/agent-src/rules/symfony-routing.md +2 -0
  104. package/dist/agent-src/rules/think-before-action.md +2 -0
  105. package/dist/agent-src/rules/token-budget-discipline.md +5 -1
  106. package/dist/agent-src/rules/token-efficiency.md +2 -0
  107. package/dist/agent-src/rules/token-optimizer-maintenance.md +2 -0
  108. package/dist/agent-src/rules/tool-safety.md +7 -1
  109. package/dist/agent-src/rules/ui-audit-gate.md +4 -0
  110. package/dist/agent-src/rules/untrusted-input-defense.md +2 -0
  111. package/dist/agent-src/rules/upstream-proposal.md +2 -0
  112. package/dist/agent-src/rules/user-interaction.md +2 -0
  113. package/dist/agent-src/rules/user-interrupt-priority.md +2 -0
  114. package/dist/agent-src/skills/adversarial-review/SKILL.md +1 -1
  115. package/dist/agent-src/skills/analysis-autonomous-mode/SKILL.md +1 -1
  116. package/dist/agent-src/skills/performance-analysis/SKILL.md +1 -1
  117. package/dist/agent-src/skills/persona-improvement/SKILL.md +1 -1
  118. package/dist/agent-src/skills/project-analyzer/SKILL.md +1 -1
  119. package/dist/agent-src/skills/security-audit/SKILL.md +1 -1
  120. package/dist/agent-src/skills/sequential-thinking/SKILL.md +1 -1
  121. package/dist/agent-src/skills/skill-improvement-pipeline/SKILL.md +1 -1
  122. package/dist/agent-src/skills/universal-project-analysis/SKILL.md +1 -1
  123. package/dist/agent-src/templates/AGENTS.md +6 -10
  124. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  125. package/dist/cli/registry.js +9 -0
  126. package/dist/cli/registry.js.map +1 -1
  127. package/dist/cli-delegate/chunk-22GCIJ2O.js +270 -0
  128. package/dist/cli-delegate/{chunk-BSLUHV2F.js → chunk-2BE5I2MN.js} +156 -141
  129. package/dist/cli-delegate/{chunk-AIIUU73N.js → chunk-7OMQCH4V.js} +1 -1
  130. package/dist/cli-delegate/{chunk-HJ6DAJHR.js → chunk-GB2FSUEE.js} +57 -7
  131. package/dist/cli-delegate/{chunk-WR6GTRHJ.js → chunk-JXARCGVN.js} +3 -3
  132. package/dist/cli-delegate/{chunk-SJQMJI7M.js → chunk-PQSAQKHW.js} +467 -305
  133. package/dist/cli-delegate/chunk-REKCMYOU.js +2991 -0
  134. package/dist/cli-delegate/cmd_analyze_session.js +1 -1
  135. package/dist/cli-delegate/cmd_brand_status.js +134 -0
  136. package/dist/cli-delegate/cmd_conformance.js +7 -7
  137. package/dist/cli-delegate/cmd_converge.js +3 -3
  138. package/dist/cli-delegate/cmd_doctor.js +4 -4
  139. package/dist/cli-delegate/cmd_explain.js +80 -330
  140. package/dist/cli-delegate/cmd_export.js +5 -5
  141. package/dist/cli-delegate/cmd_fleet.js +3 -3
  142. package/dist/cli-delegate/cmd_handoff.js +2 -2
  143. package/dist/cli-delegate/cmd_mcp_available.js +433 -0
  144. package/dist/cli-delegate/cmd_migrate.js +3 -3
  145. package/dist/cli-delegate/cmd_packs_active.js +153 -0
  146. package/dist/cli-delegate/cmd_preflight.js +1 -1
  147. package/dist/cli-delegate/cmd_prune.js +5 -5
  148. package/dist/cli-delegate/cmd_route_audit.js +8 -8
  149. package/dist/cli-delegate/cmd_route_explain.js +4 -4
  150. package/dist/cli-delegate/cmd_settings_get.js +272 -0
  151. package/dist/cli-delegate/cmd_settings_migrate.js +5 -5
  152. package/dist/cli-delegate/cmd_settings_set.js +7 -2967
  153. package/dist/cli-delegate/cmd_sync.js +5 -5
  154. package/dist/cli-delegate/cmd_uninstall.js +5 -5
  155. package/dist/cli-delegate/cmd_update.js +4 -4
  156. package/dist/cli-delegate/cmd_upgrade.js +4 -4
  157. package/dist/cli-delegate/cmd_validate.js +5 -5
  158. package/dist/cli-delegate/cmd_versions.js +4 -4
  159. package/dist/discovery/deprecation-report.md +1 -1
  160. package/dist/discovery/discovery-manifest.json +184 -124
  161. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  162. package/dist/discovery/discovery-manifest.summary.md +3 -3
  163. package/dist/discovery/orphan-report.md +1 -1
  164. package/dist/discovery/packs.json +7 -5
  165. package/dist/discovery/trust-report.md +11 -11
  166. package/dist/discovery/workspaces.json +6 -4
  167. package/dist/hooks/dispatch.js +9080 -5701
  168. package/dist/install/install.mjs +698 -508
  169. package/dist/mcp/registry-manifest.json +3 -3
  170. package/dist/mcp/server.json +2 -2
  171. package/dist/mcp/server.mjs +698 -437
  172. package/dist/router.json +1 -1
  173. package/dist/scripts/_lib/agent_settings.js +80 -15
  174. package/dist/scripts/_lib/agent_settings.js.map +1 -1
  175. package/dist/scripts/condense.js +33 -2
  176. package/dist/scripts/condense.js.map +1 -1
  177. package/dist/server/io/yamlIO.js +85 -0
  178. package/dist/server/io/yamlIO.js.map +1 -1
  179. package/dist/server/routes/settings.js +1 -12
  180. package/dist/server/routes/settings.js.map +1 -1
  181. package/dist/server/routes/wizard.js +52 -2
  182. package/dist/server/routes/wizard.js.map +1 -1
  183. package/dist/shared/settingsCarveOut.js +118 -0
  184. package/dist/shared/settingsCarveOut.js.map +1 -0
  185. package/dist/shared/settingsSurface.js +7 -1
  186. package/dist/shared/settingsSurface.js.map +1 -1
  187. package/dist/ui/assets/index-DmR0mbk4.js.map +1 -1
  188. package/docs/contracts/settings-classes.md +29 -8
  189. package/docs/guidelines/agent-infra/memory-access.md +14 -3
  190. package/package.json +1 -1
  191. package/src/agent-src/templates/AGENTS.md +6 -10
  192. package/src/config/budgets.yml +29 -0
  193. package/src/config/evaluator-budgets.json +4 -4
  194. package/src/config/gate-coverage.yml +26 -0
  195. package/src/scripts/_cli/cmd_brand_status.ts +174 -0
  196. package/src/scripts/_cli/cmd_doctor.ts +6 -21
  197. package/src/scripts/_cli/cmd_mcp_available.ts +266 -0
  198. package/src/scripts/_cli/cmd_packs_active.ts +225 -0
  199. package/src/scripts/_cli/cmd_settings_get.ts +324 -0
  200. package/src/scripts/_dispatch.bash +75 -0
  201. package/src/scripts/_lib/agent_settings.ts +82 -16
  202. package/src/scripts/_lib/carrier_divergence.ts +135 -0
  203. package/src/scripts/_lib/git_common_dir.ts +128 -0
  204. package/src/scripts/_lib/host_capability.ts +84 -1
  205. package/src/scripts/_lib/obligation_frequency.ts +391 -0
  206. package/src/scripts/_lib/prompt_shape.ts +88 -0
  207. package/src/scripts/_lib/review_skipped_record.ts +102 -0
  208. package/src/scripts/_lib/rule_layer_overlap.ts +254 -0
  209. package/src/scripts/_lib/self_repair.ts +342 -0
  210. package/src/scripts/_lib/self_repair_store.ts +100 -0
  211. package/src/scripts/_lib/session_register.ts +361 -0
  212. package/src/scripts/_lib/transcript_entry.ts +80 -0
  213. package/src/scripts/check_branch_freshness.ts +255 -0
  214. package/src/scripts/check_council_config_location.ts +125 -1
  215. package/src/scripts/check_enforcement_coverage.ts +192 -7
  216. package/src/scripts/check_rule_projection_integrity.ts +364 -0
  217. package/src/scripts/check_standing_rule_delivery.ts +314 -0
  218. package/src/scripts/condense.ts +36 -2
  219. package/src/scripts/config/profiles.ts +13 -3
  220. package/src/scripts/conformance_scan.ts +307 -24
  221. package/src/scripts/council_availability_hook.ts +188 -0
  222. package/src/scripts/council_cli.ts +126 -7
  223. package/src/scripts/generate_settings_reference.ts +232 -0
  224. package/src/scripts/hook_manifest.yaml +155 -33
  225. package/src/scripts/hooks/block_no_verify.ts +95 -3
  226. package/src/scripts/hooks/block_unauthorized_git.ts +217 -2
  227. package/src/scripts/hooks/concern_registry.ts +12 -0
  228. package/src/scripts/hooks/delegation_nudge_hook.ts +450 -0
  229. package/src/scripts/hooks/end_review_nudge_hook.ts +726 -0
  230. package/src/scripts/hooks/orchestration_record_hook.ts +342 -0
  231. package/src/scripts/install.ts +151 -0
  232. package/src/scripts/language_mirror_hook.ts +49 -6
  233. package/src/scripts/measure_scope_dedup.ts +19 -13
  234. package/src/scripts/onboarding_gate_hook.ts +40 -1
  235. package/src/scripts/orchestration_backfill.ts +354 -0
  236. package/src/scripts/orchestration_record.ts +4 -1
  237. package/src/scripts/preamble_byte_census.ts +9 -20
  238. package/src/scripts/release.ts +142 -36
  239. package/src/scripts/report_carrier_divergence.ts +364 -0
  240. package/src/scripts/report_skill_activation.ts +257 -0
  241. package/src/scripts/report_skill_obligation_violations.ts +579 -0
  242. package/src/scripts/routing_doctor.ts +77 -6
  243. package/src/scripts/schemas/rule.schema.json +5 -0
  244. package/src/scripts/self_repair_cli.ts +240 -0
  245. package/src/scripts/self_repair_hook.ts +241 -0
  246. package/src/scripts/session_canary_hook.ts +35 -3
  247. package/src/scripts/session_register_hook.ts +288 -0
  248. package/src/scripts/sessions_cli.ts +140 -0
  249. package/src/scripts/validate_frontmatter.ts +68 -0
  250. package/src/server/io/yamlIO.ts +87 -0
  251. package/src/shared/settingsCarveOut.ts +143 -0
  252. package/src/shared/settingsSurface.ts +7 -1
@@ -0,0 +1,135 @@
1
+ /**
2
+ * One definition of "these two carriers deliver the same rule" — shared by the
3
+ * C-3 reachability harness and the advisory divergence report.
4
+ *
5
+ * WHY THIS IS A LIBRARY AND NOT TWO COPIES. Two surfaces need the comparison
6
+ * and they ask different questions of it. `measure_scope_dedup` asks *how many*
7
+ * installed twins a byte-identity dedup could skip, anchored on the projection
8
+ * source. `report_carrier_divergence` asks *which* rules a reader must look at,
9
+ * anchored on the union of both carriers. The directory walks are legitimately
10
+ * different; the comparison is not, and a second copy of it is how "differs only
11
+ * in the ownership stamp" and "differs in body" drift into meaning two things.
12
+ *
13
+ * The comparison itself is the load-bearing part, so it is stated once:
14
+ *
15
+ * 1. bytes equal → `identical`
16
+ * 2. equal after removing the two
17
+ * installer ownership keys → `provenance-only`
18
+ * 3. otherwise → `body-diff`
19
+ *
20
+ * Class 2 exists because `install.ts` stamps `package:` and `source_path:` into
21
+ * every rule it installs while the in-repo projection stamps nothing, so a
22
+ * provenance-only difference survives even a perfect version alignment. Keeping
23
+ * it apart from class 3 is what turns a bare "0 identical twins" into an answer:
24
+ * `provenanceOnly == shared` means aligning versions would buy nothing, and
25
+ * `bodyDiff > 0` means the two carriers are delivering different text — the only
26
+ * class where a reader has to act.
27
+ *
28
+ * NOT A LICENCE TO DEDUP. Classifying a pair `provenance-only` says the bodies
29
+ * match; it does NOT say the rule is safe to drop from one carrier. Byte-identity
30
+ * remains the dedup predicate, deliberately — see
31
+ * `agents/settings/contexts/dedup-reachability-refusal.md`, which records that
32
+ * decision and its five reopen conditions. Nothing here relaxes it.
33
+ */
34
+ import * as fs from 'node:fs';
35
+ import * as path from 'node:path';
36
+
37
+ /**
38
+ * The keys `install.ts` adds to every installed rule (`_set_key(fm_lines,
39
+ * 'package', …)` and `source_path` at install.ts:2733). Removing them is what
40
+ * separates an installer stamp from a content change.
41
+ */
42
+ export const OWNERSHIP_KEYS = ['package', 'source_path'] as const;
43
+
44
+ export type PairVerdict = 'identical' | 'provenance-only' | 'body-diff';
45
+
46
+ /**
47
+ * Drop the ownership stamp lines, wherever they appear in the FILE.
48
+ *
49
+ * Not scoped to the frontmatter, and the docstring says so because the scope is
50
+ * wider than the intent. Two reasons it stays this way: `install.ts` writes the
51
+ * keys without any guarantee about fence position, and `scope_dedup.test.ts`
52
+ * deliberately pins the fence-less shape (a stamp with no `---` around it) as the
53
+ * install condition. Residual risk, stated rather than hidden: a rule body that
54
+ * contains a line literally starting `package:` or `source_path:` — a YAML
55
+ * example, an indent-free config snippet — has that line removed from both sides,
56
+ * so a genuine difference confined to such a line would read `provenance-only`
57
+ * instead of `body-diff`. Measured over the shipped corpus: no rule body carries
58
+ * one. Re-check that before treating the class as impossible rather than absent.
59
+ */
60
+ export function stripOwnershipKeys(text: string): string {
61
+ return text
62
+ .split('\n')
63
+ .filter((line) => !OWNERSHIP_KEYS.some((k) => line.startsWith(`${k}:`)))
64
+ .join('\n');
65
+ }
66
+
67
+ /**
68
+ * Compare one rule's two copies.
69
+ *
70
+ * Takes buffers rather than paths: the callers already hold them, and a pure
71
+ * function is what lets both surfaces pin the same three-way split in tests
72
+ * without a filesystem.
73
+ */
74
+ export function comparePair(a: Buffer, b: Buffer): PairVerdict {
75
+ if (a.equals(b)) {
76
+ return 'identical';
77
+ }
78
+ if (stripOwnershipKeys(a.toString('utf-8')) === stripOwnershipKeys(b.toString('utf-8'))) {
79
+ return 'provenance-only';
80
+ }
81
+ return 'body-diff';
82
+ }
83
+
84
+ export interface RuleDirCensus {
85
+ files: number;
86
+ /**
87
+ * BYTES, not characters — `statSync().size`. The name is kept because every
88
+ * consumer of this figure (`preamble_byte_census`, `measure_scope_dedup`, the
89
+ * conformance scan) already divides it by 4 under that name, and one honest
90
+ * docstring beats a rename that would silently move three published baselines.
91
+ *
92
+ * The gap is measured, not assumed: over the 110-rule project tree, 406 502
93
+ * bytes against 402 823 characters — **0.91 %** overhead from the em-dashes,
94
+ * arrows and status glyphs the prose uses. So the published token estimate
95
+ * reads 101 626 where a character basis gives 100 706. Under 1 % and in the
96
+ * conservative direction for a payload figure, which is why it is documented
97
+ * rather than repaired; re-measure before quoting it as negligible on a corpus
98
+ * with more non-ASCII than this one.
99
+ */
100
+ chars: number;
101
+ }
102
+
103
+ /**
104
+ * How much rule text one carrier delivers.
105
+ *
106
+ * `statSync`, not `lstatSync`, on purpose: a project-scope entry is a symlink
107
+ * into `dist/`, and the bytes the HOST reads are the target's. Measuring the link
108
+ * would report ~50 bytes per rule and make the whole payload vanish.
109
+ *
110
+ * Moved here from `preamble_byte_census` when `conformance_scan` needed it:
111
+ * that module pulls `yaml` and the cold-start report, and importing a dependency
112
+ * chain into a bundled CLI path to reach fifteen lines of `statSync` is the wrong
113
+ * trade. `preamble_byte_census` re-exports this so its own callers are unchanged
114
+ * and there is still exactly one definition.
115
+ */
116
+ export function censusRuleDir(dir: string): RuleDirCensus {
117
+ let files = 0;
118
+ let chars = 0;
119
+ let entries: string[];
120
+ try {
121
+ entries = fs.readdirSync(dir);
122
+ } catch {
123
+ return { files: 0, chars: 0 };
124
+ }
125
+ for (const name of entries) {
126
+ if (!name.endsWith('.md')) continue;
127
+ try {
128
+ chars += fs.statSync(path.join(dir, name)).size;
129
+ files += 1;
130
+ } catch {
131
+ // Unreadable entry — skip rather than fail the whole census.
132
+ }
133
+ }
134
+ return { files, chars };
135
+ }
@@ -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
+ }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Host-capability manifest normalizer.
2
+ * Host-capability manifest normalizer + committed capability registry.
3
3
  *
4
4
  * Pure, no-I/O helper that takes a partial / unknown object (typically the
5
5
  * `subagents.host_capabilities` override from `.agent-settings.yml`, or the
@@ -11,6 +11,18 @@
11
11
  * have no subagent primitive.
12
12
  * - A missing or non-boolean field resolves to `false`, never `true`.
13
13
  * - `schema_version` is always forced to `1`.
14
+ *
15
+ * `resolveHostCapabilities` (F5, road-to-orchestrator-discipline-carriers)
16
+ * adds a resolution order in front of the safe default so a fresh install on
17
+ * a KNOWN host does not ship delegation dead by default:
18
+ *
19
+ * explicit `subagents.host_capabilities` override (wins, whole-object,
20
+ * unchanged semantics) → committed registry row for the detected host
21
+ * (`HOST_CAPABILITY_REGISTRY`) → `SAFE_DEFAULT` (all false).
22
+ *
23
+ * The registry lists only OBSERVED capabilities for a host this repo has
24
+ * actually measured (see the roadmap's transcript evidence) — never a
25
+ * speculative entry for a host nobody has verified.
14
26
  */
15
27
 
16
28
  /** Resolved host-capability manifest. All booleans, `schema_version` fixed at 1. */
@@ -57,3 +69,74 @@ export function normalizeHostManifest(input: unknown): HostCapabilityManifest {
57
69
  separate_quota_pool: asBool(src.separate_quota_pool),
58
70
  };
59
71
  }
72
+
73
+ /**
74
+ * Committed registry — known host identifiers → OBSERVED capability sets.
75
+ *
76
+ * Keyed by the platform identifier this repo already uses for the same host
77
+ * across the hook/dispatch layer (`--platform claude`, `platforms['claude']`
78
+ * in `claude_settings_hooks.ts`, `TOOL_IDS` in `probe_skill_registration.ts`)
79
+ * — NOT the separate `toolDetection.ts` binary-detection id (`'claude-code'`),
80
+ * which names a different namespace (installed-CLI presence, not the running
81
+ * host identity a hook/dispatch envelope carries).
82
+ *
83
+ * Each row is partial: an omitted field stays at the `SAFE_DEFAULT` `false`
84
+ * when the row is applied. Claude Code is the one host this roadmap's
85
+ * transcript evidence measured spawning and running subagents concurrently
86
+ * (`Agent` tool dispatch, parallel tool-use blocks); polling and a separate
87
+ * quota pool were never observed, so they stay `false` — add a field only
88
+ * once it is itself observed, never by inference.
89
+ */
90
+ const HOST_CAPABILITY_REGISTRY: Readonly<Record<string, Partial<HostCapabilityManifest>>> = {
91
+ claude: { subagent_spawn: true, parallel_spawn: true },
92
+ };
93
+
94
+ /**
95
+ * Resolve the effective host-capability manifest for `hostId`.
96
+ *
97
+ * Resolution order (highest wins), per
98
+ * `host-capability-manifest.md § Resolution`:
99
+ *
100
+ * 1. `override` — the `subagents.host_capabilities` settings value, when
101
+ * present as a non-null object. Normalized exactly as {@link normalizeHostManifest}
102
+ * always has: a WHOLE-OBJECT override, each field independently coerced,
103
+ * a field absent from the override object resolves to `false`. This is
104
+ * the pre-existing, unchanged behavior — a present override still wins
105
+ * outright over the registry row, it does not merge field-by-field with it.
106
+ * 2. The committed {@link HOST_CAPABILITY_REGISTRY} row for `hostId`, when no
107
+ * override is present and the host is known.
108
+ * 3. `SAFE_DEFAULT` — unknown `hostId`, or no override and no registry row.
109
+ *
110
+ * `hostId` is a caller-supplied identifier (e.g. the same string a hook
111
+ * envelope's `platform` field carries) — this function does no environment
112
+ * probing of its own; see `host-capability-manifest.md` for why resolution
113
+ * stays a per-session, agent/caller-supplied fact rather than a live probe.
114
+ *
115
+ * An array override does NOT count as present (F8, review): `typeof [] ===
116
+ * 'object'` in JS, so a bare `typeof override === 'object'` check let a
117
+ * stray array reach `normalizeHostManifest`, which reads named properties
118
+ * off it (`.subagent_spawn`, …) that an array never has — every field
119
+ * silently coerces to `false`, exactly like an explicit empty-object
120
+ * override, even though nobody supplied one. An array is not a manifest
121
+ * shape at all, so it is treated the same as `null`/non-object: fall
122
+ * through to the registry row.
123
+ */
124
+ export function resolveHostCapabilities(
125
+ hostId: string | null | undefined,
126
+ override?: unknown,
127
+ ): HostCapabilityManifest {
128
+ if (
129
+ override !== undefined &&
130
+ override !== null &&
131
+ typeof override === 'object' &&
132
+ !Array.isArray(override)
133
+ ) {
134
+ return normalizeHostManifest(override);
135
+ }
136
+ const row =
137
+ hostId !== null && hostId !== undefined ? HOST_CAPABILITY_REGISTRY[hostId] : undefined;
138
+ if (row === undefined) {
139
+ return { ...SAFE_DEFAULT };
140
+ }
141
+ return { ...SAFE_DEFAULT, ...row, schema_version: 1 };
142
+ }