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,770 @@
1
+ /**
2
+ * test-runner/issue-reconcile.mjs — Reconcile findings with the open issue tracker.
3
+ *
4
+ * Uses execFile (NOT shell) per ADR-364 §C5. Binary allowlist + arg-boundary
5
+ * validation prevent shell-injection. Fingerprint-based dedup prevents
6
+ * duplicate issues.
7
+ *
8
+ * Exports:
9
+ * reconcileFinding({finding, existingFingerprints, dryRun, execFile})
10
+ * → Promise<{action: 'create'|'noop', iid?, command?}>
11
+ *
12
+ * listExistingFindings({project, label, maxBuffer, execFile})
13
+ * → Promise<{ok: true, issues: Array<{iid, title, body}>, fingerprints: Set<string>}
14
+ * | {ok: false, error}>
15
+ *
16
+ * createFinding({project, fingerprint, title, body, labels, dryRun, maxBuffer, execFile})
17
+ * → Promise<{ok: true, action: 'create', iid?, command?} | {ok: false, error}>
18
+ *
19
+ * updateFinding({project, iid, comment, dryRun, maxBuffer, execFile})
20
+ * → Promise<{ok: true, action: 'comment', command?} | {ok: false, error}>
21
+ *
22
+ * triageDecision(finding, candidates)
23
+ * → {action: 'create'|'update'|'ignore', target?, reason: string, confidence: number}
24
+ *
25
+ * ReconcileError
26
+ *
27
+ * Security:
28
+ * - #388 (SEC-IR-MED-1): sentinel injection hardening — sanitizeRecommendation()
29
+ * strips **Fingerprint:** literals from free-text fields before they are
30
+ * embedded in the body (before the authoritative sentinel line is appended).
31
+ * - #389 (SEC-IR-LOW-1): maxBuffer set to 4 MB on every execFile call;
32
+ * body > 65536 bytes is rejected with BODY_TOO_LARGE.
33
+ * - ADR-364 §C5 / HIGH: glabPath parameter removed. All call sites use
34
+ * RECONCILE_ALLOWLISTED_BINS.glab. Tests inject behaviour via opts.execFile
35
+ * (same DI seam as mr-draft.mjs) — never via caller-supplied binary paths.
36
+ */
37
+
38
+ import { execFile as execFileCb } from 'node:child_process';
39
+ import { promisify } from 'node:util';
40
+ import { fingerprintFinding } from './fingerprint.mjs';
41
+
42
+ const realExecFile = promisify(execFileCb);
43
+
44
+ // ---------------------------------------------------------------------------
45
+ // Constants (#389)
46
+ // ---------------------------------------------------------------------------
47
+
48
+ /** Default maxBuffer for all execFile calls — 4 MB (#389 SEC-IR-LOW-1). */
49
+ const DEFAULT_MAX_BUFFER = 4 * 1024 * 1024;
50
+
51
+ /** Maximum allowed issue body length in bytes (#389 SEC-IR-LOW-1). */
52
+ const MAX_BODY_BYTES = 65536;
53
+
54
+ // ---------------------------------------------------------------------------
55
+ // Binary allowlist (ADR-364 C5 + SEC-014)
56
+ // ---------------------------------------------------------------------------
57
+ // Bare binary names only — resolved at runtime via PATH by Node's execve.
58
+ // Reject any string containing '/' or '..' to prevent path traversal.
59
+ const RECONCILE_ALLOWLISTED_BINS = Object.freeze({
60
+ glab: 'glab',
61
+ gh: 'gh',
62
+ });
63
+
64
+ // ---------------------------------------------------------------------------
65
+ // CLI-argument-boundary rejection regex (ADR-364 §C5 / SEC-PD-MED-1)
66
+ // ---------------------------------------------------------------------------
67
+ // Reject only characters that corrupt CLI arg semantics when passed to execFile
68
+ // with shell: false. Narrowed to newline + CR + null byte — shell metacharacters
69
+ // are irrelevant because shell: false prevents shell interpretation.
70
+ const ARG_BOUNDARY_DANGEROUS = /[\n\r\0]/;
71
+
72
+ // ---------------------------------------------------------------------------
73
+ // ReconcileError
74
+ // ---------------------------------------------------------------------------
75
+
76
+ /**
77
+ * Typed error for reconciliation failures.
78
+ * Codes: 'VALIDATION' | 'EXEC_FAILURE' | 'BINARY_NOT_FOUND' | 'BODY_TOO_LARGE'
79
+ */
80
+ export class ReconcileError extends Error {
81
+ constructor(message, code) {
82
+ super(message);
83
+ this.name = 'ReconcileError';
84
+ this.code = code;
85
+ }
86
+ }
87
+
88
+ // ---------------------------------------------------------------------------
89
+ // Input validation
90
+ // ---------------------------------------------------------------------------
91
+
92
+ const VALID_SEVERITIES = new Set(['critical', 'high', 'medium', 'low']);
93
+ const REQUIRED_STRING_FIELDS = ['scope', 'checkId', 'locator', 'severity', 'title', 'description'];
94
+
95
+ /**
96
+ * Validate a finding object, throwing ReconcileError(VALIDATION) on failure.
97
+ * @param {object} finding
98
+ */
99
+ function validateFinding(finding) {
100
+ if (!finding || typeof finding !== 'object') {
101
+ throw new ReconcileError('finding must be an object', 'VALIDATION');
102
+ }
103
+ for (const key of REQUIRED_STRING_FIELDS) {
104
+ const v = finding[key];
105
+ if (typeof v !== 'string') {
106
+ throw new ReconcileError(`finding.${key} must be a string`, 'VALIDATION');
107
+ }
108
+ if (ARG_BOUNDARY_DANGEROUS.test(v)) {
109
+ throw new ReconcileError(
110
+ `finding.${key} contains forbidden characters (newline/CR/null byte)`,
111
+ 'VALIDATION',
112
+ );
113
+ }
114
+ }
115
+ if (!VALID_SEVERITIES.has(finding.severity)) {
116
+ throw new ReconcileError(
117
+ `finding.severity must be one of: ${[...VALID_SEVERITIES].join(', ')}`,
118
+ 'VALIDATION',
119
+ );
120
+ }
121
+ }
122
+
123
+ // ---------------------------------------------------------------------------
124
+ // Sentinel sanitization (#388 SEC-IR-MED-1)
125
+ // ---------------------------------------------------------------------------
126
+
127
+ /**
128
+ * Sanitize free-text recommendation fields before embedding in the issue body.
129
+ * Replaces any literal `**Fingerprint:**` with `__Fingerprint__` to prevent
130
+ * a crafted recommendation from spoofing the authoritative fingerprint sentinel
131
+ * line that is appended by buildIssueBody().
132
+ *
133
+ * Context: glab parses issue bodies as Markdown. If `recommendation` contained
134
+ * `**Fingerprint:** <attacker-hash>`, a grep for the fingerprint sentinel in
135
+ * listExistingFindings() would match the spoofed value instead of the real one,
136
+ * bypassing dedup. Replacing the literal prevents the attack without altering
137
+ * the semantic meaning of the recommendation text.
138
+ *
139
+ * @param {string|undefined|null} text
140
+ * @returns {string|undefined|null}
141
+ */
142
+ function sanitizeRecommendation(text) {
143
+ if (!text || typeof text !== 'string') return text;
144
+ // gi flag: case-insensitive match so **fingerprint:** and **FINGERPRINT:** variants
145
+ // are also neutralized. Sanitizer is intentionally broader than the case-sensitive
146
+ // extractor regex — this is the correct asymmetry (#388 SEC-IR-MED-1).
147
+ return text.replace(/\*\*Fingerprint:\*\*/gi, '__Fingerprint__');
148
+ }
149
+
150
+ // ---------------------------------------------------------------------------
151
+ // Body-length validation (#389 SEC-IR-LOW-1)
152
+ // ---------------------------------------------------------------------------
153
+
154
+ /**
155
+ * Validate that a body string does not exceed MAX_BODY_BYTES.
156
+ * Returns {ok: false, error} on violation; callers propagate this directly.
157
+ *
158
+ * @param {string} body
159
+ * @returns {{ok: false, error: {code: string, message: string}}|null}
160
+ */
161
+ function checkBodyLength(body) {
162
+ const byteLength = Buffer.byteLength(body, 'utf8');
163
+ if (byteLength > MAX_BODY_BYTES) {
164
+ return {
165
+ ok: false,
166
+ error: {
167
+ code: 'BODY_TOO_LARGE',
168
+ message: `Body exceeds maximum allowed size of ${MAX_BODY_BYTES} bytes (got ${byteLength} bytes)`,
169
+ },
170
+ };
171
+ }
172
+ return null;
173
+ }
174
+
175
+ // ---------------------------------------------------------------------------
176
+ // Issue body builder
177
+ // ---------------------------------------------------------------------------
178
+
179
+ /**
180
+ * Build the issue description body from a finding and its fingerprint.
181
+ * Newlines within the body are intentional and safe — execFile passes
182
+ * --description as a single argv element, not through a shell.
183
+ *
184
+ * Applies sanitizeRecommendation() to the recommendation field (#388) before
185
+ * embedding it, so the authoritative `**Fingerprint:** \`<fp>\`` sentinel
186
+ * line cannot be spoofed by attacker-controlled recommendation text.
187
+ *
188
+ * @param {object} finding
189
+ * @param {string} fp - 16-char hex fingerprint
190
+ * @returns {string}
191
+ */
192
+ function buildIssueBody(finding, fp) {
193
+ const safeRecommendation = sanitizeRecommendation(finding.recommendation);
194
+ const lines = [
195
+ finding.description,
196
+ '',
197
+ safeRecommendation ? `**Recommendation:** ${safeRecommendation}` : null,
198
+ '',
199
+ `**Fingerprint:** \`${fp}\``,
200
+ `**Severity:** ${finding.severity}`,
201
+ `**Check:** ${finding.checkId}`,
202
+ `**Locator:** \`${finding.locator}\``,
203
+ ];
204
+ return lines.filter((line) => line !== null).join('\n');
205
+ }
206
+
207
+ // ---------------------------------------------------------------------------
208
+ // Parse glab stdout for the created issue IID
209
+ // ---------------------------------------------------------------------------
210
+
211
+ /**
212
+ * Parse the issue IID from glab's stdout.
213
+ * glab issue create prints a URL like:
214
+ * https://gitlab.example.com/group/project/-/issues/123
215
+ * We extract the trailing integer.
216
+ *
217
+ * @param {string} stdout
218
+ * @returns {number|null}
219
+ */
220
+ function parseIidFromGlabOutput(stdout) {
221
+ if (typeof stdout !== 'string') return null;
222
+ const match = stdout.match(/\/issues\/(\d+)/);
223
+ if (match) return parseInt(match[1], 10);
224
+ // Fallback: any trailing integer on the last non-empty line
225
+ const trail = stdout.trim().match(/(\d+)\s*$/);
226
+ return trail ? parseInt(trail[1], 10) : null;
227
+ }
228
+
229
+ // ---------------------------------------------------------------------------
230
+ // Extract fingerprints from issue bodies
231
+ // ---------------------------------------------------------------------------
232
+
233
+ /**
234
+ * Extract the 16-hex fingerprint from an issue body string.
235
+ * Matches the authoritative sentinel line: `**Fingerprint:** \`<16-hex>\``
236
+ *
237
+ * @param {string} body
238
+ * @returns {string|null}
239
+ */
240
+ function extractFingerprintFromBody(body) {
241
+ if (typeof body !== 'string') return null;
242
+ const match = body.match(/\*\*Fingerprint:\*\*\s*`([0-9a-f]{16})`/);
243
+ return match ? match[1] : null;
244
+ }
245
+
246
+ // ---------------------------------------------------------------------------
247
+ // Levenshtein distance (pure) — used by triageDecision
248
+ // ---------------------------------------------------------------------------
249
+
250
+ /**
251
+ * Compute the Levenshtein edit distance between two strings.
252
+ * Pure function; no external dependencies.
253
+ *
254
+ * @param {string} a
255
+ * @param {string} b
256
+ * @returns {number}
257
+ */
258
+ function levenshtein(a, b) {
259
+ if (typeof a !== 'string' || typeof b !== 'string') return Infinity;
260
+ if (a === b) return 0;
261
+ const la = a.length;
262
+ const lb = b.length;
263
+ if (la === 0) return lb;
264
+ if (lb === 0) return la;
265
+
266
+ // Two-row DP to keep memory O(min(m,n)).
267
+ let prev = Array.from({ length: lb + 1 }, (_, i) => i);
268
+ let curr = new Array(lb + 1);
269
+
270
+ for (let i = 1; i <= la; i++) {
271
+ curr[0] = i;
272
+ for (let j = 1; j <= lb; j++) {
273
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
274
+ curr[j] = Math.min(curr[j - 1] + 1, prev[j] + 1, prev[j - 1] + cost);
275
+ }
276
+ [prev, curr] = [curr, prev];
277
+ }
278
+ return prev[lb];
279
+ }
280
+
281
+ // ---------------------------------------------------------------------------
282
+ // reconcileFinding (Track A — backwards-compatible original export)
283
+ // ---------------------------------------------------------------------------
284
+
285
+ /**
286
+ * Reconcile a finding against existing issues.
287
+ *
288
+ * If the fingerprint is already in `existingFingerprints`, return `{action: 'noop'}`.
289
+ * Otherwise, create a new issue via glab (or return the dry-run command).
290
+ *
291
+ * @param {object} opts
292
+ * @param {object} opts.finding - finding object with scope/checkId/locator/severity/title/description
293
+ * @param {Set<string>} opts.existingFingerprints - fingerprints of issues already filed
294
+ * @param {boolean} [opts.dryRun] - if true, return command without spawning
295
+ * @param {Function} [opts.execFile] - DI seam for testing; defaults to node:child_process execFile
296
+ * @returns {Promise<{action: 'create'|'noop', iid?: number, command?: string[]}>}
297
+ */
298
+ export async function reconcileFinding({
299
+ finding,
300
+ existingFingerprints,
301
+ dryRun = false,
302
+ execFile: execFileOpt,
303
+ }) {
304
+ // Resolve execFile — DI seam for tests; never caller-supplied binary path (ADR-364 §C5 HIGH fix).
305
+ const execFileFn = typeof execFileOpt === 'function' ? execFileOpt : realExecFile;
306
+ // Binary is always the allowlisted value — never caller-supplied.
307
+ const bin = RECONCILE_ALLOWLISTED_BINS.glab;
308
+
309
+ validateFinding(finding);
310
+
311
+ if (!(existingFingerprints instanceof Set)) {
312
+ throw new ReconcileError('existingFingerprints must be a Set', 'VALIDATION');
313
+ }
314
+
315
+ const fp = fingerprintFinding({
316
+ scope: finding.scope,
317
+ checkId: finding.checkId,
318
+ locator: finding.locator,
319
+ });
320
+
321
+ if (existingFingerprints.has(fp)) {
322
+ return { action: 'noop' };
323
+ }
324
+
325
+ const title = `[Test] ${finding.title}`;
326
+ // title is derived from finding.title which was already validated above,
327
+ // but we recheck the assembled value for completeness.
328
+ if (ARG_BOUNDARY_DANGEROUS.test(title)) {
329
+ throw new ReconcileError('finding.title produces unsafe issue title', 'VALIDATION');
330
+ }
331
+
332
+ const body = buildIssueBody(finding, fp);
333
+ // Body may contain newlines (intentional markdown structure) but must not
334
+ // contain null bytes, which would corrupt argv.
335
+ if (body.includes('\0')) {
336
+ throw new ReconcileError('issue body contains null byte', 'VALIDATION');
337
+ }
338
+
339
+ // #389 MED-1: body-length cap in reconcileFinding backwards-compat path.
340
+ const bodyLengthError = checkBodyLength(body);
341
+ if (bodyLengthError) {
342
+ throw new ReconcileError(bodyLengthError.error.message, bodyLengthError.error.code);
343
+ }
344
+
345
+ const labels = `from:test-runner,severity:${finding.severity},status:ready,type:bug`;
346
+ const cmd = [
347
+ 'issue',
348
+ 'create',
349
+ '--title',
350
+ title,
351
+ '--label',
352
+ labels,
353
+ '--description',
354
+ body,
355
+ ];
356
+
357
+ if (dryRun) {
358
+ return { action: 'create', command: cmd };
359
+ }
360
+
361
+ try {
362
+ const { stdout } = await execFileFn(bin, cmd, {
363
+ shell: false,
364
+ timeout: 10_000,
365
+ maxBuffer: DEFAULT_MAX_BUFFER,
366
+ });
367
+ const iid = parseIidFromGlabOutput(stdout);
368
+ return { action: 'create', iid, command: cmd };
369
+ } catch (err) {
370
+ if (err.code === 'ENOENT') {
371
+ throw new ReconcileError(`Binary '${bin}' not found in PATH`, 'BINARY_NOT_FOUND');
372
+ }
373
+ throw new ReconcileError(`execFile failed: ${err.message}`, 'EXEC_FAILURE');
374
+ }
375
+ }
376
+
377
+ // ---------------------------------------------------------------------------
378
+ // listExistingFindings — query glab for open test-runner findings (#384)
379
+ // ---------------------------------------------------------------------------
380
+
381
+ /**
382
+ * List existing test-runner findings from the issue tracker.
383
+ *
384
+ * Calls `glab issue list --label <label> --output json` and extracts
385
+ * fingerprints from issue bodies for downstream dedup.
386
+ *
387
+ * @param {object} [opts]
388
+ * @param {string} [opts.project] - GitLab project path (passed via --repo if provided)
389
+ * @param {string} [opts.label='from:test-runner'] - label filter for the query
390
+ * @param {number} [opts.maxBuffer=4194304] - maxBuffer for execFile (4 MB default, #389)
391
+ * @param {Function} [opts.execFile] - DI seam for testing; defaults to node:child_process execFile
392
+ * @returns {Promise<
393
+ * {ok: true, issues: Array<{iid: number, title: string, body: string}>, fingerprints: Set<string>}
394
+ * | {ok: false, error: {code: string, message: string}}
395
+ * >}
396
+ */
397
+ export async function listExistingFindings({
398
+ project,
399
+ label = 'from:test-runner',
400
+ maxBuffer = DEFAULT_MAX_BUFFER,
401
+ execFile: execFileOpt,
402
+ } = {}) {
403
+ // Binary is always the allowlisted value — never caller-supplied (ADR-364 §C5 HIGH fix).
404
+ const bin = RECONCILE_ALLOWLISTED_BINS.glab;
405
+ const execFileFn = typeof execFileOpt === 'function' ? execFileOpt : realExecFile;
406
+
407
+ // Validate label arg boundary
408
+ if (ARG_BOUNDARY_DANGEROUS.test(label)) {
409
+ return {
410
+ ok: false,
411
+ error: { code: 'VALIDATION', message: 'label contains forbidden characters' },
412
+ };
413
+ }
414
+
415
+ const args = ['issue', 'list', '--label', label, '--output', 'json'];
416
+
417
+ // Optionally scope to a specific project (--repo flag)
418
+ if (project !== undefined) {
419
+ if (ARG_BOUNDARY_DANGEROUS.test(project)) {
420
+ return {
421
+ ok: false,
422
+ error: { code: 'VALIDATION', message: 'project contains forbidden characters' },
423
+ };
424
+ }
425
+ args.push('--repo', project);
426
+ }
427
+
428
+ let stdout;
429
+ try {
430
+ const result = await execFileFn(bin, args, {
431
+ shell: false,
432
+ timeout: 15_000,
433
+ maxBuffer,
434
+ });
435
+ stdout = result.stdout;
436
+ } catch (err) {
437
+ if (err.code === 'ENOENT') {
438
+ return {
439
+ ok: false,
440
+ error: {
441
+ code: 'BINARY_NOT_FOUND',
442
+ message: `Binary '${bin}' not found in PATH`,
443
+ },
444
+ };
445
+ }
446
+ return {
447
+ ok: false,
448
+ error: {
449
+ code: 'EXEC_FAILURE',
450
+ message: `execFile failed: ${err.message}`,
451
+ },
452
+ };
453
+ }
454
+
455
+ let parsed;
456
+ try {
457
+ parsed = JSON.parse(stdout);
458
+ } catch {
459
+ return {
460
+ ok: false,
461
+ error: {
462
+ code: 'PARSE_ERROR',
463
+ message: 'Failed to parse glab JSON output',
464
+ },
465
+ };
466
+ }
467
+
468
+ if (!Array.isArray(parsed)) {
469
+ return {
470
+ ok: false,
471
+ error: {
472
+ code: 'PARSE_ERROR',
473
+ message: 'Expected glab output to be a JSON array',
474
+ },
475
+ };
476
+ }
477
+
478
+ const issues = parsed.map((issue) => ({
479
+ iid: issue.iid ?? issue.number ?? null,
480
+ title: issue.title ?? '',
481
+ body: issue.description ?? issue.body ?? '',
482
+ }));
483
+
484
+ // Build fingerprint dedup set from issue bodies
485
+ const fingerprints = new Set();
486
+ for (const issue of issues) {
487
+ const fp = extractFingerprintFromBody(issue.body);
488
+ if (fp !== null) {
489
+ fingerprints.add(fp);
490
+ }
491
+ }
492
+
493
+ return { ok: true, issues, fingerprints };
494
+ }
495
+
496
+ // ---------------------------------------------------------------------------
497
+ // createFinding — create a new GitLab issue (#384)
498
+ // ---------------------------------------------------------------------------
499
+
500
+ /**
501
+ * Create a new finding as a GitLab issue.
502
+ *
503
+ * @param {object} opts
504
+ * @param {string} [opts.project] - GitLab project path (--repo)
505
+ * @param {string} opts.fingerprint - 16-hex fingerprint (appended as sentinel)
506
+ * @param {string} opts.title - issue title (no [Test] prefix added here — caller decides)
507
+ * @param {string} opts.body - issue description body; must not exceed 65536 bytes (#389)
508
+ * @param {string} [opts.labels='from:test-runner'] - comma-separated label string
509
+ * @param {boolean} [opts.dryRun=false] - if true, return command without spawning
510
+ * @param {number} [opts.maxBuffer=4194304] - maxBuffer for execFile (4 MB, #389)
511
+ * @param {Function} [opts.execFile] - DI seam for testing; defaults to node:child_process execFile
512
+ * @returns {Promise<
513
+ * {ok: true, action: 'create', iid?: number, command?: string[]}
514
+ * | {ok: false, error: {code: string, message: string}}
515
+ * >}
516
+ */
517
+ export async function createFinding({
518
+ project,
519
+ fingerprint,
520
+ title,
521
+ body,
522
+ labels = 'from:test-runner',
523
+ dryRun = false,
524
+ maxBuffer = DEFAULT_MAX_BUFFER,
525
+ execFile: execFileOpt,
526
+ }) {
527
+ // --- Input validation ---
528
+ if (typeof title !== 'string' || title.length === 0) {
529
+ return { ok: false, error: { code: 'VALIDATION', message: 'title must be a non-empty string' } };
530
+ }
531
+ if (ARG_BOUNDARY_DANGEROUS.test(title)) {
532
+ return {
533
+ ok: false,
534
+ error: { code: 'VALIDATION', message: 'title contains forbidden characters (newline/CR/null byte)' },
535
+ };
536
+ }
537
+ if (typeof body !== 'string') {
538
+ return { ok: false, error: { code: 'VALIDATION', message: 'body must be a string' } };
539
+ }
540
+ if (body.includes('\0')) {
541
+ return { ok: false, error: { code: 'VALIDATION', message: 'body contains null byte' } };
542
+ }
543
+ if (typeof fingerprint !== 'string' || fingerprint.length === 0) {
544
+ return {
545
+ ok: false,
546
+ error: { code: 'VALIDATION', message: 'fingerprint must be a non-empty string' },
547
+ };
548
+ }
549
+ if (typeof labels !== 'string') {
550
+ return { ok: false, error: { code: 'VALIDATION', message: 'labels must be a string' } };
551
+ }
552
+ if (ARG_BOUNDARY_DANGEROUS.test(labels)) {
553
+ return {
554
+ ok: false,
555
+ error: { code: 'VALIDATION', message: 'labels contains forbidden characters' },
556
+ };
557
+ }
558
+
559
+ // #389: body-length cap
560
+ const bodyLengthError = checkBodyLength(body);
561
+ if (bodyLengthError) return bodyLengthError;
562
+
563
+ const args = ['issue', 'create', '--title', title, '--label', labels, '--description', body];
564
+
565
+ if (project !== undefined) {
566
+ if (ARG_BOUNDARY_DANGEROUS.test(project)) {
567
+ return {
568
+ ok: false,
569
+ error: { code: 'VALIDATION', message: 'project contains forbidden characters' },
570
+ };
571
+ }
572
+ args.push('--repo', project);
573
+ }
574
+
575
+ if (dryRun) {
576
+ return { ok: true, action: 'create', command: args };
577
+ }
578
+
579
+ // Binary is always the allowlisted value — never caller-supplied (ADR-364 §C5 HIGH fix).
580
+ const bin = RECONCILE_ALLOWLISTED_BINS.glab;
581
+ const execFileFn = typeof execFileOpt === 'function' ? execFileOpt : realExecFile;
582
+
583
+ try {
584
+ const { stdout } = await execFileFn(bin, args, {
585
+ shell: false,
586
+ timeout: 10_000,
587
+ maxBuffer,
588
+ });
589
+ const iid = parseIidFromGlabOutput(stdout);
590
+ return { ok: true, action: 'create', iid, command: args };
591
+ } catch (err) {
592
+ if (err.code === 'ENOENT') {
593
+ return {
594
+ ok: false,
595
+ error: {
596
+ code: 'BINARY_NOT_FOUND',
597
+ message: `Binary '${bin}' not found in PATH`,
598
+ },
599
+ };
600
+ }
601
+ return {
602
+ ok: false,
603
+ error: {
604
+ code: 'EXEC_FAILURE',
605
+ message: `execFile failed: ${err.message}`,
606
+ },
607
+ };
608
+ }
609
+ }
610
+
611
+ // ---------------------------------------------------------------------------
612
+ // updateFinding — add a comment to an existing issue (#384)
613
+ // ---------------------------------------------------------------------------
614
+
615
+ /**
616
+ * Add a comment to an existing finding issue.
617
+ *
618
+ * @param {object} opts
619
+ * @param {string} [opts.project] - GitLab project path (--repo)
620
+ * @param {number} opts.iid - issue IID to comment on
621
+ * @param {string} opts.comment - comment body; must not exceed 65536 bytes (#389)
622
+ * @param {boolean} [opts.dryRun=false] - if true, return command without spawning
623
+ * @param {number} [opts.maxBuffer=4194304] - maxBuffer for execFile (4 MB, #389)
624
+ * @param {Function} [opts.execFile] - DI seam for testing; defaults to node:child_process execFile
625
+ * @returns {Promise<
626
+ * {ok: true, action: 'comment', command?: string[]}
627
+ * | {ok: false, error: {code: string, message: string}}
628
+ * >}
629
+ */
630
+ export async function updateFinding({
631
+ project,
632
+ iid,
633
+ comment,
634
+ dryRun = false,
635
+ maxBuffer = DEFAULT_MAX_BUFFER,
636
+ execFile: execFileOpt,
637
+ }) {
638
+ // --- Input validation ---
639
+ if (!Number.isInteger(iid) || iid < 1) {
640
+ return { ok: false, error: { code: 'VALIDATION', message: 'iid must be a positive integer' } };
641
+ }
642
+ if (typeof comment !== 'string' || comment.length === 0) {
643
+ return {
644
+ ok: false,
645
+ error: { code: 'VALIDATION', message: 'comment must be a non-empty string' },
646
+ };
647
+ }
648
+ if (comment.includes('\0')) {
649
+ return { ok: false, error: { code: 'VALIDATION', message: 'comment contains null byte' } };
650
+ }
651
+
652
+ // #389: body-length cap (applies to comment body as well)
653
+ const commentLengthError = checkBodyLength(comment);
654
+ if (commentLengthError) return commentLengthError;
655
+
656
+ // glab issue note --message <comment> <iid>
657
+ // iid is a number — safe to convert to string; no arg-boundary concerns
658
+ const args = ['issue', 'note', String(iid), '--message', comment];
659
+
660
+ if (project !== undefined) {
661
+ if (ARG_BOUNDARY_DANGEROUS.test(project)) {
662
+ return {
663
+ ok: false,
664
+ error: { code: 'VALIDATION', message: 'project contains forbidden characters' },
665
+ };
666
+ }
667
+ args.push('--repo', project);
668
+ }
669
+
670
+ if (dryRun) {
671
+ return { ok: true, action: 'comment', command: args };
672
+ }
673
+
674
+ // Binary is always the allowlisted value — never caller-supplied (ADR-364 §C5 HIGH fix).
675
+ const bin = RECONCILE_ALLOWLISTED_BINS.glab;
676
+ const execFileFn = typeof execFileOpt === 'function' ? execFileOpt : realExecFile;
677
+
678
+ try {
679
+ await execFileFn(bin, args, {
680
+ shell: false,
681
+ timeout: 10_000,
682
+ maxBuffer,
683
+ });
684
+ return { ok: true, action: 'comment', command: args };
685
+ } catch (err) {
686
+ if (err.code === 'ENOENT') {
687
+ return {
688
+ ok: false,
689
+ error: {
690
+ code: 'BINARY_NOT_FOUND',
691
+ message: `Binary '${bin}' not found in PATH`,
692
+ },
693
+ };
694
+ }
695
+ return {
696
+ ok: false,
697
+ error: {
698
+ code: 'EXEC_FAILURE',
699
+ message: `execFile failed: ${err.message}`,
700
+ },
701
+ };
702
+ }
703
+ }
704
+
705
+ // ---------------------------------------------------------------------------
706
+ // triageDecision — pure decision function (#384)
707
+ // ---------------------------------------------------------------------------
708
+
709
+ /**
710
+ * Pure triage decision: determine the action for a finding given a list of
711
+ * existing issue candidates.
712
+ *
713
+ * Decision logic (in priority order):
714
+ * 1. Any candidate body contains `**Fingerprint:** \`<finding.fingerprint>\``
715
+ * → { action: 'ignore', target: <iid>, reason: 'fingerprint exact match', confidence: 1.0 }
716
+ * 2. Any candidate title has Levenshtein distance ≤ 2 from finding.title
717
+ * → { action: 'update', target: <iid>, reason: 'fuzzy title match', confidence: 0.7 }
718
+ * 3. Otherwise
719
+ * → { action: 'create', reason: 'no match', confidence: 1.0 }
720
+ *
721
+ * This function is intentionally pure (no execFile, fs, or fetch) so it can be
722
+ * tested deterministically without DI.
723
+ *
724
+ * @param {{ fingerprint: string, title: string }} finding
725
+ * @param {Array<{iid: number, title: string, body: string}>} candidates
726
+ * @returns {{ action: 'create'|'update'|'ignore', target?: number, reason: string, confidence: number }}
727
+ */
728
+ export function triageDecision(finding, candidates) {
729
+ if (!finding || typeof finding !== 'object') {
730
+ return { action: 'create', reason: 'invalid finding', confidence: 1.0 };
731
+ }
732
+ if (!Array.isArray(candidates)) {
733
+ return { action: 'create', reason: 'no match', confidence: 1.0 };
734
+ }
735
+
736
+ const { fingerprint, title } = finding;
737
+
738
+ // Pass 1: fingerprint exact match (highest priority)
739
+ if (typeof fingerprint === 'string' && fingerprint.length > 0) {
740
+ for (const candidate of candidates) {
741
+ const candidateFp = extractFingerprintFromBody(candidate.body ?? '');
742
+ if (candidateFp === fingerprint) {
743
+ return {
744
+ action: 'ignore',
745
+ target: candidate.iid,
746
+ reason: 'fingerprint exact match',
747
+ confidence: 1.0,
748
+ };
749
+ }
750
+ }
751
+ }
752
+
753
+ // Pass 2: fuzzy title match (Levenshtein ≤ 2)
754
+ if (typeof title === 'string' && title.length > 0) {
755
+ for (const candidate of candidates) {
756
+ const dist = levenshtein(title, candidate.title ?? '');
757
+ if (dist <= 2) {
758
+ return {
759
+ action: 'update',
760
+ target: candidate.iid,
761
+ reason: 'fuzzy title match',
762
+ confidence: 0.7,
763
+ };
764
+ }
765
+ }
766
+ }
767
+
768
+ // Pass 3: no match
769
+ return { action: 'create', reason: 'no match', confidence: 1.0 };
770
+ }