session-orchestrator 3.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 (762) hide show
  1. package/.claude-plugin/marketplace.json +29 -0
  2. package/.claude-plugin/plugin.json +18 -0
  3. package/.codex-plugin/agents/explorer.toml +14 -0
  4. package/.codex-plugin/agents/session-reviewer.toml +23 -0
  5. package/.codex-plugin/agents/wave-worker.toml +15 -0
  6. package/.codex-plugin/config.toml +20 -0
  7. package/.codex-plugin/plugin.json +37 -0
  8. package/.cursor/rules/000-session-orchestrator.mdc +73 -0
  9. package/.cursor/rules/010-session-workflow.mdc +170 -0
  10. package/.cursor/rules/020-quality-gates.mdc +128 -0
  11. package/.cursor/rules/030-wave-execution.mdc +216 -0
  12. package/.cursor/rules/040-discovery.mdc +242 -0
  13. package/.cursor/rules/050-plan.mdc +235 -0
  14. package/.cursor/rules/060-evolve.mdc +232 -0
  15. package/.cursor/rules/070-gitlab-ops.mdc +246 -0
  16. package/.cursor/rules/080-ecosystem-health.mdc +145 -0
  17. package/.mcp.json +8 -0
  18. package/CHANGELOG.md +1544 -0
  19. package/LICENSE +21 -0
  20. package/NOTICE +64 -0
  21. package/README.md +242 -0
  22. package/SECURITY.md +90 -0
  23. package/agents/AGENTS.md +136 -0
  24. package/agents/analyst.md +99 -0
  25. package/agents/architect-reviewer.md +93 -0
  26. package/agents/code-implementer.md +106 -0
  27. package/agents/db-specialist.md +104 -0
  28. package/agents/dialectic-deriver.md +139 -0
  29. package/agents/docs-writer.md +113 -0
  30. package/agents/eval-judge.md +146 -0
  31. package/agents/memory-proposal-collector.md +297 -0
  32. package/agents/qa-strategist.md +102 -0
  33. package/agents/schemas/analyst.schema.json +46 -0
  34. package/agents/schemas/architect-reviewer.schema.json +50 -0
  35. package/agents/schemas/code-implementer.schema.json +61 -0
  36. package/agents/schemas/db-specialist.schema.json +80 -0
  37. package/agents/schemas/docs-writer.schema.json +56 -0
  38. package/agents/schemas/persona-panel-sidecar.schema.json +245 -0
  39. package/agents/schemas/qa-strategist.schema.json +46 -0
  40. package/agents/schemas/security-reviewer.schema.json +86 -0
  41. package/agents/schemas/session-reviewer.schema.json +69 -0
  42. package/agents/schemas/test-writer.schema.json +69 -0
  43. package/agents/schemas/ui-developer.schema.json +90 -0
  44. package/agents/schemas/ux-evaluator.schema.json +51 -0
  45. package/agents/security-reviewer.md +236 -0
  46. package/agents/session-reviewer.md +201 -0
  47. package/agents/skill-applied-judge.md +122 -0
  48. package/agents/test-writer.md +123 -0
  49. package/agents/ui-developer.md +109 -0
  50. package/agents/ux-evaluator.md +161 -0
  51. package/assets/icon.svg +11 -0
  52. package/assets/og-card.png +0 -0
  53. package/assets/og-card.svg +47 -0
  54. package/commands/autopilot-multi.md +74 -0
  55. package/commands/autopilot.md +80 -0
  56. package/commands/bootstrap.md +56 -0
  57. package/commands/brainstorm.md +48 -0
  58. package/commands/close.md +24 -0
  59. package/commands/debug.md +36 -0
  60. package/commands/discovery.md +32 -0
  61. package/commands/dispatcher.md +59 -0
  62. package/commands/eval.md +28 -0
  63. package/commands/evolve.md +10 -0
  64. package/commands/go.md +41 -0
  65. package/commands/grill.md +45 -0
  66. package/commands/harness-audit.md +26 -0
  67. package/commands/memory-cleanup.md +25 -0
  68. package/commands/persona-panel.md +121 -0
  69. package/commands/plan.md +15 -0
  70. package/commands/portfolio.md +97 -0
  71. package/commands/reconcile.md +23 -0
  72. package/commands/repo-audit.md +24 -0
  73. package/commands/session.md +30 -0
  74. package/commands/spinout.md +15 -0
  75. package/commands/sunset-review.md +27 -0
  76. package/commands/templates-ack.md +96 -0
  77. package/commands/test.md +97 -0
  78. package/docs/README.md +105 -0
  79. package/docs/USER-GUIDE.md +1403 -0
  80. package/docs/ci-setup.md +81 -0
  81. package/docs/codex-setup.md +142 -0
  82. package/docs/components.md +74 -0
  83. package/docs/cursor-setup.md +104 -0
  84. package/docs/events-schema.md +81 -0
  85. package/docs/migration-v3.md +148 -0
  86. package/docs/owner-config-schema.md +154 -0
  87. package/docs/persona-panel.md +433 -0
  88. package/docs/pi-setup.md +115 -0
  89. package/docs/plugin-architecture-v3.md +296 -0
  90. package/docs/pm-skills-marketplace.md +114 -0
  91. package/docs/policy-cache-validation-2026-04-28.md +118 -0
  92. package/docs/rule-authoring.md +316 -0
  93. package/docs/session-config-reference.md +1439 -0
  94. package/docs/session-config-template.md +961 -0
  95. package/docs/vault-docs-architecture.md +297 -0
  96. package/hooks/_lib/lock-bootstrap.mjs +272 -0
  97. package/hooks/_lib/lock-reconcile.mjs +93 -0
  98. package/hooks/_lib/profile-gate.mjs +95 -0
  99. package/hooks/_lib/transcript-history.mjs +211 -0
  100. package/hooks/agent-teams-h3-test.sh +362 -0
  101. package/hooks/config-protection.mjs +0 -0
  102. package/hooks/cwd-change-restore.mjs +131 -0
  103. package/hooks/enforce-commands.mjs +179 -0
  104. package/hooks/enforce-scope.mjs +273 -0
  105. package/hooks/hooks-codex.json +60 -0
  106. package/hooks/hooks-cursor.json +15 -0
  107. package/hooks/hooks-pi.json +115 -0
  108. package/hooks/hooks.json +215 -0
  109. package/hooks/loop-guard.mjs +260 -0
  110. package/hooks/on-session-end.mjs +217 -0
  111. package/hooks/on-session-start.mjs +660 -0
  112. package/hooks/on-stop.mjs +294 -0
  113. package/hooks/operator-steer.mjs +64 -0
  114. package/hooks/post-edit-validate.mjs +225 -0
  115. package/hooks/post-subagent-discovery-validator.mjs +398 -0
  116. package/hooks/post-tool-batch-wave-signal.mjs +328 -0
  117. package/hooks/post-tool-failure-corrective-context.mjs +248 -0
  118. package/hooks/post-tooluse-frontend-slop.mjs +184 -0
  119. package/hooks/pre-bash-destructive-guard.mjs +515 -0
  120. package/hooks/pre-bash-memory-propose-audit.mjs +206 -0
  121. package/hooks/pre-bash-staging-fence.mjs +223 -0
  122. package/hooks/pre-bash-templates-first.mjs +404 -0
  123. package/hooks/run-node.sh +72 -0
  124. package/hooks/skill-invocation-telemetry.mjs +99 -0
  125. package/hooks/subagent-telemetry.mjs +249 -0
  126. package/hooks/wave-scope-commit-guard.mjs +191 -0
  127. package/monitors/monitors.json +14 -0
  128. package/output-styles/finding-report.md +48 -0
  129. package/output-styles/session-report.md +53 -0
  130. package/output-styles/wave-summary.md +38 -0
  131. package/package.json +94 -0
  132. package/pi/extensions/session-orchestrator.ts +25 -0
  133. package/pi/prompts/autopilot-multi.md +12 -0
  134. package/pi/prompts/autopilot.md +12 -0
  135. package/pi/prompts/bootstrap.md +12 -0
  136. package/pi/prompts/brainstorm.md +12 -0
  137. package/pi/prompts/close.md +11 -0
  138. package/pi/prompts/debug.md +12 -0
  139. package/pi/prompts/discovery.md +12 -0
  140. package/pi/prompts/dispatcher.md +12 -0
  141. package/pi/prompts/eval.md +12 -0
  142. package/pi/prompts/evolve.md +12 -0
  143. package/pi/prompts/go.md +12 -0
  144. package/pi/prompts/grill.md +12 -0
  145. package/pi/prompts/harness-audit.md +12 -0
  146. package/pi/prompts/memory-cleanup.md +12 -0
  147. package/pi/prompts/persona-panel.md +12 -0
  148. package/pi/prompts/plan.md +12 -0
  149. package/pi/prompts/portfolio.md +12 -0
  150. package/pi/prompts/reconcile.md +12 -0
  151. package/pi/prompts/repo-audit.md +12 -0
  152. package/pi/prompts/session.md +12 -0
  153. package/pi/prompts/spinout.md +12 -0
  154. package/pi/prompts/sunset-review.md +12 -0
  155. package/pi/prompts/templates-ack.md +12 -0
  156. package/pi/prompts/test.md +12 -0
  157. package/rules/_index.md +51 -0
  158. package/rules/always-on/commit-discipline.md +26 -0
  159. package/rules/always-on/npm-quality-gates.md +26 -0
  160. package/rules/always-on/parallel-sessions.md +43 -0
  161. package/rules/opt-in-domain/prompt-caching.md +270 -0
  162. package/rules/opt-in-stack/backend-data.md +188 -0
  163. package/rules/opt-in-stack/backend.md +390 -0
  164. package/rules/opt-in-stack/frontend.md +98 -0
  165. package/rules/opt-in-stack/security-web.md +194 -0
  166. package/rules/opt-in-stack/swift.md +65 -0
  167. package/scripts/archive-closed-prds.mjs +416 -0
  168. package/scripts/autopilot-multi.mjs +802 -0
  169. package/scripts/autopilot.mjs +383 -0
  170. package/scripts/backfill-abandoned-sessions.mjs +265 -0
  171. package/scripts/backfill-learnings-expires.mjs +196 -0
  172. package/scripts/backfill-learnings.mjs +203 -0
  173. package/scripts/backfill-sessions.mjs +282 -0
  174. package/scripts/check-doc-consistency.sh +279 -0
  175. package/scripts/check-package-manager.mjs +445 -0
  176. package/scripts/ci/assert-vitest-green.mjs +267 -0
  177. package/scripts/codex-install.mjs +435 -0
  178. package/scripts/compute-grounding-injection.sh +186 -0
  179. package/scripts/cursor-install.mjs +113 -0
  180. package/scripts/dialectic-deriver.mjs +573 -0
  181. package/scripts/emit-event.mjs +160 -0
  182. package/scripts/emit-session.mjs +212 -0
  183. package/scripts/eval-session.mjs +262 -0
  184. package/scripts/export-hw-learnings.mjs +437 -0
  185. package/scripts/gc-stale-worktrees.mjs +666 -0
  186. package/scripts/generate-pi-prompts.mjs +127 -0
  187. package/scripts/harness-audit.mjs +287 -0
  188. package/scripts/lib/agent-frontmatter.mjs +266 -0
  189. package/scripts/lib/agent-output-schema.mjs +166 -0
  190. package/scripts/lib/agent-status.mjs +303 -0
  191. package/scripts/lib/ajv-loader.mjs +34 -0
  192. package/scripts/lib/auto-dialectic.mjs +382 -0
  193. package/scripts/lib/auto-dream.mjs +471 -0
  194. package/scripts/lib/autonomy/suitability.mjs +212 -0
  195. package/scripts/lib/autopilot/dep-graph.mjs +417 -0
  196. package/scripts/lib/autopilot/durable-telemetry.mjs +121 -0
  197. package/scripts/lib/autopilot/flags.mjs +104 -0
  198. package/scripts/lib/autopilot/kill-switches.mjs +174 -0
  199. package/scripts/lib/autopilot/loop.mjs +320 -0
  200. package/scripts/lib/autopilot/mr-draft.mjs +520 -0
  201. package/scripts/lib/autopilot/multi-killswitch.mjs +184 -0
  202. package/scripts/lib/autopilot/recent-runs.mjs +106 -0
  203. package/scripts/lib/autopilot/stall-sampler.mjs +97 -0
  204. package/scripts/lib/autopilot/telemetry.mjs +224 -0
  205. package/scripts/lib/autopilot/worktree-pipeline.mjs +605 -0
  206. package/scripts/lib/autopilot-telemetry.mjs +11 -0
  207. package/scripts/lib/autopilot.mjs +39 -0
  208. package/scripts/lib/backlog-scan.mjs +179 -0
  209. package/scripts/lib/bootstrap-lock-freshness.mjs +260 -0
  210. package/scripts/lib/bootstrap-lock-refresh.mjs +186 -0
  211. package/scripts/lib/build-live-signals.mjs +150 -0
  212. package/scripts/lib/ci-status-banner.mjs +425 -0
  213. package/scripts/lib/claude-md-budget-lint.mjs +246 -0
  214. package/scripts/lib/cli-flags.mjs +158 -0
  215. package/scripts/lib/codex/plugin-contract.mjs +610 -0
  216. package/scripts/lib/cold-start-detector.mjs +240 -0
  217. package/scripts/lib/command-blocker.mjs +458 -0
  218. package/scripts/lib/common.mjs +333 -0
  219. package/scripts/lib/config/auto-dream.mjs +77 -0
  220. package/scripts/lib/config/block-header.mjs +94 -0
  221. package/scripts/lib/config/broken-window.mjs +114 -0
  222. package/scripts/lib/config/coercers.mjs +248 -0
  223. package/scripts/lib/config/cold-start.mjs +92 -0
  224. package/scripts/lib/config/config-protection.mjs +120 -0
  225. package/scripts/lib/config/cross-repo.mjs +104 -0
  226. package/scripts/lib/config/custom-phases.mjs +213 -0
  227. package/scripts/lib/config/dialectic.mjs +92 -0
  228. package/scripts/lib/config/discovery-validator.mjs +75 -0
  229. package/scripts/lib/config/dispatcher-autonomy-capture.mjs +240 -0
  230. package/scripts/lib/config/dispatcher-autonomy.mjs +152 -0
  231. package/scripts/lib/config/docs-orchestrator.mjs +90 -0
  232. package/scripts/lib/config/docs-staleness.mjs +96 -0
  233. package/scripts/lib/config/drift-check.mjs +155 -0
  234. package/scripts/lib/config/eval.mjs +130 -0
  235. package/scripts/lib/config/events-rotation.mjs +74 -0
  236. package/scripts/lib/config/evolve.mjs +308 -0
  237. package/scripts/lib/config/frontend-slop-hook.mjs +104 -0
  238. package/scripts/lib/config/gitlab-portfolio.mjs +150 -0
  239. package/scripts/lib/config/handover-gate.mjs +106 -0
  240. package/scripts/lib/config/host-paths.mjs +76 -0
  241. package/scripts/lib/config/io.mjs +54 -0
  242. package/scripts/lib/config/loop-guard.mjs +117 -0
  243. package/scripts/lib/config/memory.mjs +150 -0
  244. package/scripts/lib/config/persona-gate-wave.mjs +258 -0
  245. package/scripts/lib/config/reconcile.mjs +205 -0
  246. package/scripts/lib/config/section-extractor.mjs +100 -0
  247. package/scripts/lib/config/skill-evolution.mjs +112 -0
  248. package/scripts/lib/config/slopcheck.mjs +99 -0
  249. package/scripts/lib/config/state-md-lock.mjs +83 -0
  250. package/scripts/lib/config/templates-first.mjs +94 -0
  251. package/scripts/lib/config/test.mjs +113 -0
  252. package/scripts/lib/config/vault-integration.mjs +201 -0
  253. package/scripts/lib/config/vault-mirror-quality.mjs +99 -0
  254. package/scripts/lib/config/vault-staleness.mjs +84 -0
  255. package/scripts/lib/config/vault-sync.mjs +96 -0
  256. package/scripts/lib/config/verification-auto-fix.mjs +84 -0
  257. package/scripts/lib/config/wave-reviewers.mjs +133 -0
  258. package/scripts/lib/config-schema.mjs +345 -0
  259. package/scripts/lib/config.mjs +474 -0
  260. package/scripts/lib/convergence-monitor.mjs +389 -0
  261. package/scripts/lib/coordinator-snapshot.mjs +371 -0
  262. package/scripts/lib/crypto-digest-utils.mjs +91 -0
  263. package/scripts/lib/discovery/helpers.mjs +127 -0
  264. package/scripts/lib/discovery/triage-state.mjs +279 -0
  265. package/scripts/lib/dispatcher/cli.mjs +257 -0
  266. package/scripts/lib/dispatcher/enumerate.mjs +243 -0
  267. package/scripts/lib/dispatcher/rank.mjs +363 -0
  268. package/scripts/lib/ecosystem-health.mjs +224 -0
  269. package/scripts/lib/ecosystem-wizard/ci-detector.mjs +18 -0
  270. package/scripts/lib/ecosystem-wizard/config-parser.mjs +54 -0
  271. package/scripts/lib/ecosystem-wizard/config-writer.mjs +287 -0
  272. package/scripts/lib/ecosystem-wizard/package-manager-detector.mjs +42 -0
  273. package/scripts/lib/ecosystem-wizard/wizard-prompt.mjs +246 -0
  274. package/scripts/lib/ecosystem-wizard.mjs +48 -0
  275. package/scripts/lib/env-check.mjs +89 -0
  276. package/scripts/lib/eval/engine.mjs +605 -0
  277. package/scripts/lib/eval/judge.mjs +433 -0
  278. package/scripts/lib/eval/report.mjs +367 -0
  279. package/scripts/lib/eval/schema.mjs +618 -0
  280. package/scripts/lib/eval/session-resolve.mjs +137 -0
  281. package/scripts/lib/eval/sink.mjs +77 -0
  282. package/scripts/lib/events-rotation.mjs +86 -0
  283. package/scripts/lib/events-schema.mjs +81 -0
  284. package/scripts/lib/events.mjs +80 -0
  285. package/scripts/lib/evolve/autonomy-verdict.mjs +461 -0
  286. package/scripts/lib/evolve/autopilot-effectiveness.mjs +293 -0
  287. package/scripts/lib/exclusivity-matrix.mjs +68 -0
  288. package/scripts/lib/fetch-baseline.mjs +311 -0
  289. package/scripts/lib/file-lock.mjs +512 -0
  290. package/scripts/lib/frontend-detect/detect.mjs +138 -0
  291. package/scripts/lib/frontend-detect/rules.mjs +295 -0
  292. package/scripts/lib/frontmatter-guard.mjs +241 -0
  293. package/scripts/lib/gates/echo-stub-detect.mjs +39 -0
  294. package/scripts/lib/gates/gate-baseline.mjs +42 -0
  295. package/scripts/lib/gates/gate-full.mjs +85 -0
  296. package/scripts/lib/gates/gate-helpers.mjs +231 -0
  297. package/scripts/lib/gates/gate-incremental.mjs +76 -0
  298. package/scripts/lib/gates/gate-per-file.mjs +55 -0
  299. package/scripts/lib/gitlab-ops/stale-mr-sweep.mjs +447 -0
  300. package/scripts/lib/gitlab-portfolio/aggregator.mjs +383 -0
  301. package/scripts/lib/gitlab-portfolio/cli.mjs +428 -0
  302. package/scripts/lib/gitlab-portfolio/markdown-writer.mjs +289 -0
  303. package/scripts/lib/gitlab-portfolio/vcs-detect.mjs +182 -0
  304. package/scripts/lib/handover-gate.mjs +222 -0
  305. package/scripts/lib/hardening.mjs +43 -0
  306. package/scripts/lib/hardware-pattern-detector.mjs +238 -0
  307. package/scripts/lib/harness-audit/categories/category1.mjs +123 -0
  308. package/scripts/lib/harness-audit/categories/category2.mjs +145 -0
  309. package/scripts/lib/harness-audit/categories/category3.mjs +143 -0
  310. package/scripts/lib/harness-audit/categories/category4.mjs +202 -0
  311. package/scripts/lib/harness-audit/categories/category5.mjs +152 -0
  312. package/scripts/lib/harness-audit/categories/category6.mjs +211 -0
  313. package/scripts/lib/harness-audit/categories/category7.mjs +125 -0
  314. package/scripts/lib/harness-audit/categories/category8.mjs +328 -0
  315. package/scripts/lib/harness-audit/categories/category9.mjs +294 -0
  316. package/scripts/lib/harness-audit/categories/helpers.mjs +165 -0
  317. package/scripts/lib/harness-audit/categories.mjs +19 -0
  318. package/scripts/lib/historical-guard.mjs +15 -0
  319. package/scripts/lib/host-identity.mjs +262 -0
  320. package/scripts/lib/instruction-budget-guard.mjs +332 -0
  321. package/scripts/lib/io.mjs +304 -0
  322. package/scripts/lib/issue-close-strip-labels.mjs +161 -0
  323. package/scripts/lib/language-mappers/README.md +57 -0
  324. package/scripts/lib/language-mappers/index.mjs +165 -0
  325. package/scripts/lib/language-mappers/markdown.mjs +149 -0
  326. package/scripts/lib/language-mappers/python.mjs +249 -0
  327. package/scripts/lib/language-mappers/swift.mjs +201 -0
  328. package/scripts/lib/language-mappers/typescript.mjs +433 -0
  329. package/scripts/lib/learnings/expiry-sweep.mjs +164 -0
  330. package/scripts/lib/learnings/filters.mjs +43 -0
  331. package/scripts/lib/learnings/io.mjs +255 -0
  332. package/scripts/lib/learnings/schema.mjs +518 -0
  333. package/scripts/lib/learnings/surface.mjs +207 -0
  334. package/scripts/lib/learnings.mjs +42 -0
  335. package/scripts/lib/lock-reaper.mjs +648 -0
  336. package/scripts/lib/locks/index.mjs +31 -0
  337. package/scripts/lib/locks/lock-body.mjs +62 -0
  338. package/scripts/lib/locks/staging-fence-lock.mjs +267 -0
  339. package/scripts/lib/locks/state-md-lock.mjs +351 -0
  340. package/scripts/lib/loop-readiness-banner.mjs +144 -0
  341. package/scripts/lib/memory-banner.mjs +478 -0
  342. package/scripts/lib/memory-cleanup/worktree-sweep.mjs +108 -0
  343. package/scripts/lib/memory-cleanup-stamp.mjs +56 -0
  344. package/scripts/lib/memory-paths.mjs +31 -0
  345. package/scripts/lib/memory-proposals/collector.mjs +334 -0
  346. package/scripts/lib/memory-proposals/schema.mjs +289 -0
  347. package/scripts/lib/memory-proposals/sink.mjs +507 -0
  348. package/scripts/lib/memory-proposals/store.mjs +441 -0
  349. package/scripts/lib/mission-status-schema.mjs +114 -0
  350. package/scripts/lib/mode-selector/alternatives.mjs +64 -0
  351. package/scripts/lib/mode-selector/constants.mjs +29 -0
  352. package/scripts/lib/mode-selector/context-pressure.mjs +157 -0
  353. package/scripts/lib/mode-selector/rationale.mjs +55 -0
  354. package/scripts/lib/mode-selector/scoring.mjs +221 -0
  355. package/scripts/lib/mode-selector-accuracy.mjs +121 -0
  356. package/scripts/lib/mode-selector.mjs +160 -0
  357. package/scripts/lib/multi-provider-build/providers.mjs +64 -0
  358. package/scripts/lib/multi-provider-build/templating.mjs +130 -0
  359. package/scripts/lib/named-baseline-resolver.mjs +233 -0
  360. package/scripts/lib/named-vault-resolver.mjs +433 -0
  361. package/scripts/lib/owner-config/coerce.mjs +29 -0
  362. package/scripts/lib/owner-config/constants.mjs +21 -0
  363. package/scripts/lib/owner-config/defaults.mjs +50 -0
  364. package/scripts/lib/owner-config/error.mjs +19 -0
  365. package/scripts/lib/owner-config/index.mjs +13 -0
  366. package/scripts/lib/owner-config/merge.mjs +52 -0
  367. package/scripts/lib/owner-config/validate.mjs +259 -0
  368. package/scripts/lib/owner-config-banner.mjs +126 -0
  369. package/scripts/lib/owner-config-loader.mjs +159 -0
  370. package/scripts/lib/owner-config.example.yaml +72 -0
  371. package/scripts/lib/owner-config.mjs +28 -0
  372. package/scripts/lib/owner-interview.mjs +243 -0
  373. package/scripts/lib/owner-yaml.mjs +571 -0
  374. package/scripts/lib/package-manager.mjs +160 -0
  375. package/scripts/lib/path-utils.mjs +217 -0
  376. package/scripts/lib/peer-cards/merger.mjs +310 -0
  377. package/scripts/lib/peer-cards/reader.mjs +125 -0
  378. package/scripts/lib/peer-cards/schema.mjs +230 -0
  379. package/scripts/lib/peer-cards/staleness-banner.mjs +86 -0
  380. package/scripts/lib/peer-cards/writer.mjs +138 -0
  381. package/scripts/lib/peer-discovery.mjs +200 -0
  382. package/scripts/lib/persona-panel/catalog-loader.mjs +577 -0
  383. package/scripts/lib/persona-panel/consolidator.mjs +370 -0
  384. package/scripts/lib/persona-panel/persona-runner.mjs +375 -0
  385. package/scripts/lib/persona-panel/threshold.mjs +130 -0
  386. package/scripts/lib/pi-hook-bridge.mjs +328 -0
  387. package/scripts/lib/platform.mjs +266 -0
  388. package/scripts/lib/playwright-driver/runner.mjs +297 -0
  389. package/scripts/lib/plugin-root.mjs +210 -0
  390. package/scripts/lib/pre-dispatch-check.mjs +126 -0
  391. package/scripts/lib/product-repo-detect.mjs +121 -0
  392. package/scripts/lib/profiles/registry.mjs +176 -0
  393. package/scripts/lib/profiles/schema.mjs +209 -0
  394. package/scripts/lib/qg-command-drift-banner.mjs +88 -0
  395. package/scripts/lib/quality-gate/diagnostics.mjs +92 -0
  396. package/scripts/lib/quality-gate.mjs +536 -0
  397. package/scripts/lib/quality-gates-cache.mjs +228 -0
  398. package/scripts/lib/quality-gates-policy.mjs +95 -0
  399. package/scripts/lib/recommendations-v0.mjs +156 -0
  400. package/scripts/lib/reconcile/eligibility.mjs +203 -0
  401. package/scripts/lib/reconcile/emitter.mjs +244 -0
  402. package/scripts/lib/reconcile/engine.mjs +412 -0
  403. package/scripts/lib/reconcile/idempotency.mjs +239 -0
  404. package/scripts/lib/reconcile/renderer.mjs +211 -0
  405. package/scripts/lib/reconcile/writer.mjs +293 -0
  406. package/scripts/lib/reconcile-nudge-banner.mjs +284 -0
  407. package/scripts/lib/resource-probe/evaluate.mjs +190 -0
  408. package/scripts/lib/resource-probe/parsers.mjs +181 -0
  409. package/scripts/lib/resource-probe/probe-platform.mjs +300 -0
  410. package/scripts/lib/resource-probe.mjs +95 -0
  411. package/scripts/lib/rule-loader.mjs +552 -0
  412. package/scripts/lib/rules-sync.mjs +439 -0
  413. package/scripts/lib/scope-gate.mjs +496 -0
  414. package/scripts/lib/session-close-backfill.mjs +539 -0
  415. package/scripts/lib/session-discovery.mjs +256 -0
  416. package/scripts/lib/session-end/phase-skip.mjs +357 -0
  417. package/scripts/lib/session-end/worktree-cleanup.mjs +112 -0
  418. package/scripts/lib/session-id.mjs +362 -0
  419. package/scripts/lib/session-lock.mjs +703 -0
  420. package/scripts/lib/session-registry.mjs +355 -0
  421. package/scripts/lib/session-schema/aliases.mjs +71 -0
  422. package/scripts/lib/session-schema/constants.mjs +115 -0
  423. package/scripts/lib/session-schema/normalizer.mjs +66 -0
  424. package/scripts/lib/session-schema/timestamps.mjs +64 -0
  425. package/scripts/lib/session-schema/validator.mjs +453 -0
  426. package/scripts/lib/session-schema.mjs +71 -0
  427. package/scripts/lib/session-token-rollup.mjs +137 -0
  428. package/scripts/lib/sessions-staleness-banner.mjs +247 -0
  429. package/scripts/lib/skill-evolution/blast-radius-classifier.mjs +114 -0
  430. package/scripts/lib/skill-evolution/candidate-intake.mjs +270 -0
  431. package/scripts/lib/skill-evolution/config-validation-gate.mjs +279 -0
  432. package/scripts/lib/skill-evolution/engine.mjs +719 -0
  433. package/scripts/lib/skill-evolution/idempotency.mjs +279 -0
  434. package/scripts/lib/skill-evolution/mr-opener.mjs +507 -0
  435. package/scripts/lib/skill-health/join.mjs +181 -0
  436. package/scripts/lib/skill-health/score.mjs +123 -0
  437. package/scripts/lib/skill-invocations-schema.mjs +214 -0
  438. package/scripts/lib/skill-judge.mjs +348 -0
  439. package/scripts/lib/skill-judgments-schema.mjs +264 -0
  440. package/scripts/lib/slopcheck.mjs +501 -0
  441. package/scripts/lib/soul-resolve.mjs +118 -0
  442. package/scripts/lib/spiral-carryover.mjs +495 -0
  443. package/scripts/lib/state-md/body-sections.mjs +851 -0
  444. package/scripts/lib/state-md/frontmatter-mutators.mjs +453 -0
  445. package/scripts/lib/state-md/mission-status.mjs +247 -0
  446. package/scripts/lib/state-md/recommendations.mjs +57 -0
  447. package/scripts/lib/state-md/yaml-parser.mjs +234 -0
  448. package/scripts/lib/state-md-peer-guard.mjs +232 -0
  449. package/scripts/lib/state-md.mjs +53 -0
  450. package/scripts/lib/subagents-schema.mjs +309 -0
  451. package/scripts/lib/sunset/walker.mjs +1192 -0
  452. package/scripts/lib/test-runner/artifact-paths.mjs +94 -0
  453. package/scripts/lib/test-runner/fingerprint.mjs +33 -0
  454. package/scripts/lib/test-runner/issue-reconcile.mjs +770 -0
  455. package/scripts/lib/tmux-layout/layouts.mjs +224 -0
  456. package/scripts/lib/tmux-layout/telemetry-stats.mjs +100 -0
  457. package/scripts/lib/tmux-layout/telemetry.mjs +88 -0
  458. package/scripts/lib/tmux-layout/tmux-shell.mjs +82 -0
  459. package/scripts/lib/tmux-layout/vcs-detector.mjs +88 -0
  460. package/scripts/lib/validate/check-agents.mjs +457 -0
  461. package/scripts/lib/validate/check-codex-plugin.mjs +37 -0
  462. package/scripts/lib/validate/check-commands.mjs +148 -0
  463. package/scripts/lib/validate/check-component-paths.mjs +112 -0
  464. package/scripts/lib/validate/check-dead-bridge.mjs +180 -0
  465. package/scripts/lib/validate/check-hooks-symmetry.mjs +258 -0
  466. package/scripts/lib/validate/check-json-files.mjs +116 -0
  467. package/scripts/lib/validate/check-owner-leakage.mjs +1011 -0
  468. package/scripts/lib/validate/check-path-utils-canary.mjs +175 -0
  469. package/scripts/lib/validate/check-peekaboo-driver-canary.mjs +201 -0
  470. package/scripts/lib/validate/check-pi-package.mjs +110 -0
  471. package/scripts/lib/validate/check-pi-prompts.mjs +43 -0
  472. package/scripts/lib/validate/check-playwright-mcp-canary.mjs +154 -0
  473. package/scripts/lib/validate/check-plugin-json.mjs +96 -0
  474. package/scripts/lib/validate/check-plugin-monitors.mjs +206 -0
  475. package/scripts/lib/validate/check-plugin-schema.mjs +137 -0
  476. package/scripts/lib/validate/check-rules.mjs +143 -0
  477. package/scripts/lib/validate/check-session-plan-routing.mjs +154 -0
  478. package/scripts/lib/validate/check-test-fixture-shapes.mjs +280 -0
  479. package/scripts/lib/validate/check-unicode-safety.mjs +533 -0
  480. package/scripts/lib/validate/confidential-names.mjs +169 -0
  481. package/scripts/lib/validate/dead-bridge-corpus.mjs +141 -0
  482. package/scripts/lib/validate/dead-bridge-detectors.mjs +568 -0
  483. package/scripts/lib/validate/tier-inference.mjs +100 -0
  484. package/scripts/lib/validate-vendored-rules.mjs +519 -0
  485. package/scripts/lib/vault-archive.mjs +404 -0
  486. package/scripts/lib/vault-backfill/glab.mjs +164 -0
  487. package/scripts/lib/vault-backfill/manifest.mjs +75 -0
  488. package/scripts/lib/vault-backfill/template.mjs +130 -0
  489. package/scripts/lib/vault-consolidate-fs.mjs +331 -0
  490. package/scripts/lib/vault-migration-rules.mjs +155 -0
  491. package/scripts/lib/vault-mirror/auto-commit.mjs +203 -0
  492. package/scripts/lib/vault-mirror/namespace.mjs +152 -0
  493. package/scripts/lib/vault-mirror/process.mjs +567 -0
  494. package/scripts/lib/vault-mirror/pseudonym-map.mjs +164 -0
  495. package/scripts/lib/vault-mirror/render-learnings.mjs +201 -0
  496. package/scripts/lib/vault-mirror/render-sessions.mjs +367 -0
  497. package/scripts/lib/vault-mirror/render.mjs +8 -0
  498. package/scripts/lib/vault-mirror/utils.mjs +217 -0
  499. package/scripts/lib/vault-relocation-rules.mjs +555 -0
  500. package/scripts/lib/vault-repo-backfill.mjs +235 -0
  501. package/scripts/lib/vault-staleness-banner.mjs +142 -0
  502. package/scripts/lib/vault-status/board-writer.mjs +769 -0
  503. package/scripts/lib/vault-status/narrative-mirror.mjs +544 -0
  504. package/scripts/lib/vault-sync-baseline.mjs +152 -0
  505. package/scripts/lib/wave-context.mjs +29 -0
  506. package/scripts/lib/wave-executor/pool.mjs +248 -0
  507. package/scripts/lib/wave-resource-gate.mjs +204 -0
  508. package/scripts/lib/wave-sizing.mjs +75 -0
  509. package/scripts/lib/webhook-url.mjs +105 -0
  510. package/scripts/lib/workspace.mjs +198 -0
  511. package/scripts/lib/worktree/constants.mjs +35 -0
  512. package/scripts/lib/worktree/index.mjs +17 -0
  513. package/scripts/lib/worktree/lifecycle.mjs +287 -0
  514. package/scripts/lib/worktree/listing.mjs +118 -0
  515. package/scripts/lib/worktree/meta.mjs +64 -0
  516. package/scripts/lib/worktree-freshness.mjs +313 -0
  517. package/scripts/lib/worktree.mjs +15 -0
  518. package/scripts/lifecycle-sim-v6.mjs +347 -0
  519. package/scripts/lock-reaper.mjs +185 -0
  520. package/scripts/mcp-server.sh +241 -0
  521. package/scripts/measure-policy-cache-effectiveness.mjs +427 -0
  522. package/scripts/memory-propose.mjs +464 -0
  523. package/scripts/migrate-cold-start-seed.mjs +404 -0
  524. package/scripts/migrate-learnings-jsonl.mjs +189 -0
  525. package/scripts/migrate-legacy-learnings.sh +61 -0
  526. package/scripts/migrate-sessions-jsonl.mjs +448 -0
  527. package/scripts/migrate-subagents-jsonl.mjs +196 -0
  528. package/scripts/migrate-vault-paths.mjs +796 -0
  529. package/scripts/parse-config.mjs +149 -0
  530. package/scripts/pi-install.mjs +117 -0
  531. package/scripts/print-applicable-rules.mjs +247 -0
  532. package/scripts/promote-vault-strict.mjs +496 -0
  533. package/scripts/relocate-vault-corpus.mjs +1178 -0
  534. package/scripts/run-migrate-v2-cross-repo.mjs +385 -0
  535. package/scripts/run-quality-gate.mjs +216 -0
  536. package/scripts/spikes/h3-agent-teams/preflight.sh +53 -0
  537. package/scripts/spikes/h3-agent-teams/run-h3.sh +112 -0
  538. package/scripts/spikes/h3-agent-teams/setup.sh +137 -0
  539. package/scripts/spikes/h3-agent-teams/toggle.sh +38 -0
  540. package/scripts/sweep-expired-learnings.mjs +135 -0
  541. package/scripts/sync-vault-schema.mjs +376 -0
  542. package/scripts/tests/fixtures/fetch-baseline/sample-rule.md +8 -0
  543. package/scripts/tmux-layout.mjs +245 -0
  544. package/scripts/token-audit.sh +191 -0
  545. package/scripts/typecheck.mjs +42 -0
  546. package/scripts/upload-social-preview.mjs +316 -0
  547. package/scripts/validate-config.mjs +46 -0
  548. package/scripts/validate-plugin-manifests.mjs +163 -0
  549. package/scripts/validate-plugin.mjs +264 -0
  550. package/scripts/validate-wave-scope.mjs +289 -0
  551. package/scripts/vault-backfill.mjs +404 -0
  552. package/scripts/vault-consolidate.mjs +596 -0
  553. package/scripts/vault-integration-watcher.mjs +394 -0
  554. package/scripts/vault-mirror.mjs +430 -0
  555. package/skills/_shared/bootstrap-gate.md +111 -0
  556. package/skills/_shared/config-reading.md +226 -0
  557. package/skills/_shared/instruction-file-resolution.md +79 -0
  558. package/skills/_shared/model-selection.md +64 -0
  559. package/skills/_shared/monitor-patterns.md +300 -0
  560. package/skills/_shared/parallel-aware-auq.md +121 -0
  561. package/skills/_shared/parallel-aware-preamble.md +185 -0
  562. package/skills/_shared/platform-tools.md +96 -0
  563. package/skills/_shared/state-ownership.md +221 -0
  564. package/skills/architecture/DEEPENING.md +37 -0
  565. package/skills/architecture/INTERFACE-DESIGN.md +44 -0
  566. package/skills/architecture/LANGUAGE.md +53 -0
  567. package/skills/architecture/SKILL.md +92 -0
  568. package/skills/autopilot/SKILL.md +419 -0
  569. package/skills/bootstrap/SKILL.md +592 -0
  570. package/skills/bootstrap/STATE.md.template +24 -0
  571. package/skills/bootstrap/_shared-template.md +243 -0
  572. package/skills/bootstrap/deep-template.md +659 -0
  573. package/skills/bootstrap/fast-template.md +251 -0
  574. package/skills/bootstrap/intensity-heuristic.md +80 -0
  575. package/skills/bootstrap/public-fallback.md +342 -0
  576. package/skills/bootstrap/standard-template.md +736 -0
  577. package/skills/bootstrap/templates/agents/project-code-review.md +18 -0
  578. package/skills/bootstrap/templates/agents/project-discovery.md +18 -0
  579. package/skills/bootstrap/templates/agents/project-quality-gate.md +18 -0
  580. package/skills/brainstorm/SKILL.md +268 -0
  581. package/skills/brainstorm/soul.md +49 -0
  582. package/skills/claude-md-drift-check/SKILL.md +186 -0
  583. package/skills/claude-md-drift-check/checker.mjs +1380 -0
  584. package/skills/claude-md-drift-check/checker.sh +37 -0
  585. package/skills/claude-md-drift-check/package.json +16 -0
  586. package/skills/convergence-monitoring/README.md +39 -0
  587. package/skills/convergence-monitoring/SIGNALS.md +246 -0
  588. package/skills/convergence-monitoring/SKILL.md +285 -0
  589. package/skills/daily/SKILL.md +222 -0
  590. package/skills/daily/generate.sh +92 -0
  591. package/skills/daily/templates/daily.md.tpl +36 -0
  592. package/skills/debug/SKILL.md +188 -0
  593. package/skills/debug/soul.md +35 -0
  594. package/skills/discovery/SKILL.md +567 -0
  595. package/skills/discovery/issue-templates.md +237 -0
  596. package/skills/discovery/probes/docs-staleness.mjs +195 -0
  597. package/skills/discovery/probes/frontend-slop.mjs +186 -0
  598. package/skills/discovery/probes/ssot-code-diff.mjs +310 -0
  599. package/skills/discovery/probes/supply-chain-slopcheck.mjs +440 -0
  600. package/skills/discovery/probes/vault-narrative-staleness.mjs +355 -0
  601. package/skills/discovery/probes/vault-staleness.mjs +272 -0
  602. package/skills/discovery/probes-arch.md +252 -0
  603. package/skills/discovery/probes-audit.md +95 -0
  604. package/skills/discovery/probes-code.md +329 -0
  605. package/skills/discovery/probes-docs.md +76 -0
  606. package/skills/discovery/probes-feature.md +150 -0
  607. package/skills/discovery/probes-infra.md +138 -0
  608. package/skills/discovery/probes-intro.md +25 -0
  609. package/skills/discovery/probes-session.md +495 -0
  610. package/skills/discovery/probes-supply-chain.md +94 -0
  611. package/skills/discovery/probes-ui.md +147 -0
  612. package/skills/discovery/probes-vault.md +64 -0
  613. package/skills/discovery/slop-patterns.md +115 -0
  614. package/skills/dispatcher/SKILL.md +173 -0
  615. package/skills/docs-orchestrator/SKILL.md +362 -0
  616. package/skills/docs-orchestrator/audience-mapping.md +140 -0
  617. package/skills/domain-model/ADR-FORMAT.md +47 -0
  618. package/skills/domain-model/CONTEXT-FORMAT.md +77 -0
  619. package/skills/domain-model/SKILL.md +85 -0
  620. package/skills/ecosystem-health/SKILL.md +119 -0
  621. package/skills/ecosystem-health/wizard.md +193 -0
  622. package/skills/eval/SKILL.md +293 -0
  623. package/skills/eval/rubric-v1.md +218 -0
  624. package/skills/evolve/SKILL.md +546 -0
  625. package/skills/frontmatter-guard/SKILL.md +126 -0
  626. package/skills/gitlab-ops/SKILL.md +368 -0
  627. package/skills/gitlab-portfolio/SKILL.md +196 -0
  628. package/skills/grill/SKILL.md +185 -0
  629. package/skills/grill/soul.md +55 -0
  630. package/skills/hook-development/SKILL.md +413 -0
  631. package/skills/mcp-builder/SKILL.md +260 -0
  632. package/skills/memory-cleanup/SKILL.md +310 -0
  633. package/skills/mode-selector/SKILL.md +226 -0
  634. package/skills/peekaboo-driver/SKILL.md +237 -0
  635. package/skills/peekaboo-driver/soul.md +32 -0
  636. package/skills/persona-panel/SKILL.md +365 -0
  637. package/skills/persona-panel/persona-format.md +205 -0
  638. package/skills/persona-panel/presets/designer-lens.md +87 -0
  639. package/skills/persona-panel/presets/engineer-lens.md +88 -0
  640. package/skills/persona-panel/presets/pm-lens.md +86 -0
  641. package/skills/plan/SKILL.md +496 -0
  642. package/skills/plan/mode-feature.md +141 -0
  643. package/skills/plan/mode-new.md +297 -0
  644. package/skills/plan/mode-retro.md +271 -0
  645. package/skills/plan/prd-feature-template.md +132 -0
  646. package/skills/plan/prd-full-template.md +151 -0
  647. package/skills/plan/prd-reviewer-prompt.md +103 -0
  648. package/skills/plan/retro-template.md +75 -0
  649. package/skills/plan/soul.md +62 -0
  650. package/skills/playwright-driver/SKILL.md +226 -0
  651. package/skills/playwright-driver/soul.md +30 -0
  652. package/skills/quality-gates/SKILL.md +212 -0
  653. package/skills/reconcile/SKILL.md +324 -0
  654. package/skills/repo-audit/SKILL.md +272 -0
  655. package/skills/session-end/SKILL.md +1044 -0
  656. package/skills/session-end/discovery-scan.md +37 -0
  657. package/skills/session-end/drift-operations.md +97 -0
  658. package/skills/session-end/learning-patterns.md +78 -0
  659. package/skills/session-end/metrics-collection.md +175 -0
  660. package/skills/session-end/phase-3-2-docs-verification.md +148 -0
  661. package/skills/session-end/phase-3-6-tail.md +344 -0
  662. package/skills/session-end/phase-3-7a-recommendations.md +86 -0
  663. package/skills/session-end/plan-verification.md +288 -0
  664. package/skills/session-end/session-metrics-write.md +223 -0
  665. package/skills/session-end/vault-operations.md +50 -0
  666. package/skills/session-end/verification-checklist.md +20 -0
  667. package/skills/session-plan/SKILL.md +554 -0
  668. package/skills/session-plan/wave-template.md +37 -0
  669. package/skills/session-start/SKILL.md +1043 -0
  670. package/skills/session-start/phase-2-5-docs-planning.md +119 -0
  671. package/skills/session-start/phase-4-5-resource-health.md +49 -0
  672. package/skills/session-start/phase-7-1-premise-check.md +47 -0
  673. package/skills/session-start/phase-7-5-mode-selector.md +237 -0
  674. package/skills/session-start/phase-8-5-express-path.md +61 -0
  675. package/skills/session-start/presentation-format.md +81 -0
  676. package/skills/session-start/soul.md +57 -0
  677. package/skills/skill-creator/SKILL.md +168 -0
  678. package/skills/spinout/SKILL.md +76 -0
  679. package/skills/sunset-review/SKILL.md +96 -0
  680. package/skills/test-runner/SKILL.md +362 -0
  681. package/skills/test-runner/rubric-v1.md +388 -0
  682. package/skills/test-runner/soul.md +46 -0
  683. package/skills/tmux-layout/SKILL.md +104 -0
  684. package/skills/ubiquitous-language/SKILL.md +97 -0
  685. package/skills/using-orchestrator/SKILL.md +144 -0
  686. package/skills/vault-mirror/SKILL.md +234 -0
  687. package/skills/vault-sync/SKILL.md +319 -0
  688. package/skills/vault-sync/package-lock.json +40 -0
  689. package/skills/vault-sync/package.json +11 -0
  690. package/skills/vault-sync/tests/fixtures/archive-test-vault/90-archive/bad-archived.md +8 -0
  691. package/skills/vault-sync/tests/fixtures/archive-test-vault/_meta/.gitkeep +0 -0
  692. package/skills/vault-sync/tests/fixtures/archive-test-vault/live-note.md +8 -0
  693. package/skills/vault-sync/tests/fixtures/broken-frontmatter-vault/_meta/.gitkeep +0 -0
  694. package/skills/vault-sync/tests/fixtures/broken-frontmatter-vault/bad-type.md +8 -0
  695. package/skills/vault-sync/tests/fixtures/broken-frontmatter-vault/good-note.md +8 -0
  696. package/skills/vault-sync/tests/fixtures/clean-vault/.obsidian/config.md +8 -0
  697. package/skills/vault-sync/tests/fixtures/clean-vault/01-projects/foo/projects-baseline.md +10 -0
  698. package/skills/vault-sync/tests/fixtures/clean-vault/03-daily/daily-2026-04-13.md +8 -0
  699. package/skills/vault-sync/tests/fixtures/clean-vault/README.md +3 -0
  700. package/skills/vault-sync/tests/fixtures/clean-vault/hello-world.md +11 -0
  701. package/skills/vault-sync/tests/fixtures/dangling-link-vault/_meta/.gitkeep +0 -0
  702. package/skills/vault-sync/tests/fixtures/dangling-link-vault/has-dangling.md +9 -0
  703. package/skills/vault-sync/tests/fixtures/dangling-link-vault/real-target.md +8 -0
  704. package/skills/vault-sync/tests/fixtures/empty-vault/_meta/.gitkeep +0 -0
  705. package/skills/vault-sync/tests/fixtures/missing-field-vault/_meta/.gitkeep +0 -0
  706. package/skills/vault-sync/tests/fixtures/missing-field-vault/missing-id.md +7 -0
  707. package/skills/vault-sync/tests/fixtures/nested-tag-vault/03-daily/daily-2026-04-13.md +9 -0
  708. package/skills/vault-sync/tests/fixtures/nested-tag-vault/_meta/.gitkeep +0 -0
  709. package/skills/vault-sync/tests/fixtures/nested-tag-vault/nested-tags-note.md +11 -0
  710. package/skills/vault-sync/tests/fixtures/no-frontmatter-vault/README.md +3 -0
  711. package/skills/vault-sync/tests/fixtures/no-frontmatter-vault/_MOC.md +3 -0
  712. package/skills/vault-sync/tests/fixtures/no-frontmatter-vault/_meta/.gitkeep +0 -0
  713. package/skills/vault-sync/tests/fixtures/with-moc-vault/_MOC.md +11 -0
  714. package/skills/vault-sync/tests/fixtures/with-moc-vault/_meta/.gitkeep +0 -0
  715. package/skills/vault-sync/tests/fixtures/with-moc-vault/hello-world.md +11 -0
  716. package/skills/vault-sync/tests/schema-drift.test.mjs +133 -0
  717. package/skills/vault-sync/validator.mjs +658 -0
  718. package/skills/vault-sync/validator.sh +55 -0
  719. package/skills/wave-executor/SKILL.md +496 -0
  720. package/skills/wave-executor/circuit-breaker.md +169 -0
  721. package/skills/wave-executor/wave-loop.md +1043 -0
  722. package/skills/write-executable-plan/SKILL.md +237 -0
  723. package/skills/write-executable-plan/plan-template.md +154 -0
  724. package/templates/_minimal/CLAUDE.md.tmpl +41 -0
  725. package/templates/_minimal/README.md.tmpl +15 -0
  726. package/templates/_minimal/gitignore.tmpl +47 -0
  727. package/templates/_shared/harte-regeln.md +16 -0
  728. package/templates/_shared/loop.md +90 -0
  729. package/templates/_shared/rules/parallel-sessions.md +77 -0
  730. package/templates/nextjs-minimal/README.md +30 -0
  731. package/templates/nextjs-minimal/app/layout.tsx +18 -0
  732. package/templates/nextjs-minimal/app/page.tsx +7 -0
  733. package/templates/nextjs-minimal/eslint.config.mjs +16 -0
  734. package/templates/nextjs-minimal/next.config.mjs +4 -0
  735. package/templates/nextjs-minimal/package.json +27 -0
  736. package/templates/nextjs-minimal/tsconfig.json +23 -0
  737. package/templates/node-minimal/README.md +33 -0
  738. package/templates/node-minimal/eslint.config.mjs +10 -0
  739. package/templates/node-minimal/package.json +21 -0
  740. package/templates/node-minimal/src/index.ts +1 -0
  741. package/templates/node-minimal/tests/sanity.test.ts +5 -0
  742. package/templates/node-minimal/tsconfig.json +17 -0
  743. package/templates/personas/README.md +150 -0
  744. package/templates/personas/accounting-compliance.v1.md +120 -0
  745. package/templates/personas/accounting-tax-advisor.v1.md +116 -0
  746. package/templates/personas/buyer-p1-cto.v1.md +125 -0
  747. package/templates/personas/buyer-p2-kanzlei.v1.md +134 -0
  748. package/templates/personas/buyer-p3-build.v1.md +130 -0
  749. package/templates/personas/buyer-p4-tech-veto.v1.md +130 -0
  750. package/templates/personas/buyer-p5-solo.v1.md +132 -0
  751. package/templates/personas/buyer-p6-ld.v1.md +130 -0
  752. package/templates/personas/klima-ai-expert.v1.md +114 -0
  753. package/templates/personas/klima-physicist.v1.md +117 -0
  754. package/templates/python-uv/README.md +28 -0
  755. package/templates/python-uv/pyproject.toml +32 -0
  756. package/templates/python-uv/src/__PROJECT_NAME__/__init__.py +0 -0
  757. package/templates/python-uv/src/__PROJECT_NAME__/main.py +6 -0
  758. package/templates/python-uv/tests/test_sanity.py +2 -0
  759. package/templates/static-html/README.md +19 -0
  760. package/templates/static-html/index.html +15 -0
  761. package/templates/static-html/script.js +1 -0
  762. package/templates/static-html/styles.css +26 -0
