supipowers 1.5.3 → 2.0.1

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 +135 -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 +268 -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,224 @@
1
+ /**
2
+ * Pre-edit duplication probe.
3
+ *
4
+ * Registered on `tool_call` for `write` / `edit` (and the legacy `Write` / `Edit`
5
+ * casing). When the proposed write/edit's content matches an existing implementation
6
+ * above the configured threshold, the hook returns `{ block: true, reason: ... }` so the
7
+ * agent re-reads the duplicate path before retrying.
8
+ *
9
+ * Performance budget: ≤500 ms p95 on a 50k-LOC repo. We hard-fail the budget after
10
+ * `timeoutMs` and emit a warning instead of blocking — the contract is "never block on
11
+ * perf".
12
+ *
13
+ * The hook degrades gracefully when:
14
+ * - the harness marker is missing,
15
+ * - the configured backend isn't available,
16
+ * - the proposed write doesn't include enough tokens to meet `min_token_count`.
17
+ */
18
+
19
+ import type { Platform } from "../../platform/types.js";
20
+ import type { HarnessHookConfig } from "../../types.js";
21
+ import {
22
+ type SlopBackend,
23
+ } from "../anti_slop/backend.js";
24
+ import { computeQueueEntryId } from "../anti_slop/queue.js";
25
+ import { appendSlopQueueEntry } from "../storage.js";
26
+ import { getHarnessMarkerPath } from "../project-paths.js";
27
+ import * as fs from "node:fs";
28
+
29
+ export interface PreEditDupeProbeOptions {
30
+ /** Backend adapter; pass null for supi-native (probe is then a no-op). */
31
+ adapter: SlopBackend | null;
32
+ /** Hook config. */
33
+ config: HarnessHookConfig["pre_edit_dupe_probe"];
34
+ /** Override the timeout (ms). Defaults to 500. */
35
+ timeoutMs?: number;
36
+ }
37
+
38
+ interface ToolCallEvent {
39
+ toolName?: string;
40
+ input?: { path?: string; file?: string; content?: string; text?: string };
41
+ }
42
+
43
+ const TARGET_TOOLS = new Set(["write", "edit", "Write", "Edit"]);
44
+
45
+ function countWordTokens(text: string): number {
46
+ return text.split(/\s+/).filter((t) => t.length > 0).length;
47
+ }
48
+
49
+ function extractFile(event: ToolCallEvent): string | null {
50
+ return event.input?.path ?? event.input?.file ?? null;
51
+ }
52
+
53
+ function extractContent(event: ToolCallEvent): string | null {
54
+ return event.input?.content ?? event.input?.text ?? null;
55
+ }
56
+
57
+ export interface ProbeResult {
58
+ block: boolean;
59
+ reason: string;
60
+ duplicates: { path: string; line: number }[];
61
+ durationMs: number;
62
+ }
63
+
64
+ /**
65
+ * Run the probe synchronously-ish: we await the adapter but cap the await with
66
+ * `timeoutMs`. Tests inject a fake adapter to assert on dispatch shape.
67
+ */
68
+ export async function runPreEditProbe(input: {
69
+ platform: Platform;
70
+ cwd: string;
71
+ candidateFile: string;
72
+ proposedContent: string;
73
+ adapter: SlopBackend;
74
+ config: HarnessHookConfig["pre_edit_dupe_probe"];
75
+ timeoutMs?: number;
76
+ }): Promise<ProbeResult> {
77
+ const startedAt = Date.now();
78
+ const tokens = countWordTokens(input.proposedContent);
79
+ if (tokens < input.config.min_token_count) {
80
+ return {
81
+ block: false,
82
+ reason: `proposed content has ${tokens} tokens (< min_token_count ${input.config.min_token_count})`,
83
+ duplicates: [],
84
+ durationMs: Date.now() - startedAt,
85
+ };
86
+ }
87
+
88
+ const timeoutMs = input.timeoutMs ?? 500;
89
+ const probePromise = input.adapter.dupes(input.platform, {
90
+ cwd: input.cwd,
91
+ threshold: input.config.threshold,
92
+ minTokenCount: input.config.min_token_count,
93
+ files: [input.candidateFile],
94
+ proposedWrite: { file: input.candidateFile, content: input.proposedContent },
95
+ timeoutMs,
96
+ });
97
+
98
+ let result;
99
+ try {
100
+ result = await Promise.race([
101
+ probePromise,
102
+ new Promise<{ ok: false; reason: "timeout"; message: string }>((resolve) => {
103
+ setTimeout(
104
+ () =>
105
+ resolve({
106
+ ok: false,
107
+ reason: "timeout",
108
+ message: `pre-edit probe exceeded ${timeoutMs} ms; skipping`,
109
+ }),
110
+ timeoutMs + 25,
111
+ );
112
+ }),
113
+ ]);
114
+ } catch (error) {
115
+ return {
116
+ block: false,
117
+ reason: `probe threw: ${error instanceof Error ? error.message : String(error)}`,
118
+ duplicates: [],
119
+ durationMs: Date.now() - startedAt,
120
+ };
121
+ }
122
+
123
+ if (!result.ok) {
124
+ return {
125
+ block: false,
126
+ reason: `probe ${result.reason}: ${result.message}`,
127
+ duplicates: [],
128
+ durationMs: Date.now() - startedAt,
129
+ };
130
+ }
131
+
132
+ const duplicates = result.findings
133
+ .filter((f) => f.kind === "duplicate" && f.range !== null)
134
+ .map((f) => ({ path: f.file, line: f.range?.startLine ?? 1 }));
135
+
136
+ if (duplicates.length === 0) {
137
+ return {
138
+ block: false,
139
+ reason: "no duplicate above threshold",
140
+ duplicates: [],
141
+ durationMs: Date.now() - startedAt,
142
+ };
143
+ }
144
+
145
+ // Append to the queue so the user sees the violation even though we blocked.
146
+ for (const finding of result.findings) {
147
+ if (finding.kind !== "duplicate") continue;
148
+ const id = computeQueueEntryId({
149
+ kind: "duplicate",
150
+ file: finding.file,
151
+ range: finding.range,
152
+ ruleHint: typeof finding.details?.rule === "string" ? finding.details.rule : "pre-edit-probe",
153
+ });
154
+ appendSlopQueueEntry(input.platform.paths, input.cwd, {
155
+ id,
156
+ kind: "duplicate",
157
+ file: finding.file,
158
+ range: finding.range,
159
+ severity: finding.severity,
160
+ source: finding.source,
161
+ state: "open",
162
+ message: finding.message,
163
+ remediation: finding.remediation,
164
+ ts: new Date().toISOString(),
165
+ details: finding.details,
166
+ });
167
+ }
168
+
169
+ const first = duplicates[0];
170
+ return {
171
+ block: true,
172
+ reason: `Duplicate of ${first.path}:${first.line}; reuse instead of re-implementing`,
173
+ duplicates,
174
+ durationMs: Date.now() - startedAt,
175
+ };
176
+ }
177
+
178
+ /**
179
+ * Register the hook. Returns a teardown function. No-op when the adapter is null
180
+ * (supi-native backend), the marker is missing, or the hook config disables it.
181
+ */
182
+ export function registerPreEditDupeProbeHook(
183
+ platform: Platform,
184
+ options: PreEditDupeProbeOptions,
185
+ ): () => void {
186
+ if (!options.config.enabled || options.adapter === null) {
187
+ return () => {};
188
+ }
189
+ let unregistered = false;
190
+ const adapter = options.adapter;
191
+ const config = options.config;
192
+ const timeoutMs = options.timeoutMs ?? 500;
193
+
194
+ const handler = async (event: ToolCallEvent, ctx: unknown) => {
195
+ if (unregistered) return;
196
+ if (!event.toolName || !TARGET_TOOLS.has(event.toolName)) return;
197
+ const cwd = (ctx as { cwd?: string } | undefined)?.cwd ?? process.cwd();
198
+ if (!fs.existsSync(getHarnessMarkerPath(platform.paths, cwd))) return;
199
+ const candidateFile = extractFile(event);
200
+ if (!candidateFile) return;
201
+ const proposedContent = extractContent(event);
202
+ if (!proposedContent) return;
203
+
204
+ const result = await runPreEditProbe({
205
+ platform,
206
+ cwd,
207
+ candidateFile,
208
+ proposedContent,
209
+ adapter,
210
+ config,
211
+ timeoutMs,
212
+ });
213
+ if (result.block) {
214
+ return { block: true, reason: result.reason };
215
+ }
216
+ return undefined;
217
+ };
218
+
219
+ platform.on("tool_call", handler);
220
+
221
+ return () => {
222
+ unregistered = true;
223
+ };
224
+ }
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Hook registrar — wires every harness hook (pre-edit dupe probe, post-session sweep,
3
+ * layer-context-inject) onto the platform.
4
+ *
5
+ * **Project-scoped**: hooks are gated by `<repo>/.omp/supipowers/harness/marker.json`.
6
+ * The registrar runs at extension boot but each individual hook checks the marker on
7
+ * every event so a repo without the harness installed sees no behavior change.
8
+ *
9
+ * Hook activation also depends on the per-hook `enabled` flag in
10
+ * `.omp/supipowers/config.json`; we read that lazily inside each hook so config edits
11
+ * take effect on the next event without restarting OMP.
12
+ */
13
+
14
+ import type { Platform } from "../../platform/types.js";
15
+ import type { HarnessConfig, HarnessHookConfig } from "../../types.js";
16
+ import { buildBackendAdapter } from "../anti_slop/backend-factory.js";
17
+ import {
18
+ registerLayerContextInjectHook,
19
+ } from "./layer-context-inject.js";
20
+ import {
21
+ registerPostSessionSweepHook,
22
+ } from "./post-session-sweep.js";
23
+ import {
24
+ registerPreEditDupeProbeHook,
25
+ } from "./pre-edit-dupe-probe.js";
26
+
27
+ export const DEFAULT_HARNESS_HOOK_CONFIG: HarnessHookConfig = {
28
+ pre_edit_dupe_probe: { enabled: true, threshold: 0.85, min_token_count: 30 },
29
+ post_session_sweep: { enabled: true, block_on_new_dead_code: false },
30
+ layer_context_inject: { enabled: true, addendum_max_chars: 800 },
31
+ score_floor: { strict: 75, lenient: 90, release_blocking: false },
32
+ };
33
+
34
+ export const DEFAULT_HARNESS_CONFIG: HarnessConfig = {
35
+ anti_slop: DEFAULT_HARNESS_HOOK_CONFIG,
36
+ implement_in_session_threshold: 10,
37
+ };
38
+
39
+ export interface HarnessHookRegistration {
40
+ /** Tear down every registered hook. Used by tests for clean isolation. */
41
+ dispose(): void;
42
+ /** True if any hook actually subscribed (i.e. config + backend allow it). */
43
+ active: boolean;
44
+ }
45
+
46
+ export interface RegisterHooksOptions {
47
+ /** Backend to use for the duplicate / dead-code adapters. Defaults to fallow. */
48
+ backend?: "fallow" | "desloppify" | "supi-native" | "hybrid";
49
+ /** Hook config snapshot. Defaults to DEFAULT_HARNESS_HOOK_CONFIG. */
50
+ hooks?: HarnessHookConfig;
51
+ /**
52
+ * Extracts the candidate file the agent is about to edit. Implementations consult
53
+ * session state. Defaults to a no-op resolver (the layer-inject hook becomes a no-op
54
+ * unless a real resolver is wired).
55
+ */
56
+ resolveCandidateFile?: (event: unknown, ctx: unknown) => string | null;
57
+ }
58
+
59
+ // Re-export so existing call sites keep working without an import path change.
60
+ export { buildBackendAdapter };
61
+
62
+ /**
63
+ * Register every harness hook. Idempotent at the dispose boundary: calling
64
+ * `dispose()` twice is safe.
65
+ */
66
+ export function registerHarnessHooks(
67
+ platform: Platform,
68
+ options: RegisterHooksOptions = {},
69
+ ): HarnessHookRegistration {
70
+ const backend = options.backend ?? "fallow";
71
+ const hooks = options.hooks ?? DEFAULT_HARNESS_HOOK_CONFIG;
72
+ const adapter = buildBackendAdapter(backend);
73
+
74
+ const teardowns: Array<() => void> = [];
75
+ let active = false;
76
+
77
+ if (hooks.pre_edit_dupe_probe.enabled && adapter) {
78
+ teardowns.push(
79
+ registerPreEditDupeProbeHook(platform, {
80
+ adapter,
81
+ config: hooks.pre_edit_dupe_probe,
82
+ }),
83
+ );
84
+ active = true;
85
+ }
86
+ if (hooks.post_session_sweep.enabled && adapter) {
87
+ teardowns.push(
88
+ registerPostSessionSweepHook(platform, {
89
+ adapter,
90
+ config: hooks.post_session_sweep,
91
+ }),
92
+ );
93
+ active = true;
94
+ }
95
+ if (hooks.layer_context_inject.enabled) {
96
+ teardowns.push(
97
+ registerLayerContextInjectHook(platform, {
98
+ config: hooks.layer_context_inject,
99
+ resolveCandidateFile: options.resolveCandidateFile ?? (() => null),
100
+ }),
101
+ );
102
+ active = true;
103
+ }
104
+
105
+ return {
106
+ active,
107
+ dispose() {
108
+ while (teardowns.length > 0) {
109
+ const t = teardowns.pop();
110
+ try {
111
+ t?.();
112
+ } catch {
113
+ // best-effort
114
+ }
115
+ }
116
+ },
117
+ };
118
+ }
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Per-action-id model resolution for the harness pipeline.
3
+ *
4
+ * Mirrors `src/ultraplan/authoring/model.ts`:
5
+ * 1. `model.json` per-action override (`actions["harness.<stage>"]`)
6
+ * 2. `model.json#default`
7
+ * 3. Platform's harness role hint
8
+ * 4. Main session model
9
+ *
10
+ * Action ids are flat strings; researchers fan out per-topic with composable suffixes
11
+ * (`harness.research.<topicSlug>`) so per-topic overrides are a single lookup.
12
+ *
13
+ * The module body registers every fixed action id at load time. Topic-parameterized
14
+ * researcher ids are registered lazily when the researcher launches each topic — Discover
15
+ * may produce dozens of topics across runs and we prefer to register what we actually
16
+ * resolve rather than every theoretically reachable topic.
17
+ */
18
+
19
+ import { resolveModelForAction, type ModelPlatformBridge } from "../config/model-resolver.js";
20
+ import { modelRegistry } from "../config/model-registry-instance.js";
21
+ import type { ModelActionRegistry } from "../config/model-registry.js";
22
+ import type { ModelConfig, ResolvedModel } from "../types.js";
23
+
24
+ /** Action-id namespace. Centralized so call sites never inline the literal. */
25
+ export const HARNESS_ACTION_NAMESPACE = "harness";
26
+
27
+ /** Fixed (non-parameterized) action ids. */
28
+ export type HarnessFixedActionSlot =
29
+ | "discover"
30
+ | "design"
31
+ | "plan"
32
+ | "implement"
33
+ | "validate"
34
+ | "gc.fix"
35
+ | "review.architecture";
36
+
37
+ interface HarnessActionRegistration {
38
+ id: string;
39
+ label: string;
40
+ harnessRoleHint: string;
41
+ }
42
+
43
+ const FIXED_REGISTRATIONS: readonly HarnessActionRegistration[] = Object.freeze([
44
+ { id: `${HARNESS_ACTION_NAMESPACE}.discover`, label: "Harness · discover", harnessRoleHint: "research" },
45
+ { id: `${HARNESS_ACTION_NAMESPACE}.design`, label: "Harness · design", harnessRoleHint: "plan" },
46
+ { id: `${HARNESS_ACTION_NAMESPACE}.plan`, label: "Harness · plan", harnessRoleHint: "plan" },
47
+ { id: `${HARNESS_ACTION_NAMESPACE}.implement`, label: "Harness · implement", harnessRoleHint: "default" },
48
+ { id: `${HARNESS_ACTION_NAMESPACE}.validate`, label: "Harness · validate", harnessRoleHint: "review" },
49
+ { id: `${HARNESS_ACTION_NAMESPACE}.gc.fix`, label: "Harness · GC fix", harnessRoleHint: "default" },
50
+ { id: `${HARNESS_ACTION_NAMESPACE}.review.architecture`, label: "Harness · architecture review", harnessRoleHint: "review" },
51
+ ]);
52
+
53
+ /** Action ids exposed for tooling and tests (frozen at module load). */
54
+ export const HARNESS_FIXED_ACTION_IDS: readonly string[] = Object.freeze(
55
+ FIXED_REGISTRATIONS.map((r) => r.id),
56
+ );
57
+
58
+ /** Returns the canonical action id for a fixed slot. */
59
+ export function getHarnessActionId(slot: HarnessFixedActionSlot): string {
60
+ return `${HARNESS_ACTION_NAMESPACE}.${slot}`;
61
+ }
62
+
63
+ /** Returns the action id for a research topic. Topic slug is sanitized by the caller. */
64
+ export function getHarnessResearchActionId(topicSlug: string): string {
65
+ return `${HARNESS_ACTION_NAMESPACE}.research.${topicSlug}`;
66
+ }
67
+
68
+ /** Resolve the model + thinking level for a fixed harness action slot. */
69
+ export function resolveHarnessModel(
70
+ slot: HarnessFixedActionSlot,
71
+ config: ModelConfig,
72
+ registry: ModelActionRegistry,
73
+ platform: ModelPlatformBridge,
74
+ ): ResolvedModel {
75
+ return resolveModelForAction(getHarnessActionId(slot), registry, config, platform);
76
+ }
77
+
78
+ /** Resolve the model + thinking level for a research topic action. Registers lazily. */
79
+ export function resolveHarnessResearchModel(
80
+ topicSlug: string,
81
+ config: ModelConfig,
82
+ registry: ModelActionRegistry,
83
+ platform: ModelPlatformBridge,
84
+ ): ResolvedModel {
85
+ const id = getHarnessResearchActionId(topicSlug);
86
+ // Registration is idempotent at our boundary — the registry rejects duplicate ids by
87
+ // design, so we wrap in try/catch to keep lazy registration safe across multiple calls
88
+ // in the same process.
89
+ try {
90
+ registry.register({
91
+ id,
92
+ category: "sub-agent",
93
+ parent: "harness",
94
+ label: `Harness · research (${topicSlug})`,
95
+ harnessRoleHint: "research",
96
+ });
97
+ } catch {
98
+ // Already registered — fine.
99
+ }
100
+ return resolveModelForAction(id, registry, config, platform);
101
+ }
102
+
103
+ // ---------------------------------------------------------------------------
104
+ // Module-load registration. Mirrors the ultraplan/authoring/model.ts pattern: every
105
+ // `import "../model.js"` of this file ensures the harness action ids are visible to the
106
+ // shared registry. Cheap and idempotent.
107
+ // ---------------------------------------------------------------------------
108
+
109
+ for (const action of FIXED_REGISTRATIONS) {
110
+ modelRegistry.register({
111
+ id: action.id,
112
+ category: "sub-agent",
113
+ parent: "harness",
114
+ label: action.label,
115
+ harnessRoleHint: action.harnessRoleHint,
116
+ });
117
+ }