@event4u/agent-config 9.14.0 → 9.16.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 (287) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +118 -99
  3. package/README.md +8 -0
  4. package/dist/agent-src/commands/agents/audit.md +7 -7
  5. package/dist/agent-src/commands/agents/optimize.md +1 -1
  6. package/dist/agent-src/commands/analyze/reference-repo.md +1 -1
  7. package/dist/agent-src/commands/chat-history.md +1 -0
  8. package/dist/agent-src/commands/context.md +1 -0
  9. package/dist/agent-src/commands/council.md +1 -1
  10. package/dist/agent-src/commands/ghostwriter.md +1 -1
  11. package/dist/agent-src/commands/memory.md +2 -1
  12. package/dist/agent-src/commands/module.md +1 -0
  13. package/dist/agent-src/commands/optimize/agents-dir.md +1 -1
  14. package/dist/agent-src/commands/optimize/deep.md +5 -2
  15. package/dist/agent-src/commands/optimize/skills.md +5 -5
  16. package/dist/agent-src/commands/override.md +1 -0
  17. package/dist/agent-src/commands/prediction-pool.md +4 -4
  18. package/dist/agent-src/commands/research/deep.md +1 -1
  19. package/dist/agent-src/commands/roadmap.md +2 -1
  20. package/dist/agent-src/commands/rule-compliance-audit.md +1 -1
  21. package/dist/agent-src/commands/tdd.md +1 -0
  22. package/dist/agent-src/commands/tests.md +1 -0
  23. package/dist/agent-src/commands/upstream-contribute.md +5 -5
  24. package/dist/agent-src/commands/video/from-script.md +1 -1
  25. package/dist/agent-src/commands/video/from-song.md +1 -1
  26. package/dist/agent-src/commands/video/scene.md +1 -1
  27. package/dist/agent-src/commands/video/stitch.md +1 -1
  28. package/dist/agent-src/commands/video/storyboard.md +1 -1
  29. package/dist/agent-src/commands/worktree/create.md +2 -1
  30. package/dist/agent-src/contexts/augment-infrastructure.md +1 -1
  31. package/dist/agent-src/contexts/contracts/artifact-engagement-flow.md +5 -5
  32. package/dist/agent-src/contexts/contracts/command-suggestion-flow.md +3 -3
  33. package/dist/agent-src/contexts/contracts/emergency-triage-block.md +2 -2
  34. package/dist/agent-src/contexts/execution/roadmap-process-loop.md +4 -3
  35. package/dist/agent-src/contexts/execution/subagent-routing.md +19 -0
  36. package/dist/agent-src/contexts/override-system.md +1 -1
  37. package/dist/agent-src/ghostwriter/fictional-fixture-v1.md +1 -1
  38. package/dist/agent-src/rules/augment-edit-discipline.md +1 -1
  39. package/dist/agent-src/rules/autonomous-execution.md +2 -1
  40. package/dist/agent-src/rules/brand-consistency.md +1 -0
  41. package/dist/agent-src/rules/brand-source-of-truth.md +1 -0
  42. package/dist/agent-src/rules/code-comment-discipline.md +2 -0
  43. package/dist/agent-src/rules/communication-through-line.md +1 -0
  44. package/dist/agent-src/rules/content-quoting-floor.md +1 -0
  45. package/dist/agent-src/rules/cross-source-consistency.md +0 -1
  46. package/dist/agent-src/rules/downstream-changes.md +1 -0
  47. package/dist/agent-src/rules/external-code-graph-interop.md +1 -0
  48. package/dist/agent-src/rules/external-reference-deep-dive.md +1 -0
  49. package/dist/agent-src/rules/fast-path-marker-visibility.md +1 -0
  50. package/dist/agent-src/rules/finance-safety-floor.md +0 -1
  51. package/dist/agent-src/rules/icon-consistency.md +1 -0
  52. package/dist/agent-src/rules/image-likeness-and-rights.md +1 -0
  53. package/dist/agent-src/rules/improve-before-implement.md +1 -1
  54. package/dist/agent-src/rules/invite-challenge.md +1 -0
  55. package/dist/agent-src/rules/lethal-trifecta-guard.md +1 -0
  56. package/dist/agent-src/rules/low-impact-corpus-privacy-floor.md +1 -0
  57. package/dist/agent-src/rules/markdown-safe-codeblocks.md +1 -0
  58. package/dist/agent-src/rules/media-governance-routing.md +4 -3
  59. package/dist/agent-src/rules/media-sync-ground-truth.md +1 -0
  60. package/dist/agent-src/rules/no-attribution-footers.md +1 -0
  61. package/dist/agent-src/rules/no-decorative-emojis-in-git-surfaces.md +1 -0
  62. package/dist/agent-src/rules/no-pr-progress-comments.md +1 -0
  63. package/dist/agent-src/rules/onboarding-gate.md +1 -0
  64. package/dist/agent-src/rules/output-discipline.md +1 -0
  65. package/dist/agent-src/rules/prefer-enums-over-literals.md +1 -0
  66. package/dist/agent-src/rules/preservation-guard.md +1 -0
  67. package/dist/agent-src/rules/question-not-instruction.md +1 -0
  68. package/dist/agent-src/rules/roadmap-ci-steps-policy.md +1 -0
  69. package/dist/agent-src/rules/role-mode-adherence.md +1 -0
  70. package/dist/agent-src/rules/runtime-safety.md +1 -0
  71. package/dist/agent-src/rules/security-sensitive-stop.md +1 -0
  72. package/dist/agent-src/rules/session-canary.md +9 -2
  73. package/dist/agent-src/rules/source-confidentiality.md +1 -0
  74. package/dist/agent-src/rules/source-of-truth.md +1 -0
  75. package/dist/agent-src/rules/spreadsheet-source-quality.md +1 -0
  76. package/dist/agent-src/rules/think-before-action.md +1 -0
  77. package/dist/agent-src/rules/token-budget-discipline.md +1 -0
  78. package/dist/agent-src/rules/token-efficiency.md +1 -0
  79. package/dist/agent-src/rules/token-optimizer-maintenance.md +5 -5
  80. package/dist/agent-src/rules/tool-safety.md +1 -0
  81. package/dist/agent-src/rules/untrusted-input-defense.md +2 -0
  82. package/dist/agent-src/rules/user-interaction.md +1 -0
  83. package/dist/agent-src/rules/user-interrupt-priority.md +1 -0
  84. package/dist/agent-src/skills/adr-create/SKILL.md +1 -1
  85. package/dist/agent-src/skills/check-refs/SKILL.md +1 -1
  86. package/dist/agent-src/skills/competitive-positioning/SKILL.md +1 -1
  87. package/dist/agent-src/skills/condense-memory/SKILL.md +1 -1
  88. package/dist/agent-src/skills/context-authoring/SKILL.md +1 -1
  89. package/dist/agent-src/skills/copilot-agents-optimization/SKILL.md +1 -1
  90. package/dist/agent-src/skills/description-assist/SKILL.md +1 -1
  91. package/dist/agent-src/skills/github-ci/SKILL.md +1 -0
  92. package/dist/agent-src/skills/guideline-writing/SKILL.md +1 -1
  93. package/dist/agent-src/skills/lint-skills/SKILL.md +1 -1
  94. package/dist/agent-src/skills/readme-reviewer/SKILL.md +1 -1
  95. package/dist/agent-src/skills/roadmap-management/SKILL.md +8 -1
  96. package/dist/agent-src/skills/roadmap-writing/SKILL.md +1 -1
  97. package/dist/agent-src/skills/skill-improvement-pipeline/SKILL.md +1 -1
  98. package/dist/agent-src/skills/skill-writing/SKILL.md +4 -4
  99. package/dist/agent-src/skills/token-optimizer/SKILL.md +4 -4
  100. package/dist/agent-src/skills/upstream-contribute/SKILL.md +6 -6
  101. package/dist/agent-src/skills/using-git-worktrees/SKILL.md +25 -0
  102. package/dist/agent-src/templates/AGENTS.md +1 -1
  103. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  104. package/dist/agent-src/templates/command.md +1 -1
  105. package/dist/agent-src/templates/copilot-instructions.md +3 -3
  106. package/dist/agent-src/templates/rule.md +2 -2
  107. package/dist/agent-src/templates/scripts/work_engine/orchestration.ts +1 -1
  108. package/dist/agent-src/templates/skill-archive-note.md +5 -5
  109. package/dist/agent-src/templates/skill.md +1 -1
  110. package/dist/cli/agent-config.js +66 -19
  111. package/dist/cli/agent-config.js.map +1 -1
  112. package/dist/cli/main.js +9 -4
  113. package/dist/cli/main.js.map +1 -1
  114. package/dist/cli/mcp/content.js +1 -1
  115. package/dist/cli/mcp/content.js.map +1 -1
  116. package/dist/cli/python/workspace_drive.js +2 -0
  117. package/dist/cli/python/workspace_drive.js.map +1 -1
  118. package/dist/cli/python/workspace_drive_health.js +2 -0
  119. package/dist/cli/python/workspace_drive_health.js.map +1 -1
  120. package/dist/cli/python/workspace_hosts.js +2 -0
  121. package/dist/cli/python/workspace_hosts.js.map +1 -1
  122. package/dist/cli/python/workspace_sessions.js +2 -0
  123. package/dist/cli/python/workspace_sessions.js.map +1 -1
  124. package/dist/cli/registry.js +1 -0
  125. package/dist/cli/registry.js.map +1 -1
  126. package/dist/cli-delegate/{chunk-A3HJ6NTI.js → chunk-63WSP6YZ.js} +2 -1
  127. package/dist/cli-delegate/{chunk-EJYIGZ3T.js → chunk-BKO2AZ7Z.js} +1 -1
  128. package/dist/cli-delegate/{chunk-GCVFLBP2.js → chunk-H574KOUH.js} +338 -494
  129. package/dist/cli-delegate/chunk-NXFWFAYV.js +339 -0
  130. package/dist/cli-delegate/{chunk-TEGJU2JX.js → chunk-ZXXJLL5K.js} +2 -2
  131. package/dist/cli-delegate/cmd_conformance.js +29 -23
  132. package/dist/cli-delegate/cmd_doctor.js +3 -3
  133. package/dist/cli-delegate/cmd_export.js +4 -4
  134. package/dist/cli-delegate/cmd_preflight.js +2 -2
  135. package/dist/cli-delegate/cmd_prune.js +4 -4
  136. package/dist/cli-delegate/cmd_settings_migrate.js +4 -4
  137. package/dist/cli-delegate/cmd_sync.js +4 -4
  138. package/dist/cli-delegate/cmd_uninstall.js +4 -4
  139. package/dist/cli-delegate/cmd_validate.js +4 -4
  140. package/dist/discovery/deprecation-report.md +1 -1
  141. package/dist/discovery/discovery-manifest.json +127 -95
  142. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  143. package/dist/discovery/discovery-manifest.summary.md +1 -1
  144. package/dist/discovery/orphan-report.md +1 -1
  145. package/dist/discovery/packs.json +2 -2
  146. package/dist/discovery/trust-report.md +1 -1
  147. package/dist/discovery/workspaces.json +2 -2
  148. package/dist/hooks/dispatch.js +729 -430
  149. package/dist/install/install.mjs +52 -11
  150. package/dist/install/rule_scope.js +18 -2
  151. package/dist/install/rule_scope.js.map +1 -1
  152. package/dist/mcp/registry-manifest.json +2 -2
  153. package/dist/mcp/server.json +2 -2
  154. package/dist/mcp/server.mjs +518 -411
  155. package/dist/router.json +1 -1
  156. package/dist/scripts/_lib/claude_settings_hooks.js +30 -5
  157. package/dist/scripts/_lib/claude_settings_hooks.js.map +1 -1
  158. package/dist/scripts/_lib/scoped_projection.js +223 -0
  159. package/dist/scripts/_lib/scoped_projection.js.map +1 -0
  160. package/dist/scripts/_lib/surface_tiers.js +125 -0
  161. package/dist/scripts/_lib/surface_tiers.js.map +1 -0
  162. package/dist/scripts/_lib/token_count.js +166 -0
  163. package/dist/scripts/_lib/token_count.js.map +1 -0
  164. package/dist/scripts/_lib/trigger_eval_floors.js +8 -0
  165. package/dist/scripts/_lib/trigger_eval_floors.js.map +1 -1
  166. package/dist/scripts/condense.js +139 -9
  167. package/dist/scripts/condense.js.map +1 -1
  168. package/dist/scripts/project_thin_rules.js +450 -0
  169. package/dist/scripts/project_thin_rules.js.map +1 -0
  170. package/dist/server/routes/workspace.js +3 -1
  171. package/dist/server/routes/workspace.js.map +1 -1
  172. package/dist/server/schemas/settings.js +9 -3
  173. package/dist/server/schemas/settings.js.map +1 -1
  174. package/dist/shared/userMd/utils.js +1 -1
  175. package/dist/shared/userMd/utils.js.map +1 -1
  176. package/dist/ui/assets/index-DmR0mbk4.js +26 -0
  177. package/dist/ui/assets/index-DmR0mbk4.js.map +1 -0
  178. package/dist/ui/index.html +1 -1
  179. package/docs/guidelines/agent-infra/roadmap-progress-mechanics.md +1 -1
  180. package/package.json +14 -14
  181. package/src/agent-src/templates/AGENTS.md +1 -1
  182. package/src/agent-src/templates/scripts/work_engine/orchestration.ts +1 -1
  183. package/src/config/agent-settings.template.yml +61 -11
  184. package/src/config/budget-routing.json +7 -0
  185. package/src/config/ci-local-parity.yml +8 -0
  186. package/src/config/discovery/unassigned-artefacts.yml +23 -25
  187. package/src/config/evaluator-budgets.json +35 -5
  188. package/src/config/gate-coverage.yml +147 -0
  189. package/src/config/gate-violation-baselines.json +25 -0
  190. package/src/install/rule_scope.ts +27 -2
  191. package/src/scripts/_cli/cmd_conformance.ts +13 -24
  192. package/src/scripts/_dispatch.bash +10 -0
  193. package/src/scripts/_lib/claude_settings_hooks.ts +30 -4
  194. package/src/scripts/_lib/gate_baseline.ts +213 -0
  195. package/src/scripts/_lib/scoped_projection.ts +50 -0
  196. package/src/scripts/_lib/tier_budget_routing.ts +360 -0
  197. package/src/scripts/_lib/tool_adapter_registry.ts +8 -1
  198. package/src/scripts/_lib/trigger_eval_floors.ts +9 -0
  199. package/src/scripts/_lib/trigger_routers.ts +17 -5
  200. package/src/scripts/adr/regenerate_index.ts +4 -3
  201. package/src/scripts/ai-video/lib/parse-blueprint.sh +1 -1
  202. package/src/scripts/ai_council/compile_corpus.ts +1 -1
  203. package/src/scripts/anchor_scoring_run.ts +1 -1
  204. package/src/scripts/audit_cloud_compatibility.ts +37 -2
  205. package/src/scripts/audit_initial_context.ts +53 -19
  206. package/src/scripts/audit_likelihood.ts +31 -2
  207. package/src/scripts/audit_user_type_axis.ts +58 -11
  208. package/src/scripts/bench_hook_latency.ts +172 -41
  209. package/src/scripts/bench_persona_placebo.ts +1 -1
  210. package/src/scripts/bench_quality_run.ts +1 -1
  211. package/src/scripts/build_mcp_catalog.ts +12 -1
  212. package/src/scripts/check_always_budget.ts +122 -11
  213. package/src/scripts/check_augment_description_cap.ts +24 -3
  214. package/src/scripts/check_ci_strict_superset.ts +150 -0
  215. package/src/scripts/check_cli_registry_budget_sync.ts +168 -0
  216. package/src/scripts/check_cluster_patterns.ts +100 -0
  217. package/src/scripts/check_consumer_scope_flip.ts +26 -4
  218. package/src/scripts/check_evaluator_budgets.ts +198 -19
  219. package/src/scripts/check_gate_coverage.ts +338 -2
  220. package/src/scripts/check_generator_output_coverage.ts +2 -1
  221. package/src/scripts/check_iron_law_prominence.ts +1 -1
  222. package/src/scripts/check_kernel_rule_bundle.ts +1 -1
  223. package/src/scripts/check_no_new_legacy_path.ts +167 -3
  224. package/src/scripts/check_pack_size.ts +18 -4
  225. package/src/scripts/check_portability.ts +37 -7
  226. package/src/scripts/check_references.ts +19 -3
  227. package/src/scripts/check_release_pr_shape.ts +13 -0
  228. package/src/scripts/check_token_quality_golden.ts +1 -1
  229. package/src/scripts/check_token_regression.ts +9 -2
  230. package/src/scripts/condense.ts +155 -12
  231. package/src/scripts/cost/budget.mjs +77 -2
  232. package/src/scripts/evaluator_umbrella.sh +34 -1
  233. package/src/scripts/generate_capability_matrix.ts +8 -0
  234. package/src/scripts/generate_router_coverage_from_matrix.ts +155 -0
  235. package/src/scripts/hook_manifest.yaml +17 -3
  236. package/src/scripts/hooks/block_config_weakening.ts +337 -0
  237. package/src/scripts/hooks/concern_registry.ts +2 -0
  238. package/src/scripts/hooks/probe_throttle.ts +75 -0
  239. package/src/scripts/install-hooks.sh +24 -2
  240. package/src/scripts/install.ts +2 -2
  241. package/src/scripts/lint_agents_md.ts +9 -3
  242. package/src/scripts/lint_archived_skills.ts +1 -1
  243. package/src/scripts/lint_artefact_frontmatter.ts +185 -37
  244. package/src/scripts/lint_command_routing.ts +21 -52
  245. package/src/scripts/lint_command_verbs.ts +47 -28
  246. package/src/scripts/lint_context_spine_usage.ts +38 -12
  247. package/src/scripts/lint_framework_leakage.ts +25 -6
  248. package/src/scripts/lint_handoffs.ts +49 -2
  249. package/src/scripts/lint_media_policy_linkage.ts +62 -30
  250. package/src/scripts/lint_namespace.ts +92 -7
  251. package/src/scripts/lint_new_skill_gate.ts +39 -31
  252. package/src/scripts/lint_originality_shingles.ts +7 -1
  253. package/src/scripts/lint_override_kernel_guard.ts +21 -3
  254. package/src/scripts/lint_pack_boundaries.ts +461 -91
  255. package/src/scripts/lint_pack_dependencies.ts +24 -57
  256. package/src/scripts/lint_persona_governance.ts +1 -1
  257. package/src/scripts/lint_role_experiences.ts +34 -5
  258. package/src/scripts/lint_trigger_precision.ts +142 -0
  259. package/src/scripts/mcp_server/consumer_tool_catalog.json +12 -201
  260. package/src/scripts/mcp_server/tools.ts +1 -1
  261. package/src/scripts/prepack-check.mjs +40 -1
  262. package/src/scripts/prepack_router_targets.d.mts +7 -0
  263. package/src/scripts/prepack_router_targets.mjs +93 -0
  264. package/src/scripts/print_required_checks.ts +72 -18
  265. package/src/scripts/profile_staleness_hook.ts +12 -0
  266. package/src/scripts/record_evaluator_measurements.mjs +105 -0
  267. package/src/scripts/recruit_preflight.sh +1 -1
  268. package/src/scripts/release.ts +214 -7
  269. package/src/scripts/router_target_paths.d.mts +4 -0
  270. package/src/scripts/router_target_paths.mjs +69 -0
  271. package/src/scripts/router_telemetry.ts +52 -1
  272. package/src/scripts/routing_doctor.ts +560 -0
  273. package/src/scripts/rule_trigger_eval.ts +403 -0
  274. package/src/scripts/schemas/rule.schema.json +5 -1
  275. package/src/scripts/session_canary_hook.ts +96 -5
  276. package/src/scripts/skill_linter.ts +9 -28
  277. package/src/scripts/smoke/skills.sh +1 -1
  278. package/src/scripts/sweep_dead_scan_roots.ts +356 -10
  279. package/src/scripts/trigger_coverage.ts +13 -2
  280. package/src/scripts/update_counts.ts +1 -1
  281. package/src/scripts/validate_telegraph_carveouts.ts +1 -1
  282. package/src/scripts/wrapper_freshness_hook.ts +14 -0
  283. package/src/server/schemas/settings.ts +19 -5
  284. package/src/shared/userMd/utils.ts +1 -1
  285. package/dist/cli-delegate/chunk-TJIAM5FW.js +0 -133
  286. package/dist/ui/assets/index-BpSQt5vm.js +0 -40
  287. package/dist/ui/assets/index-BpSQt5vm.js.map +0 -1
