@event4u/agent-config 16.1.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 (467) 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 +98 -257
  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 +5 -5
  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/commands/work.md +4 -0
  23. package/dist/agent-src/contexts/authority/decision-revisit-gate-enforcement.md +43 -0
  24. package/dist/agent-src/contexts/communication/rules-auto/design-fidelity-enforcement.md +24 -0
  25. package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +6 -6
  26. package/dist/agent-src/contexts/execution/autonomy-mechanics.md +4 -8
  27. package/dist/agent-src/contexts/execution/roadmap-process-loop.md +32 -2
  28. package/dist/agent-src/contexts/execution/session-canary-enforcement-history.md +67 -0
  29. package/dist/agent-src/contexts/execution/toolchain-resolver.md +101 -7
  30. package/dist/agent-src/rules/active-remediation.md +1 -1
  31. package/dist/agent-src/rules/autonomous-execution.md +1 -0
  32. package/dist/agent-src/rules/broken-access-control.md +1 -0
  33. package/dist/agent-src/rules/command-suggestion-policy.md +1 -0
  34. package/dist/agent-src/rules/context-hygiene.md +1 -1
  35. package/dist/agent-src/rules/decision-revisit-gate.md +16 -33
  36. package/dist/agent-src/rules/design-fidelity.md +13 -16
  37. package/dist/agent-src/rules/doc-screenshot-hygiene.md +1 -0
  38. package/dist/agent-src/rules/domain-safety-disclaimer.md +1 -0
  39. package/dist/agent-src/rules/domain-safety-pii.md +1 -0
  40. package/dist/agent-src/rules/domain-safety-retention.md +1 -0
  41. package/dist/agent-src/rules/engineering-safety-floor.md +1 -0
  42. package/dist/agent-src/rules/evaluator-independence.md +3 -2
  43. package/dist/agent-src/rules/fast-path-marker-visibility.md +1 -0
  44. package/dist/agent-src/rules/finance-safety-floor.md +1 -0
  45. package/dist/agent-src/rules/git-history-discipline.md +1 -0
  46. package/dist/agent-src/rules/image-likeness-and-rights.md +1 -0
  47. package/dist/agent-src/rules/legal-safety-floor.md +3 -2
  48. package/dist/agent-src/rules/lethal-trifecta-guard.md +1 -0
  49. package/dist/agent-src/rules/low-impact-corpus-privacy-floor.md +1 -0
  50. package/dist/agent-src/rules/media-governance-routing.md +14 -17
  51. package/dist/agent-src/rules/persona-governance.md +2 -2
  52. package/dist/agent-src/rules/provider-lifecycle-discipline.md +1 -1
  53. package/dist/agent-src/rules/question-not-instruction.md +1 -0
  54. package/dist/agent-src/rules/roadmap-progress-sync.md +1 -1
  55. package/dist/agent-src/rules/runtime-safety.md +1 -0
  56. package/dist/agent-src/rules/secret-vcs-guard.md +1 -0
  57. package/dist/agent-src/rules/security-sensitive-stop.md +3 -2
  58. package/dist/agent-src/rules/session-canary.md +8 -62
  59. package/dist/agent-src/rules/settings-ask-protocol.md +1 -0
  60. package/dist/agent-src/rules/source-confidentiality.md +1 -0
  61. package/dist/agent-src/rules/strategy-safety-floor.md +1 -0
  62. package/dist/agent-src/rules/tool-safety.md +1 -0
  63. package/dist/agent-src/rules/ui-audit-gate.md +1 -2
  64. package/dist/agent-src/rules/untrusted-input-defense.md +1 -0
  65. package/dist/agent-src/rules/user-interrupt-priority.md +1 -0
  66. package/dist/agent-src/scripts/archive_completed_roadmaps.ts +115 -20
  67. package/dist/agent-src/scripts/deferral_carry.ts +339 -0
  68. package/dist/agent-src/scripts/update_roadmap_progress.ts +4 -1
  69. package/dist/agent-src/skills/accessibility-auditor/data/aria-patterns.csv +1 -1
  70. package/dist/agent-src/skills/accessibility-auditor/data/manifest.json +2 -2
  71. package/dist/agent-src/skills/api-design/data/manifest.json +2 -2
  72. package/dist/agent-src/skills/api-testing/SKILL.md +17 -0
  73. package/dist/agent-src/skills/code-intelligence/SKILL.md +14 -2
  74. package/dist/agent-src/skills/design-review/SKILL.md +30 -4
  75. package/dist/agent-src/skills/design-review/references/verification-automation.md +32 -0
  76. package/dist/agent-src/skills/design-system-capture/references/import-procedure.md +1 -1
  77. package/dist/agent-src/skills/existing-ui-audit/SKILL.md +18 -3
  78. package/dist/agent-src/skills/existing-ui-audit/references/component-taxonomy-detection.md +56 -0
  79. package/dist/agent-src/skills/fe-design/SKILL.md +1 -1
  80. package/dist/agent-src/skills/iconography/SKILL.md +1 -1
  81. package/dist/agent-src/skills/js-library-packaging/SKILL.md +2 -2
  82. package/dist/agent-src/skills/markitdown/SKILL.md +4 -0
  83. package/dist/agent-src/skills/monorepo-workspace/SKILL.md +1 -1
  84. package/dist/agent-src/skills/playwright-testing/SKILL.md +30 -6
  85. package/dist/agent-src/skills/quality-tools/SKILL.md +11 -1
  86. package/dist/agent-src/skills/quality-tools/references/go-tools.md +76 -0
  87. package/dist/agent-src/skills/quality-tools/references/python-tools.md +104 -0
  88. package/dist/agent-src/skills/review-routing/SKILL.md +2 -2
  89. package/dist/agent-src/skills/skill-improvement-pipeline/SKILL.md +1 -1
  90. package/dist/agent-src/skills/storybook-workshop/SKILL.md +3 -3
  91. package/dist/agent-src/skills/tailwind-engineer/SKILL.md +1 -1
  92. package/dist/agent-src/skills/test-case-discovery/SKILL.md +7 -0
  93. package/dist/agent-src/skills/test-driven-development/SKILL.md +17 -0
  94. package/dist/agent-src/skills/test-performance/SKILL.md +19 -1
  95. package/dist/agent-src/skills/ui-component-architect/SKILL.md +25 -2
  96. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  97. package/dist/agent-src/templates/roadmaps.md +30 -0
  98. package/dist/agent-src/templates/scripts/work_engine/directives/ui/app_spec.ts +10 -4
  99. package/dist/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +302 -21
  100. package/dist/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +22 -3
  101. package/dist/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +6 -1
  102. package/dist/agent-src/templates/scripts/work_engine/stack/runner.ts +774 -109
  103. package/dist/agent-src/templates/scripts/work_engine/state.ts +57 -7
  104. package/dist/agent-src/templates/scripts/work_engine/taxonomy/detect.ts +500 -0
  105. package/dist/agent-src/templates/scripts/work_engine/taxonomy/place.ts +183 -0
  106. package/dist/cli/python/knowledge_ingest.js +1 -1
  107. package/dist/cli/python/workspace_drive.js +1 -1
  108. package/dist/cli/registry.js +1 -1
  109. package/dist/cli-delegate/archive_completed_roadmaps.js +92 -12
  110. package/dist/cli-delegate/check_update_banner.js +12 -1
  111. package/dist/cli-delegate/chunk-523HCFYO.js +90 -0
  112. package/dist/cli-delegate/{chunk-M2DPGFZ4.js → chunk-6V6TA25H.js} +22 -152
  113. package/dist/cli-delegate/chunk-7IQE3D5S.js +191 -0
  114. package/dist/cli-delegate/{chunk-6KFEC3G7.js → chunk-CQWX336L.js} +1 -1
  115. package/dist/cli-delegate/chunk-DEIV7XP7.js +108 -0
  116. package/dist/cli-delegate/chunk-EEHDXSYD.js +2040 -0
  117. package/dist/cli-delegate/{chunk-P6ZHP5W2.js → chunk-GKE7ZVFA.js} +4 -4
  118. package/dist/cli-delegate/chunk-JQOM7TTM.js +1201 -0
  119. package/dist/cli-delegate/{chunk-UNCRJBT3.js → chunk-JQVYKIZA.js} +41 -0
  120. package/dist/cli-delegate/{chunk-FC75PGJE.js → chunk-JY2I2DXV.js} +12 -1
  121. package/dist/cli-delegate/chunk-KB5FZ655.js +208 -0
  122. package/dist/cli-delegate/{chunk-YUM5KURG.js → chunk-KR6EM23A.js} +235 -1455
  123. package/dist/cli-delegate/{chunk-LTYVIY76.js → chunk-LIYTLH6O.js} +1 -1
  124. package/dist/cli-delegate/chunk-NQ7WT43P.js +2729 -0
  125. package/dist/cli-delegate/chunk-NXEILF2K.js +2160 -0
  126. package/dist/cli-delegate/{chunk-5R7HOLAF.js → chunk-NYX3L2PZ.js} +1 -1
  127. package/dist/cli-delegate/{chunk-4E2W63DA.js → chunk-OKJUIAPS.js} +62 -1
  128. package/dist/cli-delegate/{chunk-QAJS5YWY.js → chunk-PHD7OOLE.js} +2 -1
  129. package/dist/cli-delegate/{chunk-RGNIWTEA.js → chunk-QLDHIPWR.js} +1 -1
  130. package/dist/cli-delegate/{chunk-HDVVGPQW.js → chunk-RQNNW7IF.js} +1405 -567
  131. package/dist/cli-delegate/{chunk-S3VFXMTM.js → chunk-S4UMZNES.js} +1 -1
  132. package/dist/cli-delegate/{chunk-6KM6ORRY.js → chunk-S4YWNRKO.js} +24 -8
  133. package/dist/cli-delegate/{chunk-GJCBB2UG.js → chunk-TRI6DQZ6.js} +3054 -325
  134. package/dist/cli-delegate/chunk-ULQRUR23.js +208 -0
  135. package/dist/cli-delegate/{chunk-NUHLI2RX.js → chunk-YCU3TIXH.js} +2 -0
  136. package/dist/cli-delegate/chunk-ZBZV2G5R.js +210 -0
  137. package/dist/cli-delegate/{chunk-WGXTVKCQ.js → chunk-ZUZKZYCN.js} +5 -3
  138. package/dist/cli-delegate/cmd_analyze_session.js +1 -1
  139. package/dist/cli-delegate/cmd_brand_status.js +1 -1
  140. package/dist/cli-delegate/cmd_conformance.js +1862 -809
  141. package/dist/cli-delegate/cmd_doctor.js +13 -6
  142. package/dist/cli-delegate/cmd_doctor_neighbours.js +23 -0
  143. package/dist/cli-delegate/cmd_explain.js +5 -5
  144. package/dist/cli-delegate/cmd_export.js +11 -8
  145. package/dist/cli-delegate/cmd_fleet.js +3 -3
  146. package/dist/cli-delegate/cmd_handoff.js +10 -7
  147. package/dist/cli-delegate/cmd_mcp_available.js +1 -1
  148. package/dist/cli-delegate/cmd_memory_get.js +3 -1
  149. package/dist/cli-delegate/cmd_migrate.js +2 -2
  150. package/dist/cli-delegate/cmd_packs_active.js +3 -3
  151. package/dist/cli-delegate/cmd_preflight.js +6 -6
  152. package/dist/cli-delegate/cmd_prune.js +11 -8
  153. package/dist/cli-delegate/cmd_refresh.js +2 -1
  154. package/dist/cli-delegate/cmd_route_audit.js +8 -6
  155. package/dist/cli-delegate/cmd_route_explain.js +4 -4
  156. package/dist/cli-delegate/cmd_session_recycle.js +4 -3
  157. package/dist/cli-delegate/cmd_settings_get.js +1 -1
  158. package/dist/cli-delegate/cmd_settings_migrate.js +11 -8
  159. package/dist/cli-delegate/cmd_settings_set.js +4 -4
  160. package/dist/cli-delegate/cmd_sync.js +11 -8
  161. package/dist/cli-delegate/cmd_uninstall.js +11 -8
  162. package/dist/cli-delegate/cmd_update.js +1 -1
  163. package/dist/cli-delegate/cmd_upgrade.js +3 -2
  164. package/dist/cli-delegate/cmd_validate.js +11 -8
  165. package/dist/cli-delegate/cmd_versions.js +1 -1
  166. package/dist/cli-delegate/deferral_carry.js +16 -0
  167. package/dist/cli-delegate/gate_execute.js +2 -2
  168. package/dist/cli-delegate/roadmap_gates.js +5 -5
  169. package/dist/cli-delegate/update_roadmap_progress.js +1 -1
  170. package/dist/discovery/deprecation-report.md +1 -1
  171. package/dist/discovery/discovery-manifest.json +132 -80
  172. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  173. package/dist/discovery/discovery-manifest.summary.md +3 -3
  174. package/dist/discovery/dormancy-report.md +1 -1
  175. package/dist/discovery/orphan-report.md +1 -1
  176. package/dist/discovery/packs.json +7 -5
  177. package/dist/discovery/trust-report.md +2 -2
  178. package/dist/discovery/workspaces.json +6 -4
  179. package/dist/hooks/dispatch.js +7972 -5186
  180. package/dist/hooks/dispatch.sha256 +1 -0
  181. package/dist/install/claudePathsPlan.js +47 -6
  182. package/dist/install/claudeRuleRewrite.js +7 -0
  183. package/dist/install/emit_host_rules_cli.js +39 -36
  184. package/dist/install/globalRuleScope.js +87 -0
  185. package/dist/install/host_rule_ownership.js +160 -0
  186. package/dist/install/install.mjs +2226 -1055
  187. package/dist/install/installThinLayer.js +261 -0
  188. package/dist/install/installedRuleLinks.js +176 -0
  189. package/dist/install/wizard-plan.js +24 -0
  190. package/dist/mcp/registry-manifest.json +3 -3
  191. package/dist/mcp/server.json +2 -2
  192. package/dist/mcp/server.mjs +40913 -34865
  193. package/dist/scripts/_lib/agent_settings.js +11 -0
  194. package/dist/scripts/_lib/claude_settings_hooks.js +10 -8
  195. package/dist/scripts/_lib/installed_layer.js +378 -0
  196. package/dist/scripts/_lib/lean_projection_mode.js +218 -15
  197. package/dist/scripts/_lib/rule_consequence_class.js +45 -0
  198. package/dist/scripts/_lib/rule_injection.js +67 -10
  199. package/dist/scripts/_lib/rule_law_section.js +122 -0
  200. package/dist/scripts/_lib/thin_rules.js +458 -0
  201. package/dist/scripts/_lib/windsurf_render.js +60 -0
  202. package/dist/scripts/condense.js +14 -26
  203. package/dist/scripts/project_thin_rules.js +14 -294
  204. package/dist/server/schemas/settings.js +29 -0
  205. package/dist/shared/skillRanking.js +74 -13
  206. package/dist/ui/assets/index-Cwq4d0hX.js +23 -0
  207. package/dist/ui/assets/index-Cyjy8bWO.css +1 -0
  208. package/dist/ui/index.html +2 -2
  209. package/docs/contracts/settings-classes.md +10 -6
  210. package/package.json +4 -4
  211. package/src/agent-src/scripts/archive_completed_roadmaps.ts +115 -20
  212. package/src/agent-src/scripts/deferral_carry.ts +339 -0
  213. package/src/agent-src/scripts/update_roadmap_progress.ts +4 -1
  214. package/src/agent-src/templates/scripts/work_engine/directives/ui/app_spec.ts +10 -4
  215. package/src/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +302 -21
  216. package/src/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +22 -3
  217. package/src/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +6 -1
  218. package/src/agent-src/templates/scripts/work_engine/stack/runner.ts +774 -109
  219. package/src/agent-src/templates/scripts/work_engine/state.ts +57 -7
  220. package/src/agent-src/templates/scripts/work_engine/taxonomy/detect.ts +500 -0
  221. package/src/agent-src/templates/scripts/work_engine/taxonomy/place.ts +183 -0
  222. package/src/config/agent-settings.template.yml +91 -22
  223. package/src/config/ci-local-parity.yml +18 -0
  224. package/src/config/continuity-surface.json +9 -2
  225. package/src/config/evaluator-budgets.json +4 -4
  226. package/src/config/gate-coverage.yml +327 -3
  227. package/src/config/gate-violation-baselines.json +21 -9
  228. package/src/config/hook-bundle-budget.json +25 -0
  229. package/src/config/hook-latency-budget.json +21 -0
  230. package/src/config/hook-token-budget.json +8 -3
  231. package/src/config/host-instruction-limits.json +33 -0
  232. package/src/config/metric-registry.yml +114 -0
  233. package/src/config/placeholder-drift-budget.json +10 -0
  234. package/src/config/routing-coverage-seed.json +38 -3
  235. package/src/config/rule-activation-census.json +7 -3
  236. package/src/config/rule-consequence-class.json +161 -0
  237. package/src/config/rule-law-ceilings.json +99 -0
  238. package/src/install/claudePathsPlan.ts +46 -4
  239. package/src/install/claudeRuleRewrite.ts +10 -1
  240. package/src/install/emit_host_rules_cli.ts +43 -34
  241. package/src/install/globalRuleScope.ts +109 -0
  242. package/src/install/host_rule_ownership.ts +170 -0
  243. package/src/install/installThinLayer.ts +312 -0
  244. package/src/install/installedRuleLinks.ts +220 -0
  245. package/src/install/wizard-plan.ts +24 -0
  246. package/src/scripts/_cli/cmd_doctor.ts +28 -90
  247. package/src/scripts/_cli/cmd_doctor_neighbours.ts +293 -0
  248. package/src/scripts/_cli/doctor_execution.ts +100 -17
  249. package/src/scripts/_cli/doctor_network_posture.ts +226 -0
  250. package/src/scripts/_dispatch.bash +16 -2
  251. package/src/scripts/_lib/agent_settings.ts +11 -0
  252. package/src/scripts/_lib/body_portable.ts +102 -0
  253. package/src/scripts/_lib/cc_transcript.ts +132 -1
  254. package/src/scripts/_lib/claude_settings_hooks.ts +10 -8
  255. package/src/scripts/_lib/concern_sla_window.ts +200 -0
  256. package/src/scripts/_lib/estate_offsets.ts +241 -0
  257. package/src/scripts/_lib/exemption_shape.ts +143 -0
  258. package/src/scripts/_lib/fnv.ts +23 -0
  259. package/src/scripts/_lib/foreign_scan.ts +106 -0
  260. package/src/scripts/_lib/forge_protection.ts +114 -0
  261. package/src/scripts/_lib/forge_reader.ts +558 -0
  262. package/src/scripts/_lib/gitignore_block.ts +314 -0
  263. package/src/scripts/_lib/global_deploy_inventory.ts +32 -0
  264. package/src/scripts/_lib/graph_feeder_record.ts +214 -0
  265. package/src/scripts/_lib/hook_settings.ts +73 -0
  266. package/src/scripts/_lib/host_capability.ts +40 -19
  267. package/src/scripts/_lib/host_env_write.ts +111 -0
  268. package/src/scripts/_lib/host_hook_merge.ts +165 -0
  269. package/src/scripts/_lib/installed_layer.ts +538 -0
  270. package/src/scripts/_lib/json_merge.ts +82 -0
  271. package/src/scripts/_lib/json_pointers.ts +93 -11
  272. package/src/scripts/_lib/lean_projection_mode.ts +275 -15
  273. package/src/scripts/_lib/llm_proposer_transport.ts +14 -4
  274. package/src/scripts/_lib/machine_wake.ts +16 -3
  275. package/src/scripts/_lib/neighbour_census.ts +760 -0
  276. package/src/scripts/_lib/neighbour_scan.ts +122 -0
  277. package/src/scripts/_lib/neighbour_tool_use.ts +189 -0
  278. package/src/scripts/_lib/obligations.ts +119 -8
  279. package/src/scripts/_lib/probe_inputs.ts +271 -0
  280. package/src/scripts/_lib/prompt_shape.ts +46 -0
  281. package/src/scripts/_lib/read_surface_scan.ts +322 -0
  282. package/src/scripts/_lib/release_highlights.ts +2 -1
  283. package/src/scripts/_lib/release_migration_gate.ts +19 -3
  284. package/src/scripts/_lib/reserved_name_sweep.ts +73 -0
  285. package/src/scripts/_lib/retrieval_sanitize.ts +77 -16
  286. package/src/scripts/_lib/review_coverage.ts +134 -0
  287. package/src/scripts/_lib/rule_consequence_class.ts +71 -0
  288. package/src/scripts/_lib/rule_injection.ts +130 -10
  289. package/src/scripts/_lib/rule_law_section.ts +147 -0
  290. package/src/scripts/_lib/rule_layer_overlap.ts +58 -0
  291. package/src/scripts/_lib/shell_write_shapes.ts +108 -0
  292. package/src/scripts/_lib/skill_origin.ts +254 -0
  293. package/src/scripts/_lib/structural_hiding.ts +622 -0
  294. package/src/scripts/_lib/structured_ask.ts +64 -21
  295. package/src/scripts/_lib/thin_rules.ts +540 -0
  296. package/src/scripts/_lib/touched_file_quality.ts +434 -0
  297. package/src/scripts/_lib/turn_end_refusals.ts +600 -4
  298. package/src/scripts/_lib/turn_end_transcript.ts +216 -0
  299. package/src/scripts/_lib/verification_command.ts +324 -0
  300. package/src/scripts/_lib/verification_evidence.ts +647 -0
  301. package/src/scripts/_lib/verify_clause.ts +280 -0
  302. package/src/scripts/_lib/windsurf_render.ts +63 -0
  303. package/src/scripts/ai_council/cli_call_budget.ts +13 -5
  304. package/src/scripts/ai_council/clients.ts +26 -57
  305. package/src/scripts/ai_council/config.ts +1 -1
  306. package/src/scripts/ai_council/vendor_defaults.ts +45 -0
  307. package/src/scripts/ai_team/team_dispatch.ts +4 -0
  308. package/src/scripts/audit_skill_overlap.ts +261 -0
  309. package/src/scripts/before_complete_hook.ts +573 -25
  310. package/src/scripts/bench_hook_latency.ts +412 -5
  311. package/src/scripts/bench_touched_file_quality.ts +238 -0
  312. package/src/scripts/cache_realization_report.ts +61 -1
  313. package/src/scripts/check_argument_hint.ts +287 -0
  314. package/src/scripts/check_claims.ts +125 -1
  315. package/src/scripts/check_enforcement_coverage.ts +38 -9
  316. package/src/scripts/check_enforcement_matrix.ts +621 -22
  317. package/src/scripts/check_estate_count.ts +130 -153
  318. package/src/scripts/check_finding_dispositions.ts +138 -7
  319. package/src/scripts/check_generator_sync.ts +71 -0
  320. package/src/scripts/check_hook_bundle_composition.ts +370 -0
  321. package/src/scripts/check_host_docs_digest.ts +786 -0
  322. package/src/scripts/check_host_format_column.ts +559 -0
  323. package/src/scripts/check_input_references.ts +228 -0
  324. package/src/scripts/check_inputs_parity.ts +235 -0
  325. package/src/scripts/check_kernel_edit_ratified.ts +89 -9
  326. package/src/scripts/check_kill_switch_table.ts +294 -0
  327. package/src/scripts/check_memory.ts +7 -1
  328. package/src/scripts/check_placeholder_drift.ts +221 -0
  329. package/src/scripts/check_read_surface_coverage.ts +515 -0
  330. package/src/scripts/check_release_highlights.ts +10 -0
  331. package/src/scripts/check_release_holds.ts +2 -1
  332. package/src/scripts/check_release_pr_shape.ts +38 -15
  333. package/src/scripts/check_routing_coverage.ts +378 -11
  334. package/src/scripts/check_standing_rule_delivery.ts +33 -13
  335. package/src/scripts/check_static_parity.ts +17 -0
  336. package/src/scripts/check_verify_expectation_delta.ts +521 -0
  337. package/src/scripts/ci_settle.ts +145 -19
  338. package/src/scripts/closure_scan.ts +115 -1
  339. package/src/scripts/code_graph/detect.ts +127 -7
  340. package/src/scripts/code_graph/foreign.ts +194 -0
  341. package/src/scripts/code_graph/query.ts +217 -4
  342. package/src/scripts/code_graph/types.ts +26 -2
  343. package/src/scripts/code_graph/validate.ts +3 -1
  344. package/src/scripts/code_graph/verbs.ts +103 -0
  345. package/src/scripts/compile_hook_manifest.ts +90 -14
  346. package/src/scripts/condense.ts +14 -27
  347. package/src/scripts/estate_exemption_shape.ts +202 -0
  348. package/src/scripts/external_sources_denylist.json +29 -4
  349. package/src/scripts/generate_host_cost_table.ts +1 -1
  350. package/src/scripts/hook_manifest.json +1 -1
  351. package/src/scripts/hook_manifest.yaml +192 -14
  352. package/src/scripts/hooks/block_config_weakening.ts +294 -4
  353. package/src/scripts/hooks/block_kernel_rule_writes.ts +9 -78
  354. package/src/scripts/hooks/block_plumbing_writes.ts +342 -0
  355. package/src/scripts/hooks/bundle_integrity.ts +291 -0
  356. package/src/scripts/hooks/chain_nudge_hook.ts +1 -2
  357. package/src/scripts/hooks/code_graph_context_hook.ts +253 -25
  358. package/src/scripts/hooks/comment_discipline_hook.ts +1 -3
  359. package/src/scripts/hooks/concern_failure_policy.ts +280 -0
  360. package/src/scripts/hooks/concern_registry.ts +2 -0
  361. package/src/scripts/hooks/concern_timings.ts +94 -0
  362. package/src/scripts/hooks/delegation_nudge_hook.ts +1 -2
  363. package/src/scripts/hooks/design_pass_hook.ts +100 -9
  364. package/src/scripts/hooks/design_slop_hook.ts +1 -2
  365. package/src/scripts/hooks/dispatch_hook.ts +77 -71
  366. package/src/scripts/hooks/edit_shape_hook.ts +1 -2
  367. package/src/scripts/hooks/end_review_nudge_hook.ts +1 -1
  368. package/src/scripts/hooks/evidence_independence.ts +1 -2
  369. package/src/scripts/hooks/exit_codes.ts +129 -0
  370. package/src/scripts/hooks/host_lowering.json +1 -0
  371. package/src/scripts/hooks/host_lowering.ts +146 -3
  372. package/src/scripts/hooks/host_lowering.yaml +262 -46
  373. package/src/scripts/hooks/host_semantics.ts +30 -2
  374. package/src/scripts/hooks/injection_budget.ts +9 -2
  375. package/src/scripts/hooks/interruption_ledger_hook.ts +1 -2
  376. package/src/scripts/hooks/journal_record_hook.ts +1 -2
  377. package/src/scripts/hooks/memory_recall_hook.ts +1 -2
  378. package/src/scripts/hooks/obligation_settle_hook.ts +151 -5
  379. package/src/scripts/hooks/one_question_per_ask_hook.ts +43 -6
  380. package/src/scripts/hooks/orchestration_record_hook.ts +1 -2
  381. package/src/scripts/hooks/payload_stub.ts +10 -1
  382. package/src/scripts/hooks/permission_gate.ts +1 -2
  383. package/src/scripts/hooks/push_settle_hook.ts +1 -3
  384. package/src/scripts/hooks/replay_hook.ts +2 -1
  385. package/src/scripts/hooks/reread_guard_hook.ts +1 -2
  386. package/src/scripts/hooks/review_baseline_hook.ts +1 -2
  387. package/src/scripts/hooks/rtk_wrap_hook.ts +1 -2
  388. package/src/scripts/hooks/rule_inject_hook.ts +765 -68
  389. package/src/scripts/hooks/run_continuation_hook.ts +13 -15
  390. package/src/scripts/hooks/skill_route_hook.ts +1 -2
  391. package/src/scripts/hooks/source_first_gate_hook.ts +1 -2
  392. package/src/scripts/hooks/spawn_guard_shadow_hook.ts +1 -2
  393. package/src/scripts/hooks/subagent_ledger_hook.ts +1 -2
  394. package/src/scripts/hooks/suggestion_capture_hook.ts +1 -1
  395. package/src/scripts/hooks/table_fingerprint.ts +70 -0
  396. package/src/scripts/hooks/telemetry_flush_hook.ts +1 -2
  397. package/src/scripts/hooks/telemetry_usage_hook.ts +65 -10
  398. package/src/scripts/hooks/tool_result_bytes_hook.ts +93 -9
  399. package/src/scripts/hooks/turn_end_gate_hook.ts +421 -324
  400. package/src/scripts/hooks/ui_route_nudge_hook.ts +1 -2
  401. package/src/scripts/hooks_status.ts +112 -0
  402. package/src/scripts/injection_scan_hook.ts +7 -0
  403. package/src/scripts/install.ts +75 -189
  404. package/src/scripts/installed_layer_report.ts +103 -0
  405. package/src/scripts/language_mirror_hook.ts +15 -6
  406. package/src/scripts/learning_sidecar.ts +119 -8
  407. package/src/scripts/lint_deny_text.ts +419 -0
  408. package/src/scripts/lint_exit_codes.ts +262 -0
  409. package/src/scripts/lint_hook_manifest.ts +108 -0
  410. package/src/scripts/lint_major_migration_sections.ts +156 -26
  411. package/src/scripts/lint_pack_boundaries.ts +63 -5
  412. package/src/scripts/lint_roadmap_blockers.ts +78 -12
  413. package/src/scripts/lint_rule_law_section.ts +517 -0
  414. package/src/scripts/lint_skill_descriptions.ts +1 -1
  415. package/src/scripts/lint_skill_scripts_readonly.ts +1 -1
  416. package/src/scripts/lint_workflow_security.ts +161 -5
  417. package/src/scripts/mcp_server/consumer_tool_catalog.json +53 -0
  418. package/src/scripts/mcp_server/graph_tools.ts +72 -3
  419. package/src/scripts/mcp_server/prompts.ts +142 -1
  420. package/src/scripts/measure_routing_signal.ts +54 -16
  421. package/src/scripts/measure_skill_ranker_baseline.ts +515 -53
  422. package/src/scripts/measure_turn_end_gate.ts +193 -0
  423. package/src/scripts/memory_learn_hook.ts +91 -6
  424. package/src/scripts/memory_lookup.ts +1 -1
  425. package/src/scripts/model_rule_injection.ts +52 -5
  426. package/src/scripts/prepack-check.mjs +53 -0
  427. package/src/scripts/project_thin_rules.ts +34 -327
  428. package/src/scripts/release_publication.ts +13 -1
  429. package/src/scripts/report_evidence_temperature.ts +657 -0
  430. package/src/scripts/report_installed_rule_links.ts +157 -0
  431. package/src/scripts/report_invocation_surface.ts +383 -0
  432. package/src/scripts/report_label_agreement.ts +233 -0
  433. package/src/scripts/report_obligation_carriers.ts +699 -0
  434. package/src/scripts/report_obligation_mechanism.ts +230 -0
  435. package/src/scripts/report_obligation_writer_split.ts +236 -0
  436. package/src/scripts/report_single_token_triggers.ts +277 -0
  437. package/src/scripts/report_skill_menu_census.ts +208 -0
  438. package/src/scripts/report_skill_ranker_confusion.ts +442 -0
  439. package/src/scripts/report_standing_payload_by_host.ts +188 -15
  440. package/src/scripts/report_verification_record_coverage.ts +306 -0
  441. package/src/scripts/roadmap_verify_share.ts +213 -0
  442. package/src/scripts/rule_activation_census.ts +5 -2
  443. package/src/scripts/rule_trigger_eval.ts +9 -9
  444. package/src/scripts/schemas/command.schema.json +44 -0
  445. package/src/scripts/schemas/review-findings.schema.json +7 -2
  446. package/src/scripts/schemas/rule.schema.json +5 -0
  447. package/src/scripts/schemas/skill.schema.json +44 -0
  448. package/src/scripts/self_review_gate.ts +421 -17
  449. package/src/scripts/semantic_parity_census.ts +314 -0
  450. package/src/scripts/session_eol_report.ts +54 -1
  451. package/src/scripts/skill_tools/score_skill_relevance.ts +290 -25
  452. package/src/scripts/sweep_dead_scan_roots.ts +1 -1
  453. package/src/scripts/sweep_skill_ranker_signals.ts +129 -0
  454. package/src/scripts/sync_gitignore.ts +29 -235
  455. package/src/scripts/trigger_eval_grandfather.json +0 -7
  456. package/src/scripts/trigger_eval_rotation.ts +122 -27
  457. package/src/scripts/ui_conformance_probe.ts +156 -4
  458. package/src/scripts/update_prices.ts +85 -3
  459. package/src/scripts/write_bundle_digest.mjs +74 -0
  460. package/src/server/schemas/settings.ts +41 -0
  461. package/src/shared/skillRanking.ts +117 -11
  462. package/src/templates/consumer-settings/ONBOARDING.md +1 -0
  463. package/dist/cli-delegate/chunk-FXYUYU4N.js +0 -4416
  464. package/dist/cli-delegate/chunk-QJWAUMJW.js +0 -1072
  465. package/dist/cli-delegate/chunk-YDPILCOL.js +0 -152
  466. package/dist/ui/assets/index-DH1xc62v.js +0 -29
  467. package/dist/ui/assets/index-jSfMW8_t.css +0 -1
