@event4u/agent-config 16.2.0 โ†’ 16.3.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 (332) hide show
  1. package/.augment-plugin/marketplace.json +3 -3
  2. package/.augment-plugin/plugin.json +1 -1
  3. package/.claude-plugin/marketplace.json +1 -1
  4. package/CHANGELOG.md +89 -237
  5. package/README.md +14 -12
  6. package/dist/agent-src/commands/analyze/inbox.md +1 -1
  7. package/dist/agent-src/commands/analyze/roadmap-repos.md +1 -1
  8. package/dist/agent-src/commands/bug/fix.md +17 -2
  9. package/dist/agent-src/commands/feature/plan.md +1 -1
  10. package/dist/agent-src/commands/feature/roadmap.md +1 -1
  11. package/dist/agent-src/commands/fix/ci.md +1 -1
  12. package/dist/agent-src/commands/implement-ticket.md +1 -1
  13. package/dist/agent-src/commands/jira-ticket.md +1 -1
  14. package/dist/agent-src/commands/roadmap/create.md +1 -1
  15. package/dist/agent-src/commands/roadmap/materialize.md +1 -1
  16. package/dist/agent-src/commands/roadmap/process-full.md +4 -4
  17. package/dist/agent-src/commands/roadmap/resolve-blockers.md +361 -0
  18. package/dist/agent-src/commands/roadmap/triage-parked.md +325 -0
  19. package/dist/agent-src/commands/roadmap.md +18 -4
  20. package/dist/agent-src/commands/tests/create.md +17 -3
  21. package/dist/agent-src/commands/tests/execute.md +22 -5
  22. package/dist/agent-src/contexts/authority/decision-revisit-gate-enforcement.md +43 -0
  23. package/dist/agent-src/contexts/communication/rules-auto/design-fidelity-enforcement.md +24 -0
  24. package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +6 -6
  25. package/dist/agent-src/contexts/execution/roadmap-process-loop.md +32 -2
  26. package/dist/agent-src/contexts/execution/session-canary-enforcement-history.md +67 -0
  27. package/dist/agent-src/contexts/execution/toolchain-resolver.md +101 -7
  28. package/dist/agent-src/rules/active-remediation.md +1 -1
  29. package/dist/agent-src/rules/autonomous-execution.md +1 -0
  30. package/dist/agent-src/rules/broken-access-control.md +1 -0
  31. package/dist/agent-src/rules/command-suggestion-policy.md +1 -0
  32. package/dist/agent-src/rules/context-hygiene.md +1 -1
  33. package/dist/agent-src/rules/decision-revisit-gate.md +16 -33
  34. package/dist/agent-src/rules/design-fidelity.md +13 -16
  35. package/dist/agent-src/rules/doc-screenshot-hygiene.md +1 -0
  36. package/dist/agent-src/rules/domain-safety-disclaimer.md +1 -0
  37. package/dist/agent-src/rules/domain-safety-pii.md +1 -0
  38. package/dist/agent-src/rules/domain-safety-retention.md +1 -0
  39. package/dist/agent-src/rules/engineering-safety-floor.md +1 -0
  40. package/dist/agent-src/rules/evaluator-independence.md +3 -2
  41. package/dist/agent-src/rules/fast-path-marker-visibility.md +1 -0
  42. package/dist/agent-src/rules/finance-safety-floor.md +1 -0
  43. package/dist/agent-src/rules/git-history-discipline.md +1 -0
  44. package/dist/agent-src/rules/image-likeness-and-rights.md +1 -0
  45. package/dist/agent-src/rules/legal-safety-floor.md +3 -2
  46. package/dist/agent-src/rules/lethal-trifecta-guard.md +1 -0
  47. package/dist/agent-src/rules/low-impact-corpus-privacy-floor.md +1 -0
  48. package/dist/agent-src/rules/media-governance-routing.md +14 -17
  49. package/dist/agent-src/rules/persona-governance.md +2 -2
  50. package/dist/agent-src/rules/provider-lifecycle-discipline.md +1 -1
  51. package/dist/agent-src/rules/question-not-instruction.md +1 -0
  52. package/dist/agent-src/rules/roadmap-progress-sync.md +1 -1
  53. package/dist/agent-src/rules/runtime-safety.md +1 -0
  54. package/dist/agent-src/rules/secret-vcs-guard.md +1 -0
  55. package/dist/agent-src/rules/security-sensitive-stop.md +3 -2
  56. package/dist/agent-src/rules/session-canary.md +8 -62
  57. package/dist/agent-src/rules/settings-ask-protocol.md +1 -0
  58. package/dist/agent-src/rules/source-confidentiality.md +1 -0
  59. package/dist/agent-src/rules/strategy-safety-floor.md +1 -0
  60. package/dist/agent-src/rules/tool-safety.md +1 -0
  61. package/dist/agent-src/rules/untrusted-input-defense.md +1 -0
  62. package/dist/agent-src/rules/user-interrupt-priority.md +1 -0
  63. package/dist/agent-src/scripts/archive_completed_roadmaps.ts +115 -20
  64. package/dist/agent-src/scripts/deferral_carry.ts +339 -0
  65. package/dist/agent-src/scripts/update_roadmap_progress.ts +4 -1
  66. package/dist/agent-src/skills/api-design/data/manifest.json +2 -2
  67. package/dist/agent-src/skills/api-testing/SKILL.md +17 -0
  68. package/dist/agent-src/skills/code-intelligence/SKILL.md +14 -2
  69. package/dist/agent-src/skills/design-system-capture/references/import-procedure.md +1 -1
  70. package/dist/agent-src/skills/existing-ui-audit/SKILL.md +2 -2
  71. package/dist/agent-src/skills/fe-design/SKILL.md +1 -1
  72. package/dist/agent-src/skills/iconography/SKILL.md +1 -1
  73. package/dist/agent-src/skills/js-library-packaging/SKILL.md +2 -2
  74. package/dist/agent-src/skills/monorepo-workspace/SKILL.md +1 -1
  75. package/dist/agent-src/skills/quality-tools/SKILL.md +11 -1
  76. package/dist/agent-src/skills/quality-tools/references/go-tools.md +76 -0
  77. package/dist/agent-src/skills/quality-tools/references/python-tools.md +104 -0
  78. package/dist/agent-src/skills/review-routing/SKILL.md +2 -2
  79. package/dist/agent-src/skills/skill-improvement-pipeline/SKILL.md +1 -1
  80. package/dist/agent-src/skills/storybook-workshop/SKILL.md +3 -3
  81. package/dist/agent-src/skills/tailwind-engineer/SKILL.md +1 -1
  82. package/dist/agent-src/skills/test-case-discovery/SKILL.md +7 -0
  83. package/dist/agent-src/skills/test-driven-development/SKILL.md +17 -0
  84. package/dist/agent-src/skills/test-performance/SKILL.md +19 -1
  85. package/dist/agent-src/skills/ui-component-architect/SKILL.md +2 -2
  86. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  87. package/dist/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +48 -6
  88. package/dist/agent-src/templates/scripts/work_engine/stack/runner.ts +774 -109
  89. package/dist/cli/registry.js +1 -1
  90. package/dist/cli-delegate/archive_completed_roadmaps.js +92 -12
  91. package/dist/cli-delegate/check_update_banner.js +12 -1
  92. package/dist/cli-delegate/chunk-523HCFYO.js +90 -0
  93. package/dist/cli-delegate/{chunk-M2DPGFZ4.js โ†’ chunk-6V6TA25H.js} +22 -152
  94. package/dist/cli-delegate/chunk-7IQE3D5S.js +191 -0
  95. package/dist/cli-delegate/{chunk-6KFEC3G7.js โ†’ chunk-CQWX336L.js} +1 -1
  96. package/dist/cli-delegate/chunk-DEIV7XP7.js +108 -0
  97. package/dist/cli-delegate/chunk-EEHDXSYD.js +2040 -0
  98. package/dist/cli-delegate/{chunk-P6ZHP5W2.js โ†’ chunk-GKE7ZVFA.js} +4 -4
  99. package/dist/cli-delegate/chunk-JQOM7TTM.js +1201 -0
  100. package/dist/cli-delegate/{chunk-IGD6ERX6.js โ†’ chunk-JQVYKIZA.js} +10 -0
  101. package/dist/cli-delegate/{chunk-FC75PGJE.js โ†’ chunk-JY2I2DXV.js} +12 -1
  102. package/dist/cli-delegate/chunk-KB5FZ655.js +208 -0
  103. package/dist/cli-delegate/{chunk-7RQP4UDH.js โ†’ chunk-KR6EM23A.js} +235 -1455
  104. package/dist/cli-delegate/{chunk-LTYVIY76.js โ†’ chunk-LIYTLH6O.js} +1 -1
  105. package/dist/cli-delegate/{chunk-YJ72UIAK.js โ†’ chunk-NQ7WT43P.js} +1186 -244
  106. package/dist/cli-delegate/chunk-NXEILF2K.js +2160 -0
  107. package/dist/cli-delegate/{chunk-5R7HOLAF.js โ†’ chunk-NYX3L2PZ.js} +1 -1
  108. package/dist/cli-delegate/{chunk-4E2W63DA.js โ†’ chunk-OKJUIAPS.js} +62 -1
  109. package/dist/cli-delegate/{chunk-QAJS5YWY.js โ†’ chunk-PHD7OOLE.js} +2 -1
  110. package/dist/cli-delegate/{chunk-RGNIWTEA.js โ†’ chunk-QLDHIPWR.js} +1 -1
  111. package/dist/cli-delegate/{chunk-7XGBX6ZH.js โ†’ chunk-RQNNW7IF.js} +1314 -560
  112. package/dist/cli-delegate/{chunk-4XFP2Y3E.js โ†’ chunk-S4UMZNES.js} +1 -1
  113. package/dist/cli-delegate/{chunk-6KM6ORRY.js โ†’ chunk-S4YWNRKO.js} +24 -8
  114. package/dist/cli-delegate/{chunk-FP36SWJD.js โ†’ chunk-TRI6DQZ6.js} +2781 -340
  115. package/dist/cli-delegate/chunk-ULQRUR23.js +208 -0
  116. package/dist/cli-delegate/{chunk-NUHLI2RX.js โ†’ chunk-YCU3TIXH.js} +2 -0
  117. package/dist/cli-delegate/chunk-ZBZV2G5R.js +210 -0
  118. package/dist/cli-delegate/{chunk-WGXTVKCQ.js โ†’ chunk-ZUZKZYCN.js} +5 -3
  119. package/dist/cli-delegate/cmd_analyze_session.js +1 -1
  120. package/dist/cli-delegate/cmd_brand_status.js +1 -1
  121. package/dist/cli-delegate/cmd_conformance.js +1167 -234
  122. package/dist/cli-delegate/cmd_doctor.js +13 -6
  123. package/dist/cli-delegate/cmd_doctor_neighbours.js +23 -0
  124. package/dist/cli-delegate/cmd_explain.js +5 -5
  125. package/dist/cli-delegate/cmd_export.js +11 -8
  126. package/dist/cli-delegate/cmd_fleet.js +3 -3
  127. package/dist/cli-delegate/cmd_handoff.js +10 -7
  128. package/dist/cli-delegate/cmd_mcp_available.js +1 -1
  129. package/dist/cli-delegate/cmd_memory_get.js +3 -1
  130. package/dist/cli-delegate/cmd_migrate.js +2 -2
  131. package/dist/cli-delegate/cmd_packs_active.js +3 -3
  132. package/dist/cli-delegate/cmd_preflight.js +6 -6
  133. package/dist/cli-delegate/cmd_prune.js +11 -8
  134. package/dist/cli-delegate/cmd_refresh.js +2 -1
  135. package/dist/cli-delegate/cmd_route_audit.js +8 -6
  136. package/dist/cli-delegate/cmd_route_explain.js +4 -4
  137. package/dist/cli-delegate/cmd_session_recycle.js +4 -3
  138. package/dist/cli-delegate/cmd_settings_get.js +1 -1
  139. package/dist/cli-delegate/cmd_settings_migrate.js +11 -8
  140. package/dist/cli-delegate/cmd_settings_set.js +4 -4
  141. package/dist/cli-delegate/cmd_sync.js +11 -8
  142. package/dist/cli-delegate/cmd_uninstall.js +11 -8
  143. package/dist/cli-delegate/cmd_update.js +1 -1
  144. package/dist/cli-delegate/cmd_upgrade.js +3 -2
  145. package/dist/cli-delegate/cmd_validate.js +11 -8
  146. package/dist/cli-delegate/cmd_versions.js +1 -1
  147. package/dist/cli-delegate/deferral_carry.js +16 -0
  148. package/dist/cli-delegate/gate_execute.js +2 -2
  149. package/dist/cli-delegate/roadmap_gates.js +5 -5
  150. package/dist/cli-delegate/update_roadmap_progress.js +1 -1
  151. package/dist/discovery/deprecation-report.md +1 -1
  152. package/dist/discovery/discovery-manifest.json +127 -75
  153. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  154. package/dist/discovery/discovery-manifest.summary.md +3 -3
  155. package/dist/discovery/dormancy-report.md +1 -1
  156. package/dist/discovery/orphan-report.md +1 -1
  157. package/dist/discovery/packs.json +7 -5
  158. package/dist/discovery/trust-report.md +2 -2
  159. package/dist/discovery/workspaces.json +6 -4
  160. package/dist/hooks/dispatch.js +5357 -6656
  161. package/dist/hooks/dispatch.sha256 +1 -1
  162. package/dist/install/claudePathsPlan.js +47 -6
  163. package/dist/install/claudeRuleRewrite.js +7 -0
  164. package/dist/install/emit_host_rules_cli.js +39 -36
  165. package/dist/install/globalRuleScope.js +87 -0
  166. package/dist/install/host_rule_ownership.js +160 -0
  167. package/dist/install/install.mjs +2080 -1024
  168. package/dist/install/installThinLayer.js +261 -0
  169. package/dist/install/installedRuleLinks.js +176 -0
  170. package/dist/install/wizard-plan.js +24 -0
  171. package/dist/mcp/registry-manifest.json +3 -3
  172. package/dist/mcp/server.json +2 -2
  173. package/dist/mcp/server.mjs +31965 -27154
  174. package/dist/scripts/_lib/agent_settings.js +11 -0
  175. package/dist/scripts/_lib/claude_settings_hooks.js +10 -8
  176. package/dist/scripts/_lib/installed_layer.js +378 -0
  177. package/dist/scripts/_lib/lean_projection_mode.js +218 -15
  178. package/dist/scripts/_lib/rule_consequence_class.js +45 -0
  179. package/dist/scripts/_lib/rule_injection.js +67 -10
  180. package/dist/scripts/_lib/rule_law_section.js +122 -0
  181. package/dist/scripts/_lib/thin_rules.js +458 -0
  182. package/dist/scripts/_lib/windsurf_render.js +60 -0
  183. package/dist/scripts/condense.js +14 -26
  184. package/dist/scripts/project_thin_rules.js +14 -294
  185. package/dist/server/schemas/settings.js +6 -0
  186. package/dist/shared/skillRanking.js +74 -13
  187. package/docs/contracts/settings-classes.md +7 -5
  188. package/package.json +1 -1
  189. package/src/agent-src/scripts/archive_completed_roadmaps.ts +115 -20
  190. package/src/agent-src/scripts/deferral_carry.ts +339 -0
  191. package/src/agent-src/scripts/update_roadmap_progress.ts +4 -1
  192. package/src/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +48 -6
  193. package/src/agent-src/templates/scripts/work_engine/stack/runner.ts +774 -109
  194. package/src/config/agent-settings.template.yml +62 -22
  195. package/src/config/continuity-surface.json +7 -0
  196. package/src/config/evaluator-budgets.json +4 -4
  197. package/src/config/gate-coverage.yml +96 -3
  198. package/src/config/gate-violation-baselines.json +17 -9
  199. package/src/config/hook-bundle-budget.json +25 -0
  200. package/src/config/hook-token-budget.json +6 -1
  201. package/src/config/host-instruction-limits.json +33 -0
  202. package/src/config/routing-coverage-seed.json +24 -3
  203. package/src/config/rule-activation-census.json +7 -3
  204. package/src/config/rule-consequence-class.json +161 -0
  205. package/src/config/rule-law-ceilings.json +99 -0
  206. package/src/install/claudePathsPlan.ts +46 -4
  207. package/src/install/claudeRuleRewrite.ts +10 -1
  208. package/src/install/emit_host_rules_cli.ts +43 -34
  209. package/src/install/globalRuleScope.ts +109 -0
  210. package/src/install/host_rule_ownership.ts +170 -0
  211. package/src/install/installThinLayer.ts +312 -0
  212. package/src/install/installedRuleLinks.ts +220 -0
  213. package/src/install/wizard-plan.ts +24 -0
  214. package/src/scripts/_cli/cmd_doctor.ts +23 -71
  215. package/src/scripts/_cli/cmd_doctor_neighbours.ts +293 -0
  216. package/src/scripts/_dispatch.bash +16 -2
  217. package/src/scripts/_lib/agent_settings.ts +11 -0
  218. package/src/scripts/_lib/claude_settings_hooks.ts +10 -8
  219. package/src/scripts/_lib/estate_offsets.ts +241 -0
  220. package/src/scripts/_lib/exemption_shape.ts +143 -0
  221. package/src/scripts/_lib/fnv.ts +23 -0
  222. package/src/scripts/_lib/foreign_scan.ts +106 -0
  223. package/src/scripts/_lib/gitignore_block.ts +314 -0
  224. package/src/scripts/_lib/global_deploy_inventory.ts +32 -0
  225. package/src/scripts/_lib/graph_feeder_record.ts +214 -0
  226. package/src/scripts/_lib/hook_settings.ts +73 -0
  227. package/src/scripts/_lib/host_capability.ts +40 -19
  228. package/src/scripts/_lib/host_hook_merge.ts +165 -0
  229. package/src/scripts/_lib/installed_layer.ts +538 -0
  230. package/src/scripts/_lib/json_merge.ts +82 -0
  231. package/src/scripts/_lib/json_pointers.ts +93 -11
  232. package/src/scripts/_lib/lean_projection_mode.ts +275 -15
  233. package/src/scripts/_lib/machine_wake.ts +16 -3
  234. package/src/scripts/_lib/neighbour_census.ts +760 -0
  235. package/src/scripts/_lib/neighbour_scan.ts +122 -0
  236. package/src/scripts/_lib/neighbour_tool_use.ts +189 -0
  237. package/src/scripts/_lib/prompt_shape.ts +46 -0
  238. package/src/scripts/_lib/release_migration_gate.ts +19 -3
  239. package/src/scripts/_lib/reserved_name_sweep.ts +73 -0
  240. package/src/scripts/_lib/review_coverage.ts +134 -0
  241. package/src/scripts/_lib/rule_consequence_class.ts +71 -0
  242. package/src/scripts/_lib/rule_injection.ts +130 -10
  243. package/src/scripts/_lib/rule_law_section.ts +147 -0
  244. package/src/scripts/_lib/rule_layer_overlap.ts +58 -0
  245. package/src/scripts/_lib/skill_origin.ts +254 -0
  246. package/src/scripts/_lib/structured_ask.ts +64 -21
  247. package/src/scripts/_lib/thin_rules.ts +540 -0
  248. package/src/scripts/_lib/touched_file_quality.ts +434 -0
  249. package/src/scripts/_lib/turn_end_refusals.ts +77 -2
  250. package/src/scripts/_lib/windsurf_render.ts +63 -0
  251. package/src/scripts/ai_council/cli_call_budget.ts +13 -5
  252. package/src/scripts/ai_council/clients.ts +26 -57
  253. package/src/scripts/ai_council/config.ts +1 -1
  254. package/src/scripts/ai_council/vendor_defaults.ts +45 -0
  255. package/src/scripts/audit_skill_overlap.ts +261 -0
  256. package/src/scripts/before_complete_hook.ts +243 -12
  257. package/src/scripts/bench_touched_file_quality.ts +238 -0
  258. package/src/scripts/check_enforcement_matrix.ts +377 -20
  259. package/src/scripts/check_estate_count.ts +130 -153
  260. package/src/scripts/check_finding_dispositions.ts +116 -4
  261. package/src/scripts/check_hook_bundle_composition.ts +370 -0
  262. package/src/scripts/check_kernel_edit_ratified.ts +49 -11
  263. package/src/scripts/check_memory.ts +7 -1
  264. package/src/scripts/check_release_highlights.ts +10 -0
  265. package/src/scripts/check_release_pr_shape.ts +38 -15
  266. package/src/scripts/check_standing_rule_delivery.ts +33 -13
  267. package/src/scripts/check_verify_expectation_delta.ts +521 -0
  268. package/src/scripts/code_graph/detect.ts +127 -7
  269. package/src/scripts/code_graph/foreign.ts +194 -0
  270. package/src/scripts/code_graph/query.ts +217 -4
  271. package/src/scripts/code_graph/types.ts +26 -2
  272. package/src/scripts/code_graph/validate.ts +3 -1
  273. package/src/scripts/code_graph/verbs.ts +103 -0
  274. package/src/scripts/compile_hook_manifest.ts +33 -1
  275. package/src/scripts/condense.ts +14 -27
  276. package/src/scripts/estate_exemption_shape.ts +202 -0
  277. package/src/scripts/generate_host_cost_table.ts +1 -1
  278. package/src/scripts/hook_manifest.json +1 -1
  279. package/src/scripts/hook_manifest.yaml +150 -9
  280. package/src/scripts/hooks/block_config_weakening.ts +67 -2
  281. package/src/scripts/hooks/block_plumbing_writes.ts +29 -1
  282. package/src/scripts/hooks/code_graph_context_hook.ts +252 -23
  283. package/src/scripts/hooks/concern_failure_policy.ts +280 -0
  284. package/src/scripts/hooks/dispatch_hook.ts +52 -52
  285. package/src/scripts/hooks/exit_codes.ts +2 -1
  286. package/src/scripts/hooks/host_lowering.json +1 -1
  287. package/src/scripts/hooks/host_lowering.ts +19 -1
  288. package/src/scripts/hooks/one_question_per_ask_hook.ts +41 -25
  289. package/src/scripts/hooks/payload_stub.ts +10 -1
  290. package/src/scripts/hooks/rule_inject_hook.ts +757 -63
  291. package/src/scripts/hooks/table_fingerprint.ts +21 -0
  292. package/src/scripts/hooks/telemetry_usage_hook.ts +64 -8
  293. package/src/scripts/hooks/turn_end_gate_hook.ts +84 -0
  294. package/src/scripts/hooks_status.ts +112 -0
  295. package/src/scripts/injection_scan_hook.ts +7 -0
  296. package/src/scripts/install.ts +72 -185
  297. package/src/scripts/installed_layer_report.ts +103 -0
  298. package/src/scripts/language_mirror_hook.ts +15 -6
  299. package/src/scripts/learning_sidecar.ts +119 -8
  300. package/src/scripts/lint_hook_manifest.ts +53 -0
  301. package/src/scripts/lint_major_migration_sections.ts +156 -26
  302. package/src/scripts/lint_pack_boundaries.ts +63 -5
  303. package/src/scripts/lint_rule_law_section.ts +517 -0
  304. package/src/scripts/lint_workflow_security.ts +161 -5
  305. package/src/scripts/mcp_server/consumer_tool_catalog.json +53 -0
  306. package/src/scripts/mcp_server/graph_tools.ts +72 -3
  307. package/src/scripts/measure_routing_signal.ts +54 -16
  308. package/src/scripts/measure_skill_ranker_baseline.ts +216 -8
  309. package/src/scripts/memory_learn_hook.ts +91 -6
  310. package/src/scripts/model_rule_injection.ts +52 -5
  311. package/src/scripts/project_thin_rules.ts +34 -327
  312. package/src/scripts/report_evidence_temperature.ts +657 -0
  313. package/src/scripts/report_installed_rule_links.ts +157 -0
  314. package/src/scripts/report_label_agreement.ts +233 -0
  315. package/src/scripts/report_obligation_mechanism.ts +230 -0
  316. package/src/scripts/report_single_token_triggers.ts +277 -0
  317. package/src/scripts/report_skill_ranker_confusion.ts +442 -0
  318. package/src/scripts/report_standing_payload_by_host.ts +188 -15
  319. package/src/scripts/report_verification_record_coverage.ts +306 -0
  320. package/src/scripts/rule_activation_census.ts +5 -2
  321. package/src/scripts/rule_trigger_eval.ts +9 -9
  322. package/src/scripts/schemas/review-findings.schema.json +3 -2
  323. package/src/scripts/schemas/rule.schema.json +5 -0
  324. package/src/scripts/self_review_gate.ts +62 -7
  325. package/src/scripts/skill_tools/score_skill_relevance.ts +290 -25
  326. package/src/scripts/sweep_skill_ranker_signals.ts +129 -0
  327. package/src/scripts/sync_gitignore.ts +29 -235
  328. package/src/scripts/trigger_eval_grandfather.json +0 -5
  329. package/src/server/schemas/settings.ts +10 -0
  330. package/src/shared/skillRanking.ts +117 -11
  331. package/dist/cli-delegate/chunk-JNMVLWLR.js +0 -4457
  332. package/dist/cli-delegate/chunk-YDPILCOL.js +0 -152
