supipowers 1.5.3 → 2.0.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 (340) hide show
  1. package/README.md +14 -8
  2. package/bin/install.mjs +20 -5
  3. package/bin/install.ts +95 -0
  4. package/package.json +8 -4
  5. package/skills/context-mode/SKILL.md +17 -10
  6. package/skills/harness/SKILL.md +94 -0
  7. package/skills/ui-design/SKILL.md +63 -0
  8. package/skills/ui-design/sub-agent-templates/component-builder.md +29 -0
  9. package/skills/ui-design/sub-agent-templates/design-critic.md +46 -0
  10. package/skills/ui-design/sub-agent-templates/pencil/component-builder.md +29 -0
  11. package/skills/ui-design/sub-agent-templates/pencil/design-critic.md +42 -0
  12. package/skills/ui-design/sub-agent-templates/pencil/section-assembler.md +27 -0
  13. package/skills/ui-design/sub-agent-templates/section-assembler.md +27 -0
  14. package/skills/ultraplan-discover/SKILL.md +96 -0
  15. package/skills/ultraplan-intake/SKILL.md +89 -0
  16. package/skills/ultraplan-research/SKILL.md +129 -0
  17. package/skills/ultraplan-review/SKILL.md +86 -0
  18. package/skills/ultraplan-review-scope/SKILL.md +111 -0
  19. package/skills/ultraplan-review-structure/SKILL.md +120 -0
  20. package/skills/ultraplan-review-tdd/SKILL.md +142 -0
  21. package/skills/ultraplan-scout/SKILL.md +110 -0
  22. package/skills/ultraplan-synthesize/SKILL.md +124 -0
  23. package/src/{quality/ai-session.ts → ai/final-message.ts} +27 -0
  24. package/src/ai/schema-text.ts +129 -0
  25. package/src/ai/structured-output.ts +274 -0
  26. package/src/ai/template.ts +27 -0
  27. package/src/bootstrap.ts +63 -28
  28. package/src/commands/agents.ts +131 -42
  29. package/src/commands/ai-review.ts +251 -30
  30. package/src/commands/clear.ts +434 -0
  31. package/src/commands/commit.ts +1 -0
  32. package/src/commands/config.ts +242 -44
  33. package/src/commands/context.ts +55 -28
  34. package/src/commands/doctor.ts +234 -6
  35. package/src/commands/fix-pr.ts +306 -131
  36. package/src/commands/generate.ts +111 -21
  37. package/src/commands/memory.ts +192 -0
  38. package/src/commands/model-picker.ts +28 -21
  39. package/src/commands/model.ts +18 -8
  40. package/src/commands/optimize-context.ts +408 -29
  41. package/src/commands/plan.ts +2 -0
  42. package/src/commands/qa.ts +312 -137
  43. package/src/commands/release.ts +259 -76
  44. package/src/commands/review.ts +293 -59
  45. package/src/commands/status.ts +200 -13
  46. package/src/commands/supi.ts +3 -35
  47. package/src/commands/ui-design.ts +394 -0
  48. package/src/commands/ultraplan.ts +1518 -0
  49. package/src/commands/update.ts +86 -0
  50. package/src/config/defaults.ts +62 -0
  51. package/src/config/loader.ts +448 -60
  52. package/src/config/schema.ts +108 -2
  53. package/src/context/optimizer.ts +25 -33
  54. package/src/context/rule-renderer.ts +223 -0
  55. package/src/context/savings.ts +258 -0
  56. package/src/context/startup-check.ts +380 -0
  57. package/src/context/startup-optimizer.ts +355 -0
  58. package/src/context/tokenignore.ts +146 -0
  59. package/src/context-mode/cache-handle.ts +49 -0
  60. package/src/context-mode/cache-preview.ts +71 -0
  61. package/src/context-mode/cache-store.ts +738 -0
  62. package/src/context-mode/compressor.ts +131 -26
  63. package/src/context-mode/dedup.ts +108 -0
  64. package/src/context-mode/detector.ts +35 -4
  65. package/src/context-mode/event-extractor.ts +14 -12
  66. package/src/context-mode/event-store.ts +91 -36
  67. package/src/context-mode/hooks.ts +798 -56
  68. package/src/context-mode/knowledge/store.ts +255 -11
  69. package/src/context-mode/memory-store.ts +325 -0
  70. package/src/context-mode/metrics-recorder.ts +158 -0
  71. package/src/context-mode/metrics-store.ts +765 -0
  72. package/src/context-mode/model.ts +24 -0
  73. package/src/context-mode/processor-keys.ts +29 -0
  74. package/src/context-mode/processors/build.ts +66 -0
  75. package/src/context-mode/processors/docker.ts +57 -0
  76. package/src/context-mode/processors/git.ts +111 -0
  77. package/src/context-mode/processors/json.ts +112 -0
  78. package/src/context-mode/processors/k8s.ts +67 -0
  79. package/src/context-mode/processors/lint.ts +67 -0
  80. package/src/context-mode/processors/log.ts +86 -0
  81. package/src/context-mode/processors/registry.ts +116 -0
  82. package/src/context-mode/processors/test-runner.ts +102 -0
  83. package/src/context-mode/processors/types.ts +20 -0
  84. package/src/context-mode/repomap.ts +400 -0
  85. package/src/context-mode/routing.ts +97 -24
  86. package/src/context-mode/sandbox/runners.ts +5 -1
  87. package/src/context-mode/snapshot-builder.ts +106 -11
  88. package/src/context-mode/source-hash.ts +173 -0
  89. package/src/context-mode/tool-name.ts +11 -0
  90. package/src/context-mode/tools.ts +654 -22
  91. package/src/context-mode/web/fetcher.ts +31 -12
  92. package/src/debug/logger.ts +2 -1
  93. package/src/deps/registry.ts +1 -1
  94. package/src/discipline/failure-summarizer.ts +170 -0
  95. package/src/discipline/failure-taxonomy.ts +131 -0
  96. package/src/discipline/workflow-invariants.ts +125 -0
  97. package/src/discovery/index.ts +31 -0
  98. package/src/discovery/lsp.ts +87 -0
  99. package/src/discovery/rank.ts +144 -0
  100. package/src/discovery/sources.ts +89 -0
  101. package/src/discovery/workflow.ts +87 -0
  102. package/src/docs/contracts.ts +39 -0
  103. package/src/docs/drift.ts +117 -87
  104. package/src/fix-pr/assessment.ts +200 -0
  105. package/src/fix-pr/contracts.ts +47 -0
  106. package/src/fix-pr/fetch-comments.ts +80 -0
  107. package/src/fix-pr/prompt-builder.ts +58 -40
  108. package/src/fix-pr/scripts/exec.ts +34 -0
  109. package/src/fix-pr/scripts/trigger-review.ts +106 -0
  110. package/src/fix-pr/scripts/wait-and-check.ts +108 -0
  111. package/src/fix-pr/types.ts +4 -0
  112. package/src/git/branch-finish.ts +5 -0
  113. package/src/git/commit-contract.ts +83 -0
  114. package/src/git/commit.ts +121 -184
  115. package/src/git/status.ts +62 -8
  116. package/src/harness/anti_slop/architecture-parser.ts +210 -0
  117. package/src/harness/anti_slop/backend-factory.ts +30 -0
  118. package/src/harness/anti_slop/backend.ts +140 -0
  119. package/src/harness/anti_slop/desloppify-adapter.ts +319 -0
  120. package/src/harness/anti_slop/fallow-adapter.ts +305 -0
  121. package/src/harness/anti_slop/installer.ts +227 -0
  122. package/src/harness/anti_slop/queue.ts +216 -0
  123. package/src/harness/anti_slop/recommend.ts +84 -0
  124. package/src/harness/anti_slop/score.ts +180 -0
  125. package/src/harness/anti_slop/synthetic-edit-test.ts +128 -0
  126. package/src/harness/artifacts/agents-md.ts +88 -0
  127. package/src/harness/artifacts/checks-wiring.ts +57 -0
  128. package/src/harness/artifacts/docs-tree.ts +79 -0
  129. package/src/harness/artifacts/lint-configs.ts +136 -0
  130. package/src/harness/artifacts/review-agents.ts +67 -0
  131. package/src/harness/bare-entry.ts +108 -0
  132. package/src/harness/command.ts +1010 -0
  133. package/src/harness/default-agents/design.md +23 -0
  134. package/src/harness/default-agents/discover.md +18 -0
  135. package/src/harness/default-agents/implement.md +24 -0
  136. package/src/harness/default-agents/plan.md +19 -0
  137. package/src/harness/default-agents/research.md +21 -0
  138. package/src/harness/default-agents/validate.md +22 -0
  139. package/src/harness/gc/reporter.ts +28 -0
  140. package/src/harness/gc/runner.ts +136 -0
  141. package/src/harness/hooks/layer-context-inject.ts +155 -0
  142. package/src/harness/hooks/post-session-sweep.ts +130 -0
  143. package/src/harness/hooks/pre-edit-dupe-probe.ts +224 -0
  144. package/src/harness/hooks/register.ts +118 -0
  145. package/src/harness/model.ts +117 -0
  146. package/src/harness/pipeline.ts +348 -0
  147. package/src/harness/project-paths.ts +235 -0
  148. package/src/harness/stage-runner.ts +107 -0
  149. package/src/harness/stages/design.ts +386 -0
  150. package/src/harness/stages/discover.ts +454 -0
  151. package/src/harness/stages/implement.ts +162 -0
  152. package/src/harness/stages/plan.ts +335 -0
  153. package/src/harness/stages/research.ts +263 -0
  154. package/src/harness/stages/validate.ts +684 -0
  155. package/src/harness/storage.ts +467 -0
  156. package/src/harness/tools.ts +426 -0
  157. package/src/lsp/bridge.ts +56 -95
  158. package/src/lsp/capabilities.ts +108 -0
  159. package/src/lsp/contracts.ts +35 -0
  160. package/src/lsp/detector.ts +8 -12
  161. package/src/markdown-frontmatter.ts +68 -0
  162. package/src/mempalace/bridge.ts +129 -0
  163. package/src/mempalace/config.ts +75 -0
  164. package/src/mempalace/format.ts +163 -0
  165. package/src/mempalace/hooks.ts +370 -0
  166. package/src/mempalace/installer-helper.ts +194 -0
  167. package/src/mempalace/python/mempalace_bridge.py +440 -0
  168. package/src/mempalace/runtime.ts +565 -0
  169. package/src/mempalace/schema.ts +264 -0
  170. package/src/mempalace/session-summary.ts +198 -0
  171. package/src/mempalace/tool.ts +186 -0
  172. package/src/mempalace/uv.ts +256 -0
  173. package/src/migrate/runner.ts +354 -0
  174. package/src/planning/approval-flow.ts +206 -9
  175. package/src/planning/plan-writer-prompt.ts +4 -3
  176. package/src/planning/planning-ask-tool.ts +39 -0
  177. package/src/planning/render-markdown.ts +74 -0
  178. package/src/planning/spec.ts +42 -0
  179. package/src/planning/system-prompt.ts +11 -8
  180. package/src/planning/validate.ts +84 -0
  181. package/src/platform/omp.ts +15 -2
  182. package/src/platform/system-prompt.ts +37 -0
  183. package/src/platform/test-utils.ts +3 -0
  184. package/src/platform/types.ts +6 -1
  185. package/src/qa/config.ts +12 -6
  186. package/src/qa/detect-app-type.ts +13 -6
  187. package/src/qa/matrix.ts +12 -6
  188. package/src/qa/prompt-builder.ts +28 -30
  189. package/src/qa/scripts/dev-server-utils.ts +72 -0
  190. package/src/qa/scripts/run-e2e-tests.ts +226 -0
  191. package/src/qa/scripts/start-dev-server.ts +138 -0
  192. package/src/qa/scripts/stop-dev-server.ts +77 -0
  193. package/src/qa/session.ts +13 -7
  194. package/src/quality/ai-setup.ts +27 -25
  195. package/src/quality/contracts.ts +34 -0
  196. package/src/quality/gates/ai-review.ts +20 -58
  197. package/src/quality/gates/command.ts +249 -46
  198. package/src/quality/review-gates.ts +18 -2
  199. package/src/quality/runner.ts +63 -22
  200. package/src/quality/schemas.ts +37 -2
  201. package/src/quality/setup.ts +96 -16
  202. package/src/release/changelog.ts +1 -1
  203. package/src/release/channels/custom.ts +13 -3
  204. package/src/release/channels/types.ts +5 -0
  205. package/src/release/contracts.ts +90 -0
  206. package/src/release/executor.ts +122 -45
  207. package/src/release/prompt.ts +18 -2
  208. package/src/release/targets.ts +86 -0
  209. package/src/release/version.ts +96 -71
  210. package/src/review/agent-loader.ts +221 -109
  211. package/src/review/fixer.ts +10 -6
  212. package/src/review/multi-agent-runner.ts +114 -13
  213. package/src/review/output.ts +12 -139
  214. package/src/review/runner.ts +12 -6
  215. package/src/review/scope.ts +144 -24
  216. package/src/review/types.ts +1 -20
  217. package/src/review/validator.ts +12 -6
  218. package/src/storage/fix-pr-sessions.ts +21 -14
  219. package/src/storage/plans.ts +14 -5
  220. package/src/storage/qa-sessions.ts +25 -19
  221. package/src/storage/reliability-metrics.ts +180 -0
  222. package/src/storage/reports.ts +8 -7
  223. package/src/storage/review-sessions.ts +55 -20
  224. package/src/tool-catalog/active-tool-controller.ts +164 -0
  225. package/src/tool-catalog/active-tool-planner.ts +212 -0
  226. package/src/tool-catalog/tool-groups.ts +102 -0
  227. package/src/types.ts +1399 -5
  228. package/src/ui-design/backend-adapter.ts +78 -0
  229. package/src/ui-design/backends/local-html.ts +82 -0
  230. package/src/ui-design/backends/pencil-mcp.ts +111 -0
  231. package/src/ui-design/components-scanner.ts +124 -0
  232. package/src/ui-design/config.ts +55 -0
  233. package/src/ui-design/pen-scanner.ts +95 -0
  234. package/src/ui-design/pen-selector.ts +72 -0
  235. package/src/ui-design/prompt-builder.ts +73 -0
  236. package/src/ui-design/scanner.ts +136 -0
  237. package/src/ui-design/session.ts +974 -0
  238. package/src/ui-design/system-prompt.ts +312 -0
  239. package/src/ui-design/tokens-scanner.ts +181 -0
  240. package/src/ui-design/types.ts +96 -0
  241. package/src/ultraplan/agent-catalog.ts +522 -0
  242. package/src/ultraplan/authoring/agent-catalog.ts +310 -0
  243. package/src/ultraplan/authoring/authoring-tools.ts +552 -0
  244. package/src/ultraplan/authoring/command-handlers.ts +339 -0
  245. package/src/ultraplan/authoring/markdown.ts +510 -0
  246. package/src/ultraplan/authoring/model.ts +162 -0
  247. package/src/ultraplan/authoring/pipeline.ts +319 -0
  248. package/src/ultraplan/authoring/stage-runner.ts +141 -0
  249. package/src/ultraplan/authoring/stages/approve.ts +249 -0
  250. package/src/ultraplan/authoring/stages/discover.ts +289 -0
  251. package/src/ultraplan/authoring/stages/intake.ts +203 -0
  252. package/src/ultraplan/authoring/stages/research.ts +399 -0
  253. package/src/ultraplan/authoring/stages/review.ts +333 -0
  254. package/src/ultraplan/authoring/stages/scout.ts +188 -0
  255. package/src/ultraplan/authoring/stages/synthesize.ts +348 -0
  256. package/src/ultraplan/authoring/storage.ts +594 -0
  257. package/src/ultraplan/authoring/synth-gate.ts +165 -0
  258. package/src/ultraplan/authoring-draft.ts +653 -0
  259. package/src/ultraplan/authoring-persist.ts +180 -0
  260. package/src/ultraplan/authoring-tool.ts +608 -0
  261. package/src/ultraplan/authoring-wizard.ts +587 -0
  262. package/src/ultraplan/batch/merge.ts +98 -0
  263. package/src/ultraplan/batch/planner.ts +150 -0
  264. package/src/ultraplan/batch/presenter.ts +97 -0
  265. package/src/ultraplan/batch/storage.ts +420 -0
  266. package/src/ultraplan/batch/supervisor.ts +317 -0
  267. package/src/ultraplan/batch/worker.ts +26 -0
  268. package/src/ultraplan/batch/worktree.ts +110 -0
  269. package/src/ultraplan/contracts.ts +1593 -0
  270. package/src/ultraplan/default-agents/authoring/discoverer.md +12 -0
  271. package/src/ultraplan/default-agents/authoring/intake.md +12 -0
  272. package/src/ultraplan/default-agents/authoring/planner.md +12 -0
  273. package/src/ultraplan/default-agents/authoring/researcher.md +12 -0
  274. package/src/ultraplan/default-agents/authoring/scope-checker.md +12 -0
  275. package/src/ultraplan/default-agents/authoring/scout.md +12 -0
  276. package/src/ultraplan/default-agents/authoring/structure-checker.md +12 -0
  277. package/src/ultraplan/default-agents/authoring/tdd-checker.md +12 -0
  278. package/src/ultraplan/default-agents/backend-domain-reviewer.md +10 -0
  279. package/src/ultraplan/default-agents/backend-executor.md +10 -0
  280. package/src/ultraplan/default-agents/backend-stack-reviewer.md +10 -0
  281. package/src/ultraplan/default-agents/backend-tester.md +10 -0
  282. package/src/ultraplan/default-agents/frontend-domain-reviewer.md +10 -0
  283. package/src/ultraplan/default-agents/frontend-executor.md +10 -0
  284. package/src/ultraplan/default-agents/frontend-stack-reviewer.md +10 -0
  285. package/src/ultraplan/default-agents/frontend-tester.md +10 -0
  286. package/src/ultraplan/default-agents/infrastructure-domain-reviewer.md +10 -0
  287. package/src/ultraplan/default-agents/infrastructure-executor.md +10 -0
  288. package/src/ultraplan/default-agents/infrastructure-stack-reviewer.md +10 -0
  289. package/src/ultraplan/default-agents/infrastructure-tester.md +10 -0
  290. package/src/ultraplan/execution/contract.ts +71 -0
  291. package/src/ultraplan/execution/policy.ts +217 -0
  292. package/src/ultraplan/execution/runtime-tools.ts +107 -0
  293. package/src/ultraplan/execution/session-runner.ts +281 -0
  294. package/src/ultraplan/next-router.ts +85 -0
  295. package/src/ultraplan/presenter.ts +359 -0
  296. package/src/ultraplan/project-paths.ts +342 -0
  297. package/src/ultraplan/runtime/active-execution.ts +72 -0
  298. package/src/ultraplan/runtime/apply-mutation.ts +416 -0
  299. package/src/ultraplan/runtime/blockers.ts +243 -0
  300. package/src/ultraplan/runtime/hook-bridge.ts +486 -0
  301. package/src/ultraplan/runtime/launch-context.ts +207 -0
  302. package/src/ultraplan/runtime/migration.ts +524 -0
  303. package/src/ultraplan/runtime/normalize.ts +281 -0
  304. package/src/ultraplan/runtime/proof.ts +260 -0
  305. package/src/ultraplan/runtime/reducer.ts +416 -0
  306. package/src/ultraplan/runtime/repair.ts +251 -0
  307. package/src/ultraplan/runtime/tracker-storage.ts +368 -0
  308. package/src/ultraplan/session-selection.ts +291 -0
  309. package/src/ultraplan/storage.ts +374 -0
  310. package/src/utils/editor.ts +38 -0
  311. package/src/utils/executable.ts +80 -0
  312. package/src/utils/paths.ts +1 -20
  313. package/src/utils/shell.ts +31 -0
  314. package/src/visual/companion.ts +2 -1
  315. package/src/visual/scripts/frame-template.html +60 -0
  316. package/src/visual/scripts/index.js +59 -13
  317. package/src/visual/scripts/package.json +3 -0
  318. package/src/visual/start-server.ts +2 -1
  319. package/src/workspace/git-scope.ts +64 -0
  320. package/src/workspace/locks.ts +23 -0
  321. package/src/workspace/package-manager.ts +117 -0
  322. package/src/workspace/path-mapping.ts +75 -0
  323. package/src/workspace/project-slug.ts +92 -0
  324. package/src/workspace/repo-root.ts +137 -0
  325. package/src/workspace/selector.ts +115 -0
  326. package/src/workspace/state-paths.ts +118 -0
  327. package/src/workspace/targets.ts +313 -0
  328. package/src/fix-pr/scripts/diff-comments.sh +0 -33
  329. package/src/fix-pr/scripts/fetch-pr-comments.sh +0 -25
  330. package/src/fix-pr/scripts/trigger-review.sh +0 -36
  331. package/src/fix-pr/scripts/wait-and-check.sh +0 -37
  332. package/src/qa/scripts/detect-app-type.sh +0 -68
  333. package/src/qa/scripts/discover-routes.sh +0 -143
  334. package/src/qa/scripts/run-e2e-tests.sh +0 -131
  335. package/src/qa/scripts/start-dev-server.sh +0 -46
  336. package/src/qa/scripts/stop-dev-server.sh +0 -36
  337. package/src/review/prompts/fix-output-schema.md +0 -18
  338. package/src/review/prompts/review-output-schema.md +0 -38
  339. package/src/review/template.ts +0 -15
  340. /package/src/{review → ai}/prompts/invalid-output-retry.md +0 -0