@@ -0,0 +1,2160 @@
1
+ import { createRequire as __acCreateRequire } from 'node:module'; const require = globalThis.require ?? __acCreateRequire(import.meta.url);
2
+ import {
3
+ HOOK_SIGNATURES,
4
+ entryCommands,
5
+ isManagedEntry
6
+ } from "./chunk-DEIV7XP7.js";
7
+ import {
8
+ scanText
9
+ } from "./chunk-KB5FZ655.js";
10
+ import {
11
+ DEFAULT_SCAN_ROOTS,
12
+ Finding,
13
+ ROOT,
14
+ TOKEN_RE,
15
+ _scan,
16
+ classifyToken,
17
+ iter_corpus,
18
+ legacy_pragma_finding,
19
+ py_json_dumps_indent2,
20
+ report,
21
+ report_child_scanned,
22
+ scan_file
23
+ } from "./chunk-JQOM7TTM.js";
24
+ import {
25
+ manifest_path,
26
+ readRecordedHashes
27
+ } from "./chunk-OKJUIAPS.js";
28
+ import {
29
+ DeadScopeError,
30
+ assertScanned
31
+ } from "./chunk-I3EIN2YG.js";
32
+ import {
33
+ require_dist
34
+ } from "./chunk-OY53J6II.js";
35
+ import {
36
+ resolvePackageRoot
37
+ } from "./chunk-MCJYCQPF.js";
38
+ import {
39
+ SRC_SKILLS
40
+ } from "./chunk-YCU3TIXH.js";
41
+ import {
42
+ __toESM
43
+ } from "./chunk-J55WI3RR.js";
44
+
45
+ // src/scripts/_cli/cmd_doctor_neighbours.ts
46
+ var YAML = __toESM(require_dist(), 1);
47
+ import * as fs9 from "node:fs";
48
+ import * as path9 from "node:path";
49
+ import { fileURLToPath as fileURLToPath5, pathToFileURL as pathToFileURL5 } from "node:url";
50
+
51
+ // src/scripts/_lib/neighbour_census.ts
52
+ import { createHash as createHash2 } from "node:crypto";
53
+ import * as fs3 from "node:fs";
54
+ import * as os2 from "node:os";
55
+ import * as path3 from "node:path";
56
+
57
+ // src/scripts/_lib/neighbour_tool_use.ts
58
+ import * as fs from "node:fs";
59
+ import * as path from "node:path";
60
+ var MCP_TOOL_PREFIX = "mcp__";
61
+ var FOREIGN_TOOL_USE_REL = "agents/runtime/neighbour-tool-use.json";
62
+ var TOOL_USE_WINDOW_DAYS = 30;
63
+ var SETTINGS_FILENAME = ".agent-settings.yml";
64
+ function usageDay(now) {
65
+ return now.toISOString().slice(0, 10);
66
+ }
67
+ var DAY_RE = /^\d{4}-\d{2}-\d{2}$/u;
68
+ function isUsageDay(value) {
69
+ return typeof value === "string" && DAY_RE.test(value);
70
+ }
71
+ function serverSegment(key) {
72
+ return key.replace(/[^A-Za-z0-9_-]/gu, "_");
73
+ }
74
+ function toolBelongsTo(tool, segment) {
75
+ if (!tool.startsWith(MCP_TOOL_PREFIX)) return false;
76
+ const rest = tool.slice(MCP_TOOL_PREFIX.length);
77
+ return rest === segment || rest.startsWith(`${segment}__`);
78
+ }
79
+ function resolveStoreRoot(start) {
80
+ let dir = path.resolve(start);
81
+ for (; ; ) {
82
+ try {
83
+ if (fs.statSync(path.join(dir, SETTINGS_FILENAME)).isFile()) return dir;
84
+ } catch {
85
+ }
86
+ const parent = path.dirname(dir);
87
+ if (parent === dir) return path.resolve(start);
88
+ dir = parent;
89
+ }
90
+ }
91
+ function toolsUsedByServer(projectRoot, now, segments) {
92
+ const known = [...new Set(segments)].sort((a, b) => b.length - a.length);
93
+ const counts = new Map(known.map((s) => [s, 0]));
94
+ if (known.length === 0) return counts;
95
+ let decoded;
96
+ try {
97
+ const root = resolveStoreRoot(projectRoot);
98
+ decoded = JSON.parse(fs.readFileSync(path.join(root, FOREIGN_TOOL_USE_REL), "utf-8"));
99
+ } catch {
100
+ return counts;
101
+ }
102
+ if (typeof decoded !== "object" || decoded === null || Array.isArray(decoded)) return counts;
103
+ const today = usageDay(now);
104
+ const floor = usageDay(new Date(now.getTime() - (TOOL_USE_WINDOW_DAYS - 1) * 864e5));
105
+ for (const [tool, seen] of Object.entries(decoded)) {
106
+ if (!isUsageDay(seen) || seen < floor || seen > today) continue;
107
+ for (const segment of known) {
108
+ if (toolBelongsTo(tool, segment)) {
109
+ counts.set(segment, (counts.get(segment) ?? 0) + 1);
110
+ break;
111
+ }
112
+ }
113
+ }
114
+ return counts;
115
+ }
116
+
117
+ // src/scripts/_lib/skill_origin.ts
118
+ var import_yaml = __toESM(require_dist(), 1);
119
+ import { createHash } from "node:crypto";
120
+ import * as fs2 from "node:fs";
121
+ import * as os from "node:os";
122
+ import * as path2 from "node:path";
123
+ var AUTHORED_SKILL_ROOT = path2.join("src", "skills");
124
+ var SCAN_CACHE_RELATIVE = path2.join("agents", "reports", "neighbour-scan.json");
125
+ function scanCachePath(projectRoot) {
126
+ return path2.join(projectRoot, SCAN_CACHE_RELATIVE);
127
+ }
128
+ function qualifiedSkillName(name, origin) {
129
+ return origin === "package" ? name : `${origin}:${name}`;
130
+ }
131
+ function packageClaimedPaths(projectRoot) {
132
+ const manifest = manifest_path(projectRoot);
133
+ let doc;
134
+ try {
135
+ doc = (0, import_yaml.parse)(fs2.readFileSync(manifest, "utf-8"), { version: "1.1" });
136
+ } catch {
137
+ return null;
138
+ }
139
+ if (doc === null || typeof doc !== "object" || Array.isArray(doc)) return null;
140
+ const tools = doc["tools"];
141
+ const out = /* @__PURE__ */ new Set();
142
+ if (!Array.isArray(tools)) return out;
143
+ for (const tool of tools) {
144
+ if (tool === null || typeof tool !== "object" || Array.isArray(tool)) continue;
145
+ const files = tool["files"];
146
+ if (!Array.isArray(files)) continue;
147
+ for (const entry of files) {
148
+ if (entry === null || typeof entry !== "object" || Array.isArray(entry)) continue;
149
+ const raw = entry["path"];
150
+ if (typeof raw !== "string" || raw === "") continue;
151
+ out.add(path2.resolve(projectRoot, _expanduser(raw)));
152
+ }
153
+ }
154
+ return out;
155
+ }
156
+ function _expanduser(p) {
157
+ if (p === "~") return os.homedir();
158
+ if (p.startsWith("~/") || process.platform === "win32" && p.startsWith("~\\")) {
159
+ return path2.join(os.homedir(), p.slice(2));
160
+ }
161
+ return p;
162
+ }
163
+ function makeSkillOriginResolver(projectRoot, opts = {}) {
164
+ const claims = opts.claims !== void 0 ? opts.claims : packageClaimedPaths(projectRoot);
165
+ const root = path2.resolve(projectRoot);
166
+ const authored = path2.join(path2.resolve(opts.packageRoot ?? projectRoot), AUTHORED_SKILL_ROOT);
167
+ const under = (abs, dir) => abs === dir || abs.startsWith(dir + path2.sep);
168
+ return (skillMdPath) => {
169
+ const abs = path2.resolve(skillMdPath);
170
+ if (under(abs, authored)) return "package";
171
+ if (claims === null) return "package";
172
+ if (claims.has(abs)) return "package";
173
+ return under(abs, root) ? "project" : "home";
174
+ };
175
+ }
176
+ function classifyCompat(args) {
177
+ if (args.unscanned !== null) return "unscanned";
178
+ if (args.shadows.length > 0) return "shadowed";
179
+ if (args.overlaps.length > 0) return "overlapping";
180
+ return "unclassified";
181
+ }
182
+ function fileDigest(file) {
183
+ try {
184
+ return createHash("sha256").update(fs2.readFileSync(file)).digest("hex");
185
+ } catch {
186
+ return null;
187
+ }
188
+ }
189
+ function scanVerdict(cache, qualified, digest) {
190
+ if (cache === null) return { ok: false, kind: "no-scan-record" };
191
+ const rec = cache.entries.find((e) => e.qualified === qualified);
192
+ if (rec === void 0) return { ok: false, kind: "no-scan-record" };
193
+ if (digest === null || rec.digest !== digest) return { ok: false, kind: "digest-changed" };
194
+ if (rec.unscanned !== null) return { ok: false, kind: rec.unscanned };
195
+ return { ok: true };
196
+ }
197
+
198
+ // src/scripts/_lib/neighbour_census.ts
199
+ var OUR_MCP_KEY = "agent-config";
200
+ function sha256(text) {
201
+ return createHash2("sha256").update(text, "utf-8").digest("hex");
202
+ }
203
+ function readJson(p) {
204
+ try {
205
+ const parsed = JSON.parse(fs3.readFileSync(p, "utf-8"));
206
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return null;
207
+ return parsed;
208
+ } catch {
209
+ return null;
210
+ }
211
+ }
212
+ function commandHead(command) {
213
+ const trimmed = command.trim();
214
+ if (trimmed === "") return "";
215
+ const lastStatement = trimmed.split(";").pop() ?? trimmed;
216
+ const tokens = lastStatement.trim().split(/\s+/).filter((t) => t !== "");
217
+ let i = 0;
218
+ while (i < tokens.length && /^[A-Za-z_][A-Za-z0-9_]*=/.test(tokens[i] ?? "")) i += 1;
219
+ const head = tokens[i] ?? "";
220
+ if (head === "") return "(redacted)";
221
+ if (!/^[A-Za-z0-9_./\\:-]+$/.test(head) || head.includes("@")) return "(redacted)";
222
+ return scanText(head).length === 0 ? head : "(redacted)";
223
+ }
224
+ function hostNameFor(label) {
225
+ const user = label.startsWith("~/");
226
+ const dir = label.replace(/^~\//, "").replace(/\/[^/]+$/, "");
227
+ const name = dir.split("/").map((seg) => seg.replace(/^\./, "")).join("/");
228
+ return user ? `user:${name}` : `project:${name}`;
229
+ }
230
+ function hookFiles(projectRoot, homeRoot) {
231
+ const home = homeRoot ?? os2.homedir();
232
+ const expand = (label) => label.startsWith("~/") ? path3.join(home, label.slice(2)) : path3.join(projectRoot, label);
233
+ const out = Object.entries(HOOK_SIGNATURES).map(([label, signature]) => ({
234
+ host: hostNameFor(label),
235
+ file: expand(label),
236
+ signature
237
+ }));
238
+ out.push({
239
+ host: "project:claude",
240
+ file: path3.join(projectRoot, ".claude", "settings.json"),
241
+ signature: "dispatch:hook --platform claude"
242
+ });
243
+ return out;
244
+ }
245
+ function censusHookFile(host, file, signature) {
246
+ const doc = readJson(file);
247
+ if (doc === null) return { entries: [], oursBound: false, present: false };
248
+ const hooks = doc["hooks"];
249
+ if (hooks === null || typeof hooks !== "object" || Array.isArray(hooks)) {
250
+ return { entries: [], oursBound: false, present: true };
251
+ }
252
+ const entries = [];
253
+ let oursBound = false;
254
+ for (const [event, list] of Object.entries(hooks)) {
255
+ if (!Array.isArray(list)) continue;
256
+ for (const raw of list) {
257
+ if (isManagedEntry(raw, signature)) {
258
+ oursBound = true;
259
+ continue;
260
+ }
261
+ const commands = entryCommands(raw);
262
+ const head = commandHead(commands[0] ?? "");
263
+ const obj = raw ?? {};
264
+ const matcher = typeof obj["matcher"] === "string" ? obj["matcher"] : null;
265
+ const timeout = typeof obj["timeout"] === "number" ? obj["timeout"] : null;
266
+ entries.push({
267
+ id: `${host}:${event}/${head === "" ? "unreadable" : head}`,
268
+ shape: "hook_group",
269
+ digest: sha256(JSON.stringify(raw)),
270
+ source: file,
271
+ event,
272
+ matcher,
273
+ command_head: head,
274
+ timeout,
275
+ // No host exposes another package's hook execution to a hook,
276
+ // so a neighbour's effect is not readable from its
277
+ // registration. `unknown` is the honest answer, and 2.3 treats
278
+ // it as capable of denying rather than assuming it is not.
279
+ effect: "unknown"
280
+ });
281
+ }
282
+ }
283
+ entries.sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
284
+ return { entries, oursBound, present: true };
285
+ }
286
+ function unclaimedUnder(dir, origin, shape, owned, opts = {}) {
287
+ let names;
288
+ try {
289
+ names = fs3.readdirSync(dir).sort();
290
+ } catch {
291
+ return [];
292
+ }
293
+ const out = [];
294
+ for (const name of names) {
295
+ if (name.startsWith(".")) continue;
296
+ const full = path3.join(dir, name);
297
+ const probe = opts.nested === true ? path3.join(full, "SKILL.md") : full;
298
+ let body;
299
+ try {
300
+ const st = fs3.statSync(probe);
301
+ if (!st.isFile()) continue;
302
+ body = fs3.readFileSync(probe, "utf-8");
303
+ } catch {
304
+ continue;
305
+ }
306
+ if (owned.has(path3.resolve(probe))) continue;
307
+ out.push({
308
+ id: `${origin}:${opts.nested === true ? name : name.replace(/\.[^.]+$/, "")}`,
309
+ shape,
310
+ digest: sha256(body),
311
+ source: probe
312
+ });
313
+ }
314
+ return out;
315
+ }
316
+ function foreignSections(file, templateFile, origin) {
317
+ let body;
318
+ try {
319
+ body = fs3.readFileSync(file, "utf-8");
320
+ } catch {
321
+ return [];
322
+ }
323
+ let ours = /* @__PURE__ */ new Set();
324
+ try {
325
+ ours = new Set(
326
+ fs3.readFileSync(templateFile, "utf-8").split("\n").filter((l) => /^#{1,2} /.test(l)).map((l) => l.replace(/^#{1,2} /, "").trim())
327
+ );
328
+ } catch {
329
+ }
330
+ const out = [];
331
+ const lines = body.split("\n");
332
+ const isHeading = (n) => /^#{1,2} /.test(lines[n] ?? "");
333
+ for (let i = 0; i < lines.length; i += 1) {
334
+ if (!isHeading(i)) continue;
335
+ const heading = (lines[i] ?? "").replace(/^#{1,2} /, "").trim();
336
+ if (ours.has(heading)) continue;
337
+ let j = i + 1;
338
+ while (j < lines.length && !isHeading(j)) j += 1;
339
+ out.push({
340
+ id: `${origin}:${heading}`,
341
+ shape: "instruction_section",
342
+ digest: sha256(lines.slice(i, j).join("\n")),
343
+ source: file
344
+ });
345
+ }
346
+ return out;
347
+ }
348
+ function foreignMcpServers(projectRoot, now) {
349
+ const file = path3.join(projectRoot, ".mcp.json");
350
+ const doc = readJson(file);
351
+ const servers = doc?.["mcpServers"];
352
+ if (servers === null || servers === void 0 || typeof servers !== "object" || Array.isArray(servers)) {
353
+ return [];
354
+ }
355
+ const foreign = Object.entries(servers).filter(
356
+ ([name]) => name !== OUR_MCP_KEY
357
+ );
358
+ const used = toolsUsedByServer(projectRoot, now, foreign.map(([name]) => serverSegment(name)));
359
+ return foreign.map(([name, spec]) => ({
360
+ id: `project:${name}`,
361
+ shape: "mcp_server",
362
+ digest: sha256(JSON.stringify(spec)),
363
+ source: file,
364
+ tools_used_30d: used.get(serverSegment(name)) ?? 0,
365
+ tools_advertised: "unknown",
366
+ tools_window_days: TOOL_USE_WINDOW_DAYS
367
+ })).sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
368
+ }
369
+ function labelFor(entries, oursBound, gatedEvents2) {
370
+ if (!oursBound) {
371
+ return {
372
+ label: "uncontrolled",
373
+ reason: "this package has no hook group registered in this file"
374
+ };
375
+ }
376
+ if (entries.length === 0) {
377
+ return { label: "controlled", reason: "no foreign hook entry in this file" };
378
+ }
379
+ const onGated = entries.filter((e) => gatedEvents2.has(e.event));
380
+ const stopEntries = entries.filter((e) => /stop/i.test(e.event));
381
+ if (onGated.length > 0 || stopEntries.length > 0) {
382
+ const named = [...new Set([...onGated, ...stopEntries].map((e) => e.id))].sort();
383
+ return {
384
+ label: "degraded",
385
+ reason: `foreign entr(ies) of unread effect on a slot this package gates: ${named.join(", ")}`
386
+ };
387
+ }
388
+ return {
389
+ label: "coordinated",
390
+ reason: `${entries.length} foreign entr(ies), none on a slot this package gates`
391
+ };
392
+ }
393
+ function census(projectRoot, opts = {}) {
394
+ const gated = opts.gatedEvents ?? /* @__PURE__ */ new Set();
395
+ const owned = new Set(readRecordedHashes(manifest_path(projectRoot), projectRoot).keys());
396
+ const home = opts.homeRoot ?? os2.homedir();
397
+ const hosts = [];
398
+ const hookGroups = [];
399
+ for (const { host, file, signature } of hookFiles(projectRoot, home)) {
400
+ const { entries, oursBound, present } = censusHookFile(host, file, signature);
401
+ if (!present) continue;
402
+ const { label, reason } = labelFor(entries, oursBound, gated);
403
+ hosts.push({
404
+ host,
405
+ file,
406
+ present,
407
+ hook_groups: entries,
408
+ ours_bound: oursBound,
409
+ label,
410
+ label_reason: reason
411
+ });
412
+ hookGroups.push(...entries);
413
+ }
414
+ const ourSkillsForCompat = opts.ourSkillNames ?? /* @__PURE__ */ new Set();
415
+ const originOf = makeSkillOriginResolver(projectRoot, {
416
+ ...opts.packageRoot === void 0 ? {} : { packageRoot: opts.packageRoot }
417
+ });
418
+ const scanCache = opts.scanCache !== void 0 ? opts.scanCache : null;
419
+ const overlaps = opts.overlaps ?? /* @__PURE__ */ new Map();
420
+ const skills = [
421
+ ...unclaimedUnder(path3.join(projectRoot, ".claude", "skills"), "project", "skill", owned, {
422
+ nested: true
423
+ }),
424
+ ...unclaimedUnder(path3.join(home, ".claude", "skills"), "user", "skill", owned, {
425
+ nested: true
426
+ })
427
+ ].map((entry) => {
428
+ const name = entry.id.split(":").slice(1).join(":");
429
+ const origin = originOf(entry.source);
430
+ const qualified = qualifiedSkillName(name, origin);
431
+ const unscanned = opts.scan !== void 0 ? opts.scan(entry.source) : (() => {
432
+ const v = scanVerdict(scanCache, qualified, fileDigest(entry.source));
433
+ return v.ok ? null : v.kind;
434
+ })();
435
+ const shadows = ourSkillsForCompat.has(name) ? [name] : [];
436
+ const hit = overlaps.get(path3.resolve(entry.source));
437
+ const overlapNames = unscanned === null && hit !== void 0 ? [...hit.names] : [];
438
+ return {
439
+ ...entry,
440
+ shape: "skill",
441
+ qualified,
442
+ compat: classifyCompat({ unscanned, shadows, overlaps: overlapNames }),
443
+ also: [.../* @__PURE__ */ new Set([...shadows, ...overlapNames])].sort(),
444
+ unscanned,
445
+ similarity: overlapNames.length > 0 && hit !== void 0 ? hit.similarity : null
446
+ };
447
+ });
448
+ const commands = unclaimedUnder(
449
+ path3.join(projectRoot, ".claude", "commands"),
450
+ "project",
451
+ "command",
452
+ owned
453
+ );
454
+ const agents = unclaimedUnder(
455
+ path3.join(projectRoot, ".claude", "agents"),
456
+ "project",
457
+ "agent",
458
+ owned
459
+ );
460
+ const ruleFiles = [
461
+ ...unclaimedUnder(path3.join(projectRoot, ".cursor", "rules"), "cursor", "rule_file", owned),
462
+ ...unclaimedUnder(path3.join(projectRoot, ".windsurf", "rules"), "windsurf", "rule_file", owned),
463
+ ...unclaimedUnder(path3.join(projectRoot, ".claude", "rules"), "claude", "rule_file", owned)
464
+ ];
465
+ const templates = opts.templatesRoot ?? "";
466
+ const instructionSections = [
467
+ ...foreignSections(
468
+ path3.join(projectRoot, "AGENTS.md"),
469
+ path3.join(templates, "AGENTS.md"),
470
+ "AGENTS.md"
471
+ ),
472
+ ...foreignSections(
473
+ path3.join(projectRoot, ".github", "copilot-instructions.md"),
474
+ path3.join(templates, "copilot-instructions.md"),
475
+ "copilot-instructions.md"
476
+ )
477
+ ];
478
+ const warnings = [];
479
+ const ourSkills = opts.ourSkillNames ?? /* @__PURE__ */ new Set();
480
+ for (const s of skills) {
481
+ const name = s.id.split(":").slice(1).join(":");
482
+ if (ourSkills.has(name)) {
483
+ warnings.push({
484
+ kind: "shadowed",
485
+ detail: `a skill named \`${name}\` sits at ${s.source} and this package ships one under the same name; which one the host resolves is the host's choice, not ours`
486
+ });
487
+ }
488
+ }
489
+ warnings.push(...doubleGateWarnings(hookGroups, gated));
490
+ warnings.push(...settingsTakeover(path3.join(projectRoot, ".claude", "settings.json")));
491
+ warnings.push(...settingsTakeover(path3.join(home, ".claude", "settings.json")));
492
+ for (const h of hosts) {
493
+ if (!h.ours_bound) {
494
+ warnings.push({
495
+ kind: "removed-after-install",
496
+ detail: `${h.file} carries hook entries but none of ours \u2014 this package's group is absent after its own install`
497
+ });
498
+ }
499
+ }
500
+ return {
501
+ project_root: projectRoot,
502
+ hosts,
503
+ hook_groups: hookGroups,
504
+ skills,
505
+ commands,
506
+ agents,
507
+ mcp_servers: foreignMcpServers(projectRoot, opts.now ?? /* @__PURE__ */ new Date()),
508
+ rule_files: ruleFiles,
509
+ instruction_sections: instructionSections,
510
+ warnings
511
+ };
512
+ }
513
+ function doubleGateWarnings(entries, gatedEvents2) {
514
+ const byEvent = /* @__PURE__ */ new Map();
515
+ for (const e of entries) {
516
+ if (!gatedEvents2.has(e.event)) continue;
517
+ const arr = byEvent.get(e.event);
518
+ if (arr) arr.push(e);
519
+ else byEvent.set(e.event, [e]);
520
+ }
521
+ return [...byEvent.entries()].sort((a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0).map(([event, list]) => ({
522
+ kind: "double-gate",
523
+ detail: `${event}: ${list.length} foreign entr(ies) (${list.map((e) => e.id).join(", ")}) share this event with a gate of ours. The host runs both; either deny applies.`
524
+ }));
525
+ }
526
+ function settingsTakeover(settingsFile) {
527
+ const doc = readJson(settingsFile);
528
+ if (doc === null) return [];
529
+ const out = [];
530
+ if (doc["model"] !== void 0) {
531
+ out.push({
532
+ kind: "settings-takeover",
533
+ detail: `${settingsFile} sets \`model\` \u2014 the host resolves every turn against it, including the ones this package's rules are loaded into`
534
+ });
535
+ }
536
+ const env = doc["env"];
537
+ if (env !== null && typeof env === "object" && !Array.isArray(env)) {
538
+ const keys = Object.keys(env).filter((k) => k.startsWith("CLAUDE_CODE_")).sort();
539
+ if (keys.length > 0) {
540
+ out.push({
541
+ kind: "settings-takeover",
542
+ detail: `${settingsFile} sets host environment key(s) ${keys.join(", ")} \u2014 these change the harness the hooks this package bound run inside`
543
+ });
544
+ }
545
+ }
546
+ return out;
547
+ }
548
+ function gatedEventsFrom(manifest, nativeAliases = {}) {
549
+ const concerns = manifest.concerns ?? {};
550
+ const permission = new Set(
551
+ Object.keys(concerns).filter((n) => concerns[n]?.effect === "permission")
552
+ );
553
+ const out = /* @__PURE__ */ new Set();
554
+ for (const [host, events] of Object.entries(manifest.platforms ?? {})) {
555
+ if (events === null || typeof events !== "object") continue;
556
+ for (const [event, chain] of Object.entries(events)) {
557
+ if (!Array.isArray(chain)) continue;
558
+ if (!chain.some((c) => permission.has(String(c)))) continue;
559
+ out.add(event);
560
+ const native = nativeAliases[host]?.[event];
561
+ if (typeof native === "string") out.add(native);
562
+ for (const [nativeName, acEvent] of Object.entries(nativeAliases[host] ?? {})) {
563
+ if (acEvent === event) out.add(nativeName);
564
+ }
565
+ }
566
+ }
567
+ return out;
568
+ }
569
+
570
+ // src/scripts/_lib/neighbour_scan.ts
571
+ import * as fs7 from "node:fs";
572
+ import * as path7 from "node:path";
573
+
574
+ // src/scripts/lint_instruction_smuggling.ts
575
+ import * as path4 from "node:path";
576
+ import * as fs4 from "node:fs";
577
+ import { fileURLToPath, pathToFileURL } from "node:url";
578
+ var CHECK = "instruction-smuggling";
579
+ var _INLINE_CODE = /`[^`]*`/g;
580
+ var _SUPPRESS = new RegExp(
581
+ "\\b(do not|don'?t|never)\\s+(mention|tell|inform|disclose|reveal|notify)\\b[^.]{0,40}\\b(the )?(user|human|reviewer|operator|owner)\\b|without (telling|informing|notifying|explaining to) (the )?(user|them|anyone)",
582
+ "i"
583
+ );
584
+ var _INJECT = new RegExp(
585
+ "<\\s*(important|system|admin|secret|critical)\\s*>|ignore (all |the )?(previous|prior|above) (instructions|prompts|rules)|disregard (all |the )?(previous|prior|above)|you are now (a|an|the)\\b|new system prompt",
586
+ "i"
587
+ );
588
+ var _MED = [
589
+ [/\bcurl\b[^\n|]*\|\s*(ba|z|fi)?sh\b/i, "pipe-to-shell (curl|sh)"],
590
+ [/\bwget\b[^\n|]*\|\s*(ba|z|fi)?sh\b/i, "pipe-to-shell (wget|sh)"],
591
+ [/\b(socat|nc)\b[^\n]*\b(exec|-e)\b|\/dev\/tcp\//i, "reverse-shell signature"],
592
+ [/(~\/\.ssh\/id_[rd]sa|\/etc\/shadow|\.aws\/credentials)/, "secret-path read"]
593
+ ];
594
+ function _strip_inline_code(text) {
595
+ return text.replace(_INLINE_CODE, (m) => " ".repeat(m.length));
596
+ }
597
+ function _scan2(sf) {
598
+ const pragmaForm = sf.pragma_form(CHECK);
599
+ if (pragmaForm === "legacy") {
600
+ return [legacy_pragma_finding(sf, CHECK)];
601
+ }
602
+ const finish = (hits) => pragmaForm === "bound" ? sf.filter_bound_pragma(CHECK, hits) : hits;
603
+ const out = [];
604
+ for (const [lineno, text] of sf.iter_lines({ skip_example_fence: true, skip_any_fence: true })) {
605
+ const prose = _strip_inline_code(text);
606
+ if (_SUPPRESS.test(prose)) {
607
+ out.push(
608
+ new Finding(
609
+ sf.rel,
610
+ lineno,
611
+ CHECK,
612
+ "HIGH",
613
+ "disclosure-suppression imperative in prose",
614
+ sf.weight
615
+ )
616
+ );
617
+ }
618
+ if (_INJECT.test(prose)) {
619
+ out.push(
620
+ new Finding(
621
+ sf.rel,
622
+ lineno,
623
+ CHECK,
624
+ "HIGH",
625
+ "injection / role-takeover phrase in prose",
626
+ sf.weight
627
+ )
628
+ );
629
+ }
630
+ for (const [rx, label] of _MED) {
631
+ if (rx.test(prose)) {
632
+ out.push(
633
+ new Finding(
634
+ sf.rel,
635
+ lineno,
636
+ CHECK,
637
+ "MED",
638
+ `${label} in prose (verify intent)`,
639
+ sf.weight
640
+ )
641
+ );
642
+ }
643
+ }
644
+ }
645
+ return finish(out);
646
+ }
647
+ function _argError(msg) {
648
+ process.stderr.write("usage: lint_instruction_smuggling [-h] [--json] [--root DIR]\n");
649
+ process.stderr.write(`lint_instruction_smuggling: error: ${msg}
650
+ `);
651
+ process.exit(2);
652
+ }
653
+ function parse_args(argv) {
654
+ const out = { json: false };
655
+ const extra = [];
656
+ let rootPending = false;
657
+ for (const a of argv) {
658
+ if (rootPending) {
659
+ out.root = a;
660
+ rootPending = false;
661
+ continue;
662
+ }
663
+ if (a === "-h" || a === "--help") {
664
+ process.stdout.write("usage: lint_instruction_smuggling [-h] [--json] [--root DIR]\n");
665
+ process.exit(0);
666
+ } else if (a === "--json") {
667
+ out.json = true;
668
+ } else if (a === "--root" || a.startsWith("--root=")) {
669
+ const eq = a.indexOf("=");
670
+ if (eq !== -1) {
671
+ out.root = a.slice(eq + 1);
672
+ } else {
673
+ rootPending = true;
674
+ }
675
+ } else {
676
+ extra.push(a);
677
+ }
678
+ }
679
+ if (extra.length > 0) {
680
+ _argError(`unrecognized arguments: ${extra.join(" ")}`);
681
+ }
682
+ return out;
683
+ }
684
+ function main(argv = null) {
685
+ const args = parse_args(argv ?? process.argv.slice(2));
686
+ const scanBase = args.root === void 0 ? ROOT : path4.resolve(args.root);
687
+ const findings = [];
688
+ let corpusFiles = 0;
689
+ for (const sf of iter_corpus(DEFAULT_SCAN_ROOTS, [".md"], scanBase)) {
690
+ corpusFiles += 1;
691
+ for (const h of _scan2(sf)) {
692
+ findings.push(h);
693
+ }
694
+ }
695
+ try {
696
+ assertScanned({
697
+ gate: "lint_instruction_smuggling",
698
+ scanned: corpusFiles,
699
+ units: "corpus file(s)",
700
+ roots: DEFAULT_SCAN_ROOTS
701
+ });
702
+ } catch (exc) {
703
+ if (exc instanceof DeadScopeError) {
704
+ process.stderr.write(`\u274C ${exc.message}
705
+ `);
706
+ return 1;
707
+ }
708
+ throw exc;
709
+ }
710
+ if (args.json) {
711
+ report_child_scanned(corpusFiles);
712
+ }
713
+ if (args.json) {
714
+ const payload = py_json_dumps_indent2(findings.map((f) => f.toDict()));
715
+ process.stdout.write(payload + "\n");
716
+ return findings.some((f) => f.is_fail) ? 1 : 0;
717
+ }
718
+ return report(findings, { check_label: "instruction-smuggling" });
719
+ }
720
+ function _isCliEntry() {
721
+ if (process.argv[1] === void 0) {
722
+ return false;
723
+ }
724
+ const argvUrl = pathToFileURL(path4.resolve(process.argv[1])).href;
725
+ if (import.meta.url === argvUrl) {
726
+ return true;
727
+ }
728
+ try {
729
+ const here = fs4.realpathSync(fileURLToPath(import.meta.url));
730
+ const argv = fs4.realpathSync(path4.resolve(process.argv[1]));
731
+ return here === argv;
732
+ } catch {
733
+ return false;
734
+ }
735
+ }
736
+ if (_isCliEntry()) {
737
+ process.exitCode = main();
738
+ }
739
+
740
+ // src/scripts/lint_confusables.ts
741
+ import * as path5 from "node:path";
742
+ import * as fs5 from "node:fs";
743
+ import { fileURLToPath as fileURLToPath2, pathToFileURL as pathToFileURL2 } from "node:url";
744
+ var CHECK2 = "mixed-script-confusable";
745
+ var _TOKEN = TOKEN_RE;
746
+ var _classify_token = classifyToken;
747
+ function _scan3(sf) {
748
+ const pragmaForm = sf.pragma_form(CHECK2);
749
+ if (pragmaForm === "legacy") {
750
+ return [legacy_pragma_finding(sf, CHECK2)];
751
+ }
752
+ const finish = (hits) => pragmaForm === "bound" ? sf.filter_bound_pragma(CHECK2, hits) : hits;
753
+ const out = [];
754
+ for (const [lineno, text] of sf.iter_lines({ skip_example_fence: true })) {
755
+ const tokens = text.match(_TOKEN);
756
+ if (tokens === null) continue;
757
+ for (const tok of tokens) {
758
+ const desc = _classify_token(tok);
759
+ if (desc !== null) {
760
+ out.push(
761
+ new Finding(
762
+ sf.rel,
763
+ lineno,
764
+ CHECK2,
765
+ "HIGH",
766
+ `mixed-script confusable token "${tok}" \u2014 ${desc}`,
767
+ sf.weight
768
+ )
769
+ );
770
+ }
771
+ }
772
+ }
773
+ return finish(out);
774
+ }
775
+ function _argError2(msg) {
776
+ process.stderr.write("usage: lint_confusables [-h] [--json] [--root DIR]\n");
777
+ process.stderr.write(`lint_confusables: error: ${msg}
778
+ `);
779
+ process.exit(2);
780
+ }
781
+ function parse_args2(argv) {
782
+ const out = { json: false };
783
+ const extra = [];
784
+ let rootPending = false;
785
+ for (const a of argv) {
786
+ if (rootPending) {
787
+ out.root = a;
788
+ rootPending = false;
789
+ continue;
790
+ }
791
+ if (a === "-h" || a === "--help") {
792
+ process.stdout.write("usage: lint_confusables [-h] [--json] [--root DIR]\n");
793
+ process.exit(0);
794
+ } else if (a === "--json") {
795
+ out.json = true;
796
+ } else if (a === "--root" || a.startsWith("--root=")) {
797
+ const eq = a.indexOf("=");
798
+ if (eq !== -1) {
799
+ out.root = a.slice(eq + 1);
800
+ } else {
801
+ rootPending = true;
802
+ }
803
+ } else {
804
+ extra.push(a);
805
+ }
806
+ }
807
+ if (extra.length > 0) {
808
+ _argError2(`unrecognized arguments: ${extra.join(" ")}`);
809
+ }
810
+ return out;
811
+ }
812
+ function main2(argv = null) {
813
+ const args = parse_args2(argv ?? process.argv.slice(2));
814
+ const scanBase = args.root === void 0 ? ROOT : path5.resolve(args.root);
815
+ const findings = [];
816
+ let scanned = 0;
817
+ for (const sf of iter_corpus(DEFAULT_SCAN_ROOTS, [".md"], scanBase)) {
818
+ scanned += 1;
819
+ for (const h of _scan3(sf)) {
820
+ findings.push(h);
821
+ }
822
+ }
823
+ try {
824
+ assertScanned({
825
+ gate: "lint_confusables",
826
+ scanned,
827
+ units: "file(s)",
828
+ roots: DEFAULT_SCAN_ROOTS
829
+ });
830
+ } catch (exc) {
831
+ if (exc instanceof DeadScopeError) {
832
+ process.stderr.write(`\u274C ${exc.message}
833
+ `);
834
+ return 2;
835
+ }
836
+ throw exc;
837
+ }
838
+ if (args.json) {
839
+ report_child_scanned(scanned);
840
+ }
841
+ if (args.json) {
842
+ const payload = py_json_dumps_indent2(findings.map((f) => f.toDict()));
843
+ process.stdout.write(payload + "\n");
844
+ return findings.some((f) => f.is_fail) ? 1 : 0;
845
+ }
846
+ return report(findings, { check_label: "mixed-script-confusable" });
847
+ }
848
+ function _isCliEntry2() {
849
+ if (process.argv[1] === void 0) {
850
+ return false;
851
+ }
852
+ const argvUrl = pathToFileURL2(path5.resolve(process.argv[1])).href;
853
+ if (import.meta.url === argvUrl) {
854
+ return true;
855
+ }
856
+ try {
857
+ const here = fs5.realpathSync(fileURLToPath2(import.meta.url));
858
+ const argv = fs5.realpathSync(path5.resolve(process.argv[1]));
859
+ return here === argv;
860
+ } catch {
861
+ return false;
862
+ }
863
+ }
864
+ if (_isCliEntry2()) {
865
+ process.exitCode = main2();
866
+ }
867
+
868
+ // src/scripts/lint_skill_frontmatter_safety.ts
869
+ import * as path6 from "node:path";
870
+ import * as fs6 from "node:fs";
871
+ import { fileURLToPath as fileURLToPath3, pathToFileURL as pathToFileURL3 } from "node:url";
872
+ var CHECK3 = "dangerous-frontmatter";
873
+ var _WILDCARD_TOOL = /(^|[\s,[\"'])(\*|Bash\(\*\))(\s|,|\]|"|'|$)/;
874
+ var _BARE_BASH = /(^|[\s,[\"'])Bash(\s|,|\]|"|'|$)/;
875
+ var _RE_EXECUTION = /^execution:\s*$/;
876
+ var _RE_EXEC_KEY = /^\s+([A-Za-z0-9_-]+):\s*(.*)$/;
877
+ var _RE_PERMISSION_MODE = /^\s*permissionMode:\s*['"]?bypassPermissions/;
878
+ var _RE_ALLOWED_TOOLS = /^\s*allowed-tools:\s*(.+)$/;
879
+ var _RE_TOOLS_KEY = /^tools:\s*(.*)$/;
880
+ var _RE_SEQ_ITEM = /^\s+-\s*(.+)$/;
881
+ function _stripQuotes(s) {
882
+ let lo = 0;
883
+ let hi = s.length;
884
+ while (lo < hi && (s[lo] === "'" || s[lo] === '"')) lo++;
885
+ while (hi > lo && (s[hi - 1] === "'" || s[hi - 1] === '"')) hi--;
886
+ return s.slice(lo, hi);
887
+ }
888
+ function _indent(text) {
889
+ return text.length - text.replace(/^\s+/, "").length;
890
+ }
891
+ function _frontmatter(sf) {
892
+ if (sf.lines.length === 0 || sf.lines[0].trim() !== "---") {
893
+ return null;
894
+ }
895
+ const body = [];
896
+ for (let i = 1; i < sf.lines.length; i++) {
897
+ if (sf.lines[i].trim() === "---") {
898
+ return [body, i + 1];
899
+ }
900
+ body.push([i + 1, sf.lines[i]]);
901
+ }
902
+ return null;
903
+ }
904
+ function _exec_block(body) {
905
+ const out = {};
906
+ let in_exec = false;
907
+ let base_indent = 0;
908
+ for (const [lineno, text] of body) {
909
+ if (_RE_EXECUTION.test(text)) {
910
+ in_exec = true;
911
+ base_indent = _indent(text);
912
+ continue;
913
+ }
914
+ if (in_exec) {
915
+ const indent = _indent(text);
916
+ if (text.trim() && indent <= base_indent) {
917
+ in_exec = false;
918
+ continue;
919
+ }
920
+ const m = _RE_EXEC_KEY.exec(text);
921
+ if (m) {
922
+ out[m[1]] = [lineno, m[2].trim()];
923
+ }
924
+ }
925
+ }
926
+ return out;
927
+ }
928
+ function grantSurface(rel) {
929
+ return rel.replace(/\\/g, "/").includes("src/subagents/") ? "subagent" : "skill-like";
930
+ }
931
+ function _scan4(sf) {
932
+ const pragmaForm = sf.pragma_form(CHECK3);
933
+ if (pragmaForm === "legacy") {
934
+ return [legacy_pragma_finding(sf, CHECK3)];
935
+ }
936
+ const finish = (hits) => pragmaForm === "bound" ? sf.filter_bound_pragma(CHECK3, hits) : hits;
937
+ const fm = _frontmatter(sf);
938
+ if (!fm) {
939
+ return [];
940
+ }
941
+ const [body] = fm;
942
+ const out = [];
943
+ const ex = _exec_block(body);
944
+ const handler = _stripQuotes((ex["handler"] ?? [0, ""])[1]);
945
+ const is_execution_skill = Object.keys(ex).length > 0 && handler !== "" && handler !== "none";
946
+ const surface = grantSurface(sf.rel);
947
+ for (const [lineno, text] of body) {
948
+ if (surface === "skill-like" && _RE_TOOLS_KEY.test(text)) {
949
+ out.push(
950
+ new Finding(
951
+ sf.rel,
952
+ lineno,
953
+ CHECK3,
954
+ "HIGH",
955
+ "top-level `tools:` on a non-subagent surface \u2014 subagent-v1 only; the loader ignores it here, so this artefact is NOT restricted (use execution.allowed_tools)",
956
+ sf.weight
957
+ )
958
+ );
959
+ }
960
+ if (surface === "subagent" && _RE_EXECUTION.test(text)) {
961
+ out.push(
962
+ new Finding(
963
+ sf.rel,
964
+ lineno,
965
+ CHECK3,
966
+ "HIGH",
967
+ "`execution:` block on a subagent definition \u2014 subagent-v1 has no such key, so any allowed_tools under it restricts nothing (use top-level `tools:`)",
968
+ sf.weight
969
+ )
970
+ );
971
+ }
972
+ }
973
+ for (const [lineno, text] of body) {
974
+ if (_RE_PERMISSION_MODE.test(text)) {
975
+ out.push(
976
+ new Finding(
977
+ sf.rel,
978
+ lineno,
979
+ CHECK3,
980
+ "HIGH",
981
+ "permissionMode: bypassPermissions (consent bypass)",
982
+ sf.weight
983
+ )
984
+ );
985
+ }
986
+ const m = _RE_ALLOWED_TOOLS.exec(text);
987
+ if (m && (_WILDCARD_TOOL.test(m[1]) || _BARE_BASH.test(m[1]) && !is_execution_skill)) {
988
+ out.push(
989
+ new Finding(
990
+ sf.rel,
991
+ lineno,
992
+ CHECK3,
993
+ "HIGH",
994
+ "wildcard / bare-Bash tool grant (over-broad)",
995
+ sf.weight
996
+ )
997
+ );
998
+ }
999
+ }
1000
+ for (let i = 0; i < body.length; i += 1) {
1001
+ const [lineno, text] = body[i];
1002
+ const key = _RE_TOOLS_KEY.exec(text);
1003
+ if (!key) {
1004
+ continue;
1005
+ }
1006
+ const grants = [];
1007
+ const inline = (key[1] ?? "").trim();
1008
+ if (inline !== "") {
1009
+ grants.push(inline);
1010
+ }
1011
+ for (let j = i + 1; j < body.length; j += 1) {
1012
+ const [, next] = body[j];
1013
+ const item = _RE_SEQ_ITEM.exec(next);
1014
+ if (!item) {
1015
+ break;
1016
+ }
1017
+ grants.push(_stripQuotes(item[1].trim()));
1018
+ }
1019
+ const joined = grants.join(", ");
1020
+ if (joined !== "" && (_WILDCARD_TOOL.test(joined) || _BARE_BASH.test(joined))) {
1021
+ out.push(
1022
+ new Finding(
1023
+ sf.rel,
1024
+ lineno,
1025
+ CHECK3,
1026
+ "HIGH",
1027
+ "wildcard / bare-Bash tool grant (over-broad)",
1028
+ sf.weight
1029
+ )
1030
+ );
1031
+ }
1032
+ }
1033
+ if (Object.keys(ex).length === 0) {
1034
+ return finish(out);
1035
+ }
1036
+ const etype = _stripQuotes((ex["type"] ?? [0, ""])[1]);
1037
+ const safety = _stripQuotes((ex["safety_mode"] ?? [0, ""])[1]);
1038
+ const exec_line = (ex["type"] ?? ex["handler"] ?? [0, ""])[0];
1039
+ if (etype === "automated") {
1040
+ if (handler === "" || handler === "none") {
1041
+ out.push(
1042
+ new Finding(
1043
+ sf.rel,
1044
+ exec_line,
1045
+ CHECK3,
1046
+ "HIGH",
1047
+ "automated execution with handler none/missing (runtime-safety)",
1048
+ sf.weight
1049
+ )
1050
+ );
1051
+ }
1052
+ if (safety !== "strict") {
1053
+ out.push(
1054
+ new Finding(
1055
+ sf.rel,
1056
+ exec_line,
1057
+ CHECK3,
1058
+ "HIGH",
1059
+ "automated execution without safety_mode: strict (runtime-safety)",
1060
+ sf.weight
1061
+ )
1062
+ );
1063
+ }
1064
+ if (!("allowed_tools" in ex)) {
1065
+ out.push(
1066
+ new Finding(
1067
+ sf.rel,
1068
+ exec_line,
1069
+ CHECK3,
1070
+ "HIGH",
1071
+ "automated execution without an explicit allowed_tools declaration",
1072
+ sf.weight
1073
+ )
1074
+ );
1075
+ }
1076
+ }
1077
+ const [at_line, at_val] = ex["allowed_tools"] ?? [0, ""];
1078
+ if (at_val && _WILDCARD_TOOL.test(at_val)) {
1079
+ out.push(
1080
+ new Finding(
1081
+ sf.rel,
1082
+ at_line,
1083
+ CHECK3,
1084
+ "HIGH",
1085
+ "execution.allowed_tools wildcard (* / Bash(*)) grant",
1086
+ sf.weight
1087
+ )
1088
+ );
1089
+ } else if (at_val && _BARE_BASH.test(at_val) && !is_execution_skill) {
1090
+ out.push(
1091
+ new Finding(
1092
+ sf.rel,
1093
+ at_line,
1094
+ CHECK3,
1095
+ "HIGH",
1096
+ "bare Bash grant on a non-execution skill (handler none/missing)",
1097
+ sf.weight
1098
+ )
1099
+ );
1100
+ }
1101
+ return finish(out);
1102
+ }
1103
+ function _argError3(msg) {
1104
+ process.stderr.write("usage: lint_skill_frontmatter_safety [-h] [--json] [--root DIR]\n");
1105
+ process.stderr.write(`lint_skill_frontmatter_safety: error: ${msg}
1106
+ `);
1107
+ process.exit(2);
1108
+ }
1109
+ function parse_args3(argv) {
1110
+ const out = { json: false };
1111
+ const extra = [];
1112
+ let rootPending = false;
1113
+ for (const a of argv) {
1114
+ if (rootPending) {
1115
+ out.root = a;
1116
+ rootPending = false;
1117
+ continue;
1118
+ }
1119
+ if (a === "-h" || a === "--help") {
1120
+ process.stdout.write("usage: lint_skill_frontmatter_safety [-h] [--json] [--root DIR]\n");
1121
+ process.exit(0);
1122
+ } else if (a === "--json") {
1123
+ out.json = true;
1124
+ } else if (a === "--root" || a.startsWith("--root=")) {
1125
+ const eq = a.indexOf("=");
1126
+ if (eq !== -1) {
1127
+ out.root = a.slice(eq + 1);
1128
+ } else {
1129
+ rootPending = true;
1130
+ }
1131
+ } else {
1132
+ extra.push(a);
1133
+ }
1134
+ }
1135
+ if (extra.length > 0) {
1136
+ _argError3(`unrecognized arguments: ${extra.join(" ")}`);
1137
+ }
1138
+ return out;
1139
+ }
1140
+ function main3(argv = null) {
1141
+ const args = parse_args3(argv ?? process.argv.slice(2));
1142
+ const scanBase = args.root === void 0 ? ROOT : path6.resolve(args.root);
1143
+ const findings = [];
1144
+ const roots = ["src/skills", "src/agent-src", "src/domains", "src/subagents"];
1145
+ let scanned = 0;
1146
+ for (const sf of iter_corpus(roots, [".md"], scanBase)) {
1147
+ scanned += 1;
1148
+ for (const h of _scan4(sf)) {
1149
+ findings.push(h);
1150
+ }
1151
+ }
1152
+ try {
1153
+ assertScanned({
1154
+ gate: "lint_skill_frontmatter_safety",
1155
+ scanned,
1156
+ units: "markdown file(s)",
1157
+ roots
1158
+ });
1159
+ } catch (exc) {
1160
+ if (exc instanceof DeadScopeError) {
1161
+ process.stderr.write(`\u274C ${exc.message}
1162
+ `);
1163
+ return 1;
1164
+ }
1165
+ throw exc;
1166
+ }
1167
+ if (args.json) {
1168
+ report_child_scanned(scanned);
1169
+ }
1170
+ if (args.json) {
1171
+ const payload = py_json_dumps_indent2(findings.map((f) => f.toDict()));
1172
+ process.stdout.write(payload + "\n");
1173
+ return findings.some((f) => f.is_fail) ? 1 : 0;
1174
+ }
1175
+ return report(findings, { check_label: "dangerous-frontmatter", scanned_roots: roots });
1176
+ }
1177
+ function _isCliEntry3() {
1178
+ if (process.argv[1] === void 0) {
1179
+ return false;
1180
+ }
1181
+ const argvUrl = pathToFileURL3(path6.resolve(process.argv[1])).href;
1182
+ if (import.meta.url === argvUrl) {
1183
+ return true;
1184
+ }
1185
+ try {
1186
+ const here = fs6.realpathSync(fileURLToPath3(import.meta.url));
1187
+ const argv = fs6.realpathSync(path6.resolve(process.argv[1]));
1188
+ return here === argv;
1189
+ } catch {
1190
+ return false;
1191
+ }
1192
+ }
1193
+ if (_isCliEntry3()) {
1194
+ process.exitCode = main3();
1195
+ }
1196
+
1197
+ // src/scripts/_lib/neighbour_scan.ts
1198
+ var SHAPES = [
1199
+ ["instruction-smuggling", _scan2],
1200
+ ["dangerous-frontmatter", _scan4],
1201
+ ["hidden-unicode", _scan],
1202
+ ["mixed-script-confusable", _scan3]
1203
+ ];
1204
+ function scanNeighbourBody(skillMdPath) {
1205
+ let sf;
1206
+ try {
1207
+ sf = scan_file(skillMdPath);
1208
+ } catch {
1209
+ return { kind: "unreadable", findings: [] };
1210
+ }
1211
+ const findings = [];
1212
+ for (const [id, scan] of SHAPES) {
1213
+ try {
1214
+ findings.push(...scan(sf));
1215
+ } catch {
1216
+ findings.push(
1217
+ new Finding(sf.rel, 0, id, "HIGH", "shape check threw on this body", 1)
1218
+ );
1219
+ }
1220
+ }
1221
+ return { kind: findings.length === 0 ? null : findings[0].check, findings };
1222
+ }
1223
+ function writeScanCache(file, entries) {
1224
+ const payload = {
1225
+ schema_version: 1,
1226
+ entries: [...entries].sort((a, b) => a.qualified < b.qualified ? -1 : 1)
1227
+ };
1228
+ try {
1229
+ fs7.mkdirSync(path7.dirname(file), { recursive: true });
1230
+ fs7.writeFileSync(file, `${JSON.stringify(payload, null, 2)}
1231
+ `, "utf-8");
1232
+ return true;
1233
+ } catch {
1234
+ return false;
1235
+ }
1236
+ }
1237
+
1238
+ // src/scripts/audit_skill_overlap.ts
1239
+ var import_yaml2 = __toESM(require_dist(), 1);
1240
+ import { createHash as createHash3 } from "node:crypto";
1241
+ import * as fs8 from "node:fs";
1242
+ import * as path8 from "node:path";
1243
+ import { fileURLToPath as fileURLToPath4, pathToFileURL as pathToFileURL4 } from "node:url";
1244
+ var _HERE = fileURLToPath4(import.meta.url);
1245
+ var ROOT2 = path8.resolve(path8.dirname(_HERE), "..", "..");
1246
+ var REPORT_DIR = path8.join(ROOT2, "agents", "reports");
1247
+ var OUT_JSON_NAME = "skill-overlap.json";
1248
+ var OUT_MD_NAME = "skill-overlap.md";
1249
+ var OUT_JSON = path8.join(REPORT_DIR, OUT_JSON_NAME);
1250
+ var OUT_MD = path8.join(REPORT_DIR, OUT_MD_NAME);
1251
+ var ALLOWLIST = path8.join(ROOT2, "src", "scripts", "audit_skill_overlap_allowlist.json");
1252
+ var CROSS_CACHE_NAME = "neighbour-overlap.json";
1253
+ var ALLOWLIST_CAP = 20;
1254
+ var FM_RE = /^---\n([\s\S]*?)\n---/m;
1255
+ var OVERLAP_THRESHOLD = 0.7;
1256
+ var STOPWORDS = /* @__PURE__ */ new Set([
1257
+ "the",
1258
+ "and",
1259
+ "for",
1260
+ "with",
1261
+ "when",
1262
+ "use",
1263
+ "or",
1264
+ "of",
1265
+ "to",
1266
+ "a",
1267
+ "an",
1268
+ "is",
1269
+ "in",
1270
+ "on",
1271
+ "by",
1272
+ "be",
1273
+ "at",
1274
+ "as",
1275
+ "it",
1276
+ "if",
1277
+ "are",
1278
+ "this",
1279
+ "that",
1280
+ "from",
1281
+ "but",
1282
+ "not",
1283
+ "can",
1284
+ "any",
1285
+ "all",
1286
+ "no",
1287
+ "after",
1288
+ "before",
1289
+ "during",
1290
+ "user",
1291
+ "agent",
1292
+ "code",
1293
+ "project",
1294
+ "via",
1295
+ "into",
1296
+ "onto",
1297
+ "even",
1298
+ "without",
1299
+ "naming",
1300
+ "run",
1301
+ "runs",
1302
+ "running",
1303
+ "each",
1304
+ "every",
1305
+ "one",
1306
+ "two",
1307
+ "now",
1308
+ "then",
1309
+ "also",
1310
+ "based",
1311
+ "default",
1312
+ "skill",
1313
+ "you",
1314
+ "your",
1315
+ "should",
1316
+ "must",
1317
+ "see",
1318
+ "do",
1319
+ "not"
1320
+ ]);
1321
+ function _isDir(p) {
1322
+ try {
1323
+ return fs8.statSync(p).isDirectory();
1324
+ } catch {
1325
+ return false;
1326
+ }
1327
+ }
1328
+ function _isFile(p) {
1329
+ try {
1330
+ return fs8.statSync(p).isFile();
1331
+ } catch {
1332
+ return false;
1333
+ }
1334
+ }
1335
+ function _default_skill_root() {
1336
+ return SRC_SKILLS();
1337
+ }
1338
+ function _keyword_vector(text) {
1339
+ const counter = /* @__PURE__ */ new Map();
1340
+ const re = /[a-z][a-z0-9_-]{2,}/g;
1341
+ const lowered = text.toLowerCase();
1342
+ let m;
1343
+ while ((m = re.exec(lowered)) !== null) {
1344
+ const t = m[0];
1345
+ if (!STOPWORDS.has(t)) {
1346
+ counter.set(t, (counter.get(t) ?? 0) + 1);
1347
+ }
1348
+ }
1349
+ return counter;
1350
+ }
1351
+ function _cosine(a, b) {
1352
+ const shared = [];
1353
+ for (const k of a.keys()) {
1354
+ if (b.has(k)) {
1355
+ shared.push(k);
1356
+ }
1357
+ }
1358
+ if (shared.length === 0) {
1359
+ return 0;
1360
+ }
1361
+ let num = 0;
1362
+ for (const t of shared) {
1363
+ num += a.get(t) * b.get(t);
1364
+ }
1365
+ let da = 0;
1366
+ for (const v of a.values()) {
1367
+ da += v * v;
1368
+ }
1369
+ let db = 0;
1370
+ for (const v of b.values()) {
1371
+ db += v * v;
1372
+ }
1373
+ da = Math.sqrt(da);
1374
+ db = Math.sqrt(db);
1375
+ return da && db ? num / (da * db) : 0;
1376
+ }
1377
+ function _parse(md, field = "body") {
1378
+ const text = fs8.readFileSync(md, "utf-8");
1379
+ let fm = {};
1380
+ let body = text;
1381
+ const m = FM_RE.exec(text);
1382
+ if (m && m.index === 0) {
1383
+ try {
1384
+ fm = (0, import_yaml2.parse)(m[1], { version: "1.1" }) ?? {};
1385
+ } catch {
1386
+ fm = {};
1387
+ }
1388
+ body = text.slice(m.index + m[0].length);
1389
+ }
1390
+ const isObj = fm !== null && typeof fm === "object" && !Array.isArray(fm);
1391
+ const name = isObj ? fm["name"] : null;
1392
+ const packsVal = isObj ? fm["packs"] : null;
1393
+ const descVal = isObj ? fm["description"] : null;
1394
+ const source = field === "description" ? descVal ? String(descVal) : "" : body;
1395
+ return {
1396
+ name: name ? String(name) : path8.basename(path8.dirname(md)),
1397
+ relpath: _relPosix(md, ROOT2),
1398
+ abspath: path8.resolve(md),
1399
+ packs: Array.isArray(packsVal) ? new Set(packsVal.map((x) => String(x))) : /* @__PURE__ */ new Set(),
1400
+ vector: _keyword_vector(source)
1401
+ };
1402
+ }
1403
+ function _relPosix(child, root) {
1404
+ return path8.relative(root, child).split(path8.sep).join("/");
1405
+ }
1406
+ function _rglobSkillMd(root) {
1407
+ const out = [];
1408
+ const walk = (dir) => {
1409
+ let entries;
1410
+ try {
1411
+ entries = fs8.readdirSync(dir, { withFileTypes: true });
1412
+ } catch {
1413
+ return;
1414
+ }
1415
+ for (const ent of entries) {
1416
+ const full = path8.join(dir, ent.name);
1417
+ if (ent.name === "SKILL.md") {
1418
+ out.push(full);
1419
+ }
1420
+ if (ent.isDirectory() || ent.isSymbolicLink() && _isDir(full)) {
1421
+ walk(full);
1422
+ }
1423
+ }
1424
+ };
1425
+ walk(root);
1426
+ out.sort(_pathlibCompare);
1427
+ return out;
1428
+ }
1429
+ function _pathlibCompare(a, b) {
1430
+ const pa = a.split(path8.sep);
1431
+ const pb = b.split(path8.sep);
1432
+ const n = Math.min(pa.length, pb.length);
1433
+ for (let i = 0; i < n; i++) {
1434
+ const x = pa[i];
1435
+ const y = pb[i];
1436
+ if (x !== y) {
1437
+ return x < y ? -1 : 1;
1438
+ }
1439
+ }
1440
+ return pa.length - pb.length;
1441
+ }
1442
+ function collect(root = _default_skill_root(), field = "body") {
1443
+ const skills = [];
1444
+ const seen = /* @__PURE__ */ new Set();
1445
+ for (const md of _rglobSkillMd(root)) {
1446
+ if (md.split(path8.sep).includes("_archive")) {
1447
+ continue;
1448
+ }
1449
+ const key = path8.basename(path8.dirname(md));
1450
+ if (seen.has(key)) {
1451
+ continue;
1452
+ }
1453
+ seen.add(key);
1454
+ skills.push(_parse(md, field));
1455
+ }
1456
+ return skills;
1457
+ }
1458
+ function pyRound(x, ndigits) {
1459
+ const factor = 10 ** ndigits;
1460
+ const scaled = x * factor;
1461
+ const floor = Math.floor(scaled);
1462
+ const diff = scaled - floor;
1463
+ let r;
1464
+ if (diff > 0.5) {
1465
+ r = floor + 1;
1466
+ } else if (diff < 0.5) {
1467
+ r = floor;
1468
+ } else {
1469
+ r = floor % 2 === 0 ? floor : floor + 1;
1470
+ }
1471
+ return r / factor;
1472
+ }
1473
+ function find_pairs(skills, threshold) {
1474
+ const pairs = [];
1475
+ for (let i = 0; i < skills.length; i++) {
1476
+ for (let j = i + 1; j < skills.length; j++) {
1477
+ const a = skills[i];
1478
+ const b = skills[j];
1479
+ const sim = _cosine(a.vector, b.vector);
1480
+ if (sim >= threshold) {
1481
+ const shared = [];
1482
+ for (const p of a.packs) {
1483
+ if (b.packs.has(p)) {
1484
+ shared.push(p);
1485
+ }
1486
+ }
1487
+ shared.sort();
1488
+ pairs.push({
1489
+ a: a.name,
1490
+ b: b.name,
1491
+ a_path: a.relpath,
1492
+ b_path: b.relpath,
1493
+ similarity: pyRound(sim, 3),
1494
+ same_domain: shared.length > 0,
1495
+ shared_packs: shared
1496
+ });
1497
+ }
1498
+ }
1499
+ }
1500
+ return stableSort(pairs, (p) => [p.same_domain ? 0 : 1, -p.similarity]);
1501
+ }
1502
+ function skillMdsUnder(root) {
1503
+ return _rglobSkillMd(root).filter((md) => !md.split(path8.sep).includes("_archive"));
1504
+ }
1505
+ function crossPairsForFiles(neighbourFiles, ourRoot, threshold = OVERLAP_THRESHOLD, field = "body") {
1506
+ const theirs = [];
1507
+ for (const md of neighbourFiles) {
1508
+ try {
1509
+ theirs.push(_parse(md, field));
1510
+ } catch {
1511
+ }
1512
+ }
1513
+ const ours = collect(ourRoot, field);
1514
+ const out = [];
1515
+ for (const n of theirs) {
1516
+ for (const o of ours) {
1517
+ const sim = _cosine(n.vector, o.vector);
1518
+ if (sim < threshold) continue;
1519
+ out.push({
1520
+ neighbour: n.name,
1521
+ neighbour_path: n.abspath,
1522
+ ours: o.name,
1523
+ similarity: pyRound(sim, 3)
1524
+ });
1525
+ }
1526
+ }
1527
+ return stableSort(out, (p) => [-p.similarity]);
1528
+ }
1529
+ function crossRootOverlapMap(pairs) {
1530
+ const out = /* @__PURE__ */ new Map();
1531
+ for (const p of pairs) {
1532
+ const hit = out.get(p.neighbour_path);
1533
+ if (hit === void 0) {
1534
+ out.set(p.neighbour_path, { names: [p.ours], similarity: p.similarity });
1535
+ continue;
1536
+ }
1537
+ if (!hit.names.includes(p.ours)) hit.names.push(p.ours);
1538
+ if (p.similarity > hit.similarity) hit.similarity = p.similarity;
1539
+ }
1540
+ for (const hit of out.values()) hit.names.sort();
1541
+ return out;
1542
+ }
1543
+ function censusDigest(neighbourFiles, ourRoot, threshold, field = "body") {
1544
+ const h = createHash3("sha256");
1545
+ h.update(`${String(threshold)}
1546
+ ${field}
1547
+ `);
1548
+ const sides = [
1549
+ [...neighbourFiles].sort(),
1550
+ skillMdsUnder(ourRoot)
1551
+ ];
1552
+ for (const side of sides) {
1553
+ h.update("side\n");
1554
+ for (const md of side) {
1555
+ let bytes;
1556
+ try {
1557
+ bytes = fs8.readFileSync(md);
1558
+ } catch {
1559
+ continue;
1560
+ }
1561
+ h.update(path8.resolve(md));
1562
+ h.update("\0");
1563
+ h.update(createHash3("sha256").update(bytes).digest("hex"));
1564
+ h.update("\n");
1565
+ }
1566
+ }
1567
+ return h.digest("hex");
1568
+ }
1569
+ function cachedCrossRootPairs(neighbourFiles, ourRoot, cacheFile, threshold = OVERLAP_THRESHOLD, field = "body") {
1570
+ const digest = censusDigest(neighbourFiles, ourRoot, threshold, field);
1571
+ try {
1572
+ const parsed = JSON.parse(fs8.readFileSync(cacheFile, "utf-8"));
1573
+ if (parsed.digest === digest && Array.isArray(parsed.pairs)) {
1574
+ return { pairs: parsed.pairs, digest, cached: true };
1575
+ }
1576
+ } catch {
1577
+ }
1578
+ const pairs = crossPairsForFiles(neighbourFiles, ourRoot, threshold, field);
1579
+ const payload = { schema_version: 1, digest, threshold, pairs };
1580
+ try {
1581
+ fs8.mkdirSync(path8.dirname(cacheFile), { recursive: true });
1582
+ fs8.writeFileSync(cacheFile, `${JSON.stringify(payload, null, 2)}
1583
+ `, "utf-8");
1584
+ } catch {
1585
+ }
1586
+ return { pairs, digest, cached: false };
1587
+ }
1588
+ function stableSort(arr, key) {
1589
+ return arr.map((v, i) => [v, i]).sort((x, y) => {
1590
+ const kx = key(x[0]);
1591
+ const ky = key(y[0]);
1592
+ for (let i = 0; i < Math.max(kx.length, ky.length); i++) {
1593
+ const d = (kx[i] ?? 0) - (ky[i] ?? 0);
1594
+ if (d !== 0) {
1595
+ return d;
1596
+ }
1597
+ }
1598
+ return x[1] - y[1];
1599
+ }).map(([v]) => v);
1600
+ }
1601
+ function _pct(x) {
1602
+ const pct = pyRound(x * 100, 0);
1603
+ return `${pct}%`;
1604
+ }
1605
+ function render_md(skills, pairs, threshold, field = "body") {
1606
+ const merge = pairs.filter((p) => p.same_domain);
1607
+ const cross = pairs.filter((p) => !p.same_domain);
1608
+ const L = [
1609
+ "# Skill-family overlap report (6.0.0-C Phase 4 Step 8)\n",
1610
+ field === "description" ? `> **NON-CANONICAL measurement.** Cosine over the \`description:\` frontmatter only, across ${skills.length} skills, at \u2265 ${_pct(threshold)}. Routing happens on descriptions, so this answers a question the canonical body metric does not \u2014 but its scores are NOT comparable to the canonical 0.70 merge threshold and must never be mixed into a merge decision. The canonical run is the one without \`--descriptions\`.
1611
+ ` : `> Content cosine-similarity over ${skills.length} skills; pairs at \u2265 ${_pct(threshold)}. **Same-domain pairs (shared \`packs:\`) are the merge candidates** consumed by [\`evidence-based-pruning.md\`](../../docs/contracts/evidence-based-pruning.md). This report merges NOTHING \u2014 it is input to a future, human-driven consolidation roadmap.
1612
+ `,
1613
+ `
1614
+ - Skills scanned: **${skills.length}**`,
1615
+ `
1616
+ - Overlap pairs \u2265 ${_pct(threshold)}: **${pairs.length}** (${merge.length} same-domain merge candidates, ${cross.length} cross-domain)
1617
+ `,
1618
+ "\n## Merge candidates \u2014 same-domain, \u2265 threshold\n"
1619
+ ];
1620
+ if (merge.length === 0) {
1621
+ L.push("None \u2014 no same-domain pair exceeds the overlap threshold.\n");
1622
+ } else {
1623
+ L.push("| Skill A | Skill B | similarity | shared packs |");
1624
+ L.push("|---|---|--:|---|");
1625
+ for (const p of merge) {
1626
+ L.push(
1627
+ `| \`${p.a}\` | \`${p.b}\` | ${_pct(p.similarity)} | ${p.shared_packs.join(", ")} |`
1628
+ );
1629
+ }
1630
+ }
1631
+ L.push("\n## Cross-domain overlaps (informational, not merge candidates)\n");
1632
+ if (cross.length === 0) {
1633
+ L.push("None.\n");
1634
+ } else {
1635
+ L.push("| Skill A | Skill B | similarity |");
1636
+ L.push("|---|---|--:|");
1637
+ for (const p of cross) {
1638
+ L.push(`| \`${p.a}\` | \`${p.b}\` | ${_pct(p.similarity)} |`);
1639
+ }
1640
+ }
1641
+ return L.join("\n") + "\n";
1642
+ }
1643
+ function _pyJsonStr(s) {
1644
+ let out = '"';
1645
+ for (const ch of s) {
1646
+ const code = ch.codePointAt(0);
1647
+ if (ch === '"') {
1648
+ out += '\\"';
1649
+ } else if (ch === "\\") {
1650
+ out += "\\\\";
1651
+ } else if (ch === "\n") {
1652
+ out += "\\n";
1653
+ } else if (ch === "\r") {
1654
+ out += "\\r";
1655
+ } else if (ch === " ") {
1656
+ out += "\\t";
1657
+ } else if (ch === "\b") {
1658
+ out += "\\b";
1659
+ } else if (ch === "\f") {
1660
+ out += "\\f";
1661
+ } else if (code < 32) {
1662
+ out += `\\u${code.toString(16).padStart(4, "0")}`;
1663
+ } else if (code < 127) {
1664
+ out += ch;
1665
+ } else if (code <= 65535) {
1666
+ out += `\\u${code.toString(16).padStart(4, "0")}`;
1667
+ } else {
1668
+ const c = code - 65536;
1669
+ const hi = 55296 + (c >> 10);
1670
+ const lo = 56320 + (c & 1023);
1671
+ out += `\\u${hi.toString(16).padStart(4, "0")}\\u${lo.toString(16).padStart(4, "0")}`;
1672
+ }
1673
+ }
1674
+ return out + '"';
1675
+ }
1676
+ function _pyNumber(n) {
1677
+ if (Number.isInteger(n)) {
1678
+ return String(n);
1679
+ }
1680
+ return String(n);
1681
+ }
1682
+ function _floatStr(n) {
1683
+ if (Number.isInteger(n)) {
1684
+ return `${n}.0`;
1685
+ }
1686
+ return String(n);
1687
+ }
1688
+ function _pairToJson(p) {
1689
+ return {
1690
+ a: p.a,
1691
+ b: p.b,
1692
+ a_path: p.a_path,
1693
+ b_path: p.b_path,
1694
+ // similarity is a float (round(x,3)); force float repr.
1695
+ similarity: new FloatTag(p.similarity),
1696
+ same_domain: p.same_domain,
1697
+ shared_packs: p.shared_packs
1698
+ };
1699
+ }
1700
+ var FloatTag = class {
1701
+ constructor(value) {
1702
+ this.value = value;
1703
+ }
1704
+ value;
1705
+ };
1706
+ function main4(argv = null) {
1707
+ const args = parse_args4(argv ?? process.argv.slice(2));
1708
+ const root = args.root ?? _default_skill_root();
1709
+ if (args.neighbourRoot !== void 0) {
1710
+ return _mainCrossRoot(args, root);
1711
+ }
1712
+ const skills = collect(root, args.field);
1713
+ try {
1714
+ assertScanned({
1715
+ gate: "audit_skill_overlap",
1716
+ scanned: skills.length,
1717
+ units: "skill(s)",
1718
+ roots: [_relPosix(root, ROOT2)]
1719
+ });
1720
+ } catch (exc) {
1721
+ if (!(exc instanceof DeadScopeError)) {
1722
+ throw exc;
1723
+ }
1724
+ process.stderr.write(`\u274C ${exc.message}
1725
+ `);
1726
+ return 3;
1727
+ }
1728
+ process.stderr.write(`scanned: ${String(skills.length)}
1729
+ `);
1730
+ const pairs = find_pairs(skills, args.threshold);
1731
+ const outDir = args.outDir ?? REPORT_DIR;
1732
+ const outJson = path8.join(outDir, OUT_JSON_NAME);
1733
+ const outMd = path8.join(outDir, OUT_MD_NAME);
1734
+ fs8.mkdirSync(outDir, { recursive: true });
1735
+ const jsonObj = {
1736
+ threshold: new FloatTag(args.threshold),
1737
+ skills_scanned: skills.length,
1738
+ pairs: pairs.map(_pairToJson)
1739
+ };
1740
+ fs8.writeFileSync(outJson, _dumpsWithFloats(jsonObj), "utf-8");
1741
+ fs8.writeFileSync(outMd, render_md(skills, pairs, args.threshold, args.field), "utf-8");
1742
+ const merge = pairs.filter((p) => p.same_domain);
1743
+ if (!args.quiet) {
1744
+ process.stdout.write(
1745
+ `\u2705 Skill overlap: ${skills.length} skills, ${pairs.length} pair(s) \u2265 ${_pct(args.threshold)} (${merge.length} same-domain merge candidate(s)).
1746
+ `
1747
+ );
1748
+ process.stdout.write(` JSON: ${_relPosix(outJson, ROOT2)}
1749
+ `);
1750
+ process.stdout.write(` MD: ${_relPosix(outMd, ROOT2)}
1751
+ `);
1752
+ }
1753
+ if (args.printTable) {
1754
+ process.stdout.write(
1755
+ `\u2139\uFE0F skill overlap${args.strict ? "" : " (advisory)"}: ${skills.length} skills scanned, ${merge.length} same-domain pair(s) \u2265 ${_pct(args.threshold)}
1756
+ `
1757
+ );
1758
+ for (const p of merge) {
1759
+ process.stdout.write(
1760
+ ` ${p.similarity.toFixed(3)} ${p.a} \u2194 ${p.b} [${p.shared_packs.join(", ")}]
1761
+ `
1762
+ );
1763
+ }
1764
+ }
1765
+ if (!args.strict) {
1766
+ return 0;
1767
+ }
1768
+ let allow;
1769
+ try {
1770
+ allow = _loadAllowlist(args.allowlist ?? ALLOWLIST);
1771
+ } catch (exc) {
1772
+ process.stderr.write(`\u274C ${exc instanceof Error ? exc.message : String(exc)}
1773
+ `);
1774
+ return 2;
1775
+ }
1776
+ const blocking = merge.filter((p) => !allow.has(_pairKey(p.a, p.b)));
1777
+ if (blocking.length === 0) {
1778
+ return 0;
1779
+ }
1780
+ process.stderr.write(
1781
+ `\u274C audit_skill_overlap: ${blocking.length} same-pack pair(s) at or above ${_pct(args.threshold)} with no reviewed justification:
1782
+ `
1783
+ );
1784
+ for (const p of blocking) {
1785
+ process.stderr.write(
1786
+ ` ${p.similarity.toFixed(3)} ${p.a} \u2194 ${p.b} [${p.shared_packs.join(", ")}]
1787
+ `
1788
+ );
1789
+ }
1790
+ process.stderr.write(
1791
+ ` Merge the pair, or add {"pair": "${_pairKey(blocking[0].a, blocking[0].b)}", "reason": "\u2026"} to ${_relPosix(args.allowlist ?? ALLOWLIST, ROOT2)} (cap ${ALLOWLIST_CAP}).
1792
+ `
1793
+ );
1794
+ return 1;
1795
+ }
1796
+ function _mainCrossRoot(args, ourRoot) {
1797
+ const neighbourRoot = args.neighbourRoot;
1798
+ const outDir = args.outDir ?? REPORT_DIR;
1799
+ const cacheFile = args.crossCache ?? path8.join(outDir, CROSS_CACHE_NAME);
1800
+ const { pairs, digest, cached } = cachedCrossRootPairs(
1801
+ skillMdsUnder(neighbourRoot),
1802
+ ourRoot,
1803
+ cacheFile,
1804
+ args.threshold,
1805
+ args.field
1806
+ );
1807
+ const theirs = collect(neighbourRoot, args.field);
1808
+ process.stderr.write(`scanned: ${String(theirs.length)}
1809
+ `);
1810
+ if (!args.quiet) {
1811
+ process.stdout.write(
1812
+ `\u2705 Neighbour overlap: ${theirs.length} neighbour skill(s) \xD7 ${collect(ourRoot, args.field).length} of ours, ${pairs.length} pair(s) \u2265 ${_pct(args.threshold)} (${cached ? "from cache" : "recomputed"}).
1813
+ `
1814
+ );
1815
+ process.stdout.write(` census digest: ${digest.slice(0, 12)}
1816
+ `);
1817
+ process.stdout.write(` cache: ${_relPosix(cacheFile, ROOT2)}
1818
+ `);
1819
+ }
1820
+ const byPath = crossRootOverlapMap(pairs);
1821
+ for (const skill of theirs) {
1822
+ const hit = byPath.get(skill.abspath);
1823
+ if (hit === void 0) continue;
1824
+ process.stdout.write(
1825
+ ` ${hit.similarity.toFixed(3)} ${skill.name} also: ${hit.names.join(", ")}
1826
+ `
1827
+ );
1828
+ }
1829
+ return 0;
1830
+ }
1831
+ function _pairKey(a, b) {
1832
+ return a < b ? `${a}::${b}` : `${b}::${a}`;
1833
+ }
1834
+ function _loadAllowlist(file) {
1835
+ if (!_isFile(file)) {
1836
+ return /* @__PURE__ */ new Set();
1837
+ }
1838
+ const data = JSON.parse(fs8.readFileSync(file, "utf-8"));
1839
+ const entries = Array.isArray(data.entries) ? data.entries : [];
1840
+ if (entries.length > ALLOWLIST_CAP) {
1841
+ throw new Error(
1842
+ `audit_skill_overlap: allowlist has ${entries.length} entries (> ${ALLOWLIST_CAP}). Per the autonomous-execution allowlist-growth antipattern, the threshold is wrong, not the corpus \u2014 re-litigate the bar, do not grow the list.`
1843
+ );
1844
+ }
1845
+ const out = /* @__PURE__ */ new Set();
1846
+ for (const e of entries) {
1847
+ const [a, b] = String(e.pair).split("::");
1848
+ if (a === void 0 || b === void 0 || (e.reason ?? "").trim() === "") {
1849
+ throw new Error(
1850
+ `audit_skill_overlap: allowlist entry ${JSON.stringify(e)} needs "pair": "a::b" and a non-empty "reason".`
1851
+ );
1852
+ }
1853
+ out.add(_pairKey(a, b));
1854
+ }
1855
+ return out;
1856
+ }
1857
+ function _dumpsWithFloats(obj, level = 0) {
1858
+ if (obj instanceof FloatTag) {
1859
+ return _floatStr(obj.value);
1860
+ }
1861
+ if (obj === null || obj === void 0) {
1862
+ return "null";
1863
+ }
1864
+ if (typeof obj === "number") {
1865
+ return _pyNumber(obj);
1866
+ }
1867
+ if (typeof obj === "string") {
1868
+ return _pyJsonStr(obj);
1869
+ }
1870
+ if (obj === true) {
1871
+ return "true";
1872
+ }
1873
+ if (obj === false) {
1874
+ return "false";
1875
+ }
1876
+ if (Array.isArray(obj)) {
1877
+ if (obj.length === 0) {
1878
+ return "[]";
1879
+ }
1880
+ const pad2 = " ".repeat(2 * (level + 1));
1881
+ const closePad2 = " ".repeat(2 * level);
1882
+ return `[
1883
+ ${obj.map((v) => pad2 + _dumpsWithFloats(v, level + 1)).join(",\n")}
1884
+ ${closePad2}]`;
1885
+ }
1886
+ const keys = Object.keys(obj);
1887
+ if (keys.length === 0) {
1888
+ return "{}";
1889
+ }
1890
+ const pad = " ".repeat(2 * (level + 1));
1891
+ const closePad = " ".repeat(2 * level);
1892
+ const parts = keys.map(
1893
+ (k) => `${pad}${_pyJsonStr(k)}: ${_dumpsWithFloats(obj[k], level + 1)}`
1894
+ );
1895
+ return `{
1896
+ ${parts.join(",\n")}
1897
+ ${closePad}}`;
1898
+ }
1899
+ function parse_args4(argv) {
1900
+ const args = {
1901
+ threshold: OVERLAP_THRESHOLD,
1902
+ quiet: false,
1903
+ printTable: false,
1904
+ strict: false,
1905
+ field: "body"
1906
+ };
1907
+ const takeValue = (flag, inline, next) => {
1908
+ const v = inline ?? next();
1909
+ if (v === void 0) {
1910
+ process.stderr.write(`argument ${flag}: expected one argument
1911
+ `);
1912
+ process.exit(2);
1913
+ }
1914
+ return v;
1915
+ };
1916
+ for (let i = 0; i < argv.length; i++) {
1917
+ const a = argv[i];
1918
+ if (a === "--quiet") {
1919
+ args.quiet = true;
1920
+ } else if (a === "--strict") {
1921
+ args.strict = true;
1922
+ } else if (a === "--allowlist" || a.startsWith("--allowlist=")) {
1923
+ const inline = a.startsWith("--allowlist=") ? a.slice("--allowlist=".length) : void 0;
1924
+ args.allowlist = path8.resolve(ROOT2, takeValue("--allowlist", inline, () => argv[++i]));
1925
+ } else if (a === "--descriptions") {
1926
+ args.field = "description";
1927
+ } else if (a === "--print-table") {
1928
+ args.printTable = true;
1929
+ } else if (a === "--threshold" || a.startsWith("--threshold=")) {
1930
+ const inline = a.startsWith("--threshold=") ? a.slice("--threshold=".length) : void 0;
1931
+ args.threshold = Number(takeValue("--threshold", inline, () => argv[++i]));
1932
+ } else if (a === "--neighbour-root" || a.startsWith("--neighbour-root=")) {
1933
+ const inline = a.startsWith("--neighbour-root=") ? a.slice("--neighbour-root=".length) : void 0;
1934
+ args.neighbourRoot = path8.resolve(
1935
+ ROOT2,
1936
+ takeValue("--neighbour-root", inline, () => argv[++i])
1937
+ );
1938
+ } else if (a === "--cross-cache" || a.startsWith("--cross-cache=")) {
1939
+ const inline = a.startsWith("--cross-cache=") ? a.slice("--cross-cache=".length) : void 0;
1940
+ args.crossCache = path8.resolve(ROOT2, takeValue("--cross-cache", inline, () => argv[++i]));
1941
+ } else if (a === "--root" || a.startsWith("--root=")) {
1942
+ const inline = a.startsWith("--root=") ? a.slice("--root=".length) : void 0;
1943
+ args.root = path8.resolve(ROOT2, takeValue("--root", inline, () => argv[++i]));
1944
+ } else if (a === "--out-dir" || a.startsWith("--out-dir=")) {
1945
+ const inline = a.startsWith("--out-dir=") ? a.slice("--out-dir=".length) : void 0;
1946
+ args.outDir = path8.resolve(ROOT2, takeValue("--out-dir", inline, () => argv[++i]));
1947
+ } else {
1948
+ process.stderr.write(`unrecognized arguments: ${a}
1949
+ `);
1950
+ process.exit(2);
1951
+ }
1952
+ }
1953
+ return args;
1954
+ }
1955
+ function _isCliEntry4() {
1956
+ if (process.argv[1] === void 0) {
1957
+ return false;
1958
+ }
1959
+ const argvUrl = pathToFileURL4(path8.resolve(process.argv[1])).href;
1960
+ if (import.meta.url === argvUrl) {
1961
+ return true;
1962
+ }
1963
+ try {
1964
+ const here = fs8.realpathSync(fileURLToPath4(import.meta.url));
1965
+ const argv = fs8.realpathSync(path8.resolve(process.argv[1]));
1966
+ return here === argv;
1967
+ } catch {
1968
+ return false;
1969
+ }
1970
+ }
1971
+ var _isMain = _isCliEntry4();
1972
+ if (_isMain) {
1973
+ process.exitCode = main4();
1974
+ }
1975
+
1976
+ // src/scripts/_cli/cmd_doctor_neighbours.ts
1977
+ function gatedEvents(packageRoot) {
1978
+ try {
1979
+ const file = path9.join(packageRoot, "src", "scripts", "hook_manifest.yaml");
1980
+ const doc = YAML.parse(fs9.readFileSync(file, "utf-8"));
1981
+ return gatedEventsFrom(doc, doc.native_event_aliases ?? {});
1982
+ } catch {
1983
+ return /* @__PURE__ */ new Set();
1984
+ }
1985
+ }
1986
+ function ourSkillNames(packageRoot) {
1987
+ try {
1988
+ return new Set(
1989
+ fs9.readdirSync(path9.join(packageRoot, "src", "skills"), { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name)
1990
+ );
1991
+ } catch {
1992
+ return /* @__PURE__ */ new Set();
1993
+ }
1994
+ }
1995
+ function runCensus(projectRoot, packageRoot, homeRoot) {
1996
+ const scanned = /* @__PURE__ */ new Map();
1997
+ const scan = (p) => {
1998
+ const hit = scanned.get(p);
1999
+ if (hit !== void 0) return hit;
2000
+ const kind = scanNeighbourBody(p).kind;
2001
+ scanned.set(p, kind);
2002
+ return kind;
2003
+ };
2004
+ const base = {
2005
+ gatedEvents: gatedEvents(packageRoot),
2006
+ templatesRoot: path9.join(packageRoot, "src", "agent-src", "templates"),
2007
+ ourSkillNames: ourSkillNames(packageRoot),
2008
+ packageRoot,
2009
+ scan,
2010
+ ...homeRoot === void 0 ? {} : { homeRoot }
2011
+ };
2012
+ const first = census(projectRoot, base);
2013
+ const ourRoot = path9.join(packageRoot, "src", "skills");
2014
+ let overlaps = /* @__PURE__ */ new Map();
2015
+ if (first.skills.length > 0 && fs9.existsSync(ourRoot)) {
2016
+ try {
2017
+ const { pairs } = cachedCrossRootPairs(
2018
+ first.skills.map((s) => s.source),
2019
+ ourRoot,
2020
+ path9.join(projectRoot, "agents", "reports", CROSS_CACHE_NAME),
2021
+ OVERLAP_THRESHOLD
2022
+ );
2023
+ overlaps = crossRootOverlapMap(pairs);
2024
+ } catch {
2025
+ }
2026
+ }
2027
+ const full = census(projectRoot, { ...base, overlaps });
2028
+ writeScanCache(
2029
+ scanCachePath(projectRoot),
2030
+ // `fileDigest`, not the census's own `digest`: the ranker re-digests the
2031
+ // file bytes it is about to index, and a cache written under a different
2032
+ // digest function would read as `digest-changed` on every prompt.
2033
+ full.skills.map((s) => ({
2034
+ qualified: s.qualified,
2035
+ digest: fileDigest(s.source) ?? "",
2036
+ unscanned: s.unscanned
2037
+ }))
2038
+ );
2039
+ return full;
2040
+ }
2041
+ function summaryLine(c) {
2042
+ const counts = [
2043
+ `${c.hook_groups.length} hook group(s)`,
2044
+ `${c.skills.length} skill(s)`,
2045
+ `${c.commands.length} command(s)`,
2046
+ `${c.agents.length} agent(s)`,
2047
+ `${c.mcp_servers.length} mcp server(s)`,
2048
+ `${c.rule_files.length} rule file(s)`,
2049
+ `${c.instruction_sections.length} instruction section(s)`
2050
+ ].join(", ");
2051
+ const labels = c.hosts.map((h) => `${h.host}=${h.label}`).join(" ");
2052
+ return ` \u{1F3D8}\uFE0F neighbours: ${counts}${labels === "" ? "" : ` \xB7 ${labels}`} (run \`doctor neighbours\` for the census)`;
2053
+ }
2054
+ function renderText(c) {
2055
+ const out = [];
2056
+ out.push(` \u{1F4CD} project_root: ${c.project_root}`);
2057
+ out.push("");
2058
+ for (const h of c.hosts) {
2059
+ out.push(` ${h.host}: ${h.label} \u2014 ${h.label_reason}`);
2060
+ for (const e of h.hook_groups) {
2061
+ const matcher = e.matcher === null ? "" : ` matcher=${e.matcher}`;
2062
+ const timeout = e.timeout === null ? "" : ` timeout=${e.timeout}`;
2063
+ out.push(` ${e.event}: ${e.command_head}${matcher}${timeout} effect=${e.effect}`);
2064
+ }
2065
+ }
2066
+ const section = (title, rows) => {
2067
+ if (rows.length === 0) return;
2068
+ out.push("");
2069
+ out.push(` ${title} (${rows.length})`);
2070
+ for (const r of rows) out.push(` ${r.id}`);
2071
+ };
2072
+ if (c.skills.length > 0) {
2073
+ out.push("");
2074
+ out.push(` skills (${c.skills.length})`);
2075
+ for (const s of c.skills) {
2076
+ const also = s.also.length === 0 ? "" : ` also: ${s.also.join(", ")}`;
2077
+ const why = s.unscanned === null ? "" : ` unscanned: ${s.unscanned}`;
2078
+ out.push(` ${s.qualified} compat: ${s.compat}${also}${why}`);
2079
+ }
2080
+ }
2081
+ section("commands", c.commands);
2082
+ section("agents", c.agents);
2083
+ if (c.mcp_servers.length > 0) {
2084
+ out.push("");
2085
+ out.push(` mcp servers (${c.mcp_servers.length})`);
2086
+ for (const m of c.mcp_servers) {
2087
+ out.push(
2088
+ ` ${m.id} tools used (${m.tools_window_days}d): ${m.tools_used_30d} advertised: ${m.tools_advertised}`
2089
+ );
2090
+ }
2091
+ }
2092
+ section("rule files", c.rule_files);
2093
+ section("instruction sections", c.instruction_sections);
2094
+ if (c.warnings.length > 0) {
2095
+ out.push("");
2096
+ for (const w of c.warnings) out.push(` \u26A0\uFE0F ${w.kind}: ${w.detail}`);
2097
+ }
2098
+ out.push("");
2099
+ out.push(
2100
+ " The host owns load order. This census says what is there and what each entry would do;"
2101
+ );
2102
+ out.push(" it cannot say what ran first, and no label here refuses anything.");
2103
+ return out;
2104
+ }
2105
+ function main5(argv = process.argv.slice(2)) {
2106
+ let json = false;
2107
+ let root = process.cwd();
2108
+ let home;
2109
+ for (let i = 0; i < argv.length; i += 1) {
2110
+ if (argv[i] === "--json") json = true;
2111
+ else if (argv[i] === "--project") {
2112
+ root = argv[i + 1] ?? root;
2113
+ i += 1;
2114
+ } else if (argv[i] === "--home") {
2115
+ home = argv[i + 1];
2116
+ i += 1;
2117
+ } else if (argv[i] === "neighbours") {
2118
+ continue;
2119
+ } else {
2120
+ process.stderr.write(`doctor neighbours: unknown argument: ${argv[i]}
2121
+ `);
2122
+ return 2;
2123
+ }
2124
+ }
2125
+ const c = runCensus(path9.resolve(root), resolvePackageRoot(import.meta.url), home);
2126
+ if (json) {
2127
+ process.stdout.write(`${JSON.stringify(c)}
2128
+ `);
2129
+ } else {
2130
+ for (const line of renderText(c)) process.stdout.write(`${line}
2131
+ `);
2132
+ }
2133
+ return 0;
2134
+ }
2135
+ function _isCliEntry5() {
2136
+ const bundled = true;
2137
+ const cliDelegate = true;
2138
+ if (bundled && !cliDelegate) return false;
2139
+ if (process.argv[1] === void 0) return false;
2140
+ if (cliDelegate && path9.basename(process.argv[1], ".js") === "cmd_doctor_neighbours") {
2141
+ return true;
2142
+ }
2143
+ const argvUrl = pathToFileURL5(path9.resolve(process.argv[1])).href;
2144
+ if (import.meta.url === argvUrl) return true;
2145
+ try {
2146
+ return fs9.realpathSync(fileURLToPath5(import.meta.url)) === fs9.realpathSync(path9.resolve(process.argv[1]));
2147
+ } catch {
2148
+ return false;
2149
+ }
2150
+ }
2151
+ if (_isCliEntry5()) {
2152
+ process.exit(main5());
2153
+ }
2154
+
2155
+ export {
2156
+ runCensus,
2157
+ summaryLine,
2158
+ renderText,
2159
+ main5 as main
2160
+ };