@@ -0,0 +1,220 @@
1
+ /**
2
+ * Do the links inside an installed rule file resolve from where the install
3
+ * puts it?
4
+ *
5
+ * A rule body is authored in `src/rules/` and projected to
6
+ * `dist/agent-src/rules/`, where `](../guidelines/x.md)` resolves because the
7
+ * sibling directory is right there. The installer then copies a SUBSET of
8
+ * those sibling directories into the host's rule directory, so the same link
9
+ * resolves or does not depending on a list in `install.ts` that the rule
10
+ * author never sees. Nothing checked the two against each other.
11
+ *
12
+ * This module answers one question per link โ€” resolved, or why not โ€” against a
13
+ * deploy plan rather than against a materialised tree, so the audit costs a
14
+ * `statSync` per distinct target instead of a copy of 299 skills.
15
+ *
16
+ * Side-effect-free, no CLI entry, no `process.exit`.
17
+ */
18
+ import * as fs from 'node:fs';
19
+ import * as path from 'node:path';
20
+
21
+ /** Why a link does not resolve from the installed file's own directory. */
22
+ export type LinkVerdict =
23
+ /** The target file is present in the installed tree. */
24
+ | 'resolved'
25
+ /** The link climbs above the install root โ€” a repo-tree path, never shipped. */
26
+ | 'outside-install-root'
27
+ /** The first segment names a directory the deploy plan does not write. */
28
+ | 'directory-not-deployed'
29
+ /** The directory IS deployed, but the file is not in it. */
30
+ | 'file-missing';
31
+
32
+ export interface LinkAudit {
33
+ /** Rule file the link was written in, as a bare id (no directory, no `.md`). */
34
+ rule: string;
35
+ /** The link target exactly as the markdown carries it, anchor included. */
36
+ target: string;
37
+ /** Install-root-relative path the target resolves to, or null when it escapes. */
38
+ resolved_to: string | null;
39
+ verdict: LinkVerdict;
40
+ }
41
+
42
+ export interface LinkAuditReport {
43
+ audits: LinkAudit[];
44
+ /** Count per verdict, every verdict present even at zero. */
45
+ counts: Record<LinkVerdict, number>;
46
+ /** Unresolved links grouped by the first path segment, highest first. */
47
+ by_directory: { directory: string; count: number; verdict: LinkVerdict }[];
48
+ }
49
+
50
+ /**
51
+ * A deploy plan: installed directory name -> the package directory copied into
52
+ * it. Exactly the shape of one `GLOBAL_DEPLOY_SOURCES` entry, inverted, so a
53
+ * caller can hand this module the real plan rather than a restatement of it.
54
+ */
55
+ export type DeployPlan = Map<string, string>;
56
+
57
+ /**
58
+ * Build a plan from `GLOBAL_DEPLOY_SOURCES`-shaped pairs.
59
+ *
60
+ * The empty destination (`cline` maps rules to the root) is deliberately kept:
61
+ * a rule installed at the root resolves `../x` differently, and silently
62
+ * dropping the entry would make this module answer a question about a layout
63
+ * that is not the one being installed.
64
+ */
65
+ export function deployPlanFrom(pairs: ReadonlyArray<readonly [string, string]>): DeployPlan {
66
+ const plan: DeployPlan = new Map();
67
+ for (const [source, dest] of pairs) plan.set(dest, source);
68
+ return plan;
69
+ }
70
+
71
+ /** Markdown inline links, minus the ones that are not a path into the tree. */
72
+ export function relativeLinkTargets(markdown: string): string[] {
73
+ const out: string[] = [];
74
+ for (const m of markdown.matchAll(/\]\(([^)\s]+)\)/g)) {
75
+ const target = m[1] as string;
76
+ if (/^[a-z][a-z0-9+.-]*:/i.test(target)) continue; // http:, mailto:, etc.
77
+ if (target.startsWith('#')) continue; // same-file anchor
78
+ if (target.startsWith('/')) continue; // absolute โ€” not ours to resolve
79
+ out.push(target);
80
+ }
81
+ return out;
82
+ }
83
+
84
+ /** Strip a trailing `#anchor`; a bare directory link keeps its trailing slash. */
85
+ function withoutAnchor(target: string): string {
86
+ const hash = target.indexOf('#');
87
+ return hash === -1 ? target : target.slice(0, hash);
88
+ }
89
+
90
+ /**
91
+ * Audit one link written in a rule that the install places at
92
+ * `<install-root>/<rulesDest>/<rule>.md`.
93
+ */
94
+ export function auditLink(
95
+ rule: string,
96
+ target: string,
97
+ rulesDest: string,
98
+ plan: DeployPlan,
99
+ packageRoot: string,
100
+ ): LinkAudit {
101
+ const bare = withoutAnchor(target);
102
+ // Resolve the link the way a reader does: from the installed file's own
103
+ // DIRECTORY โ€” `rulesDest` is that directory, not the file โ€” as a POSIX
104
+ // path relative to the install root. A sibling link (`[x](other.md)`, no
105
+ // `../`) therefore stays inside the rules directory, which is where the
106
+ // installer puts its sibling.
107
+ const fromDir = rulesDest === '' ? '.' : rulesDest;
108
+ const resolved = path.posix.normalize(path.posix.join(fromDir, bare));
109
+
110
+ if (resolved.startsWith('..')) {
111
+ return { rule, target, resolved_to: null, verdict: 'outside-install-root' };
112
+ }
113
+
114
+ const segments = resolved.split('/');
115
+ // Which deployed directory does this land in? Normally the first segment
116
+ // names it. When rules install at the install ROOT (`cline` maps them to
117
+ // `''`), there is no such segment: a sibling link like `](scope-control.md)`
118
+ // resolves to `scope-control.md`, whose first segment is a FILE NAME. Read
119
+ // literally that is `directory-not-deployed` for a file the install writes
120
+ // right beside the rule โ€” 275 of cline's 523 links, every one of them fine.
121
+ // The `''` key exists in the plan precisely for this and could never be
122
+ // reached by a first-segment lookup.
123
+ const rootSource = plan.get('');
124
+ // The root entry answers for a BARE filename only. A link with a directory
125
+ // segment the plan does not carry is `directory-not-deployed`, the same as
126
+ // anywhere else โ€” resolving it against the rules source would probe a path
127
+ // the install never writes and report `file-missing`, whose documented
128
+ // meaning ("the directory IS deployed") would then be false.
129
+ const useRoot = rootSource !== undefined && segments.length === 1;
130
+ const source = useRoot ? rootSource : plan.get(segments[0] as string);
131
+ const rest = useRoot ? segments : segments.slice(1);
132
+ if (source === undefined) {
133
+ return { rule, target, resolved_to: resolved, verdict: 'directory-not-deployed' };
134
+ }
135
+
136
+ // An empty `rest` means the link points at the deployed directory itself.
137
+ const onDisk = path.join(packageRoot, source, ...rest);
138
+ const exists = fs.existsSync(onDisk);
139
+ return {
140
+ rule,
141
+ target,
142
+ resolved_to: resolved,
143
+ verdict: exists ? 'resolved' : 'file-missing',
144
+ };
145
+ }
146
+
147
+ const EMPTY_COUNTS = (): Record<LinkVerdict, number> => ({
148
+ resolved: 0,
149
+ 'outside-install-root': 0,
150
+ 'directory-not-deployed': 0,
151
+ 'file-missing': 0,
152
+ });
153
+
154
+ /**
155
+ * Audit every link in every rule the plan installs.
156
+ *
157
+ * `packageRoot` is the package checkout the installer copies FROM; the rule
158
+ * bodies are read from the plan's own rules source, so the audit cannot drift
159
+ * from what is shipped.
160
+ */
161
+ export function auditInstalledRuleLinks(plan: DeployPlan, packageRoot: string): LinkAuditReport {
162
+ const audits: LinkAudit[] = [];
163
+ for (const [dest, source] of plan) {
164
+ if (!source.endsWith('/rules')) continue;
165
+ const dir = path.join(packageRoot, source);
166
+ if (!fs.existsSync(dir)) continue;
167
+ const files = fs
168
+ .readdirSync(dir, { withFileTypes: true })
169
+ .filter((e) => !e.isDirectory() && e.name.endsWith('.md'))
170
+ .map((e) => e.name)
171
+ .sort();
172
+ for (const file of files) {
173
+ const rule = file.replace(/\.md$/, '');
174
+ const body = fs.readFileSync(path.join(dir, file), 'utf8');
175
+ for (const target of relativeLinkTargets(body)) {
176
+ audits.push(auditLink(rule, target, dest, plan, packageRoot));
177
+ }
178
+ }
179
+ }
180
+
181
+ const counts = EMPTY_COUNTS();
182
+ for (const a of audits) counts[a.verdict] += 1;
183
+
184
+ const grouped = new Map<string, { count: number; verdict: LinkVerdict }>();
185
+ for (const a of audits) {
186
+ if (a.verdict === 'resolved') continue;
187
+ const key = a.resolved_to === null ? a.target.replace(/[^/]*$/, '') : (a.resolved_to.split('/')[0] as string);
188
+ const row = grouped.get(key);
189
+ if (row) row.count += 1;
190
+ else grouped.set(key, { count: 1, verdict: a.verdict });
191
+ }
192
+ const by_directory = [...grouped.entries()]
193
+ .map(([directory, v]) => ({ directory, count: v.count, verdict: v.verdict }))
194
+ .sort((a, b) => b.count - a.count || a.directory.localeCompare(b.directory));
195
+
196
+ return { audits, counts, by_directory };
197
+ }
198
+
199
+ /**
200
+ * Standing-text cost of the rewrite option, in characters.
201
+ *
202
+ * Option A of this roadmap's step 1.2: leave the install plan alone and
203
+ * rewrite every link to an absolute package path. Each unresolved link then
204
+ * grows by the absolute prefix minus the `../` it replaces โ€” and it grows
205
+ * INSIDE a rule body, which is the text the host loads as instructions.
206
+ *
207
+ * `prefixChars` is the installed package root's own length, which is a
208
+ * property of the machine, so the caller supplies it and the report says which
209
+ * value it used.
210
+ */
211
+ export function rewriteOptionCost(report: LinkAuditReport, prefixChars: number): number {
212
+ const unresolved = report.audits.filter((a) => a.verdict !== 'resolved');
213
+ let total = 0;
214
+ for (const a of unresolved) {
215
+ // `../x` and `../../x` both become `<abs>/x`: the climb is replaced.
216
+ const climb = (a.target.match(/\.\.\//g) ?? []).length * 3;
217
+ total += prefixChars + 1 - climb;
218
+ }
219
+ return total;
220
+ }
@@ -64,6 +64,30 @@ export const USER_SCOPE_PATHS: Readonly<Record<string, string>> = {
64
64
  /**
65
65
  * Universal Anthropic-shaped skill bundle deployed to markdown-skills
66
66
  * tools. Mirrors `_CLAUDE_SKILL_BUNDLE` in the retired Python installer (ADR-200).
67
+ *
68
+ * NOT deploying `contexts/` and `guidelines/` here is a held decision, not an
69
+ * oversight โ€” see the `rule-link-targets-change-the-frozen-install-abi` blocker
70
+ * on `road-to-rule-triggers-and-links-that-hold`.
71
+ *
72
+ * A rule is authored against the package tree and projected verbatim, so
73
+ * `](../guidelines/x.md)` resolves in `dist/agent-src/rules/` and then does
74
+ * not resolve in the install, because this table does not carry those two
75
+ * directories. Measured by `report_installed_rule_links` on 2026-10-03: 136 of
76
+ * 523 links in an installed Claude rule directory point nowhere, 112 of them
77
+ * into exactly those two. Adding them is the cheaper of the two repairs on the
78
+ * budget that is scarce โ€” 0 standing characters against **5,152** for rewriting
79
+ * those same 112 links to absolute package paths, at a 48-character install
80
+ * prefix (the report prints the prefix it used; the figure scales with it).
81
+ *
82
+ * The totals were 160 of 550 when this was first measured and 158 of 545 on
83
+ * 2026-10-02; what fell since is the OTHER population โ€” repo-tree link forms no
84
+ * install ever held โ€” so the 112 this paragraph is about has not moved.
85
+ *
86
+ * It is held because this table is part of the FROZEN install ABI
87
+ * (`docs/contracts/install-layout.md`), and any change to it owes an
88
+ * `install_layout_version` bump plus a deprecation window: old and new shape
89
+ * side by side for a minor cycle, with in-place migration. That is a release
90
+ * commitment, not an implementation detail, so it is the owner's to make.
67
91
  */
68
92
  const CLAUDE_SKILL_BUNDLE: ReadonlyArray<readonly [string, string]> = [
69
93
  ['dist/agent-src/rules', 'rules'],
@@ -134,6 +134,8 @@ import {
134
134
  } from './detection_report.js';
135
135
  import { review_gate_doctor_signal } from '../ai_team/review_gate.js';
136
136
  import { resolvePackageRoot } from '../_lib/package_root.js';
137
+ import { rglob as _rglob_raw, scanForeign } from '../_lib/foreign_scan.js';
138
+ import { runCensus, summaryLine } from './cmd_doctor_neighbours.js';
137
139
  import {
138
140
  LEGACY_ALL,
139
141
  excludedRuleBasenames,
@@ -617,77 +619,17 @@ function _collect_manifest_entries(
617
619
  return [records, known];
618
620
  }
619
621
 
620
- /**
621
- * Walk every declared deploy root and surface unclaimed files. Only regular
622
- * files under `deploy_roots` count; bookkeeping is on the resolved final path.
623
- */
624
- function _scan_foreign(
625
- project_root: string,
626
- manifest: Dict,
627
- known: Set<string>,
628
- ): string[] {
629
- const roots =
630
- (manifest['deploy_roots'] as unknown[] | undefined) ||
631
- Array.from(installed_tools.DEFAULT_DEPLOY_ROOTS);
632
- const foreign: string[] = [];
633
- const seen = new Set<string>();
634
- for (const root_rel of roots) {
635
- const root = _resolve_path(project_root, String(root_rel));
636
- if (!pathExists(root) || !isDir(root)) {
637
- continue;
638
- }
639
- for (const child of rglob(root)) {
640
- if (!isFile(child)) {
641
- continue;
642
- }
643
- let resolved: string;
644
- try {
645
- resolved = resolvePath(child);
646
- } catch {
647
- resolved = child;
648
- }
649
- if (known.has(resolved) || seen.has(resolved)) {
650
- continue;
651
- }
652
- seen.add(resolved);
653
- foreign.push(child);
654
- }
655
- }
656
- foreign.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
657
- return foreign;
658
- }
659
-
660
- /**
661
- * `Path.rglob("*")` โ€” every descendant path (dirs + files), recursively.
662
- * Order is not relied upon by callers (they sort the matched set), but the
663
- * walk is deterministic (sorted per directory) to keep behaviour stable.
664
- */
665
- function rglob(root: string): string[] {
666
- const out: string[] = [];
667
- const walk = (dir: string): void => {
668
- let entries: fs.Dirent[];
669
- try {
670
- entries = fs.readdirSync(dir, { withFileTypes: true });
671
- } catch {
672
- return;
673
- }
674
- entries.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
675
- for (const ent of entries) {
676
- const full = path.join(dir, ent.name);
677
- out.push(full);
678
- let dirLike = ent.isDirectory();
679
- if (ent.isSymbolicLink()) {
680
- // pathlib rglob follows into symlinked dirs.
681
- dirLike = isDir(full);
682
- }
683
- if (dirLike) {
684
- walk(full);
685
- }
686
- }
687
- };
688
- walk(root);
689
- return out;
690
- }
622
+ const rglob = (root: string): string[] => _rglob_raw(root, isDir, path.join);
623
+ const _scan_foreign = (project_root: string, manifest: Dict, known: Set<string>): string[] =>
624
+ scanForeign(project_root, manifest, known, {
625
+ pathExists,
626
+ isDir,
627
+ isFile,
628
+ resolvePath,
629
+ join: path.join,
630
+ resolveRoot: _resolve_path,
631
+ defaultRoots: installed_tools.DEFAULT_DEPLOY_ROOTS,
632
+ });
691
633
 
692
634
  /**
693
635
  * Split manifest records into missing / modified / tag-drift lists.
@@ -3028,6 +2970,11 @@ function _run_no_manifest(
3028
2970
  _emit_checks_text(checks);
3029
2971
  _emit_detection_text(project_root, checks);
3030
2972
  }
2973
+ // Also on the no-manifest branch: a consumer with no project lockfile
2974
+ // still has neighbours, and a census that appeared only once a lockfile
2975
+ // existed would be missing on exactly the install shape most likely to
2976
+ // have other packages in it.
2977
+ if (opts.check === null && !opts.json) print(summaryLine(runCensus(project_root, _package_root())));
3031
2978
 
3032
2979
  if (opts.check !== null) {
3033
2980
  if (skipped_requested) {
@@ -3452,6 +3399,11 @@ function main(argv: string[] | null = null): number {
3452
3399
  _emit_detection_text(project_root, checks);
3453
3400
  if (opts.check === null) {
3454
3401
  _emit_text(project_root, missing, modified, foreign, tag_drift);
3402
+ // One line, counts only โ€” the `foreign` listing above stays as it
3403
+ // is and nothing is removed from it. That listing walks THIS
3404
+ // package's deploy roots; the census walks where the hosts keep
3405
+ // things, which is a strictly larger question with its own verb.
3406
+ print(summaryLine(runCensus(project_root, _package_root())));
3455
3407
  }
3456
3408
  }
3457
3409
 
@@ -0,0 +1,293 @@
1
+ /**
2
+ * `agent-config doctor neighbours` โ€” render the neighbour census.
3
+ *
4
+ * A separate entry point rather than another branch inside `cmd_doctor.ts`,
5
+ * which sits 2,000 lines above the source-size ceiling: a verb this size does
6
+ * not belong in a file that already cannot grow. The census itself is
7
+ * `_lib/neighbour_census.ts`; everything here is presentation and the one
8
+ * summary line the full `doctor` run links to.
9
+ *
10
+ * Refuses nothing. No label here removes, blocks, or rewrites a neighbour's
11
+ * artifact โ€” a label is a report, never a gate.
12
+ *
13
+ * It is no longer read-ONLY, and the two writes are named rather than implied.
14
+ * Both land under `agents/reports/` and both are caches of this run's own
15
+ * findings: `neighbour-scan.json`, the shape-scan verdicts the ranker reads on
16
+ * the hook path, and `neighbour-overlap.json`, the cross-root pairs keyed by the
17
+ * census digest. Nothing outside that directory is touched, and a failed write
18
+ * is not a failed census.
19
+ */
20
+
21
+ import * as fs from 'node:fs';
22
+ import * as path from 'node:path';
23
+ import { fileURLToPath, pathToFileURL } from 'node:url';
24
+
25
+ import * as YAML from 'yaml';
26
+
27
+ import {
28
+ census,
29
+ fileDigest,
30
+ gatedEventsFrom,
31
+ scanCachePath,
32
+ type NeighbourCensus,
33
+ } from '../_lib/neighbour_census.js';
34
+ import { scanNeighbourBody, writeScanCache } from '../_lib/neighbour_scan.js';
35
+ import { resolvePackageRoot } from '../_lib/package_root.js';
36
+ import {
37
+ OVERLAP_THRESHOLD,
38
+ CROSS_CACHE_NAME,
39
+ cachedCrossRootPairs,
40
+ crossRootOverlapMap,
41
+ } from '../audit_skill_overlap.js';
42
+
43
+ interface ManifestShape {
44
+ concerns?: Record<string, { effect?: string }>;
45
+ platforms?: Record<string, Record<string, unknown>>;
46
+ native_event_aliases?: Record<string, Record<string, string>>;
47
+ }
48
+
49
+ /** Read the hook manifest for the events a `permission` concern of ours sits on. */
50
+ function gatedEvents(packageRoot: string): Set<string> {
51
+ try {
52
+ // A missing or unreadable manifest yields an empty gate set, which
53
+ // downgrades `degraded` to `coordinated` rather than inventing a gate
54
+ // that is not there.
55
+ const file = path.join(packageRoot, 'src', 'scripts', 'hook_manifest.yaml');
56
+ const doc = YAML.parse(fs.readFileSync(file, 'utf-8')) as ManifestShape;
57
+ return gatedEventsFrom(doc, doc.native_event_aliases ?? {});
58
+ } catch {
59
+ return new Set<string>();
60
+ }
61
+ }
62
+
63
+ /** This package's own skill names, for the `shadowed` warning. */
64
+ function ourSkillNames(packageRoot: string): Set<string> {
65
+ try {
66
+ return new Set(
67
+ fs
68
+ .readdirSync(path.join(packageRoot, 'src', 'skills'), { withFileTypes: true })
69
+ .filter((e) => e.isDirectory())
70
+ .map((e) => e.name),
71
+ );
72
+ } catch {
73
+ return new Set<string>();
74
+ }
75
+ }
76
+
77
+ /**
78
+ * The census, in two passes, and the second one is not an accident.
79
+ *
80
+ * The cross-root overlap can only be computed over the neighbour skills the
81
+ * census FOUND โ€” the unclaimed entries of two roots that also hold claimed ones,
82
+ * since `agent-config install` writes this package's own skills into
83
+ * `~/.claude/skills`. Pairing whole roots instead would compare our installed
84
+ * copies against our own source tree and publish the install as an overlap. So
85
+ * pass one finds them, the overlap runs over exactly those files, and pass two
86
+ * labels them.
87
+ *
88
+ * The shape scan is memoized across both passes, so the four linters run once
89
+ * per neighbour body rather than twice. The scan verdicts are then WRITTEN to
90
+ * the cache the ranker reads on the hook path โ€” this verb is the only producer,
91
+ * which is why its absence reads there as `no-scan-record` rather than as a pass.
92
+ */
93
+ export function runCensus(
94
+ projectRoot: string,
95
+ packageRoot: string,
96
+ homeRoot?: string,
97
+ ): NeighbourCensus {
98
+ const scanned = new Map<string, string | null>();
99
+ const scan = (p: string): string | null => {
100
+ const hit = scanned.get(p);
101
+ if (hit !== undefined) return hit;
102
+ const kind = scanNeighbourBody(p).kind;
103
+ scanned.set(p, kind);
104
+ return kind;
105
+ };
106
+ const base = {
107
+ gatedEvents: gatedEvents(packageRoot),
108
+ templatesRoot: path.join(packageRoot, 'src', 'agent-src', 'templates'),
109
+ ourSkillNames: ourSkillNames(packageRoot),
110
+ packageRoot,
111
+ scan,
112
+ ...(homeRoot === undefined ? {} : { homeRoot }),
113
+ };
114
+ const first = census(projectRoot, base);
115
+ const ourRoot = path.join(packageRoot, 'src', 'skills');
116
+ let overlaps = new Map<string, { names: string[]; similarity: number }>();
117
+ if (first.skills.length > 0 && fs.existsSync(ourRoot)) {
118
+ try {
119
+ const { pairs } = cachedCrossRootPairs(
120
+ first.skills.map((s) => s.source),
121
+ ourRoot,
122
+ path.join(projectRoot, 'agents', 'reports', CROSS_CACHE_NAME),
123
+ OVERLAP_THRESHOLD,
124
+ );
125
+ overlaps = crossRootOverlapMap(pairs);
126
+ } catch {
127
+ // An overlap pass that cannot run leaves every entry unlabelled by
128
+ // it, which reads as `unclassified` โ€” the absence of a signal โ€” and
129
+ // never as a claim that the pair is disjoint.
130
+ }
131
+ }
132
+ const full = census(projectRoot, { ...base, overlaps });
133
+ writeScanCache(
134
+ scanCachePath(projectRoot),
135
+ // `fileDigest`, not the census's own `digest`: the ranker re-digests the
136
+ // file bytes it is about to index, and a cache written under a different
137
+ // digest function would read as `digest-changed` on every prompt.
138
+ full.skills.map((s) => ({
139
+ qualified: s.qualified,
140
+ digest: fileDigest(s.source) ?? '',
141
+ unscanned: s.unscanned,
142
+ })),
143
+ );
144
+ return full;
145
+ }
146
+
147
+ /** The one line the full `doctor` run prints, linking to this verb. */
148
+ export function summaryLine(c: NeighbourCensus): string {
149
+ const counts = [
150
+ `${c.hook_groups.length} hook group(s)`,
151
+ `${c.skills.length} skill(s)`,
152
+ `${c.commands.length} command(s)`,
153
+ `${c.agents.length} agent(s)`,
154
+ `${c.mcp_servers.length} mcp server(s)`,
155
+ `${c.rule_files.length} rule file(s)`,
156
+ `${c.instruction_sections.length} instruction section(s)`,
157
+ ].join(', ');
158
+ const labels = c.hosts.map((h) => `${h.host}=${h.label}`).join(' ');
159
+ return ` ๐Ÿ˜๏ธ neighbours: ${counts}${labels === '' ? '' : ` ยท ${labels}`} (run \`doctor neighbours\` for the census)`;
160
+ }
161
+
162
+ export function renderText(c: NeighbourCensus): string[] {
163
+ const out: string[] = [];
164
+ out.push(` ๐Ÿ“ project_root: ${c.project_root}`);
165
+ out.push('');
166
+ for (const h of c.hosts) {
167
+ out.push(` ${h.host}: ${h.label} โ€” ${h.label_reason}`);
168
+ for (const e of h.hook_groups) {
169
+ const matcher = e.matcher === null ? '' : ` matcher=${e.matcher}`;
170
+ const timeout = e.timeout === null ? '' : ` timeout=${e.timeout}`;
171
+ out.push(` ${e.event}: ${e.command_head}${matcher}${timeout} effect=${e.effect}`);
172
+ }
173
+ }
174
+ const section = (title: string, rows: ReadonlyArray<{ id: string }>): void => {
175
+ if (rows.length === 0) return;
176
+ out.push('');
177
+ out.push(` ${title} (${rows.length})`);
178
+ for (const r of rows) out.push(` ${r.id}`);
179
+ };
180
+ if (c.skills.length > 0) {
181
+ out.push('');
182
+ out.push(` skills (${c.skills.length})`);
183
+ for (const s of c.skills) {
184
+ const also = s.also.length === 0 ? '' : ` also: ${s.also.join(', ')}`;
185
+ const why = s.unscanned === null ? '' : ` unscanned: ${s.unscanned}`;
186
+ out.push(` ${s.qualified} compat: ${s.compat}${also}${why}`);
187
+ }
188
+ }
189
+ section('commands', c.commands);
190
+ section('agents', c.agents);
191
+ if (c.mcp_servers.length > 0) {
192
+ out.push('');
193
+ out.push(` mcp servers (${c.mcp_servers.length})`);
194
+ for (const m of c.mcp_servers) {
195
+ // `used` and `advertised` are printed side by side so the reader
196
+ // never takes the first for a coverage ratio: the denominator is
197
+ // not missing by accident, it is not obtainable without launching
198
+ // the neighbour's process, which this census does not do.
199
+ out.push(
200
+ ` ${m.id} tools used (${m.tools_window_days}d): ${m.tools_used_30d}` +
201
+ ` advertised: ${m.tools_advertised}`,
202
+ );
203
+ }
204
+ }
205
+ section('rule files', c.rule_files);
206
+ section('instruction sections', c.instruction_sections);
207
+ if (c.warnings.length > 0) {
208
+ out.push('');
209
+ for (const w of c.warnings) out.push(` โš ๏ธ ${w.kind}: ${w.detail}`);
210
+ }
211
+ out.push('');
212
+ out.push(
213
+ ' The host owns load order. This census says what is there and what each entry would do;',
214
+ );
215
+ out.push(' it cannot say what ran first, and no label here refuses anything.');
216
+ return out;
217
+ }
218
+
219
+ export function main(argv: readonly string[] = process.argv.slice(2)): number {
220
+ let json = false;
221
+ let root = process.cwd();
222
+ let home: string | undefined;
223
+ for (let i = 0; i < argv.length; i += 1) {
224
+ if (argv[i] === '--json') json = true;
225
+ else if (argv[i] === '--project') {
226
+ root = argv[i + 1] ?? root;
227
+ i += 1;
228
+ } else if (argv[i] === '--home') {
229
+ // Fixture-only: the user-scope half of the census is real `$HOME`
230
+ // in production, and a test must be able to plant one.
231
+ home = argv[i + 1];
232
+ i += 1;
233
+ } else if (argv[i] === 'neighbours') {
234
+ continue;
235
+ } else {
236
+ process.stderr.write(`doctor neighbours: unknown argument: ${argv[i]}\n`);
237
+ return 2;
238
+ }
239
+ }
240
+ const c = runCensus(path.resolve(root), resolvePackageRoot(import.meta.url), home);
241
+ if (json) {
242
+ // Compact, not pretty-printed: this is the machine contract, and the
243
+ // gate that reads it matches on adjacency rather than on indentation.
244
+ process.stdout.write(`${JSON.stringify(c)}\n`);
245
+ } else {
246
+ for (const line of renderText(c)) process.stdout.write(`${line}\n`);
247
+ }
248
+ return 0;
249
+ }
250
+
251
+ /**
252
+ * Same entry guard the sibling CLIs use โ€” including the delegate branch, which
253
+ * an earlier version of this file left out and which the delegate smoke test
254
+ * caught: `no bundle is a silent no-op` reported `cmd_doctor_neighbours.js (no
255
+ * output, exit 0)`, the exact shape the paragraph below describes.
256
+ *
257
+ * Inside the cli-delegate bundle `--splitting` moves this module's body into a
258
+ * shared chunk, so the URL comparison weighs the CHUNK against `argv[1]` and
259
+ * never matches. `agent-config doctor` shipped that way once already โ€” a
260
+ * diagnostic reporting success while saying nothing. The invoked file name is
261
+ * the reliable signal there; a miss falls THROUGH to the realpath comparison
262
+ * rather than returning false, so a symlinked or renamed invocation still
263
+ * resolves. Inlined into the installer / hook / MCP bundle instead, it must
264
+ * never auto-run, which is why one "am I bundled" flag cannot answer "may I
265
+ * run" and both flags are read.
266
+ */
267
+ declare const __AGENT_CONFIG_BUNDLE__: boolean | undefined;
268
+ declare const __AGENT_CONFIG_CLI_DELEGATE__: boolean | undefined;
269
+
270
+ function _isCliEntry(): boolean {
271
+ const bundled = typeof __AGENT_CONFIG_BUNDLE__ !== 'undefined' && __AGENT_CONFIG_BUNDLE__;
272
+ const cliDelegate =
273
+ typeof __AGENT_CONFIG_CLI_DELEGATE__ !== 'undefined' && __AGENT_CONFIG_CLI_DELEGATE__;
274
+ if (bundled && !cliDelegate) return false;
275
+ if (process.argv[1] === undefined) return false;
276
+ if (cliDelegate && path.basename(process.argv[1], '.js') === 'cmd_doctor_neighbours') {
277
+ return true;
278
+ }
279
+ const argvUrl = pathToFileURL(path.resolve(process.argv[1])).href;
280
+ if (import.meta.url === argvUrl) return true;
281
+ try {
282
+ return (
283
+ fs.realpathSync(fileURLToPath(import.meta.url)) ===
284
+ fs.realpathSync(path.resolve(process.argv[1]))
285
+ );
286
+ } catch {
287
+ return false;
288
+ }
289
+ }
290
+
291
+ if (_isCliEntry()) {
292
+ process.exit(main());
293
+ }