@@ -0,0 +1,1192 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * walker.mjs — Sunset-review surface walker (issue #444).
4
+ *
5
+ * READ-ONLY analysis of the plugin's skill/agent/command surface. Combines
6
+ * agent-dispatch telemetry (subagents.jsonl) with static reference scanning to
7
+ * classify each surface item into a 4-tier verdict:
8
+ *
9
+ * Active — clearly in use (dispatch, command-linkage, or prose refs)
10
+ * Investigate — default-safe; conflicting signals OR low telemetry confidence
11
+ * Demote — near-zero use (single dispatch, single cross-ref)
12
+ * Retire — provably cold (zero dispatch AND zero non-boilerplate refs)
13
+ *
14
+ * IMPORTANT GUARDRAIL (Discovery, grep-verified):
15
+ * Telemetry only spans ~18 days, but the default window is 90. When
16
+ * coverageDays < windowDays, EVERY Retire verdict is downgraded to
17
+ * Investigate and meta.lowConfidence is set. Retiring on <window data is
18
+ * unsafe.
19
+ *
20
+ * Telemetry facts the walker is built to respect:
21
+ * - subagents.jsonl agent_type is reliable ONLY on event==="start"; all nulls
22
+ * are on "stop" events. The walker counts START events only.
23
+ * - Skill-invocation telemetry NOW EXISTS in skill-invocations.jsonl (L1
24
+ * telemetry, epic #645). Skills are assessed by real selection counts from
25
+ * that file. Static reference scanning remains a SUPPLEMENTARY signal.
26
+ * - NO command-invocation telemetry exists. Commands are assessed via static
27
+ * reference scanning only.
28
+ *
29
+ * This module is pure-ish and side-effect free: it reads files but writes
30
+ * none, holds no mutable runtime state, and exposes no concurrency surface.
31
+ * The consuming SKILL writes the last-run timestamp — NOT the walker.
32
+ *
33
+ * JSON-first CLI per .claude/rules/cli-design.md:
34
+ * node scripts/lib/sunset/walker.mjs [--json] [--window-days N] [--kind K]
35
+ * exit 0 = walk done (cold findings are exit 0, NOT an error)
36
+ * exit 1 = bad args / surface dir missing
37
+ * exit 2 = system error
38
+ */
39
+
40
+ import { existsSync, readdirSync, readFileSync } from 'node:fs';
41
+ import path from 'node:path';
42
+ import { pathToFileURL } from 'node:url';
43
+ import { normalizeSkillInvocation } from '../skill-invocations-schema.mjs';
44
+ import { normalizeSkillJudgment } from '../skill-judgments-schema.mjs';
45
+
46
+ // ---------------------------------------------------------------------------
47
+ // Named threshold constants
48
+ // ---------------------------------------------------------------------------
49
+
50
+ /** Default sunset-review window in days. */
51
+ export const DEFAULT_WINDOW_DAYS = 90;
52
+
53
+ /** Agent dispatch count strictly greater than this floor ⇒ Active. */
54
+ export const ACTIVE_DISPATCH_FLOOR = 1;
55
+
56
+ /** Agent dispatch count in (0, this ceiling] ⇒ Demote (near-zero). */
57
+ export const DEMOTE_DISPATCH_CEILING = 1;
58
+
59
+ /** Non-boilerplate prose/strict refs at or above this floor ⇒ Active. */
60
+ export const ACTIVE_REF_FLOOR = 2;
61
+
62
+ /** Default agent-type prefix stripped from telemetry. */
63
+ const AGENT_TYPE_PREFIX = 'session-orchestrator:';
64
+
65
+ /** Surface directories excluded from enumeration. */
66
+ const EXCLUDED_SKILL_DIRS = new Set(['_shared']);
67
+ const EXCLUDED_AGENT_FILES = new Set(['AGENTS.md']);
68
+
69
+ /** Directories scanned for static references. */
70
+ const SCAN_DIRS = ['skills', 'agents', 'commands', 'hooks', 'scripts'];
71
+
72
+ const MS_PER_DAY = 24 * 60 * 60 * 1000;
73
+
74
+ // ---------------------------------------------------------------------------
75
+ // Surface enumeration
76
+ // ---------------------------------------------------------------------------
77
+
78
+ /**
79
+ * Enumerate the plugin's skill/agent/command surface.
80
+ * Excludes `skills/_shared` (shared helpers, not a skill) and
81
+ * `agents/AGENTS.md` (authoring spec, not an agent).
82
+ *
83
+ * @param {string} repoRoot
84
+ * @returns {{skills: string[], agents: string[], commands: string[]}}
85
+ */
86
+ export function enumerateSurface(repoRoot) {
87
+ const skills = [];
88
+ const skillsDir = path.join(repoRoot, 'skills');
89
+ if (existsSync(skillsDir)) {
90
+ for (const entry of readdirSync(skillsDir, { withFileTypes: true })) {
91
+ if (!entry.isDirectory()) continue;
92
+ if (EXCLUDED_SKILL_DIRS.has(entry.name)) continue;
93
+ // A skill dir must contain a SKILL.md to count as a skill.
94
+ if (existsSync(path.join(skillsDir, entry.name, 'SKILL.md'))) {
95
+ skills.push(entry.name);
96
+ }
97
+ }
98
+ }
99
+
100
+ const agents = [];
101
+ const agentsDir = path.join(repoRoot, 'agents');
102
+ if (existsSync(agentsDir)) {
103
+ for (const entry of readdirSync(agentsDir, { withFileTypes: true })) {
104
+ if (!entry.isFile()) continue;
105
+ if (!entry.name.endsWith('.md')) continue;
106
+ if (EXCLUDED_AGENT_FILES.has(entry.name)) continue;
107
+ agents.push(entry.name.replace(/\.md$/, ''));
108
+ }
109
+ }
110
+
111
+ const commands = [];
112
+ const commandsDir = path.join(repoRoot, 'commands');
113
+ if (existsSync(commandsDir)) {
114
+ for (const entry of readdirSync(commandsDir, { withFileTypes: true })) {
115
+ if (!entry.isFile()) continue;
116
+ if (!entry.name.endsWith('.md')) continue;
117
+ commands.push(entry.name.replace(/\.md$/, ''));
118
+ }
119
+ }
120
+
121
+ return {
122
+ skills: skills.sort(),
123
+ agents: agents.sort(),
124
+ commands: commands.sort(),
125
+ };
126
+ }
127
+
128
+ // ---------------------------------------------------------------------------
129
+ // Dispatch telemetry
130
+ // ---------------------------------------------------------------------------
131
+
132
+ /**
133
+ * Read agent-dispatch counts from subagents.jsonl.
134
+ *
135
+ * Only `event === "start"` records are counted — `agent_type` is null on
136
+ * "stop" events, so a stop event must never mark an agent cold.
137
+ *
138
+ * @param {string} subagentsJsonlPath
139
+ * @param {{windowDays: number, now?: number}} opts
140
+ * @returns {{
141
+ * byAgent: Map<string,{count:number,lastTs:string|null}>,
142
+ * earliestTs: string|null,
143
+ * latestTs: string|null,
144
+ * coverageDays: number
145
+ * }}
146
+ */
147
+ export function readDispatchCounts(subagentsJsonlPath, { windowDays, now } = {}) {
148
+ const nowMs = typeof now === 'number' ? now : Date.now();
149
+ const windowStartMs = nowMs - (windowDays ?? DEFAULT_WINDOW_DAYS) * MS_PER_DAY;
150
+
151
+ /** @type {Map<string,{count:number,lastTs:string|null}>} */
152
+ const byAgent = new Map();
153
+ let earliestMs = null;
154
+ let latestMs = null;
155
+
156
+ if (!existsSync(subagentsJsonlPath)) {
157
+ return { byAgent, earliestTs: null, latestTs: null, coverageDays: 0 };
158
+ }
159
+
160
+ let raw;
161
+ try {
162
+ raw = readFileSync(subagentsJsonlPath, 'utf8');
163
+ } catch {
164
+ return { byAgent, earliestTs: null, latestTs: null, coverageDays: 0 };
165
+ }
166
+
167
+ for (const line of raw.split('\n')) {
168
+ const trimmed = line.trim();
169
+ if (trimmed === '') continue;
170
+ let rec;
171
+ try {
172
+ rec = JSON.parse(trimmed);
173
+ } catch {
174
+ // Malformed line — skip, never throw.
175
+ continue;
176
+ }
177
+ if (!rec || typeof rec !== 'object') continue;
178
+ if (rec.event !== 'start') continue;
179
+ const ts = typeof rec.timestamp === 'string' ? rec.timestamp : null;
180
+ const tsMs = ts ? Date.parse(ts) : NaN;
181
+
182
+ // Track coverage envelope from ALL start events (regardless of window).
183
+ if (!Number.isNaN(tsMs)) {
184
+ if (earliestMs === null || tsMs < earliestMs) earliestMs = tsMs;
185
+ if (latestMs === null || tsMs > latestMs) latestMs = tsMs;
186
+ }
187
+
188
+ const rawType = typeof rec.agent_type === 'string' ? rec.agent_type : null;
189
+ if (!rawType) continue;
190
+ const name = rawType.startsWith(AGENT_TYPE_PREFIX)
191
+ ? rawType.slice(AGENT_TYPE_PREFIX.length)
192
+ : rawType;
193
+
194
+ // Only count dispatches inside the window for the per-agent tally.
195
+ if (!Number.isNaN(tsMs) && tsMs < windowStartMs) continue;
196
+
197
+ const cur = byAgent.get(name) ?? { count: 0, lastTs: null };
198
+ cur.count += 1;
199
+ if (ts && (cur.lastTs === null || Date.parse(ts) > Date.parse(cur.lastTs))) {
200
+ cur.lastTs = ts;
201
+ }
202
+ byAgent.set(name, cur);
203
+ }
204
+
205
+ const coverageDays =
206
+ earliestMs === null ? 0 : Math.max(0, (nowMs - earliestMs) / MS_PER_DAY);
207
+
208
+ return {
209
+ byAgent,
210
+ earliestTs: earliestMs === null ? null : new Date(earliestMs).toISOString(),
211
+ latestTs: latestMs === null ? null : new Date(latestMs).toISOString(),
212
+ coverageDays,
213
+ };
214
+ }
215
+
216
+ // ---------------------------------------------------------------------------
217
+ // Skill-invocation telemetry (L1, epic #645)
218
+ // ---------------------------------------------------------------------------
219
+
220
+ /**
221
+ * Read skill-selection counts from skill-invocations.jsonl.
222
+ *
223
+ * Mirrors `readDispatchCounts` style: synchronous, window-aware, never throws.
224
+ * Counts only `event === "selected"` records inside the window.
225
+ *
226
+ * Uses `normalizeSkillInvocation` from skill-invocations-schema.mjs for
227
+ * consistent field defaulting, keeping parity with the async reader the schema
228
+ * module also provides.
229
+ *
230
+ * @param {string} invocationsPath
231
+ * @param {{windowDays: number, now?: number}} opts
232
+ * @returns {{
233
+ * bySkill: Map<string,{count:number,lastTs:string|null}>,
234
+ * earliestTs: string|null,
235
+ * latestTs: string|null,
236
+ * coverageDays: number
237
+ * }}
238
+ */
239
+ export function readSkillInvocationCounts(invocationsPath, { windowDays, now } = {}) {
240
+ const nowMs = typeof now === 'number' ? now : Date.now();
241
+ const windowStartMs = nowMs - (windowDays ?? DEFAULT_WINDOW_DAYS) * MS_PER_DAY;
242
+
243
+ /** @type {Map<string,{count:number,lastTs:string|null}>} */
244
+ const bySkill = new Map();
245
+ let earliestMs = null;
246
+ let latestMs = null;
247
+
248
+ if (!existsSync(invocationsPath)) {
249
+ return { bySkill, earliestTs: null, latestTs: null, coverageDays: 0 };
250
+ }
251
+
252
+ let raw;
253
+ try {
254
+ raw = readFileSync(invocationsPath, 'utf8');
255
+ } catch {
256
+ return { bySkill, earliestTs: null, latestTs: null, coverageDays: 0 };
257
+ }
258
+
259
+ for (const line of raw.split('\n')) {
260
+ const trimmed = line.trim();
261
+ if (trimmed === '') continue;
262
+ let rec;
263
+ try {
264
+ rec = normalizeSkillInvocation(JSON.parse(trimmed));
265
+ } catch {
266
+ // Malformed line — skip, never throw.
267
+ continue;
268
+ }
269
+ if (!rec || typeof rec !== 'object') continue;
270
+ if (rec.event !== 'selected') continue;
271
+
272
+ const ts = typeof rec.timestamp === 'string' ? rec.timestamp : null;
273
+ const tsMs = ts ? Date.parse(ts) : NaN;
274
+
275
+ // Track coverage envelope from ALL selected events (regardless of window).
276
+ if (!Number.isNaN(tsMs)) {
277
+ if (earliestMs === null || tsMs < earliestMs) earliestMs = tsMs;
278
+ if (latestMs === null || tsMs > latestMs) latestMs = tsMs;
279
+ }
280
+
281
+ const skillName = typeof rec.skill === 'string' && rec.skill.trim() ? rec.skill.trim() : null;
282
+ if (!skillName) continue;
283
+
284
+ // Only count invocations inside the window for the per-skill tally.
285
+ if (!Number.isNaN(tsMs) && tsMs < windowStartMs) continue;
286
+
287
+ const cur = bySkill.get(skillName) ?? { count: 0, lastTs: null };
288
+ cur.count += 1;
289
+ if (ts && (cur.lastTs === null || Date.parse(ts) > Date.parse(cur.lastTs))) {
290
+ cur.lastTs = ts;
291
+ }
292
+ bySkill.set(skillName, cur);
293
+ }
294
+
295
+ const coverageDays =
296
+ earliestMs === null ? 0 : Math.max(0, (nowMs - earliestMs) / MS_PER_DAY);
297
+
298
+ return {
299
+ bySkill,
300
+ earliestTs: earliestMs === null ? null : new Date(earliestMs).toISOString(),
301
+ latestTs: latestMs === null ? null : new Date(latestMs).toISOString(),
302
+ coverageDays,
303
+ };
304
+ }
305
+
306
+ // ---------------------------------------------------------------------------
307
+ // Skill-judgment telemetry (L3, epic #645 — ADVISORY ONLY)
308
+ // ---------------------------------------------------------------------------
309
+
310
+ /**
311
+ * Read skill-applied JUDGMENT counts from skill-judgments.jsonl (L3).
312
+ *
313
+ * Mirrors `readSkillInvocationCounts` style: synchronous, window-aware, never
314
+ * throws, existsSync-guarded. Counts only `event === "judged"` records inside
315
+ * the window and aggregates the tri-state applied/completed tallies per skill.
316
+ *
317
+ * Uses `normalizeSkillJudgment` from skill-judgments-schema.mjs for consistent
318
+ * field defaulting (advisory:true, session_id:null, schema_version).
319
+ *
320
+ * IMPORTANT (firewall): the data returned here is ADVISORY ONLY. The walker
321
+ * uses it to ANNOTATE a diagnosis or DOWNGRADE a verdict — never to escalate a
322
+ * skill toward Retire (see classifyItem #645 R9 advisory-only).
323
+ *
324
+ * @param {string} judgmentsPath
325
+ * @param {{windowDays: number, now?: number}} opts
326
+ * @returns {{
327
+ * bySkill: Map<string,{
328
+ * appliedYes:number, appliedNo:number, appliedUnknown:number,
329
+ * completedYes:number, completedNo:number, completedUnknown:number,
330
+ * total:number, lastTs:string|null
331
+ * }>,
332
+ * earliestTs: string|null,
333
+ * latestTs: string|null,
334
+ * coverageDays: number
335
+ * }}
336
+ */
337
+ export function readSkillJudgmentCounts(judgmentsPath, { windowDays, now } = {}) {
338
+ const nowMs = typeof now === 'number' ? now : Date.now();
339
+ const windowStartMs = nowMs - (windowDays ?? DEFAULT_WINDOW_DAYS) * MS_PER_DAY;
340
+
341
+ /** @type {Map<string,{
342
+ * appliedYes:number, appliedNo:number, appliedUnknown:number,
343
+ * completedYes:number, completedNo:number, completedUnknown:number,
344
+ * total:number, lastTs:string|null
345
+ * }>} */
346
+ const bySkill = new Map();
347
+ let earliestMs = null;
348
+ let latestMs = null;
349
+
350
+ if (!existsSync(judgmentsPath)) {
351
+ return { bySkill, earliestTs: null, latestTs: null, coverageDays: 0 };
352
+ }
353
+
354
+ let raw;
355
+ try {
356
+ raw = readFileSync(judgmentsPath, 'utf8');
357
+ } catch {
358
+ return { bySkill, earliestTs: null, latestTs: null, coverageDays: 0 };
359
+ }
360
+
361
+ for (const line of raw.split('\n')) {
362
+ const trimmed = line.trim();
363
+ if (trimmed === '') continue;
364
+ let rec;
365
+ try {
366
+ rec = normalizeSkillJudgment(JSON.parse(trimmed));
367
+ } catch {
368
+ // Malformed line — skip, never throw.
369
+ continue;
370
+ }
371
+ if (!rec || typeof rec !== 'object') continue;
372
+ if (rec.event !== 'judged') continue;
373
+
374
+ const ts = typeof rec.timestamp === 'string' ? rec.timestamp : null;
375
+ const tsMs = ts ? Date.parse(ts) : NaN;
376
+
377
+ // Track coverage envelope from ALL judged events (regardless of window).
378
+ if (!Number.isNaN(tsMs)) {
379
+ if (earliestMs === null || tsMs < earliestMs) earliestMs = tsMs;
380
+ if (latestMs === null || tsMs > latestMs) latestMs = tsMs;
381
+ }
382
+
383
+ const skillName = typeof rec.skill === 'string' && rec.skill.trim() ? rec.skill.trim() : null;
384
+ if (!skillName) continue;
385
+
386
+ // Only count judgments inside the window for the per-skill tally.
387
+ if (!Number.isNaN(tsMs) && tsMs < windowStartMs) continue;
388
+
389
+ const cur = bySkill.get(skillName) ?? {
390
+ appliedYes: 0,
391
+ appliedNo: 0,
392
+ appliedUnknown: 0,
393
+ completedYes: 0,
394
+ completedNo: 0,
395
+ completedUnknown: 0,
396
+ total: 0,
397
+ lastTs: null,
398
+ };
399
+ cur.total += 1;
400
+ if (rec.applied === 'yes') cur.appliedYes += 1;
401
+ else if (rec.applied === 'no') cur.appliedNo += 1;
402
+ else cur.appliedUnknown += 1;
403
+ if (rec.completed === 'yes') cur.completedYes += 1;
404
+ else if (rec.completed === 'no') cur.completedNo += 1;
405
+ else cur.completedUnknown += 1;
406
+ if (ts && (cur.lastTs === null || Date.parse(ts) > Date.parse(cur.lastTs))) {
407
+ cur.lastTs = ts;
408
+ }
409
+ bySkill.set(skillName, cur);
410
+ }
411
+
412
+ const coverageDays =
413
+ earliestMs === null ? 0 : Math.max(0, (nowMs - earliestMs) / MS_PER_DAY);
414
+
415
+ return {
416
+ bySkill,
417
+ earliestTs: earliestMs === null ? null : new Date(earliestMs).toISOString(),
418
+ latestTs: latestMs === null ? null : new Date(latestMs).toISOString(),
419
+ coverageDays,
420
+ };
421
+ }
422
+
423
+ // ---------------------------------------------------------------------------
424
+ // Static reference scanning
425
+ // ---------------------------------------------------------------------------
426
+
427
+ /**
428
+ * Recursively collect file paths under a directory (skipping node_modules and
429
+ * dot-directories). Returns absolute paths.
430
+ *
431
+ * @param {string} dir
432
+ * @returns {string[]}
433
+ */
434
+ function collectFiles(dir) {
435
+ /** @type {string[]} */
436
+ const out = [];
437
+ if (!existsSync(dir)) return out;
438
+ const stack = [dir];
439
+ while (stack.length > 0) {
440
+ const cur = stack.pop();
441
+ let entries;
442
+ try {
443
+ entries = readdirSync(cur, { withFileTypes: true });
444
+ } catch {
445
+ continue;
446
+ }
447
+ for (const entry of entries) {
448
+ const full = path.join(cur, entry.name);
449
+ if (entry.isDirectory()) {
450
+ if (entry.name === 'node_modules') continue;
451
+ if (entry.name.startsWith('.')) continue;
452
+ stack.push(full);
453
+ } else if (entry.isFile()) {
454
+ out.push(full);
455
+ }
456
+ }
457
+ }
458
+ return out;
459
+ }
460
+
461
+ /**
462
+ * Determine whether a file path is a boilerplate (self-definition or
463
+ * authoring/registry) site for a given surface item, and therefore must be
464
+ * excluded from the "is anyone else using this?" reference count.
465
+ *
466
+ * @param {string} relPath - repo-root-relative POSIX path
467
+ * @param {'skill'|'agent'|'command'} kind
468
+ * @param {string} name
469
+ * @returns {boolean}
470
+ */
471
+ function isBoilerplateSite(relPath, kind, name) {
472
+ if (kind === 'agent') {
473
+ if (relPath === `agents/${name}.md`) return true;
474
+ if (relPath === 'agents/AGENTS.md') return true;
475
+ if (relPath === `agents/schemas/${name}.schema.json`) return true;
476
+ // Routing-table / validator boilerplate.
477
+ if (relPath === 'agents/schemas/routing-table.json') return true;
478
+ // The dead-bridge validator (subsumed check-subagent-types per #671) carries
479
+ // agent names in its corpus/detector fixtures — exempt it as a validator site.
480
+ if (relPath.startsWith('scripts/lib/validate/dead-bridge')) return true;
481
+ if (relPath.startsWith('scripts/lib/validate/check-dead-bridge')) return true;
482
+ if (relPath.startsWith('scripts/lib/validate/check-agents')) return true;
483
+ return false;
484
+ }
485
+ if (kind === 'skill') {
486
+ // The skill's own directory is boilerplate.
487
+ if (relPath.startsWith(`skills/${name}/`)) return true;
488
+ // README index / top-level docs index entries are boilerplate.
489
+ if (relPath === 'README.md') return true;
490
+ return false;
491
+ }
492
+ // command
493
+ if (relPath === `commands/${name}.md`) return true;
494
+ if (relPath === 'README.md') return true;
495
+ return false;
496
+ }
497
+
498
+ /**
499
+ * Build the strict-token and prose-invocation matchers for a surface item.
500
+ * Bare-word matching is deliberately NOT used (too noisy per Discovery: e.g.
501
+ * "daily" yields 0 strict refs but 16 name-anywhere refs).
502
+ *
503
+ * @param {'skill'|'agent'|'command'} kind
504
+ * @param {string} name
505
+ * @returns {{strict: RegExp[], prose: RegExp[]}}
506
+ */
507
+ function buildMatchers(kind, name) {
508
+ const esc = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
509
+ if (kind === 'agent') {
510
+ return {
511
+ strict: [
512
+ new RegExp(`session-orchestrator:${esc}\\b`),
513
+ new RegExp(`subagent_type[^\\n]*["']${esc}["']`),
514
+ ],
515
+ prose: [
516
+ new RegExp(`\\bdispatch(?:es|ed|ing)?\\b[^\\n]*\\b${esc}\\b`, 'i'),
517
+ new RegExp(`\\b${esc}\\b[^\\n]*\\bagent\\b`, 'i'),
518
+ ],
519
+ };
520
+ }
521
+ if (kind === 'skill') {
522
+ return {
523
+ strict: [
524
+ new RegExp(`skills/${esc}/`),
525
+ new RegExp(`session-orchestrator:${esc}\\b`),
526
+ ],
527
+ prose: [
528
+ new RegExp(`\\b[Ii]nvoke[^\\n]*\\b${esc}\\b[^\\n]*\\bskill\\b`),
529
+ new RegExp(`\\b${esc}\\b[^\\n]*\\bskill\\b`, 'i'),
530
+ ],
531
+ };
532
+ }
533
+ // command
534
+ return {
535
+ strict: [new RegExp(`/${esc}\\b`)],
536
+ prose: [new RegExp(`\\b${esc}\\b[^\\n]*\\bcommand\\b`, 'i')],
537
+ };
538
+ }
539
+
540
+ /**
541
+ * Scan skills/agents/commands/hooks/scripts for references to a surface item,
542
+ * excluding boilerplate (self-definition + authoring/registry) sites.
543
+ *
544
+ * @param {string} repoRoot
545
+ * @param {{kind:'skill'|'agent'|'command', name:string}} opts
546
+ * @returns {{strictRefs:number, proseRefs:number, nonBoilerplateRefs:number, refFiles:string[]}}
547
+ */
548
+ export function staticReferenceScan(repoRoot, { kind, name }) {
549
+ const { strict, prose } = buildMatchers(kind, name);
550
+ let strictRefs = 0;
551
+ let proseRefs = 0;
552
+ /** @type {Set<string>} */
553
+ const refFiles = new Set();
554
+
555
+ for (const dir of SCAN_DIRS) {
556
+ const absDir = path.join(repoRoot, dir);
557
+ for (const file of collectFiles(absDir)) {
558
+ const relPath = path.relative(repoRoot, file).split(path.sep).join('/');
559
+ if (isBoilerplateSite(relPath, kind, name)) continue;
560
+ let content;
561
+ try {
562
+ content = readFileSync(file, 'utf8');
563
+ } catch {
564
+ continue;
565
+ }
566
+ let hit = false;
567
+ for (const re of strict) {
568
+ if (re.test(content)) {
569
+ strictRefs += 1;
570
+ hit = true;
571
+ }
572
+ }
573
+ for (const re of prose) {
574
+ if (re.test(content)) {
575
+ proseRefs += 1;
576
+ hit = true;
577
+ }
578
+ }
579
+ if (hit) refFiles.add(relPath);
580
+ }
581
+ }
582
+
583
+ return {
584
+ strictRefs,
585
+ proseRefs,
586
+ nonBoilerplateRefs: refFiles.size,
587
+ refFiles: [...refFiles].sort(),
588
+ };
589
+ }
590
+
591
+ // ---------------------------------------------------------------------------
592
+ // Command ↔ skill linkage
593
+ // ---------------------------------------------------------------------------
594
+
595
+ /**
596
+ * Parse commands/*.md for the skill each command invokes. Recognises the
597
+ * conventional `skills/<name>/SKILL.md` reference inside a command file.
598
+ *
599
+ * @param {string} repoRoot
600
+ * @returns {{commandToSkill: Map<string,string|null>, skillToCommands: Map<string,string[]>}}
601
+ */
602
+ export function commandSkillLinkage(repoRoot) {
603
+ /** @type {Map<string,string|null>} */
604
+ const commandToSkill = new Map();
605
+ /** @type {Map<string,string[]>} */
606
+ const skillToCommands = new Map();
607
+
608
+ const commandsDir = path.join(repoRoot, 'commands');
609
+ if (!existsSync(commandsDir)) {
610
+ return { commandToSkill, skillToCommands };
611
+ }
612
+
613
+ const skillRefRe = /skills\/([a-z0-9-]+)\/SKILL\.md/i;
614
+ for (const entry of readdirSync(commandsDir, { withFileTypes: true })) {
615
+ if (!entry.isFile() || !entry.name.endsWith('.md')) continue;
616
+ const command = entry.name.replace(/\.md$/, '');
617
+ let content;
618
+ try {
619
+ content = readFileSync(path.join(commandsDir, entry.name), 'utf8');
620
+ } catch {
621
+ commandToSkill.set(command, null);
622
+ continue;
623
+ }
624
+ const m = skillRefRe.exec(content);
625
+ const skill = m ? m[1] : null;
626
+ commandToSkill.set(command, skill);
627
+ if (skill) {
628
+ const list = skillToCommands.get(skill) ?? [];
629
+ list.push(command);
630
+ skillToCommands.set(skill, list);
631
+ }
632
+ }
633
+
634
+ return { commandToSkill, skillToCommands };
635
+ }
636
+
637
+ // ---------------------------------------------------------------------------
638
+ // Classification
639
+ // ---------------------------------------------------------------------------
640
+
641
+ /**
642
+ * Strong-applied-no threshold: a skill selected but judged applied=no for at
643
+ * least this many judgments (and no applied=yes) is a strong "selected but not
644
+ * applied" advisory signal — enough to DOWNGRADE Active → Investigate.
645
+ */
646
+ const STRONG_APPLIED_NO_FLOOR = 2;
647
+
648
+ /**
649
+ * Apply the L3 judge advisory layer to an already-computed deterministic
650
+ * verdict. ADVISORY ONLY (#645 R9):
651
+ * - ANNOTATE: append a diagnosis axis derived from applied/completed tallies.
652
+ * - DOWNGRADE: Active → Investigate on a STRONG applied=no signal.
653
+ * - It MUST NEVER escalate a verdict toward Retire/Demote, and MUST NEVER
654
+ * promote a verdict upward (no Retire→Active). Judge data only softens.
655
+ *
656
+ * Clean-degrade contract: when `judge` is null/absent OR carries no judgments,
657
+ * the result is returned byte-for-byte unchanged.
658
+ *
659
+ * @param {{name:string,kind:string,verdict:string,score:number,reasons:string[],signals:object}} result
660
+ * the deterministic verdict (mutated in place: reasons/signals only)
661
+ * @param {{appliedYes:number,appliedNo:number,appliedUnknown:number,completedYes:number,completedNo:number,completedUnknown:number,total:number,lastTs:string|null}|null} judge
662
+ * @returns {{name:string,kind:string,verdict:string,score:number,reasons:string[],signals:object}}
663
+ */
664
+ function applyJudgeAdvisory(result, judge) {
665
+ // Disabled-path guarantee: null judge, or judge with zero recorded judgments,
666
+ // leaves the deterministic result completely untouched.
667
+ if (!judge || typeof judge !== 'object' || (judge.total ?? 0) <= 0) {
668
+ return result;
669
+ }
670
+
671
+ const appliedYes = judge.appliedYes ?? 0;
672
+ const appliedNo = judge.appliedNo ?? 0;
673
+ const completedNo = judge.completedNo ?? 0;
674
+ const total = judge.total ?? 0;
675
+
676
+ // Surface the raw judge tallies on signals for downstream consumers (always
677
+ // advisory — never read by the deterministic verdict gate above).
678
+ result.signals.judge = {
679
+ appliedYes,
680
+ appliedNo,
681
+ appliedUnknown: judge.appliedUnknown ?? 0,
682
+ completedYes: judge.completedYes ?? 0,
683
+ completedNo,
684
+ completedUnknown: judge.completedUnknown ?? 0,
685
+ total,
686
+ lastTs: judge.lastTs ?? null,
687
+ };
688
+
689
+ // Annotation axis 1: selected but applied=no → trigger description may be unclear.
690
+ const strongAppliedNo = appliedNo >= STRONG_APPLIED_NO_FLOOR && appliedYes === 0;
691
+ if (appliedNo > 0) {
692
+ result.reasons.push(
693
+ `[advisory] judged applied=no in ${appliedNo}/${total} judgment(s)` +
694
+ (appliedYes === 0
695
+ ? ' (selected but never applied → trigger description may be unclear)'
696
+ : ''),
697
+ );
698
+ }
699
+
700
+ // Annotation axis 2: applied but completed=no → instructions may be wrong.
701
+ if (appliedYes > 0 && completedNo > 0) {
702
+ result.reasons.push(
703
+ `[advisory] judged applied=yes but completed=no in ${completedNo}/${total} judgment(s) (instructions may be wrong)`,
704
+ );
705
+ }
706
+
707
+ // DOWNGRADE (never escalate): a strong "selected but not applied" signal
708
+ // softens an Active verdict to Investigate so an operator reviews the trigger.
709
+ // Firewall: we ONLY move Active → Investigate. We never touch Demote/Retire
710
+ // (those are deterministic-cold), and we never promote upward.
711
+ if (strongAppliedNo && result.verdict === 'Active') {
712
+ result.reasons.push(
713
+ `[advisory] strong applied=no signal (${appliedNo}/${total}, 0 applied=yes) → Active downgraded to Investigate for operator review`,
714
+ );
715
+ result.verdict = 'Investigate';
716
+ result.score = 2;
717
+ }
718
+
719
+ return result;
720
+ }
721
+
722
+ /**
723
+ * Classify a single surface item into a 4-tier verdict.
724
+ *
725
+ * Verdict precedence (default-safe):
726
+ * - Active — agent dispatch>floor, OR skill invoked-by-command /
727
+ * command invokes-a-live-skill, OR proseRefs>=ACTIVE_REF_FLOOR
728
+ * - Investigate — coverage<window (low-confidence), OR conflicting signals
729
+ * (DEFAULT-SAFE bucket)
730
+ * - Demote — 0<dispatch<=DEMOTE_DISPATCH_CEILING, OR a skill with only a
731
+ * single cross-ref and no command linkage
732
+ * - Retire — dispatch===0 AND nonBoilerplateRefs===0 AND coverage>=window
733
+ *
734
+ * The optional `judge` (L3, epic #645) is ADVISORY ONLY. It is consumed AFTER
735
+ * the deterministic verdict is computed, and may only ANNOTATE the diagnosis or
736
+ * DOWNGRADE Active → Investigate on a strong applied=no signal. It can NEVER
737
+ * escalate a skill toward Retire/Demote nor promote it upward. When `judge` is
738
+ * null/absent the classification is byte-for-byte identical to the L1-only path
739
+ * (disabled-path guarantee).
740
+ *
741
+ * @param {{
742
+ * kind:'skill'|'agent'|'command',
743
+ * name:string,
744
+ * dispatch?:{count:number,lastTs:string|null}|null,
745
+ * static:{strictRefs:number,proseRefs:number,nonBoilerplateRefs:number,refFiles:string[]},
746
+ * linkage?:{invokedByCommands?:string[], invokesSkill?:string|null, invokedSkillExists?:boolean},
747
+ * windowDays:number,
748
+ * coverageDays:number,
749
+ * judge?:{appliedYes:number,appliedNo:number,appliedUnknown:number,completedYes:number,completedNo:number,completedUnknown:number,total:number,lastTs:string|null}|null
750
+ * }} args
751
+ * @returns {{name:string,kind:string,verdict:string,score:number,reasons:string[],signals:object}}
752
+ */
753
+ export function classifyItem({
754
+ kind,
755
+ name,
756
+ dispatch,
757
+ static: staticRefs,
758
+ linkage,
759
+ windowDays,
760
+ coverageDays,
761
+ judge = null,
762
+ }) {
763
+ const reasons = [];
764
+ const dispatchCount = dispatch?.count ?? 0;
765
+ const proseRefs = staticRefs.proseRefs;
766
+ const strictRefs = staticRefs.strictRefs;
767
+ const nonBoilerplateRefs = staticRefs.nonBoilerplateRefs;
768
+ const invokedByCommands = linkage?.invokedByCommands ?? [];
769
+ const invokesSkill = linkage?.invokesSkill ?? null;
770
+ // When a command invokes a skill, the caller passes whether that skill is
771
+ // present on disk. Default-true so callers that don't thread the surface set
772
+ // (older callers/tests) preserve the historical "Active on linkage" behavior.
773
+ const invokedSkillExists = linkage?.invokedSkillExists ?? true;
774
+ const lowConfidence = coverageDays < windowDays;
775
+
776
+ const signals = {
777
+ dispatchCount,
778
+ lastDispatch: dispatch?.lastTs ?? null,
779
+ strictRefs,
780
+ proseRefs,
781
+ nonBoilerplateRefs,
782
+ invokedByCommands,
783
+ invokesSkill,
784
+ invokedSkillExists,
785
+ lowConfidence,
786
+ };
787
+
788
+ // --- Agent dispatch tier takes precedence over ref-based Active -----------
789
+ // For agents, telemetry is authoritative. An agent with exactly one dispatch
790
+ // is near-zero (Demote-tier) even though its definition is referenced across
791
+ // many docs — those references are largely boilerplate-adjacent and do NOT
792
+ // prove the agent is actually being dispatched. Discovery: db-specialist /
793
+ // ui-developer are n=1 (Demote), distinct from never-dispatched (Retire).
794
+ if (kind === 'agent') {
795
+ if (dispatchCount > ACTIVE_DISPATCH_FLOOR) {
796
+ reasons.push(
797
+ `agent dispatched ${dispatchCount}× in window (>floor ${ACTIVE_DISPATCH_FLOOR})`,
798
+ );
799
+ return applyJudgeAdvisory({ name, kind, verdict: 'Active', score: 0, reasons, signals }, judge);
800
+ }
801
+ if (dispatchCount > 0 && dispatchCount <= DEMOTE_DISPATCH_CEILING) {
802
+ reasons.push(
803
+ `near-zero dispatch (${dispatchCount} in (0, ${DEMOTE_DISPATCH_CEILING}] ceiling), last ${dispatch?.lastTs ?? 'n/a'}`,
804
+ );
805
+ if (lowConfidence) {
806
+ reasons.push(
807
+ `low-confidence telemetry (coverage ${coverageDays.toFixed(1)}d < window ${windowDays}d) → Investigate`,
808
+ );
809
+ return applyJudgeAdvisory({ name, kind, verdict: 'Investigate', score: 2, reasons, signals }, judge);
810
+ }
811
+ return applyJudgeAdvisory({ name, kind, verdict: 'Demote', score: 1, reasons, signals }, judge);
812
+ }
813
+ // dispatchCount === 0 falls through to the cold/Retire + ref checks below.
814
+ }
815
+
816
+ // --- Active checks (linkage + ref-floor) -----------------------------------
817
+ // NOTE: the ref-floor Active promotion applies to skills & commands ONLY.
818
+ // For agents, dispatch telemetry is authoritative — a zero-dispatch agent
819
+ // must NOT be promoted to Active by mere documentation mentions (every agent
820
+ // is referenced across docs; that proves nothing about dispatch). A
821
+ // zero-dispatch agent with some non-boilerplate refs lands in Investigate via
822
+ // the fallthrough below, never Active. This is the Discovery "single-grep is
823
+ // noisy" guard realised at the verdict layer.
824
+ // --- Command → removed-skill staleness guard -------------------------------
825
+ // A command that invokes a skill which is no longer present on disk is the
826
+ // exact staleness a sunset tool must surface. Such a command is NOT Active —
827
+ // the linkage points at a deleted skill. Route it to Investigate so an
828
+ // operator confirms whether the command (or its target skill) should go.
829
+ if (kind === 'command' && invokesSkill && !invokedSkillExists) {
830
+ reasons.push(`command invokes a skill not present on disk: ${invokesSkill}`);
831
+ return applyJudgeAdvisory({ name, kind, verdict: 'Investigate', score: 2, reasons, signals }, judge);
832
+ }
833
+
834
+ let active = false;
835
+ if (kind === 'skill' && invokedByCommands.length > 0) {
836
+ active = true;
837
+ reasons.push(`skill invoked by command(s): ${invokedByCommands.join(', ')}`);
838
+ }
839
+ if (kind === 'command' && invokesSkill && invokedSkillExists) {
840
+ active = true;
841
+ reasons.push(`command invokes live skill: ${invokesSkill}`);
842
+ }
843
+ if (
844
+ kind !== 'agent' &&
845
+ (nonBoilerplateRefs >= ACTIVE_REF_FLOOR || proseRefs >= ACTIVE_REF_FLOOR)
846
+ ) {
847
+ active = true;
848
+ reasons.push(
849
+ `${nonBoilerplateRefs} non-boilerplate ref file(s), ${proseRefs} prose ref(s) (>=floor ${ACTIVE_REF_FLOOR})`,
850
+ );
851
+ }
852
+ if (active) {
853
+ return applyJudgeAdvisory({ name, kind, verdict: 'Active', score: 0, reasons, signals }, judge);
854
+ }
855
+
856
+ // --- Retire (provably cold) — gated on full coverage -----------------------
857
+ const provablyCold =
858
+ dispatchCount === 0 && nonBoilerplateRefs === 0;
859
+ if (provablyCold) {
860
+ if (lowConfidence) {
861
+ // GUARDRAIL: never Retire on < window of telemetry. Downgrade.
862
+ reasons.push(
863
+ `cold (0 dispatch, 0 non-boilerplate refs) BUT coverage ${coverageDays.toFixed(1)}d < window ${windowDays}d → downgraded to Investigate`,
864
+ );
865
+ return applyJudgeAdvisory({ name, kind, verdict: 'Investigate', score: 2, reasons, signals }, judge);
866
+ }
867
+ reasons.push(
868
+ `0 dispatch AND 0 non-boilerplate refs AND coverage ${coverageDays.toFixed(1)}d >= window ${windowDays}d`,
869
+ );
870
+ return applyJudgeAdvisory({ name, kind, verdict: 'Retire', score: 3, reasons, signals }, judge);
871
+ }
872
+
873
+ // --- Demote (near-zero) — skills & commands (agents handled above) ---------
874
+ const lonelySkill =
875
+ kind === 'skill' && nonBoilerplateRefs <= 1 && invokedByCommands.length === 0;
876
+ const lonelyCommand =
877
+ kind === 'command' && !invokesSkill && nonBoilerplateRefs <= 1;
878
+ if (lonelySkill || lonelyCommand) {
879
+ if (lonelySkill) {
880
+ reasons.push(`skill has only ${nonBoilerplateRefs} cross-ref and no command linkage`);
881
+ }
882
+ if (lonelyCommand) {
883
+ reasons.push(`command invokes no skill and has only ${nonBoilerplateRefs} cross-ref`);
884
+ }
885
+ if (lowConfidence) {
886
+ reasons.push(
887
+ `low-confidence telemetry (coverage ${coverageDays.toFixed(1)}d < window ${windowDays}d) → Investigate`,
888
+ );
889
+ return applyJudgeAdvisory({ name, kind, verdict: 'Investigate', score: 2, reasons, signals }, judge);
890
+ }
891
+ return applyJudgeAdvisory({ name, kind, verdict: 'Demote', score: 1, reasons, signals }, judge);
892
+ }
893
+
894
+ // --- Investigate (default-safe fallthrough) --------------------------------
895
+ reasons.push('conflicting or inconclusive signals → default-safe Investigate');
896
+ return applyJudgeAdvisory({ name, kind, verdict: 'Investigate', score: 2, reasons, signals }, judge);
897
+ }
898
+
899
+ // ---------------------------------------------------------------------------
900
+ // Top-level walk
901
+ // ---------------------------------------------------------------------------
902
+
903
+ /**
904
+ * Run the full sunset walk across the plugin surface.
905
+ *
906
+ * @param {string} repoRoot
907
+ * @param {{windowDays?:number, kind?:'skill'|'agent'|'command', now?:number, subagentsPath?:string, invocationsPath?:string, judgmentsPath?:string}} [opts]
908
+ * @returns {{
909
+ * meta:{generatedAt:string,windowDays:number,coverageDays:number,lowConfidence:boolean,telemetrySources:object},
910
+ * summary:{active:number,investigate:number,demote:number,retire:number},
911
+ * items:Array<object>
912
+ * }}
913
+ */
914
+ export function runSunsetWalk(repoRoot, opts = {}) {
915
+ const windowDays = opts.windowDays ?? DEFAULT_WINDOW_DAYS;
916
+ const nowMs = typeof opts.now === 'number' ? opts.now : Date.now();
917
+ const kindFilter = opts.kind ?? null;
918
+
919
+ const subagentsPath =
920
+ opts.subagentsPath ??
921
+ path.join(repoRoot, '.orchestrator', 'metrics', 'subagents.jsonl');
922
+
923
+ const invocationsPath =
924
+ opts.invocationsPath ??
925
+ path.join(repoRoot, '.orchestrator', 'metrics', 'skill-invocations.jsonl');
926
+
927
+ const judgmentsPath =
928
+ opts.judgmentsPath ??
929
+ path.join(repoRoot, '.orchestrator', 'metrics', 'skill-judgments.jsonl');
930
+
931
+ const surface = enumerateSurface(repoRoot);
932
+ const dispatch = readDispatchCounts(subagentsPath, { windowDays, now: nowMs });
933
+ const skillInvocations = readSkillInvocationCounts(invocationsPath, { windowDays, now: nowMs });
934
+ // L3 advisory judge telemetry (#645). Clean-degrade: when the sidecar is
935
+ // absent (judge disabled) this returns an empty bySkill map and the skill
936
+ // classification below behaves byte-for-byte identically to the L1-only path.
937
+ const skillJudgments = readSkillJudgmentCounts(judgmentsPath, { windowDays, now: nowMs });
938
+ const linkage = commandSkillLinkage(repoRoot);
939
+ const coverageDays = dispatch.coverageDays;
940
+ const lowConfidence = coverageDays < windowDays;
941
+
942
+ /** @type {Array<object>} */
943
+ const items = [];
944
+
945
+ const wantKind = (k) => !kindFilter || kindFilter === k;
946
+
947
+ if (wantKind('skill')) {
948
+ for (const name of surface.skills) {
949
+ const staticRefs = staticReferenceScan(repoRoot, { kind: 'skill', name });
950
+ const invokedByCommands = linkage.skillToCommands.get(name) ?? [];
951
+ // Use real L1 selection counts (#645). Fall back to zero-count (NOT null)
952
+ // so the coverage guard treats skills with no invocations as "cold but
953
+ // possibly low-coverage" rather than "no telemetry at all".
954
+ const skillDispatch = skillInvocations.bySkill.get(name) ?? { count: 0, lastTs: null };
955
+ // L3 advisory judge data (#645). null when no in-window judgment exists
956
+ // for this skill → classifyItem ignores it entirely (disabled-path parity).
957
+ const judge = skillJudgments.bySkill.get(name) ?? null;
958
+ items.push(
959
+ classifyItem({
960
+ kind: 'skill',
961
+ name,
962
+ dispatch: skillDispatch,
963
+ static: staticRefs,
964
+ linkage: { invokedByCommands },
965
+ windowDays,
966
+ coverageDays,
967
+ judge,
968
+ }),
969
+ );
970
+ }
971
+ }
972
+
973
+ if (wantKind('agent')) {
974
+ for (const name of surface.agents) {
975
+ const staticRefs = staticReferenceScan(repoRoot, { kind: 'agent', name });
976
+ items.push(
977
+ classifyItem({
978
+ kind: 'agent',
979
+ name,
980
+ dispatch: dispatch.byAgent.get(name) ?? { count: 0, lastTs: null },
981
+ static: staticRefs,
982
+ windowDays,
983
+ coverageDays,
984
+ }),
985
+ );
986
+ }
987
+ }
988
+
989
+ if (wantKind('command')) {
990
+ const skillSet = new Set(surface.skills);
991
+ for (const name of surface.commands) {
992
+ const staticRefs = staticReferenceScan(repoRoot, { kind: 'command', name });
993
+ const invokesSkill = linkage.commandToSkill.get(name) ?? null;
994
+ // Cross-check the linkage target against the enumerated skill surface so a
995
+ // command pointing at a deleted skill is flagged Investigate, not Active.
996
+ const invokedSkillExists = invokesSkill ? skillSet.has(invokesSkill) : true;
997
+ items.push(
998
+ classifyItem({
999
+ kind: 'command',
1000
+ name,
1001
+ dispatch: null,
1002
+ static: staticRefs,
1003
+ linkage: { invokesSkill, invokedSkillExists },
1004
+ windowDays,
1005
+ coverageDays,
1006
+ }),
1007
+ );
1008
+ }
1009
+ }
1010
+
1011
+ const summary = { active: 0, investigate: 0, demote: 0, retire: 0 };
1012
+ for (const item of items) {
1013
+ const key = item.verdict.toLowerCase();
1014
+ if (key in summary) summary[key] += 1;
1015
+ }
1016
+
1017
+ return {
1018
+ meta: {
1019
+ generatedAt: new Date(nowMs).toISOString(),
1020
+ windowDays,
1021
+ coverageDays: Number(coverageDays.toFixed(2)),
1022
+ lowConfidence,
1023
+ telemetrySources: {
1024
+ subagentsJsonl: subagentsPath,
1025
+ earliestTs: dispatch.earliestTs,
1026
+ latestTs: dispatch.latestTs,
1027
+ skillInvocationsJsonl: invocationsPath,
1028
+ skillInvocationsEarliestTs: skillInvocations.earliestTs,
1029
+ skillInvocationsLatestTs: skillInvocations.latestTs,
1030
+ skillJudgmentsJsonl: judgmentsPath,
1031
+ skillJudgmentsEarliestTs: skillJudgments.earliestTs,
1032
+ skillJudgmentsLatestTs: skillJudgments.latestTs,
1033
+ skillJudgmentsAssessment: 'L3 advisory only — annotates/downgrades, never escalates toward Retire',
1034
+ commandsAssessment: 'static-analysis only (no command-invocation telemetry)',
1035
+ },
1036
+ },
1037
+ summary,
1038
+ items,
1039
+ };
1040
+ }
1041
+
1042
+ // ---------------------------------------------------------------------------
1043
+ // CLI
1044
+ // ---------------------------------------------------------------------------
1045
+
1046
+ /**
1047
+ * Parse argv into options. Returns { error } on bad args.
1048
+ * @param {string[]} argv
1049
+ */
1050
+ function parseArgs(argv) {
1051
+ const opts = { json: false, windowDays: DEFAULT_WINDOW_DAYS, kind: null };
1052
+ for (let i = 0; i < argv.length; i += 1) {
1053
+ const arg = argv[i];
1054
+ if (arg === '--json') {
1055
+ opts.json = true;
1056
+ } else if (arg === '--window-days') {
1057
+ const val = argv[i + 1];
1058
+ const n = Number(val);
1059
+ if (!Number.isFinite(n) || n <= 0) {
1060
+ return { error: `--window-days requires a positive number, got: ${val}` };
1061
+ }
1062
+ opts.windowDays = n;
1063
+ i += 1;
1064
+ } else if (arg.startsWith('--window-days=')) {
1065
+ const n = Number(arg.split('=')[1]);
1066
+ if (!Number.isFinite(n) || n <= 0) {
1067
+ return { error: `--window-days requires a positive number` };
1068
+ }
1069
+ opts.windowDays = n;
1070
+ } else if (arg === '--kind') {
1071
+ const val = argv[i + 1];
1072
+ if (!['skill', 'agent', 'command'].includes(val)) {
1073
+ return { error: `--kind must be one of skill|agent|command, got: ${val}` };
1074
+ }
1075
+ opts.kind = val;
1076
+ i += 1;
1077
+ } else if (arg.startsWith('--kind=')) {
1078
+ const val = arg.split('=')[1];
1079
+ if (!['skill', 'agent', 'command'].includes(val)) {
1080
+ return { error: `--kind must be one of skill|agent|command` };
1081
+ }
1082
+ opts.kind = val;
1083
+ } else if (arg === '--help' || arg === '-h') {
1084
+ opts.help = true;
1085
+ } else {
1086
+ return { error: `unknown argument: ${arg}` };
1087
+ }
1088
+ }
1089
+ return opts;
1090
+ }
1091
+
1092
+ const HELP = `sunset-review walker — READ-ONLY surface usage analysis
1093
+
1094
+ Usage:
1095
+ node scripts/lib/sunset/walker.mjs [--json] [--window-days N] [--kind K]
1096
+
1097
+ Options:
1098
+ --json Emit only JSON to stdout (machine-parseable).
1099
+ --window-days N Sunset window in days (default ${DEFAULT_WINDOW_DAYS}).
1100
+ --kind K Limit to one of: skill | agent | command.
1101
+ --help, -h Show this help.
1102
+
1103
+ Exit codes:
1104
+ 0 walk done (cold findings are exit 0, not an error)
1105
+ 1 bad args / surface directory missing
1106
+ 2 system error
1107
+ `;
1108
+
1109
+ /**
1110
+ * Render a human-readable table grouped by verdict.
1111
+ * @param {ReturnType<typeof runSunsetWalk>} result
1112
+ * @returns {string}
1113
+ */
1114
+ function renderHuman(result) {
1115
+ const lines = [];
1116
+ lines.push('Sunset Review — surface usage walk');
1117
+ lines.push(` generated: ${result.meta.generatedAt}`);
1118
+ lines.push(
1119
+ ` window: ${result.meta.windowDays}d coverage: ${result.meta.coverageDays}d lowConfidence: ${result.meta.lowConfidence}`,
1120
+ );
1121
+ lines.push(
1122
+ ` summary: Active=${result.summary.active} Investigate=${result.summary.investigate} Demote=${result.summary.demote} Retire=${result.summary.retire}`,
1123
+ );
1124
+ if (result.meta.lowConfidence) {
1125
+ lines.push(
1126
+ ' NOTE: coverage < window — all Retire verdicts downgraded to Investigate.',
1127
+ );
1128
+ }
1129
+ for (const verdict of ['Retire', 'Demote', 'Investigate', 'Active']) {
1130
+ const group = result.items.filter((i) => i.verdict === verdict);
1131
+ if (group.length === 0) continue;
1132
+ lines.push('');
1133
+ lines.push(`== ${verdict} (${group.length}) ==`);
1134
+ for (const item of group) {
1135
+ lines.push(` [${item.kind}] ${item.name}`);
1136
+ for (const reason of item.reasons) {
1137
+ lines.push(` - ${reason}`);
1138
+ }
1139
+ }
1140
+ }
1141
+ return lines.join('\n');
1142
+ }
1143
+
1144
+ function main(argv) {
1145
+ const opts = parseArgs(argv);
1146
+ if (opts.error) {
1147
+ process.stderr.write(`Error: ${opts.error}\n\n${HELP}`);
1148
+ return 1;
1149
+ }
1150
+ if (opts.help) {
1151
+ process.stdout.write(HELP);
1152
+ return 0;
1153
+ }
1154
+
1155
+ let repoRoot;
1156
+ try {
1157
+ repoRoot = process.cwd();
1158
+ // Prefer git root if available, but cwd is acceptable.
1159
+ const skillsDir = path.join(repoRoot, 'skills');
1160
+ if (!existsSync(skillsDir)) {
1161
+ process.stderr.write(`Error: surface directory not found: ${skillsDir}\n`);
1162
+ return 1;
1163
+ }
1164
+ } catch (err) {
1165
+ process.stderr.write(`System error resolving repo root: ${err.message}\n`);
1166
+ return 2;
1167
+ }
1168
+
1169
+ let result;
1170
+ try {
1171
+ result = runSunsetWalk(repoRoot, {
1172
+ windowDays: opts.windowDays,
1173
+ kind: opts.kind,
1174
+ });
1175
+ } catch (err) {
1176
+ process.stderr.write(`System error during walk: ${err.message}\n`);
1177
+ return 2;
1178
+ }
1179
+
1180
+ if (opts.json) {
1181
+ process.stdout.write(JSON.stringify(result, null, 2) + '\n');
1182
+ } else {
1183
+ process.stdout.write(renderHuman(result) + '\n');
1184
+ }
1185
+ return 0;
1186
+ }
1187
+
1188
+ // Import-safe main-guard.
1189
+ if (import.meta.url === pathToFileURL(process.argv[1] ?? '').href) {
1190
+ const code = main(process.argv.slice(2));
1191
+ process.exit(code);
1192
+ }