@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,293 @@
1
+ /**
2
+ * `agent-config doctor neighbours` — render the neighbour census.
3
+ *
4
+ * A separate entry point rather than another branch inside `cmd_doctor.ts`,
5
+ * which sits 2,000 lines above the source-size ceiling: a verb this size does
6
+ * not belong in a file that already cannot grow. The census itself is
7
+ * `_lib/neighbour_census.ts`; everything here is presentation and the one
8
+ * summary line the full `doctor` run links to.
9
+ *
10
+ * Refuses nothing. No label here removes, blocks, or rewrites a neighbour's
11
+ * artifact — a label is a report, never a gate.
12
+ *
13
+ * It is no longer read-ONLY, and the two writes are named rather than implied.
14
+ * Both land under `agents/reports/` and both are caches of this run's own
15
+ * findings: `neighbour-scan.json`, the shape-scan verdicts the ranker reads on
16
+ * the hook path, and `neighbour-overlap.json`, the cross-root pairs keyed by the
17
+ * census digest. Nothing outside that directory is touched, and a failed write
18
+ * is not a failed census.
19
+ */
20
+
21
+ import * as fs from 'node:fs';
22
+ import * as path from 'node:path';
23
+ import { fileURLToPath, pathToFileURL } from 'node:url';
24
+
25
+ import * as YAML from 'yaml';
26
+
27
+ import {
28
+ census,
29
+ fileDigest,
30
+ gatedEventsFrom,
31
+ scanCachePath,
32
+ type NeighbourCensus,
33
+ } from '../_lib/neighbour_census.js';
34
+ import { scanNeighbourBody, writeScanCache } from '../_lib/neighbour_scan.js';
35
+ import { resolvePackageRoot } from '../_lib/package_root.js';
36
+ import {
37
+ OVERLAP_THRESHOLD,
38
+ CROSS_CACHE_NAME,
39
+ cachedCrossRootPairs,
40
+ crossRootOverlapMap,
41
+ } from '../audit_skill_overlap.js';
42
+
43
+ interface ManifestShape {
44
+ concerns?: Record<string, { effect?: string }>;
45
+ platforms?: Record<string, Record<string, unknown>>;
46
+ native_event_aliases?: Record<string, Record<string, string>>;
47
+ }
48
+
49
+ /** Read the hook manifest for the events a `permission` concern of ours sits on. */
50
+ function gatedEvents(packageRoot: string): Set<string> {
51
+ try {
52
+ // A missing or unreadable manifest yields an empty gate set, which
53
+ // downgrades `degraded` to `coordinated` rather than inventing a gate
54
+ // that is not there.
55
+ const file = path.join(packageRoot, 'src', 'scripts', 'hook_manifest.yaml');
56
+ const doc = YAML.parse(fs.readFileSync(file, 'utf-8')) as ManifestShape;
57
+ return gatedEventsFrom(doc, doc.native_event_aliases ?? {});
58
+ } catch {
59
+ return new Set<string>();
60
+ }
61
+ }
62
+
63
+ /** This package's own skill names, for the `shadowed` warning. */
64
+ function ourSkillNames(packageRoot: string): Set<string> {
65
+ try {
66
+ return new Set(
67
+ fs
68
+ .readdirSync(path.join(packageRoot, 'src', 'skills'), { withFileTypes: true })
69
+ .filter((e) => e.isDirectory())
70
+ .map((e) => e.name),
71
+ );
72
+ } catch {
73
+ return new Set<string>();
74
+ }
75
+ }
76
+
77
+ /**
78
+ * The census, in two passes, and the second one is not an accident.
79
+ *
80
+ * The cross-root overlap can only be computed over the neighbour skills the
81
+ * census FOUND — the unclaimed entries of two roots that also hold claimed ones,
82
+ * since `agent-config install` writes this package's own skills into
83
+ * `~/.claude/skills`. Pairing whole roots instead would compare our installed
84
+ * copies against our own source tree and publish the install as an overlap. So
85
+ * pass one finds them, the overlap runs over exactly those files, and pass two
86
+ * labels them.
87
+ *
88
+ * The shape scan is memoized across both passes, so the four linters run once
89
+ * per neighbour body rather than twice. The scan verdicts are then WRITTEN to
90
+ * the cache the ranker reads on the hook path — this verb is the only producer,
91
+ * which is why its absence reads there as `no-scan-record` rather than as a pass.
92
+ */
93
+ export function runCensus(
94
+ projectRoot: string,
95
+ packageRoot: string,
96
+ homeRoot?: string,
97
+ ): NeighbourCensus {
98
+ const scanned = new Map<string, string | null>();
99
+ const scan = (p: string): string | null => {
100
+ const hit = scanned.get(p);
101
+ if (hit !== undefined) return hit;
102
+ const kind = scanNeighbourBody(p).kind;
103
+ scanned.set(p, kind);
104
+ return kind;
105
+ };
106
+ const base = {
107
+ gatedEvents: gatedEvents(packageRoot),
108
+ templatesRoot: path.join(packageRoot, 'src', 'agent-src', 'templates'),
109
+ ourSkillNames: ourSkillNames(packageRoot),
110
+ packageRoot,
111
+ scan,
112
+ ...(homeRoot === undefined ? {} : { homeRoot }),
113
+ };
114
+ const first = census(projectRoot, base);
115
+ const ourRoot = path.join(packageRoot, 'src', 'skills');
116
+ let overlaps = new Map<string, { names: string[]; similarity: number }>();
117
+ if (first.skills.length > 0 && fs.existsSync(ourRoot)) {
118
+ try {
119
+ const { pairs } = cachedCrossRootPairs(
120
+ first.skills.map((s) => s.source),
121
+ ourRoot,
122
+ path.join(projectRoot, 'agents', 'reports', CROSS_CACHE_NAME),
123
+ OVERLAP_THRESHOLD,
124
+ );
125
+ overlaps = crossRootOverlapMap(pairs);
126
+ } catch {
127
+ // An overlap pass that cannot run leaves every entry unlabelled by
128
+ // it, which reads as `unclassified` — the absence of a signal — and
129
+ // never as a claim that the pair is disjoint.
130
+ }
131
+ }
132
+ const full = census(projectRoot, { ...base, overlaps });
133
+ writeScanCache(
134
+ scanCachePath(projectRoot),
135
+ // `fileDigest`, not the census's own `digest`: the ranker re-digests the
136
+ // file bytes it is about to index, and a cache written under a different
137
+ // digest function would read as `digest-changed` on every prompt.
138
+ full.skills.map((s) => ({
139
+ qualified: s.qualified,
140
+ digest: fileDigest(s.source) ?? '',
141
+ unscanned: s.unscanned,
142
+ })),
143
+ );
144
+ return full;
145
+ }
146
+
147
+ /** The one line the full `doctor` run prints, linking to this verb. */
148
+ export function summaryLine(c: NeighbourCensus): string {
149
+ const counts = [
150
+ `${c.hook_groups.length} hook group(s)`,
151
+ `${c.skills.length} skill(s)`,
152
+ `${c.commands.length} command(s)`,
153
+ `${c.agents.length} agent(s)`,
154
+ `${c.mcp_servers.length} mcp server(s)`,
155
+ `${c.rule_files.length} rule file(s)`,
156
+ `${c.instruction_sections.length} instruction section(s)`,
157
+ ].join(', ');
158
+ const labels = c.hosts.map((h) => `${h.host}=${h.label}`).join(' ');
159
+ return ` 🏘️ neighbours: ${counts}${labels === '' ? '' : ` · ${labels}`} (run \`doctor neighbours\` for the census)`;
160
+ }
161
+
162
+ export function renderText(c: NeighbourCensus): string[] {
163
+ const out: string[] = [];
164
+ out.push(` 📍 project_root: ${c.project_root}`);
165
+ out.push('');
166
+ for (const h of c.hosts) {
167
+ out.push(` ${h.host}: ${h.label} — ${h.label_reason}`);
168
+ for (const e of h.hook_groups) {
169
+ const matcher = e.matcher === null ? '' : ` matcher=${e.matcher}`;
170
+ const timeout = e.timeout === null ? '' : ` timeout=${e.timeout}`;
171
+ out.push(` ${e.event}: ${e.command_head}${matcher}${timeout} effect=${e.effect}`);
172
+ }
173
+ }
174
+ const section = (title: string, rows: ReadonlyArray<{ id: string }>): void => {
175
+ if (rows.length === 0) return;
176
+ out.push('');
177
+ out.push(` ${title} (${rows.length})`);
178
+ for (const r of rows) out.push(` ${r.id}`);
179
+ };
180
+ if (c.skills.length > 0) {
181
+ out.push('');
182
+ out.push(` skills (${c.skills.length})`);
183
+ for (const s of c.skills) {
184
+ const also = s.also.length === 0 ? '' : ` also: ${s.also.join(', ')}`;
185
+ const why = s.unscanned === null ? '' : ` unscanned: ${s.unscanned}`;
186
+ out.push(` ${s.qualified} compat: ${s.compat}${also}${why}`);
187
+ }
188
+ }
189
+ section('commands', c.commands);
190
+ section('agents', c.agents);
191
+ if (c.mcp_servers.length > 0) {
192
+ out.push('');
193
+ out.push(` mcp servers (${c.mcp_servers.length})`);
194
+ for (const m of c.mcp_servers) {
195
+ // `used` and `advertised` are printed side by side so the reader
196
+ // never takes the first for a coverage ratio: the denominator is
197
+ // not missing by accident, it is not obtainable without launching
198
+ // the neighbour's process, which this census does not do.
199
+ out.push(
200
+ ` ${m.id} tools used (${m.tools_window_days}d): ${m.tools_used_30d}` +
201
+ ` advertised: ${m.tools_advertised}`,
202
+ );
203
+ }
204
+ }
205
+ section('rule files', c.rule_files);
206
+ section('instruction sections', c.instruction_sections);
207
+ if (c.warnings.length > 0) {
208
+ out.push('');
209
+ for (const w of c.warnings) out.push(` ⚠️ ${w.kind}: ${w.detail}`);
210
+ }
211
+ out.push('');
212
+ out.push(
213
+ ' The host owns load order. This census says what is there and what each entry would do;',
214
+ );
215
+ out.push(' it cannot say what ran first, and no label here refuses anything.');
216
+ return out;
217
+ }
218
+
219
+ export function main(argv: readonly string[] = process.argv.slice(2)): number {
220
+ let json = false;
221
+ let root = process.cwd();
222
+ let home: string | undefined;
223
+ for (let i = 0; i < argv.length; i += 1) {
224
+ if (argv[i] === '--json') json = true;
225
+ else if (argv[i] === '--project') {
226
+ root = argv[i + 1] ?? root;
227
+ i += 1;
228
+ } else if (argv[i] === '--home') {
229
+ // Fixture-only: the user-scope half of the census is real `$HOME`
230
+ // in production, and a test must be able to plant one.
231
+ home = argv[i + 1];
232
+ i += 1;
233
+ } else if (argv[i] === 'neighbours') {
234
+ continue;
235
+ } else {
236
+ process.stderr.write(`doctor neighbours: unknown argument: ${argv[i]}\n`);
237
+ return 2;
238
+ }
239
+ }
240
+ const c = runCensus(path.resolve(root), resolvePackageRoot(import.meta.url), home);
241
+ if (json) {
242
+ // Compact, not pretty-printed: this is the machine contract, and the
243
+ // gate that reads it matches on adjacency rather than on indentation.
244
+ process.stdout.write(`${JSON.stringify(c)}\n`);
245
+ } else {
246
+ for (const line of renderText(c)) process.stdout.write(`${line}\n`);
247
+ }
248
+ return 0;
249
+ }
250
+
251
+ /**
252
+ * Same entry guard the sibling CLIs use — including the delegate branch, which
253
+ * an earlier version of this file left out and which the delegate smoke test
254
+ * caught: `no bundle is a silent no-op` reported `cmd_doctor_neighbours.js (no
255
+ * output, exit 0)`, the exact shape the paragraph below describes.
256
+ *
257
+ * Inside the cli-delegate bundle `--splitting` moves this module's body into a
258
+ * shared chunk, so the URL comparison weighs the CHUNK against `argv[1]` and
259
+ * never matches. `agent-config doctor` shipped that way once already — a
260
+ * diagnostic reporting success while saying nothing. The invoked file name is
261
+ * the reliable signal there; a miss falls THROUGH to the realpath comparison
262
+ * rather than returning false, so a symlinked or renamed invocation still
263
+ * resolves. Inlined into the installer / hook / MCP bundle instead, it must
264
+ * never auto-run, which is why one "am I bundled" flag cannot answer "may I
265
+ * run" and both flags are read.
266
+ */
267
+ declare const __AGENT_CONFIG_BUNDLE__: boolean | undefined;
268
+ declare const __AGENT_CONFIG_CLI_DELEGATE__: boolean | undefined;
269
+
270
+ function _isCliEntry(): boolean {
271
+ const bundled = typeof __AGENT_CONFIG_BUNDLE__ !== 'undefined' && __AGENT_CONFIG_BUNDLE__;
272
+ const cliDelegate =
273
+ typeof __AGENT_CONFIG_CLI_DELEGATE__ !== 'undefined' && __AGENT_CONFIG_CLI_DELEGATE__;
274
+ if (bundled && !cliDelegate) return false;
275
+ if (process.argv[1] === undefined) return false;
276
+ if (cliDelegate && path.basename(process.argv[1], '.js') === 'cmd_doctor_neighbours') {
277
+ return true;
278
+ }
279
+ const argvUrl = pathToFileURL(path.resolve(process.argv[1])).href;
280
+ if (import.meta.url === argvUrl) return true;
281
+ try {
282
+ return (
283
+ fs.realpathSync(fileURLToPath(import.meta.url)) ===
284
+ fs.realpathSync(path.resolve(process.argv[1]))
285
+ );
286
+ } catch {
287
+ return false;
288
+ }
289
+ }
290
+
291
+ if (_isCliEntry()) {
292
+ process.exit(main());
293
+ }
@@ -23,6 +23,15 @@ import {
23
23
  protectionActions,
24
24
  type ForgeReading,
25
25
  } from '../_lib/forge_protection.js';