@@ -0,0 +1,560 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * routing:doctor — read-only, live-installation routing diagnosis.
4
+ *
5
+ * Answers, against the REAL installation (not fixtures), the question the
6
+ * session-canary incident exposed as untestable: "why did / didn't concern X
7
+ * fire on session start?" — plus the freshness facts a routing complaint
8
+ * needs ruled out first. Composes existing single-purpose surfaces instead
9
+ * of duplicating them:
10
+ *
11
+ * * `hooks_status.collect()` — per-platform bridge presence + bindings
12
+ * (the "is the dispatcher even registered in the host?" layer).
13
+ * * `dispatch_hook._resolve_concerns()` — the exact session_start concern
14
+ * chain the dispatcher would run for the platform.
15
+ * * `concern_registry.CONCERN_REGISTRY` — in-process gate PROBES for the
16
+ * read-only session_start concerns: each probe runs the concern's real
17
+ * `main()` with a synthetic session_start envelope and reports
18
+ * ACTIVE (context injected, with the concern's own `reason`) or
19
+ * INACTIVE (clean no-op). This is the live gate evaluation, not a
20
+ * simulation.
21
+ * * compile_router `--check` + `condense.sh --changed` — router and
22
+ * projection freshness (skippable via `--no-freshness`; auto-skipped
23
+ * when the source tree is absent, i.e. in a consumer install).
24
+ *
25
+ * PROBE SAFETY. Only concerns in `PROBE_SAFE` are invoked — verified to
26
+ * perform zero state writes on the session_start path (session-canary,
27
+ * onboarding-gate, profile-staleness). Stateful concerns (chat-history,
28
+ * hot-context, first-run-gate, wrapper-freshness, surface-probe) are listed
29
+ * with status `stateful` and NOT invoked; their last outcome is visible via
30
+ * `hooks:doctor` (dispatcher feedback files). Deeper diagnosis of a
31
+ * stateful concern stays a `hooks:doctor` job — this doctor never widens
32
+ * the probe set silently.
33
+ *
34
+ * Read-only contract: no file is created or modified; the only child
35
+ * processes are the two freshness checks, both themselves read-only.
36
+ */
37
+ import fs from "node:fs";
38
+ import path from "node:path";
39
+ import { spawnSync } from "node:child_process";
40
+ import { fileURLToPath, pathToFileURL } from "node:url";
41
+
42
+ import {
43
+ type JsonObject,
44
+ type JsonValue,
45
+ _load_yaml,
46
+ _resolve_concerns,
47
+ } from "./hooks/dispatch_hook.js";
48
+ import { CONCERN_REGISTRY } from "./hooks/concern_registry.js";
49
+ import {
50
+ clearHookStdinOverride,
51
+ setHookStdinOverride,
52
+ } from "./hooks/hook_stdin.js";
53
+ import * as hooks_status from "./hooks_status.js";
54
+ import { load_agent_settings } from "./_lib/agent_settings.js";
55
+ import {
56
+ normalizeHostManifest,
57
+ type HostCapabilityManifest,
58
+ } from "./_lib/host_capability.js";
59
+ import {
60
+ classifyLookup,
61
+ classifyTask,
62
+ type ActivationInputs,
63
+ } from "./_lib/auto_dispatch.js";
64
+ import { TIER_ORDER, readCooldowns } from "./_lib/tier_budget_routing.js";
65
+
66
+ const REPO_ROOT = path.resolve(
67
+ path.dirname(fileURLToPath(import.meta.url)),
68
+ "..",
69
+ "..",
70
+ );
71
+
72
+ const MANIFEST_PATH = path.join(REPO_ROOT, "src", "scripts", "hook_manifest.yaml");
73
+
74
+ /**
75
+ * Concerns whose session_start `main()` performs zero state writes — the
76
+ * only ones the doctor may invoke live. Adding a concern here requires
77
+ * re-verifying its write-free path (grep for writeFileSync / appendFileSync /
78
+ * mkdirSync / renameSync on the session_start branch).
79
+ */
80
+ export const PROBE_SAFE: ReadonlySet<string> = new Set([
81
+ "session-canary",
82
+ "onboarding-gate",
83
+ "profile-staleness",
84
+ ]);
85
+
86
+ export interface GateReport {
87
+ concern: string;
88
+ script: string;
89
+ status: "ACTIVE" | "INACTIVE" | "ERROR" | "stateful" | "unregistered";
90
+ reason: string;
91
+ injects_context: boolean;
92
+ }
93
+
94
+ export interface FreshnessReport {
95
+ router: "fresh" | "stale" | "skipped" | "unavailable";
96
+ projection: "clean" | "stale" | "skipped" | "unavailable";
97
+ projection_detail: string;
98
+ dispatch_bundle: "built" | "absent (tsx fallback path)";
99
+ }
100
+
101
+ export interface OrchestrationReport {
102
+ enabled: boolean;
103
+ auto: string;
104
+ downshift: boolean;
105
+ budget_routing: string;
106
+ /** cost.budgets.per_tier ceilings + live cool-down state per tier. */
107
+ tier_budgets: Record<string, { ceiling_usd: number | null; cooldown_until_ms: number }>;
108
+ host_manifest: HostCapabilityManifest;
109
+ /** Verdict of the real activation gate for a canonical delegable probe. */
110
+ activation: { action: string; reason: string };
111
+ cost_budgets: Record<string, unknown>;
112
+ ledger_present: boolean;
113
+ /** Optional --classify dry-run result. */
114
+ sample: {
115
+ prompt: string;
116
+ lookup_route: string;
117
+ lookup_primitive: string | null;
118
+ dispatch: string;
119
+ mode: string | null;
120
+ reason: string;
121
+ } | null;
122
+ /** Budget-routing delivery evidence (external review 2026-08-03, Finding 2). */
123
+ delivery: {
124
+ eligible_dispatches: number;
125
+ budget_evidence_lines: number;
126
+ warning: string;
127
+ };
128
+ }
129
+
130
+ /**
131
+ * Adherence ≠ delivery: budget routing binds via policy prose, so the
132
+ * doctor checks the DELIVERY evidence — orchestration audit lines that
133
+ * carry a tier decision. Eligible dispatches recorded while
134
+ * `budget_routing != off` but ZERO tier-carrying lines → WARN with the
135
+ * exact reason. Read-only over agents/runtime/state/audit/*.jsonl; no
136
+ * new state. This closes the same blind-spot class the session-canary
137
+ * incident exposed (policy present, delivery silently absent).
138
+ */
139
+ export function check_budget_delivery(
140
+ workspace_root: string,
141
+ budget_routing: string,
142
+ ): OrchestrationReport["delivery"] {
143
+ const out = { eligible_dispatches: 0, budget_evidence_lines: 0, warning: "" };
144
+ if (budget_routing === "off") {
145
+ return out;
146
+ }
147
+ const auditDir = path.join(workspace_root, "agents", "runtime", "state", "audit");
148
+ let files: string[] = [];
149
+ try {
150
+ files = fs.readdirSync(auditDir).filter((f) => f.endsWith(".jsonl"));
151
+ } catch {
152
+ return out; // no audit dir — nothing dispatched, nothing to warn about
153
+ }
154
+ for (const f of files) {
155
+ let text = "";
156
+ try {
157
+ text = fs.readFileSync(path.join(auditDir, f), "utf-8");
158
+ } catch {
159
+ continue;
160
+ }
161
+ for (const line of text.split("\n")) {
162
+ if (!line.trim()) continue;
163
+ try {
164
+ const rec = JSON.parse(line) as Record<string, unknown>;
165
+ const spawns = Number(rec["spawn_count"] ?? 0);
166
+ if (!Number.isFinite(spawns) || spawns < 1) continue;
167
+ out.eligible_dispatches += 1;
168
+ if (typeof rec["tier"] === "string" && rec["tier"] !== "") {
169
+ out.budget_evidence_lines += 1;
170
+ }
171
+ } catch {
172
+ // non-record line — audit files are shared JSONL surfaces
173
+ }
174
+ }
175
+ }
176
+ if (out.eligible_dispatches > 0 && out.budget_evidence_lines === 0) {
177
+ out.warning =
178
+ `budget_routing=${budget_routing} is policy-bound but shows no delivery evidence: ` +
179
+ `${out.eligible_dispatches} orchestration dispatch(es) recorded, 0 carry a tier decision — ` +
180
+ `the relation may not be running (adherence != delivery)`;
181
+ }
182
+ return out;
183
+ }
184
+
185
+ export interface DoctorReport {
186
+ platform: string;
187
+ chain: string[];
188
+ gates: GateReport[];
189
+ freshness: FreshnessReport;
190
+ orchestration: OrchestrationReport;
191
+ bridge_table: string;
192
+ }
193
+
194
+ /**
195
+ * Orchestration-routing state: settings gates, host capability, budget
196
+ * inputs, and (optionally) a dry-run classification for a sample prompt.
197
+ * Read-only — mirrors exactly what the delegation layer would resolve.
198
+ */
199
+ export function collect_orchestration(
200
+ workspace_root: string,
201
+ sample_prompt: string | null,
202
+ ): OrchestrationReport {
203
+ const settings = load_agent_settings({ cwd: workspace_root });
204
+ const sub = (settings["subagents"] ?? {}) as Record<string, unknown>;
205
+ const enabled = sub["enabled"] !== false;
206
+ const auto = typeof sub["auto"] === "string" ? (sub["auto"] as string) : "ask";
207
+ const downshift = sub["downshift"] !== false;
208
+ const host_manifest = normalizeHostManifest(sub["host_capabilities"]);
209
+ const activationInputs: ActivationInputs = {
210
+ enabled,
211
+ auto: (auto === "on" || auto === "off" ? auto : "ask") as ActivationInputs["auto"],
212
+ subagent_spawn: host_manifest.subagent_spawn,
213
+ };
214
+ // Canonical delegable probe: the gate verdict shows which activation
215
+ // layer (settings / host primitive) decides on THIS installation.
216
+ const probe = classifyTask(
217
+ { size_estimate: 5, independent_slices: 3 },
218
+ activationInputs,
219
+ );
220
+ const cost = (settings["cost"] ?? {}) as Record<string, unknown>;
221
+ const cost_budgets = (cost["budgets"] ?? {}) as Record<string, unknown>;
222
+ const tracking_dir = path.join(workspace_root, "agents", "cost-tracking");
223
+ const ledger_present = fs.existsSync(path.join(tracking_dir, "sessions.jsonl"));
224
+ // YAML 1.1 trap: an unquoted `off` parses as boolean false — coerce it
225
+ // back to the enum string so the check honors the user's intent.
226
+ const budget_routing =
227
+ sub["budget_routing"] === false
228
+ ? "off"
229
+ : typeof sub["budget_routing"] === "string"
230
+ ? (sub["budget_routing"] as string)
231
+ : "ask";
232
+ const per_tier = (cost_budgets["per_tier"] ?? {}) as Record<string, unknown>;
233
+ const cooldowns = readCooldowns(tracking_dir);
234
+ const tier_budgets: OrchestrationReport["tier_budgets"] = {};
235
+ for (const t of TIER_ORDER) {
236
+ const v = per_tier[t];
237
+ tier_budgets[t] = {
238
+ ceiling_usd: typeof v === "number" && Number.isFinite(v) ? v : null,
239
+ cooldown_until_ms: cooldowns[t],
240
+ };
241
+ }
242
+
243
+ let sample: OrchestrationReport["sample"] = null;
244
+ if (sample_prompt) {
245
+ const lookup = classifyLookup(sample_prompt);
246
+ const cls = classifyTask({ size_estimate: 5 }, activationInputs);
247
+ sample = {
248
+ prompt: sample_prompt,
249
+ lookup_route: lookup.route,
250
+ lookup_primitive: lookup.primitive,
251
+ dispatch: lookup.route === "primitive" ? "primitive (no spawn)" : cls.action,
252
+ mode: cls.mode,
253
+ reason: lookup.route === "primitive" ? lookup.reason : cls.reason,
254
+ };
255
+ }
256
+ return {
257
+ enabled,
258
+ auto,
259
+ downshift,
260
+ budget_routing,
261
+ tier_budgets,
262
+ host_manifest,
263
+ activation: { action: probe.action, reason: probe.reason },
264
+ cost_budgets,
265
+ ledger_present,
266
+ sample,
267
+ delivery: check_budget_delivery(workspace_root, budget_routing),
268
+ };
269
+ }
270
+
271
+ /** Invoke one PROBE_SAFE concern's registered main() and observe its gate. */
272
+ export function probe_gate(
273
+ name: string,
274
+ script: string,
275
+ workspace_root: string,
276
+ ): GateReport {
277
+ const main_fn = CONCERN_REGISTRY[script];
278
+ if (!main_fn) {
279
+ return {
280
+ concern: name,
281
+ script,
282
+ status: "unregistered",
283
+ reason: "no CONCERN_REGISTRY entry — dispatcher would use the slow tsx spawn path",
284
+ injects_context: false,
285
+ };
286
+ }
287
+ const envelope: JsonObject = {
288
+ event: "session_start",
289
+ workspace_root,
290
+ platform: "routing-doctor-probe",
291
+ };
292
+ let out = "";
293
+ const prevWrite = process.stdout.write;
294
+ type WriteFn = typeof process.stdout.write;
295
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
296
+ const capture: WriteFn = ((chunk: any, enc?: any, cb?: any): boolean => {
297
+ out += typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf-8");
298
+ const callback = typeof enc === "function" ? enc : cb;
299
+ if (typeof callback === "function") callback();
300
+ return true;
301
+ }) as WriteFn;
302
+
303
+ let rc = 0;
304
+ setHookStdinOverride(JSON.stringify(envelope));
305
+ process.stdout.write = capture;
306
+ try {
307
+ const result = main_fn([]);
308
+ rc = typeof result === "number" ? result : 0;
309
+ } catch (exc) {
310
+ rc = 3;
311
+ out = "";
312
+ process.stdout.write = prevWrite;
313
+ return {
314
+ concern: name,
315
+ script,
316
+ status: "ERROR",
317
+ reason: exc instanceof Error ? exc.message : String(exc),
318
+ injects_context: false,
319
+ };
320
+ } finally {
321
+ process.stdout.write = prevWrite;
322
+ clearHookStdinOverride();
323
+ }
324
+
325
+ if (rc !== 0) {
326
+ return {
327
+ concern: name,
328
+ script,
329
+ status: "ERROR",
330
+ reason: `probe exited rc=${rc}`,
331
+ injects_context: false,
332
+ };
333
+ }
334
+ const text = out.trim();
335
+ if (text === "") {
336
+ return {
337
+ concern: name,
338
+ script,
339
+ status: "INACTIVE",
340
+ reason: "gate closed — concern emitted nothing (clean no-op)",
341
+ injects_context: false,
342
+ };
343
+ }
344
+ let reason = "gate open";
345
+ let injects = false;
346
+ try {
347
+ const parsed = JSON.parse(text) as JsonObject;
348
+ if (typeof parsed["reason"] === "string" && parsed["reason"] !== "") {
349
+ reason = parsed["reason"] as string;
350
+ }
351
+ injects = typeof parsed["context"] === "string" && parsed["context"] !== "";
352
+ } catch {
353
+ reason = "gate open (non-JSON stdout)";
354
+ }
355
+ return { concern: name, script, status: "ACTIVE", reason, injects_context: injects };
356
+ }
357
+
358
+ /** session_start chain for the platform, from the real manifest. */
359
+ export function resolve_chain(
360
+ manifest: JsonObject,
361
+ platform: string,
362
+ ): Array<{ name: string; script: string }> {
363
+ const concerns = _resolve_concerns(manifest, platform, "session_start");
364
+ return concerns.map((c) => ({
365
+ name: String((c as JsonObject)["name"] ?? "unknown"),
366
+ script: String((c as JsonObject)["script"] ?? ""),
367
+ }));
368
+ }
369
+
370
+ function _freshness(no_freshness: boolean): FreshnessReport {
371
+ const bundle = fs.existsSync(path.join(REPO_ROOT, "dist", "hooks", "dispatch.js"))
372
+ ? ("built" as const)
373
+ : ("absent (tsx fallback path)" as const);
374
+ if (no_freshness) {
375
+ return { router: "skipped", projection: "skipped", projection_detail: "", dispatch_bundle: bundle };
376
+ }
377
+ const compiler = path.join(REPO_ROOT, "src", "scripts", "compile_router.ts");
378
+ let router: FreshnessReport["router"] = "unavailable";
379
+ if (fs.existsSync(compiler)) {
380
+ const r = spawnSync("npx", ["tsx", compiler, "--check"], {
381
+ cwd: REPO_ROOT,
382
+ encoding: "utf-8",
383
+ timeout: 120_000,
384
+ });
385
+ router = r.status === 0 ? "fresh" : "stale";
386
+ }
387
+ const condense = path.join(REPO_ROOT, "src", "scripts", "condense.sh");
388
+ let projection: FreshnessReport["projection"] = "unavailable";
389
+ let detail = "";
390
+ if (fs.existsSync(condense)) {
391
+ const r = spawnSync("bash", [condense, "--changed"], {
392
+ cwd: REPO_ROOT,
393
+ encoding: "utf-8",
394
+ timeout: 300_000,
395
+ });
396
+ const outText = `${r.stdout ?? ""}${r.stderr ?? ""}`;
397
+ if (outText.includes("Every .md projection matches")) {
398
+ projection = "clean";
399
+ } else {
400
+ projection = "stale";
401
+ detail = outText
402
+ .split("\n")
403
+ .filter((l) => l.trim().startsWith("rules/") || l.trim().startsWith("skills/") || l.trim().startsWith("commands/"))
404
+ .slice(0, 10)
405
+ .join("; ");
406
+ }
407
+ }
408
+ return { router, projection, projection_detail: detail, dispatch_bundle: bundle };
409
+ }
410
+
411
+ export function collect_report(options: {
412
+ platform: string;
413
+ workspace_root: string;
414
+ no_freshness: boolean;
415
+ classify?: string | null;
416
+ }): DoctorReport {
417
+ const manifest = _load_yaml(MANIFEST_PATH);
418
+ const chain = resolve_chain(manifest, options.platform);
419
+ const gates: GateReport[] = chain.map(({ name, script }) => {
420
+ if (!PROBE_SAFE.has(name)) {
421
+ return {
422
+ concern: name,
423
+ script,
424
+ status: "stateful" as const,
425
+ reason: "not probed (writes state) — see hooks:doctor last-feedback",
426
+ injects_context: false,
427
+ };
428
+ }
429
+ return probe_gate(name, script, options.workspace_root);
430
+ });
431
+ let bridge_table = "";
432
+ try {
433
+ const matrix = hooks_status.collect(options.workspace_root, manifest);
434
+ bridge_table = hooks_status._render_table(matrix);
435
+ } catch (exc) {
436
+ bridge_table = `bridge status unavailable: ${exc instanceof Error ? exc.message : String(exc)}`;
437
+ }
438
+ return {
439
+ platform: options.platform,
440
+ chain: chain.map((c) => c.name),
441
+ gates,
442
+ freshness: _freshness(options.no_freshness),
443
+ orchestration: collect_orchestration(options.workspace_root, options.classify ?? null),
444
+ bridge_table,
445
+ };
446
+ }
447
+
448
+ function _render(report: DoctorReport): string {
449
+ const lines: string[] = [];
450
+ lines.push(`routing:doctor · platform=${report.platform} · event=session_start`);
451
+ lines.push("");
452
+ lines.push("Gate status (live probes, read-only):");
453
+ for (const g of report.gates) {
454
+ const marker =
455
+ g.status === "ACTIVE" ? "✅" : g.status === "ERROR" ? "❌" : g.status === "INACTIVE" ? "⚠️" : "·";
456
+ const ctx = g.injects_context ? " [injects context]" : "";
457
+ lines.push(` ${marker} ${g.concern.padEnd(20)} ${g.status.padEnd(10)} ${g.reason}${ctx}`);
458
+ }
459
+ lines.push("");
460
+ lines.push(
461
+ `Freshness: router=${report.freshness.router} · projection=${report.freshness.projection}` +
462
+ (report.freshness.projection_detail ? ` (${report.freshness.projection_detail})` : "") +
463
+ ` · dispatch bundle=${report.freshness.dispatch_bundle}`,
464
+ );
465
+ lines.push("");
466
+ const o = report.orchestration;
467
+ lines.push(
468
+ `Orchestration: enabled=${o.enabled} · auto=${o.auto} · downshift=${o.downshift} · ` +
469
+ `budget_routing=${o.budget_routing} · subagent_spawn=${o.host_manifest.subagent_spawn} · ` +
470
+ `ledger=${o.ledger_present ? "present" : "absent"}`,
471
+ );
472
+ const tierBits = Object.entries(o.tier_budgets).map(([t, s]) => {
473
+ const cap = s.ceiling_usd === null ? "no cap" : `$${s.ceiling_usd}`;
474
+ const cool = s.cooldown_until_ms > 0 ? " COOLING" : "";
475
+ return `${t}=${cap}${cool}`;
476
+ });
477
+ lines.push(` tier budgets (rolling-24h): ${tierBits.join(" · ")}`);
478
+ if (o.delivery.warning) {
479
+ lines.push(` ⚠️ ${o.delivery.warning}`);
480
+ } else if (o.delivery.eligible_dispatches > 0) {
481
+ lines.push(
482
+ ` delivery evidence: ${o.delivery.budget_evidence_lines}/${o.delivery.eligible_dispatches} dispatches carry a tier decision`,
483
+ );
484
+ }
485
+ lines.push(` activation probe: ${o.activation.action} — ${o.activation.reason}`);
486
+ if (o.sample) {
487
+ lines.push(
488
+ ` dry-run "${o.sample.prompt.slice(0, 60)}": ${o.sample.dispatch}` +
489
+ (o.sample.mode ? ` (${o.sample.mode})` : "") +
490
+ (o.sample.lookup_primitive ? ` via ${o.sample.lookup_primitive}` : "") +
491
+ ` — ${o.sample.reason}`,
492
+ );
493
+ }
494
+ lines.push("");
495
+ lines.push("Host bridge / bindings:");
496
+ lines.push(report.bridge_table);
497
+ return lines.join("\n");
498
+ }
499
+
500
+ export function main(argv?: string[]): number {
501
+ const args = argv ?? process.argv.slice(2);
502
+ let platform = "claude";
503
+ let workspace_root = process.cwd();
504
+ let json = false;
505
+ let strict = false;
506
+ let no_freshness = false;
507
+ let classify: string | null = null;
508
+ for (let i = 0; i < args.length; i += 1) {
509
+ const a = args[i];
510
+ if (a === "--platform" && args[i + 1]) {
511
+ platform = String(args[(i += 1)]);
512
+ } else if (a === "--workspace" && args[i + 1]) {
513
+ workspace_root = path.resolve(String(args[(i += 1)]));
514
+ } else if (a === "--classify" && args[i + 1]) {
515
+ classify = String(args[(i += 1)]);
516
+ } else if (a === "--json") {
517
+ json = true;
518
+ } else if (a === "--strict") {
519
+ strict = true;
520
+ } else if (a === "--no-freshness") {
521
+ no_freshness = true;
522
+ }
523
+ }
524
+ const report = collect_report({ platform, workspace_root, no_freshness, classify });
525
+ if (json) {
526
+ process.stdout.write(`${JSON.stringify(report as unknown as JsonValue, null, 2)}\n`);
527
+ } else {
528
+ process.stdout.write(`${_render(report)}\n`);
529
+ }
530
+ const red =
531
+ report.gates.some((g) => g.status === "ERROR" || g.status === "unregistered") ||
532
+ report.freshness.router === "stale" ||
533
+ report.freshness.projection === "stale";
534
+ return strict && red ? 1 : 0;
535
+ }
536
+
537
+ declare const __AGENT_CONFIG_BUNDLE__: boolean | undefined;
538
+ function _isCliEntry(): boolean {
539
+ if (typeof __AGENT_CONFIG_BUNDLE__ !== "undefined" && __AGENT_CONFIG_BUNDLE__) {
540
+ return false;
541
+ }
542
+ if (process.argv[1] === undefined) {
543
+ return false;
544
+ }
545
+ const argvUrl = pathToFileURL(path.resolve(process.argv[1])).href;
546
+ if (import.meta.url === argvUrl) {
547
+ return true;
548
+ }
549
+ try {
550
+ const here = fs.realpathSync(fileURLToPath(import.meta.url));
551
+ const argv1 = fs.realpathSync(path.resolve(process.argv[1]));
552
+ return here === argv1;
553
+ } catch {
554
+ return false;
555
+ }
556
+ }
557
+
558
+ if (_isCliEntry()) {
559
+ process.exit(main());
560
+ }