@@ -0,0 +1,335 @@
1
+ /**
2
+ * PLAN stage runner.
3
+ *
4
+ * Reads the design spec and emits a Plan markdown into the canonical plans directory.
5
+ * The plan is the user-approved task list that Implement will execute.
6
+ *
7
+ * The plan structure is the same shape `parsePlan` (src/storage/plans.ts) understands —
8
+ * we run `validatePlanMarkdown` on the rendered output so the same approval flow that
9
+ * gates `/supi:plan` gates the harness plan, no special-casing.
10
+ */
11
+
12
+ import * as fs from "node:fs";
13
+ import * as path from "node:path";
14
+
15
+ import type { HarnessDesignSpec } from "../../types.js";
16
+ import { validatePlanMarkdown } from "../../planning/validate.js";
17
+ import { savePlan } from "../../storage/plans.js";
18
+ import { getProjectStatePath } from "../../workspace/state-paths.js";
19
+ import {
20
+ type HarnessStageRunResult,
21
+ type HarnessStageRunner,
22
+ type HarnessStageRunnerContext,
23
+ nowIso,
24
+ } from "../stage-runner.js";
25
+ import {
26
+ loadHarnessDesignSpecJson,
27
+ } from "../storage.js";
28
+
29
+ export interface HarnessPlanTask {
30
+ id: number;
31
+ name: string;
32
+ description: string;
33
+ files: string[];
34
+ criteria: string;
35
+ complexity: "small" | "medium" | "large";
36
+ }
37
+
38
+ /**
39
+ * Build the canonical task list from a design spec. Always emits the "harden" tasks
40
+ * (AGENTS.md, docs/architecture.md, docs/golden-principles.md, lint/structural/eval
41
+ * configs); appends the conditional anti-slop tasks per Design's backend choice.
42
+ */
43
+ export function buildHarnessPlanTasks(spec: HarnessDesignSpec): HarnessPlanTask[] {
44
+ const tasks: HarnessPlanTask[] = [];
45
+ let id = 1;
46
+
47
+ tasks.push({
48
+ id: id++,
49
+ name: "Generate AGENTS.md",
50
+ description: "Write a ≤120-line AGENTS.md at the repo root summarizing the harness contract for any agent.",
51
+ files: ["AGENTS.md"],
52
+ criteria: "AGENTS.md exists, references docs/architecture.md and docs/golden-principles.md, and ends with a 'When in doubt' section.",
53
+ complexity: "small",
54
+ });
55
+
56
+ tasks.push({
57
+ id: id++,
58
+ name: "Write docs/architecture.md",
59
+ description: "Render the layer-table convention required by the layer-context-inject hook.",
60
+ files: ["docs/architecture.md"],
61
+ criteria: "docs/architecture.md parses cleanly via parseArchitectureMarkdown and reflects the design's layer rules.",
62
+ complexity: "small",
63
+ });
64
+
65
+ tasks.push({
66
+ id: id++,
67
+ name: "Write docs/golden-principles.md",
68
+ description: "List the ≤10 mechanical rules from the design spec.",
69
+ files: ["docs/golden-principles.md"],
70
+ criteria: "docs/golden-principles.md exists and contains every principle from the design spec.",
71
+ complexity: "small",
72
+ });
73
+
74
+ if (spec.tooling.lint) {
75
+ tasks.push({
76
+ id: id++,
77
+ name: `Wire lint tool (${spec.tooling.lint})`,
78
+ description: `Install and configure ${spec.tooling.lint} per the design spec.`,
79
+ files: [],
80
+ criteria: `${spec.tooling.lint} runs cleanly via the recommended invocation.`,
81
+ complexity: "small",
82
+ });
83
+ }
84
+
85
+ if (spec.tooling.structuralTest) {
86
+ tasks.push({
87
+ id: id++,
88
+ name: `Wire structural test tool (${spec.tooling.structuralTest})`,
89
+ description: `Install and configure ${spec.tooling.structuralTest} per the design spec.`,
90
+ files: [],
91
+ criteria: `${spec.tooling.structuralTest} runs cleanly.`,
92
+ complexity: "small",
93
+ });
94
+ }
95
+
96
+ if (spec.tooling.eval) {
97
+ tasks.push({
98
+ id: id++,
99
+ name: `Wire eval framework (${spec.tooling.eval})`,
100
+ description: `Set up the eval framework per the design spec.`,
101
+ files: [],
102
+ criteria: `Eval framework runs cleanly.`,
103
+ complexity: "small",
104
+ });
105
+ }
106
+
107
+ tasks.push({
108
+ id: id++,
109
+ name: "Wire local harness quality command",
110
+ description: `Add a local quality command that runs every validation gate from the design spec. The canonical command must be \`${spec.ci.localCommand}\`.`,
111
+ files: ["package.json"],
112
+ criteria: `Running \`${spec.ci.localCommand}\` executes the configured validation gates and exits non-zero on the first failed required gate.`,
113
+ complexity: "small",
114
+ });
115
+
116
+ tasks.push({
117
+ id: id++,
118
+ name: "Wire CI harness quality workflow",
119
+ description: `Create ${spec.ci.workflowPath} for ${spec.ci.provider}. It must invoke \`${spec.ci.localCommand}\` instead of duplicating gate logic inline.`,
120
+ files: [spec.ci.workflowPath],
121
+ criteria: `Workflow exists, runs on ${
122
+ spec.ci.trigger.mode === "all-prs" ? "all pull requests" : `pull requests targeting ${spec.ci.trigger.branches.join(", ")}`
123
+ }, and calls \`${spec.ci.localCommand}\`.`,
124
+ complexity: "small",
125
+ });
126
+
127
+ // Anti-slop conditional tasks
128
+ if (spec.antiSlop.backend === "fallow" || spec.antiSlop.backend === "hybrid") {
129
+ tasks.push({
130
+ id: id++,
131
+ name: "Install fallow + write .fallowrc.json",
132
+ description: "Run `bun install -g fallow` (or set up `npx fallow` shim) and emit `.fallowrc.json` with detected entry points + architecture rules.",
133
+ files: [".fallowrc.json"],
134
+ criteria: "`fallow audit --format json` runs cleanly on the repo.",
135
+ complexity: "small",
136
+ });
137
+ }
138
+
139
+ if (spec.antiSlop.backend === "desloppify" || spec.antiSlop.backend === "hybrid") {
140
+ tasks.push({
141
+ id: id++,
142
+ name: "Install desloppify",
143
+ description: 'Run `pip install --upgrade "desloppify[full]"` and add `.desloppify/` to `.gitignore`.',
144
+ files: [".gitignore"],
145
+ criteria: "`desloppify scan --format json` runs cleanly on the repo.",
146
+ complexity: "small",
147
+ });
148
+ if (spec.antiSlop.skillTargets.length > 0) {
149
+ tasks.push({
150
+ id: id++,
151
+ name: "Distribute agent-skills",
152
+ description: `Run desloppify update-skill for each target: ${spec.antiSlop.skillTargets.join(", ")}.`,
153
+ files: [],
154
+ criteria: "Every target client has the desloppify skill installed.",
155
+ complexity: "small",
156
+ });
157
+ }
158
+ }
159
+
160
+ tasks.push({
161
+ id: id++,
162
+ name: "Register anti-slop hooks",
163
+ description: "Ensure src/harness/hooks/register.ts wires pre-edit dupe probe, post-session sweep, and layer-context-inject only when the harness marker exists.",
164
+ files: ["src/harness/hooks/register.ts"],
165
+ criteria: "Hooks are registered idempotently and gated by the marker file.",
166
+ complexity: "small",
167
+ });
168
+
169
+ tasks.push({
170
+ id: id++,
171
+ name: "Initialize slop queue",
172
+ description: "Touch the project-scoped queue.jsonl so the queue exists before hooks fire.",
173
+ files: [],
174
+ criteria: "queue.jsonl exists and is readable; readSlopQueue returns []",
175
+ complexity: "small",
176
+ });
177
+
178
+ tasks.push({
179
+ id: id++,
180
+ name: "Generate scorecard skeleton + README badge",
181
+ description: "Compute the initial score and write a repo-local snapshot at .omp/supipowers/harness/score.json.",
182
+ files: [".omp/supipowers/harness/score.json"],
183
+ criteria: "Score JSON exists; SVG badge is renderable from it.",
184
+ complexity: "small",
185
+ });
186
+
187
+ if (spec.supipowersWiring.addReviewAgent) {
188
+ tasks.push({
189
+ id: id++,
190
+ name: "Add architecture-aware review agent",
191
+ description: "Generate `.omp/supipowers/review-agents/harness-architecture.md` from architecture + golden principles.",
192
+ files: [".omp/supipowers/review-agents/harness-architecture.md"],
193
+ criteria: "Review agent file exists and is loaded by `/supi:review`.",
194
+ complexity: "small",
195
+ });
196
+ }
197
+
198
+ if (spec.supipowersWiring.wireChecksGate) {
199
+ tasks.push({
200
+ id: id++,
201
+ name: "Wire `/supi:checks` gate",
202
+ description: `Add a custom gate to .omp/supipowers/config.json that runs the ${spec.antiSlop.backend} scan.`,
203
+ files: [".omp/supipowers/config.json"],
204
+ criteria: "`/supi:checks` runs the anti-slop scan as part of its gate set.",
205
+ complexity: "medium",
206
+ });
207
+ }
208
+
209
+ return tasks;
210
+ }
211
+
212
+ /** Render the plan markdown that lands in the canonical plans directory. */
213
+ export function renderHarnessPlanMarkdown(input: {
214
+ spec: HarnessDesignSpec;
215
+ tasks: readonly HarnessPlanTask[];
216
+ recordedAt: string;
217
+ planName: string;
218
+ }): string {
219
+ const lines: string[] = [];
220
+ lines.push("---");
221
+ lines.push(`name: ${input.planName}`);
222
+ lines.push(`created: ${input.recordedAt}`);
223
+ lines.push("tags: [harness, anti-slop]");
224
+ lines.push("---");
225
+ lines.push("");
226
+ lines.push(`# ${input.planName}`);
227
+ lines.push("");
228
+ lines.push("## Context");
229
+ lines.push("");
230
+ lines.push(
231
+ `Generated by /supi:harness from design spec ${input.spec.sessionId}. Anti-slop backend: \`${input.spec.antiSlop.backend}\`.`,
232
+ );
233
+ lines.push("");
234
+ lines.push("## Tasks");
235
+ lines.push("");
236
+ for (const task of input.tasks) {
237
+ lines.push(`### Task ${task.id}: ${task.name}`);
238
+ lines.push("");
239
+ lines.push(task.description);
240
+ lines.push("");
241
+ if (task.files.length > 0) {
242
+ lines.push("**Files:**");
243
+ for (const f of task.files) lines.push(`- Modify: \`${f}\``);
244
+ lines.push("");
245
+ }
246
+ lines.push(`**criteria**: ${task.criteria}`);
247
+ lines.push(`**complexity**: ${task.complexity}`);
248
+ lines.push("");
249
+ }
250
+ return lines.join("\n");
251
+ }
252
+
253
+ export interface PlanStageInput {
254
+ /** Override for the plan filename. Defaults to `harness-<sessionId>.md`. */
255
+ planFilename?: string;
256
+ }
257
+
258
+ export class HarnessPlanStage implements HarnessStageRunner {
259
+ readonly stage = "plan" as const;
260
+
261
+ constructor(private readonly input: PlanStageInput = {}) {}
262
+
263
+ async isReady(ctx: HarnessStageRunnerContext): Promise<boolean> {
264
+ return loadHarnessDesignSpecJson(ctx.paths, ctx.cwd, ctx.sessionId).ok;
265
+ }
266
+
267
+ async isComplete(ctx: HarnessStageRunnerContext): Promise<boolean> {
268
+ const designResult = loadHarnessDesignSpecJson(ctx.paths, ctx.cwd, ctx.sessionId);
269
+ if (!designResult.ok) return false;
270
+ const planName = this.input.planFilename ?? `harness-${ctx.sessionId}`;
271
+ const planPath = path.join(
272
+ getProjectStatePath(ctx.paths, ctx.cwd, "plans"),
273
+ `${planName}.md`,
274
+ );
275
+ return fs.existsSync(planPath);
276
+ }
277
+
278
+ async run(ctx: HarnessStageRunnerContext): Promise<HarnessStageRunResult> {
279
+ const designResult = loadHarnessDesignSpecJson(ctx.paths, ctx.cwd, ctx.sessionId);
280
+ if (!designResult.ok) {
281
+ return {
282
+ status: "blocked",
283
+ stage: this.stage,
284
+ artifactPaths: [],
285
+ blocker: {
286
+ code: "design-spec-missing",
287
+ message:
288
+ "Plan stage requires <session>/design-spec.json. Run /supi:harness design first to produce it.",
289
+ },
290
+ };
291
+ }
292
+ const result = emitHarnessPlanFromSpec({
293
+ ctx: { paths: ctx.paths, cwd: ctx.cwd },
294
+ spec: designResult.value,
295
+ recordedAt: nowIso(ctx),
296
+ planName: this.input.planFilename ?? `harness-${ctx.sessionId}`,
297
+ });
298
+ return {
299
+ status: "awaiting-user",
300
+ stage: this.stage,
301
+ artifactPaths: [result.planPath],
302
+ details: {
303
+ taskCount: result.tasks.length,
304
+ planPath: result.planPath,
305
+ },
306
+ };
307
+ }
308
+ }
309
+
310
+ /**
311
+ * Convenience helper for the command handler: render and persist the plan in one call.
312
+ * The plan goes into the standard plans directory used by `/supi:plan` so the same
313
+ * approval flow lights up.
314
+ */
315
+ export function emitHarnessPlanFromSpec(input: {
316
+ ctx: Pick<HarnessStageRunnerContext, "paths" | "cwd">;
317
+ spec: HarnessDesignSpec;
318
+ recordedAt?: string;
319
+ planName?: string;
320
+ }): { planPath: string; planMarkdown: string; tasks: HarnessPlanTask[] } {
321
+ const recordedAt = input.recordedAt ?? new Date().toISOString();
322
+ const planName = input.planName ?? `harness-${input.spec.sessionId}`;
323
+ const tasks = buildHarnessPlanTasks(input.spec);
324
+ const planMarkdown = renderHarnessPlanMarkdown({ spec: input.spec, tasks, recordedAt, planName });
325
+ const filename = `${planName}.md`;
326
+ const planPath = savePlan(input.ctx.paths, input.ctx.cwd, filename, planMarkdown);
327
+ return { planPath, planMarkdown, tasks };
328
+ }
329
+
330
+ /** Validator wrapper — re-uses the canonical validator. */
331
+ export function validateHarnessPlanMarkdown(markdown: string, planName: string): string[] {
332
+ const result = validatePlanMarkdown(markdown, planName);
333
+ if (result.output) return [];
334
+ return result.errors.map((e: { path: string; message: string }) => `${e.path}: ${e.message}`);
335
+ }
@@ -0,0 +1,263 @@
1
+ /**
2
+ * RESEARCH stage runner.
3
+ *
4
+ * Builds `<session>/research/<topic>.md` for every mandatory research topic. The plan
5
+ * specifies these topics (§5 Phase 4):
6
+ *
7
+ * - "AGENTS.md best practices"
8
+ * - "Layered architecture enforcement for <lang>" (capped at 3 languages; ≥3 → polyglot)
9
+ * - "Structural test patterns for <stack>"
10
+ * - "Eval frameworks for <stack>"
11
+ * - "Drift detection patterns"
12
+ * - "LLM-failure-mode taxonomy"
13
+ * - "Code-duplication detection tools"
14
+ * - "Dead-code detection per language"
15
+ * - "Pre-edit duplicate-probe ergonomics"
16
+ * - "Persistent execution-queue patterns"
17
+ *
18
+ * The runner emits a structured stub for each topic (mandatory headings + frontmatter)
19
+ * the user / a downstream agent fills in. The validator (which lives in the command
20
+ * handler) requires ≥2 primary-source URLs and the `## Options` + `## Recommendation`
21
+ * sections; stubs only ship those headings, so a validation pass after Research
22
+ * intentionally fails until a researcher completes them.
23
+ *
24
+ * Why deterministic stubs and not auto-fan-out? Two reasons:
25
+ * 1. Web search results drift; we must not bake them in.
26
+ * 2. The plan calls for parallel sub-agents. Spawning them properly requires the model
27
+ * resolution wired through model.json — which we do here, but the actual writeups
28
+ * are best authored manually for v1 to keep the dependency surface narrow. The agent
29
+ * entry point lives in command-handlers, not the stage runner.
30
+ */
31
+
32
+ import * as fs from "node:fs";
33
+ import * as path from "node:path";
34
+
35
+ import {
36
+ type HarnessStageRunResult,
37
+ type HarnessStageRunner,
38
+ type HarnessStageRunnerContext,
39
+ nowIso,
40
+ } from "../stage-runner.js";
41
+ import {
42
+ loadHarnessDiscover,
43
+ saveHarnessResearchTopic,
44
+ } from "../storage.js";
45
+ import { getHarnessResearchDir } from "../project-paths.js";
46
+
47
+ const BASE_TOPICS: readonly string[] = [
48
+ "agents-md-best-practices",
49
+ "drift-detection-patterns",
50
+ "llm-failure-mode-taxonomy",
51
+ "code-duplication-detection-tools",
52
+ "dead-code-detection-per-language",
53
+ "pre-edit-duplicate-probe-ergonomics",
54
+ "persistent-execution-queue-patterns",
55
+ ];
56
+
57
+ const STACK_TOPICS_PREFIX = "layered-architecture-enforcement";
58
+ const STRUCTURAL_PREFIX = "structural-test-patterns";
59
+ const EVAL_PREFIX = "eval-frameworks";
60
+
61
+ const POLYGLOT_BUCKET_THRESHOLD = 3;
62
+ const TOPIC_CAP = 12;
63
+
64
+ export interface ResearchTopicPlan {
65
+ slug: string;
66
+ title: string;
67
+ context: string;
68
+ }
69
+
70
+ /**
71
+ * Compute the topic plan from the discover artifact's languages. Caps at TOPIC_CAP and
72
+ * collapses to a polyglot bucket when ≥3 languages are present.
73
+ */
74
+ export function buildResearchTopicPlan(input: { languages: readonly string[] }): ResearchTopicPlan[] {
75
+ const languages = [...new Set(input.languages.map((l) => l.toLowerCase()))].filter((l) => l.length > 0);
76
+ const topics: ResearchTopicPlan[] = [];
77
+
78
+ for (const slug of BASE_TOPICS) {
79
+ topics.push({
80
+ slug,
81
+ title: humanizeSlug(slug),
82
+ context: "Always-on base topic (independent of stack).",
83
+ });
84
+ }
85
+
86
+ if (languages.length >= POLYGLOT_BUCKET_THRESHOLD) {
87
+ topics.push({
88
+ slug: `${STACK_TOPICS_PREFIX}-polyglot`,
89
+ title: "Layered architecture enforcement (polyglot)",
90
+ context: `${languages.length} languages detected; collapsed into a single polyglot topic.`,
91
+ });
92
+ topics.push({
93
+ slug: `${STRUCTURAL_PREFIX}-polyglot`,
94
+ title: "Structural test patterns (polyglot)",
95
+ context: "Multi-language repo; structural tests need to span every detected stack.",
96
+ });
97
+ topics.push({
98
+ slug: `${EVAL_PREFIX}-polyglot`,
99
+ title: "Eval frameworks (polyglot)",
100
+ context: "Multi-language repo; eval suites must support multiple test runners.",
101
+ });
102
+ } else {
103
+ for (const lang of languages.slice(0, 3)) {
104
+ topics.push({
105
+ slug: `${STACK_TOPICS_PREFIX}-${lang}`,
106
+ title: `Layered architecture enforcement for ${lang}`,
107
+ context: `${lang} detected as a primary language.`,
108
+ });
109
+ }
110
+ const dominant = languages[0];
111
+ if (dominant) {
112
+ topics.push({
113
+ slug: `${STRUCTURAL_PREFIX}-${dominant}`,
114
+ title: `Structural test patterns for ${dominant}`,
115
+ context: `${dominant} is the dominant language.`,
116
+ });
117
+ topics.push({
118
+ slug: `${EVAL_PREFIX}-${dominant}`,
119
+ title: `Eval frameworks for ${dominant}`,
120
+ context: `${dominant} is the dominant language.`,
121
+ });
122
+ }
123
+ }
124
+
125
+ return topics.slice(0, TOPIC_CAP);
126
+ }
127
+
128
+ function humanizeSlug(slug: string): string {
129
+ return slug.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
130
+ }
131
+
132
+ /**
133
+ * Render the markdown stub for a research topic. Always includes the mandatory `##
134
+ * Options` and `## Recommendation` headings + a frontmatter block; sources start empty
135
+ * (the validator demands ≥2 before Research is considered complete).
136
+ */
137
+ export function renderResearchTopicStub(input: {
138
+ topic: ResearchTopicPlan;
139
+ recordedAt: string;
140
+ }): string {
141
+ return [
142
+ "---",
143
+ `topic: ${input.topic.slug}`,
144
+ `title: ${JSON.stringify(input.topic.title)}`,
145
+ `lastVerified: ${input.recordedAt}`,
146
+ "sources: []",
147
+ "---",
148
+ "",
149
+ `# ${input.topic.title}`,
150
+ "",
151
+ `> ${input.topic.context}`,
152
+ "",
153
+ "## Background",
154
+ "",
155
+ "_Pending researcher writeup. Validator will fail until this section is filled in._",
156
+ "",
157
+ "## Options",
158
+ "",
159
+ "_List candidate approaches with tradeoffs._",
160
+ "",
161
+ "## Recommendation",
162
+ "",
163
+ "_State the recommended approach and the criteria that drove the choice._",
164
+ "",
165
+ "## Sources",
166
+ "",
167
+ "_Cite at least two primary sources (papers, official docs, RFCs). Validator rejects writeups with fewer._",
168
+ "",
169
+ "## Last verified",
170
+ "",
171
+ `${input.recordedAt}`,
172
+ "",
173
+ ].join("\n");
174
+ }
175
+
176
+ export class HarnessResearchStage implements HarnessStageRunner {
177
+ readonly stage = "research" as const;
178
+
179
+ async isReady(ctx: HarnessStageRunnerContext): Promise<boolean> {
180
+ const discover = loadHarnessDiscover(ctx.paths, ctx.cwd, ctx.sessionId);
181
+ return discover.ok;
182
+ }
183
+
184
+ async isComplete(ctx: HarnessStageRunnerContext): Promise<boolean> {
185
+ const dir = getHarnessResearchDir(ctx.paths, ctx.cwd, ctx.sessionId);
186
+ if (!fs.existsSync(dir)) return false;
187
+ try {
188
+ const entries = fs.readdirSync(dir).filter((f) => f.endsWith(".md"));
189
+ return entries.length > 0;
190
+ } catch {
191
+ return false;
192
+ }
193
+ }
194
+
195
+ async run(ctx: HarnessStageRunnerContext): Promise<HarnessStageRunResult> {
196
+ if (await this.isComplete(ctx)) {
197
+ return {
198
+ status: "skipped",
199
+ stage: this.stage,
200
+ artifactPaths: ["research/"],
201
+ details: { reason: "research/ already populated" },
202
+ };
203
+ }
204
+
205
+ const discover = loadHarnessDiscover(ctx.paths, ctx.cwd, ctx.sessionId);
206
+ if (!discover.ok) {
207
+ return {
208
+ status: "blocked",
209
+ stage: this.stage,
210
+ artifactPaths: [],
211
+ blocker: {
212
+ code: "discover-missing",
213
+ message: "Research requires a completed Discover artifact",
214
+ },
215
+ };
216
+ }
217
+
218
+ const plan = buildResearchTopicPlan({ languages: discover.value.languages });
219
+ const recordedAt = nowIso(ctx);
220
+ const written: string[] = [];
221
+ for (const topic of plan) {
222
+ const stub = renderResearchTopicStub({ topic, recordedAt });
223
+ const result = saveHarnessResearchTopic(ctx.paths, ctx.cwd, ctx.sessionId, topic.slug, stub);
224
+ if (!result.ok) {
225
+ return {
226
+ status: "failed",
227
+ stage: this.stage,
228
+ artifactPaths: written,
229
+ error: `failed to write research topic ${topic.slug}: ${result.error.message}`,
230
+ };
231
+ }
232
+ written.push(path.posix.join("research", `${topic.slug}.md`));
233
+ }
234
+
235
+ return {
236
+ status: "completed",
237
+ stage: this.stage,
238
+ artifactPaths: written,
239
+ details: { topicCount: plan.length },
240
+ };
241
+ }
242
+ }
243
+
244
+ /**
245
+ * Validate a research writeup. Returns an array of error messages (empty when valid).
246
+ * Mirrors the plan's validator: ≥2 sources + presence of `## Options` and `##
247
+ * Recommendation` headings.
248
+ */
249
+ export function validateResearchTopic(markdown: string): string[] {
250
+ const errors: string[] = [];
251
+ if (!/^##\s+Options\b/im.test(markdown)) errors.push("missing `## Options` heading");
252
+ if (!/^##\s+Recommendation\b/im.test(markdown)) errors.push("missing `## Recommendation` heading");
253
+
254
+ // Sources: count distinct URLs in the doc body. We accept either a frontmatter
255
+ // `sources: [...]` block or inline `https://` links anywhere.
256
+ const urls = new Set<string>();
257
+ for (const match of markdown.matchAll(/https?:\/\/[^\s)<>"']+/g)) {
258
+ urls.add(match[0]);
259
+ }
260
+ if (urls.size < 2) errors.push(`requires ≥2 source URLs (found ${urls.size})`);
261
+
262
+ return errors;
263
+ }