26
+ import {
27
+ FORGE_TOTAL_BUDGET_MS,
28
+ budgetOf,
29
+ forgeReadingFor,
30
+ liveForgeApi,
31
+ originUrl,
32
+ resolveForgeRepo,
33
+ type Runner,
34
+ } from '../_lib/forge_reader.js';
26
35
 
27
36
  type Dict = Record<string, unknown>;
28
37
 
@@ -52,12 +61,17 @@ export function executionJson(overrides: () => OverrideStream): Dict {
52
61
  /**
53
62
  * The `forge_protection` block of `doctor --json` — Phase 3.2.
54
63
  *
55
- * **`doctor` does NOT reach the network on its own.** A diagnostic that makes a
56
- * `gh` call on every invocation is one nobody runs offline, and the sibling
57
- * anchor gate already declined that cost for the same reason. So the reading is
58
- * INJECTED: a caller that has queried the forge passes it, and a caller that has
59
- * not passes the all-null reading, which produces five `unread` rows that still
60
- * name the call each value would come from.
64
+ * **THIS function does not reach the network; `doctor` now does.** The mapper
65
+ * stays pure and takes an INJECTED reading — a caller that queried the forge
66
+ * passes it, one that did not passes the all-null reading, which produces five
67
+ * `unread` rows that still name the call each value would come from. What
68
+ * changed is the caller: `forgeProtectionJsonFor` below acquires that reading,
69
+ * so `doctor --json` does make `gh` calls. Phase 3.2's "`doctor` does not reach
70
+ * the network" is SUPERSEDED, and this header said otherwise for three review
71
+ * rounds while the function forty lines down called it a reversal — the third
72
+ * copy of a refuted argument in this diff, each found by a different round.
73
+ * Corrected here for the reason the others were: a header is where a dead
74
+ * argument gets reused.
61
75
  *
62
76
  * That is the honest shape for *read, never guessed*: the block is always
63
77
  * present, a value appears only when a named call produced it, and `unread` is a
@@ -67,24 +81,93 @@ export function executionJson(overrides: () => OverrideStream): Dict {
67
81
  * `actions` carries the human ACTION lines — the step's own words are that a
68
82
  * missing row is a blocker entry and NOT a halt, so this reports and returns.
69
83
  */
70
- export function forgeProtectionJson(reading: ForgeReading): Dict {
84
+ export function forgeProtectionJson(reading: ForgeReading, repo: string | null = null): Dict {
71
85
  const rows = forgeProtectionRows(reading);
86
+ const readFromForge = rows.some((r) => r.state !== 'unread');
87
+ // **The slug is reported only when something was actually read, and that
88
+ // invariant is structural rather than a caller's promise.** The first
89
+ // version reported whatever the git remote resolved to, which is a LOCAL
90
+ // read that succeeds with no network — so an ordinary offline run (GitHub
91
+ // remote present, no connectivity, no opt-out) emitted a named repository
92
+ // and substituted sources while every row was `unread`. That made the
93
+ // claim this change rests on — offline output is what Phase 3.2 shipped —
94
+ // false on exactly the failed-live path the council condition names. The
95
+ // earlier verification only ever exercised the opt-out switch and read it
96
+ // as offline; the two are not the same path.
97
+ const anonymous = repo === null || !readFromForge;
98
+ const name = (s: string): string =>
99
+ anonymous ? s : s.replace(/\{owner\}\/\{repo\}/g, () => repo);
72
100
  return {
101
+ repository: readFromForge ? repo : null,
73
102
  rows: rows.map((r) => ({
74
103
  id: r.id,
75
104
  state: r.state,
76
- source: r.source,
105
+ source: name(r.source),
77
106
  detail: r.detail,
78
107
  })),
79
- actions: protectionActions(rows),
80
- read_from_forge: rows.some((r) => r.state !== 'unread'),
108
+ actions: protectionActions(rows).map(name),
109
+ read_from_forge: readFromForge,
81
110
  };
82
111
  }
83
112
 
84
- /** The all-null reading: nothing was queried. */
85
- export const UNREAD_FORGE: ForgeReading = {
86
- rulesets: null,
87
- defaultBranch: null,
88
- allowAutoMerge: null,
89
- deployRestricted: null,
90
- };
113
+ /**
114
+ * The block as `doctor` itself builds it — AC-5.
115
+ *
116
+ * Phase 3.2 left `forgeProtectionJson` with no production caller, so the
117
+ * command the criterion names reported five `unread` rows while the forge
118
+ * satisfied all five, and the rows were only ever established by a human
119
+ * running `gh api` and reading the mapper in a test. This is the wiring that
120
+ * makes the named command answer for itself.
121
+ *
122
+ * **This IS a reversal of Phase 3.2's module boundary, and the roadmap records
123
+ * the supersession.** An earlier draft of this comment argued the opposite —
124
+ * "an addition rather than a reversal" — which a 2/2 convergent council pass
125
+ * refuted: that note recorded two boundaries, `doctor` does not reach the
126
+ * network AND the reading is injected, and this reverses both. The refuted
127
+ * argument is corrected here rather than left standing, because a comment is
128
+ * what the next reader of this module finds first and is where a refuted
129
+ * reasoning would get reused.
130
+ *
131
+ * What survives is the AVAILABILITY property, which is narrower than the
132
+ * earlier claim and is the one the supersession rests on: every failure path in
133
+ * `forgeReadingFor` — opt-out switch, no GitHub remote, no `gh`, no
134
+ * credentials, timeout, unparseable payload, a throw — returns the same
135
+ * `UNREAD_FORGE` reading, so offline the ROWS, their `source` templates and the
136
+ * action lines are what Phase 3.2 printed. Not the whole document: the block
137
+ * gained a top-level `repository` key, which is `null` on every such path. A
138
+ * consumer validating `forge_protection` against a closed schema sees that one
139
+ * added key; "prints exactly what it printed before" was too strong and is not
140
+ * claimed.
141
+ *
142
+ * `process.env` and the git remote are read HERE rather than inside the reader,
143
+ * so the reader stays injectable and every branch above is reachable from a
144
+ * test without spawning anything.
145
+ */
146
+ export interface ForgeDeps {
147
+ readonly env?: Readonly<Record<string, string | undefined>>;
148
+ /** The subprocess runner for both `git` and `gh`. */
149
+ readonly run?: Runner;
150
+ }
151
+
152
+ export function forgeProtectionJsonFor(root: string, deps: ForgeDeps = {}): Dict {
153
+ // The composition root — budget, thunk, host and cwd wired together — had
154
+ // no seam and therefore no test, so the opt-out-before-spawn and host
155
+ // binding were asserted one layer down against fakes while a regression in
156
+ // the real wiring stayed invisible. `deps` is that seam; production passes
157
+ // nothing and gets `process.env` plus the real spawn.
158
+ const env = deps.env ?? process.env;
159
+ const run = deps.run;
160
+ const remaining = budgetOf(FORGE_TOTAL_BUDGET_MS);
161
+ const read = forgeReadingFor({
162
+ env,
163
+ // A thunk, so the opt-out short-circuits before `git` spawns.
164
+ resolveRepo: () => resolveForgeRepo(originUrl(root, run)),
165
+ // Built from the RESOLVED target, so `gh` is addressed at the host and
166
+ // directory the slug came from. The per-call ceiling is whatever is
167
+ // left of the whole-read budget, so a call admitted near the deadline
168
+ // cannot run past it.
169
+ apiFor: (t) => liveForgeApi(remaining, run, { host: t.host, cwd: root }),
170
+ remaining,
171
+ });
172
+ return forgeProtectionJson(read.reading, read.repo);
173
+ }
@@ -0,0 +1,226 @@
1
+ /**
2
+ * What this install does on the network — the two `doctor` surfaces that
3
+ * answer that question, in one module under the source-size cap.
4
+ *
5
+ * `road-to-host-traffic-knobs-that-ship` Phases 2.1 and 2.2. Its own module
6
+ * rather than a function inside `cmd_doctor.ts` for the reason
7
+ * `_lib/doctor_runtime_checks.ts` states for itself: that file is ~3,600 lines
8
+ * and `check_source_size_budget` counts every line above 1,500, so a change
9
+ * that adds lines there pays for them by taking lines away. Wiring the traffic
10
+ * section in costs two lines; `checkOfflineReadiness` moving here returns more
11
+ * than that, and lands beside a cohesive sibling rather than in an arbitrary
12
+ * bin — "can this install work with no registry" and "which host traffic is
13
+ * switched off" are the same question asked from two ends.
14
+ *
15
+ * `checkOfflineReadiness` is a PURE MOVE: same id, same statuses, same message
16
+ * and remedy strings. Its `pathExists` is `fs.statSync` in a try, matching the
17
+ * helper it used in `cmd_doctor.ts` — `existsSync` would differ on a path whose
18
+ * parent denies traversal, and this is a move, not a repair.
19
+ *
20
+ * Why the traffic section reports rather than decides: the variables below belong to the HOST, not to this package. Writing one into
21
+ * a consumer environment changes behaviour the consumer never asked to change,
22
+ * and — per the mapping measured in `docs/setup/host-traffic-environment.md` —
23
+ * could stop their background security updates as a side effect of a telemetry
24
+ * preference. So this module reads `env` and returns; it writes nothing, opens
25
+ * no socket, and touches no settings file. Two consecutive calls return equal
26
+ * payloads and leave the tree byte-identical.
27
+ *
28
+ * Why an inapplicable row is PRESENT and says so: the obvious implementation lists only the variables that resolve on the
29
+ * current host, which produces a short, clean section on a host that documents
30
+ * none of them. An operator reading that concludes their traffic is governed,
31
+ * when in fact the package simply had nothing to say. An omission that reads as
32
+ * a clean bill of health is the precise failure this surface exists to prevent,
33
+ * so a row that does not apply is emitted with the literal state
34
+ * {@link NOT_APPLICABLE} instead of being dropped.
35
+ *
36
+ * A variable that is SET on a host that does not document it still reports its
37
+ * value beside that state. The state says "this host is not known to read it";
38
+ * dropping the value as well would hide a fact the reader has.
39
+ */
40
+ import * as fs from 'node:fs';
41
+ import * as path from 'node:path';
42
+
43
+ /** The doctor's structured check row. Mirrors `cmd_doctor`'s internal `Dict`. */
44
+ export interface NetworkCheck {
45
+ id: string;
46
+ status: 'ok' | 'warn' | 'fail' | 'skipped';
47
+ message: string;
48
+ remedy: string;
49
+ /** Structural compatibility with `cmd_doctor`'s `Dict` row type. */
50
+ [k: string]: string;
51
+ }
52
+
53
+ /** The literal a row carries where the host has no documented meaning for it. */
54
+ export const NOT_APPLICABLE = 'not applicable on this host';
55
+
56
+ /** The documentation surface every row points at. One copy of the mapping. */
57
+ export const TRAFFIC_DOC = 'docs/setup/host-traffic-environment.md';
58
+
59
+ /** Host id used when nothing in the environment identifies the host. */
60
+ export const UNKNOWN_HOST = 'unknown';
61
+
62
+ /**
63
+ * One documented host traffic variable.
64
+ *
65
+ * `hosts` lists the host ids whose shipped build was actually READ for this
66
+ * variable — never a host somebody expects to behave the same way. A host
67
+ * absent from the list gets {@link NOT_APPLICABLE}, which is the honest answer
68
+ * and also the safe one.
69
+ */
70
+ export interface TrafficVariable {
71
+ readonly variable: string;
72
+ readonly hosts: readonly string[];
73
+ /** Host build and date the row was read from, e.g. `claude-code 2.1.284 · 2026-09-29`. */
74
+ readonly checked_against: string;
75
+ /**
76
+ * May THIS PACKAGE write the variable into the host env block?
77
+ *
78
+ * `false` on both traffic variables and it is not a policy setting — it is a
79
+ * structural fact: `install.ts`'s `WRITABLE_HOST_ENV` is a two-row allow
80
+ * table and the settings schema admits only those two keys, so neither
81
+ * traffic variable is expressible anywhere in the write path. Reported
82
+ * because a consumer reading `set` on a row deserves to know whether this
83
+ * package could have been the one that set it.
84
+ */
85
+ readonly writable_by_this_package: boolean;
86
+ }
87
+
88
+ /** The host build every row below was read from. One place to bump on a re-check. */
89
+ const CHECKED = 'claude-code 2.1.284 · 2026-09-29';
90
+
91
+ /**
92
+ * The documented set, in the order `docs/setup/host-traffic-environment.md`
93
+ * lists it. What each one governs — and does not — lives in that page and is
94
+ * deliberately not duplicated here: a second copy is a second thing to keep
95
+ * true, and this one would be the copy nobody re-reads.
96
+ */
97
+ export const TRAFFIC_VARIABLES: readonly TrafficVariable[] = [
98
+ // Both traffic variables reach the host's update-disabled resolver, the
99
+ // blanket one as its third rung, so neither is writable by this package —
100
+ // owner-decided 2026-09-30.
101
+ { variable: 'CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC', hosts: ['claude-code'], checked_against: CHECKED, writable_by_this_package: false },
102
+ { variable: 'DISABLE_AUTOUPDATER', hosts: ['claude-code'], checked_against: CHECKED, writable_by_this_package: false },
103
+ // Size caps. Neither reaches the updater resolver, and neither suppresses a
104
+ // call, an update check or a download.
105
+ { variable: 'BASH_MAX_OUTPUT_LENGTH', hosts: ['claude-code'], checked_against: CHECKED, writable_by_this_package: true },
106
+ { variable: 'MAX_MCP_OUTPUT_TOKENS', hosts: ['claude-code'], checked_against: CHECKED, writable_by_this_package: true },
107
+ ];
108
+
109
+ /** An environment map, in the shape `process.env` has. */
110
+ export type EnvMap = Readonly<Record<string, string | undefined>>;
111
+
112
+ /** Which host this process is running under, and whether that was observed. */
113
+ export interface TrafficHost {
114
+ host: string;
115
+ observed: boolean;
116
+ }
117
+
118
+ /**
119
+ * Resolve the host from the environment it exported.
120
+ *
121
+ * `observed` carries the same distinction `routing:doctor` draws for its own
122
+ * platform field: a host that identified itself is a fact, and the absence of
123
+ * any marker is NOT evidence of a different host — only evidence that nobody
124
+ * said. An unidentified host resolves to {@link UNKNOWN_HOST}, which documents
125
+ * no variable, so every row reads {@link NOT_APPLICABLE} rather than reporting
126
+ * against a host this code guessed at.
127
+ */
128
+ export function resolveTrafficHost(env: EnvMap): TrafficHost {
129
+ const claude =
130
+ (env['CLAUDECODE'] ?? '').trim() !== '' || (env['CLAUDE_CODE_SESSION_ID'] ?? '').trim() !== '';
131
+ if (claude) return { host: 'claude-code', observed: true };
132
+ return { host: UNKNOWN_HOST, observed: false };
133
+ }
134
+
135
+ /** One emitted row: the variable, its state on this host, and its value if set. */
136
+ export interface TrafficRow {
137
+ variable: string;
138
+ state: string;
139
+ value: string | null;
140
+ checked_against: string;
141
+ writable_by_this_package: boolean;
142
+ }
143
+
144
+ /**
145
+ * Build one row. A variable the host does not document reports
146
+ * {@link NOT_APPLICABLE} whether or not it happens to be exported.
147
+ */
148
+ function trafficRow(spec: TrafficVariable, env: EnvMap, host: string): TrafficRow {
149
+ const raw = env[spec.variable];
150
+ const present = raw !== undefined && raw !== '';
151
+ const applicable = spec.hosts.includes(host);
152
+ return {
153
+ variable: spec.variable,
154
+ state: applicable ? (present ? 'set' : 'unset') : NOT_APPLICABLE,
155
+ value: present ? String(raw) : null,
156
+ checked_against: spec.checked_against,
157
+ writable_by_this_package: spec.writable_by_this_package,
158
+ };
159
+ }
160
+
161
+ /**
162
+ * The `traffic_environment` block of `agent-config doctor --json`.
163
+ *
164
+ * Read-only by construction: the only input is the injected `env` map, and the
165
+ * only output is a fresh object. `hostOverride` is the test seam Phase 2.2's
166
+ * non-Claude fixture needs, and is never passed by the production call site.
167
+ *
168
+ * `reporting_read_only` replaced a flat `read_only: true` on 2026-09-30, and the
169
+ * rename is the point rather than cosmetics: this REPORT is still read-only, but
170
+ * the PACKAGE is no longer, so the old key had become a true statement about the
171
+ * wrong subject. Two of the four rows are now writable by the installer when the
172
+ * consumer sets a cap, and `writable_by_this_package` says which — a consumer
173
+ * reading `set` deserves to know whether we could have been the one who set it.
174
+ */
175
+ export function trafficEnvironmentJson(
176
+ env: EnvMap,
177
+ hostOverride?: TrafficHost,
178
+ ): Record<string, unknown> {
179
+ const resolved = hostOverride ?? resolveTrafficHost(env);
180
+ return {
181
+ host: resolved.host,
182
+ host_observed: resolved.observed,
183
+ doc: TRAFFIC_DOC,
184
+ reporting_read_only: true,
185
+ writable_by_this_package: TRAFFIC_VARIABLES.filter((v) => v.writable_by_this_package).map(
186
+ (v) => v.variable,
187
+ ),
188
+ rows: TRAFFIC_VARIABLES.map((spec) => trafficRow(spec, env, resolved.host)),
189
+ };
190
+ }
191
+
192
+ /** `fs.statSync` in a try — the `cmd_doctor.ts` helper this move preserves. */
193
+ function pathExists(p: string): boolean {
194
+ try {
195
+ fs.statSync(p);
196
+ return true;
197
+ } catch {
198
+ return false;
199
+ }
200
+ }
201
+
202
+ /**
203
+ * The `offline-readiness` check — a pure move out of `cmd_doctor.ts`.
204
+ *
205
+ * `packageRoot` is passed IN rather than resolved here for the reason
206
+ * `doctor_runtime_checks.ts` gives for its `which` parameter: `_package_root`
207
+ * lives in `cmd_doctor.ts` and importing it back would make the two modules
208
+ * circular.
209
+ */
210
+ export function checkOfflineReadiness(packageRoot: string): NetworkCheck {
211
+ const script = path.join(packageRoot, 'src', 'scripts', 'hermetic-install.sh');
212
+ if (!pathExists(script)) {
213
+ return {
214
+ id: 'offline-readiness',
215
+ status: 'warn',
216
+ message: 'src/scripts/hermetic-install.sh not found in package',
217
+ remedy: 'reinstall @event4u/agent-config or pull missing files',
218
+ };
219
+ }
220
+ return {
221
+ id: 'offline-readiness',
222
+ status: 'ok',
223
+ message: 'verified-offline install entrypoint present',
224
+ remedy: '',
225
+ };
226
+ }