supipowers 1.5.2 → 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 +149 -45
  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 +19 -9
  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 +298 -127
  211. package/src/review/fixer.ts +10 -6
  212. package/src/review/multi-agent-runner.ts +115 -14
  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 +11 -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 +1401 -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
package/src/git/status.ts CHANGED
@@ -1,12 +1,45 @@
1
1
  import { normalizeLineEndings } from "../text.js";
2
2
 
3
-
4
3
  type ExecFn = (cmd: string, args: string[], opts?: { cwd?: string }) => Promise<{ stdout: string; code: number }>;
5
4
 
6
5
  export interface WorkingTreeStatus {
7
6
  dirty: boolean;
8
7
  /** Files with uncommitted changes (staged + unstaged + untracked) */
9
8
  files: string[];
9
+ /** Files currently staged in the index. */
10
+ stagedFiles: string[];
11
+ /** Files with unstaged or untracked changes. */
12
+ unstagedFiles: string[];
13
+ }
14
+
15
+ interface ParsedPorcelainEntry {
16
+ path: string;
17
+ staged: boolean;
18
+ unstaged: boolean;
19
+ }
20
+
21
+ function parsePorcelainPath(rawPath: string): string {
22
+ const renameSegments = rawPath.split(" -> ");
23
+ return renameSegments[renameSegments.length - 1]?.trim() ?? rawPath.trim();
24
+ }
25
+
26
+ function parsePorcelainLine(line: string): ParsedPorcelainEntry | null {
27
+ if (line.length < 4) {
28
+ return null;
29
+ }
30
+
31
+ const indexStatus = line[0] ?? " ";
32
+ const worktreeStatus = line[1] ?? " ";
33
+ const rawPath = line.slice(3).trim();
34
+ if (!rawPath) {
35
+ return null;
36
+ }
37
+
38
+ return {
39
+ path: parsePorcelainPath(rawPath),
40
+ staged: indexStatus !== " " && indexStatus !== "?",
41
+ unstaged: worktreeStatus !== " " || indexStatus === "?",
42
+ };
10
43
  }
11
44
 
12
45
  /**
@@ -17,14 +50,35 @@ export async function getWorkingTreeStatus(exec: ExecFn, cwd: string): Promise<W
17
50
  try {
18
51
  const result = await exec("git", ["status", "--porcelain"], { cwd });
19
52
  if (result.code !== 0) {
20
- return { dirty: false, files: [] };
53
+ return { dirty: false, files: [], stagedFiles: [], unstagedFiles: [] };
54
+ }
55
+
56
+ const files: string[] = [];
57
+ const stagedFiles: string[] = [];
58
+ const unstagedFiles: string[] = [];
59
+
60
+ for (const line of normalizeLineEndings(result.stdout).split("\n").filter(Boolean)) {
61
+ const entry = parsePorcelainLine(line);
62
+ if (!entry) {
63
+ continue;
64
+ }
65
+
66
+ files.push(entry.path);
67
+ if (entry.staged) {
68
+ stagedFiles.push(entry.path);
69
+ }
70
+ if (entry.unstaged) {
71
+ unstagedFiles.push(entry.path);
72
+ }
21
73
  }
22
- const files = normalizeLineEndings(result.stdout)
23
- .split("\n")
24
- .filter(Boolean)
25
- .map((line) => line.slice(3)); // strip 2-char status + space
26
- return { dirty: files.length > 0, files };
74
+
75
+ return {
76
+ dirty: files.length > 0,
77
+ files,
78
+ stagedFiles,
79
+ unstagedFiles,
80
+ };
27
81
  } catch {
28
- return { dirty: false, files: [] };
82
+ return { dirty: false, files: [], stagedFiles: [], unstagedFiles: [] };
29
83
  }
30
84
  }
@@ -0,0 +1,210 @@
1
+ /**
2
+ * Parse `docs/architecture.md` into a layer table.
3
+ *
4
+ * Convention: the architecture doc contains a single GFM-style table with the columns
5
+ * `Layer`, `Files`, `Allowed`, `Forbidden`, optionally followed by a `Description` column.
6
+ * `Files` and `Allowed`/`Forbidden` cells use comma-separated values; backticks around
7
+ * globs are stripped.
8
+ *
9
+ * Example:
10
+ * ```
11
+ * | Layer | Files | Allowed | Forbidden |
12
+ * |----------|----------------------|--------------------------|--------------------|
13
+ * | domain | `src/domain/**` | domain | infra, ui |
14
+ * | infra | `src/infrastructure/**` | domain, infra | ui |
15
+ * | ui | `src/ui/**` | domain, infra, ui | — |
16
+ * ```
17
+ *
18
+ * Empty cells (`—`, `-`, blank) are treated as the empty list.
19
+ *
20
+ * The parser is deliberately permissive: malformed tables produce `[]`, not an error. The
21
+ * layer-context-inject hook degrades gracefully when no rules are parsed.
22
+ */
23
+
24
+ import type { HarnessLayerRule } from "../../types.js";
25
+
26
+ interface Row {
27
+ layer: string;
28
+ files: string[];
29
+ allowed: string[];
30
+ forbidden: string[];
31
+ description?: string;
32
+ }
33
+
34
+ const LAYER_HEADERS = ["layer"];
35
+ const FILE_HEADERS = ["files", "globs", "paths"];
36
+ const ALLOWED_HEADERS = ["allowed", "allowedimports", "imports"];
37
+ const FORBIDDEN_HEADERS = ["forbidden", "forbiddenimports", "denied"];
38
+ const DESCRIPTION_HEADERS = ["description", "notes"];
39
+
40
+ function normalizeHeader(value: string): string {
41
+ return value.toLowerCase().replace(/[^a-z]/g, "");
42
+ }
43
+
44
+ function parseListCell(cell: string): string[] {
45
+ const stripped = cell.trim();
46
+ if (!stripped || stripped === "—" || stripped === "-") return [];
47
+ return stripped
48
+ .split(",")
49
+ .map((part) => part.replace(/`/g, "").trim())
50
+ .filter((part) => part.length > 0);
51
+ }
52
+
53
+ function isSeparatorRow(cells: string[]): boolean {
54
+ // GFM table separators look like `|---|---|---|` — every cell is dashes (with optional
55
+ // colons for alignment).
56
+ return cells.every((cell) => /^[-:\s]+$/.test(cell));
57
+ }
58
+
59
+ function splitRow(line: string): string[] {
60
+ // Strip leading/trailing pipe, split on `|`, trim each cell.
61
+ const trimmed = line.trim();
62
+ const inner = trimmed.replace(/^\||\|$/g, "");
63
+ return inner.split("|").map((c) => c.trim());
64
+ }
65
+
66
+ /**
67
+ * Walk markdown line-by-line and extract every well-formed table. Returns rows from the
68
+ * FIRST table whose header includes a `Layer` column. Subsequent tables are ignored —
69
+ * the architecture doc convention is one canonical layer table.
70
+ */
71
+ function findLayerTable(markdown: string): Row[] {
72
+ const lines = markdown.split(/\r?\n/);
73
+ let inTable = false;
74
+ let columnMap: { layer?: number; files?: number; allowed?: number; forbidden?: number; description?: number } | null = null;
75
+ const rows: Row[] = [];
76
+
77
+ for (let i = 0; i < lines.length; i += 1) {
78
+ const line = lines[i];
79
+ const isPipeLine = line.includes("|");
80
+
81
+ if (!inTable) {
82
+ if (!isPipeLine) continue;
83
+ const cells = splitRow(line);
84
+ const next = i + 1 < lines.length ? lines[i + 1] : "";
85
+ if (!next.includes("|")) continue;
86
+ if (!isSeparatorRow(splitRow(next))) continue;
87
+
88
+ // Header row.
89
+ const map: { layer?: number; files?: number; allowed?: number; forbidden?: number; description?: number } = {};
90
+ for (let c = 0; c < cells.length; c += 1) {
91
+ const header = normalizeHeader(cells[c]);
92
+ if (LAYER_HEADERS.includes(header)) map.layer = c;
93
+ else if (FILE_HEADERS.includes(header)) map.files = c;
94
+ else if (ALLOWED_HEADERS.includes(header)) map.allowed = c;
95
+ else if (FORBIDDEN_HEADERS.includes(header)) map.forbidden = c;
96
+ else if (DESCRIPTION_HEADERS.includes(header)) map.description = c;
97
+ }
98
+
99
+ // Reject tables that are missing the essential columns.
100
+ if (map.layer === undefined || map.files === undefined) continue;
101
+ if (map.allowed === undefined && map.forbidden === undefined) continue;
102
+
103
+ columnMap = map;
104
+ inTable = true;
105
+ i += 1; // skip the separator on next iteration
106
+ continue;
107
+ }
108
+
109
+ if (!isPipeLine) {
110
+ // Blank line or non-table content terminates the table.
111
+ break;
112
+ }
113
+
114
+ const cells = splitRow(line);
115
+ if (isSeparatorRow(cells)) continue;
116
+ if (!columnMap) continue;
117
+
118
+ const layer = cells[columnMap.layer ?? 0]?.trim();
119
+ if (!layer) continue;
120
+
121
+ rows.push({
122
+ layer,
123
+ files: parseListCell(cells[columnMap.files ?? 0] ?? ""),
124
+ allowed: columnMap.allowed !== undefined ? parseListCell(cells[columnMap.allowed] ?? "") : [],
125
+ forbidden: columnMap.forbidden !== undefined ? parseListCell(cells[columnMap.forbidden] ?? "") : [],
126
+ description:
127
+ columnMap.description !== undefined && cells[columnMap.description]?.trim()
128
+ ? cells[columnMap.description].trim()
129
+ : undefined,
130
+ });
131
+ }
132
+
133
+ return rows;
134
+ }
135
+
136
+ /**
137
+ * Parse architecture markdown into HarnessLayerRule entries. Returns `[]` when no
138
+ * recognizable layer table is present.
139
+ */
140
+ export function parseArchitectureMarkdown(markdown: string): HarnessLayerRule[] {
141
+ const rows = findLayerTable(markdown);
142
+ return rows.map((row) => ({
143
+ layer: row.layer,
144
+ globs: row.files,
145
+ allowedImports: row.allowed,
146
+ forbiddenImports: row.forbidden,
147
+ ...(row.description ? { description: row.description } : {}),
148
+ }));
149
+ }
150
+
151
+ /**
152
+ * Resolve the layer for a given file path against parsed rules. Glob matching is naive:
153
+ * we strip ``**`` and trailing slashes and check `startsWith` after normalization. This
154
+ * handles the convention `src/<layer>/**` without dragging in a glob library.
155
+ */
156
+ export function resolveLayerForFile(
157
+ filePath: string,
158
+ rules: readonly HarnessLayerRule[],
159
+ ): HarnessLayerRule | null {
160
+ const normalized = filePath.replace(/\\/g, "/");
161
+ for (const rule of rules) {
162
+ for (const glob of rule.globs) {
163
+ if (matchesGlob(normalized, glob)) return rule;
164
+ }
165
+ }
166
+ return null;
167
+ }
168
+
169
+ /**
170
+ * Naive glob matcher tuned for the conventions parsed from architecture tables. Supports
171
+ * `**` (any path segments) and `*` (any single segment characters). Sufficient for the
172
+ * `src/<layer>/**` and `packages/<scope>/**\/*.ts` shapes the doc relies on.
173
+ */
174
+ function matchesGlob(filePath: string, glob: string): boolean {
175
+ const normalizedGlob = glob.replace(/\\/g, "/");
176
+ const regexSrc = normalizedGlob
177
+ .split(/(\*\*|\*)/g)
178
+ .map((segment) => {
179
+ if (segment === "**") return ".*";
180
+ if (segment === "*") return "[^/]*";
181
+ return segment.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
182
+ })
183
+ .join("");
184
+ const regex = new RegExp(`^${regexSrc}$`);
185
+ return regex.test(filePath);
186
+ }
187
+
188
+ /**
189
+ * Build the addendum string injected by the layer-context-inject hook. Caps at the
190
+ * configured `addendum_max_chars` to bound prompt growth; truncation appends `…` so the
191
+ * agent sees the cut.
192
+ */
193
+ export function buildLayerAddendum(
194
+ filePath: string,
195
+ rule: HarnessLayerRule,
196
+ maxChars: number,
197
+ ): string {
198
+ const allowed = rule.allowedImports.length > 0 ? rule.allowedImports.join(", ") : "(none)";
199
+ const forbidden = rule.forbiddenImports.length > 0 ? rule.forbiddenImports.join(", ") : "(none)";
200
+ const lines = [
201
+ `# Architecture context (from docs/architecture.md)`,
202
+ `You are editing \`${filePath}\` in the \`${rule.layer}\` layer.`,
203
+ `- Permitted imports: ${allowed}`,
204
+ `- Forbidden imports: ${forbidden}`,
205
+ ];
206
+ if (rule.description) lines.push(`- Notes: ${rule.description}`);
207
+ const text = lines.join("\n");
208
+ if (text.length <= maxChars) return text;
209
+ return `${text.slice(0, Math.max(0, maxChars - 1))}…`;
210
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Build a `SlopBackend` adapter from the user-selected backend choice.
3
+ *
4
+ * Lives here (in `anti_slop/`) so both `hooks/register.ts` and the per-stage CLI
5
+ * subcommands in `command.ts` can call it without going through `register.ts`. Keeping
6
+ * it in `register.ts` would create a circular import once the validate subcommand
7
+ * needs the adapter.
8
+ */
9
+
10
+ import type { HarnessAntiSlopBackend } from "../../types.js";
11
+ import type { SlopBackend } from "./backend.js";
12
+ import { DesloppifyAdapter } from "./desloppify-adapter.js";
13
+ import { FallowAdapter } from "./fallow-adapter.js";
14
+
15
+ export function buildBackendAdapter(
16
+ backend: HarnessAntiSlopBackend,
17
+ ): SlopBackend | null {
18
+ switch (backend) {
19
+ case "fallow":
20
+ return new FallowAdapter();
21
+ case "desloppify":
22
+ return new DesloppifyAdapter();
23
+ case "hybrid":
24
+ // Hybrid prefers fallow for TS subtrees; the hook adapter is a single instance, so
25
+ // we default to fallow and let GC fan out to desloppify for non-TS.
26
+ return new FallowAdapter();
27
+ case "supi-native":
28
+ return null;
29
+ }
30
+ }
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Anti-slop backend abstraction.
3
+ *
4
+ * Stages (Discover, Validate, GC) and runtime hooks (pre-edit dupe probe, post-session
5
+ * sweep) consume slop scans through this interface. Concrete adapters wrap external CLIs
6
+ * (`fallow`, `desloppify`) or implement supi-native scanning.
7
+ *
8
+ * Adapter contract:
9
+ * - `scan` returns the union of duplicate, dead-code, and other findings;
10
+ * - `dupes` is a focused near-duplicate scan (used by the pre-edit probe);
11
+ * - `deadCode` is a focused dead-export scan (used by post-session sweep);
12
+ * - `audit` runs the backend's full "everything that's wrong" pass (used by Validate);
13
+ * - `fix` applies the backend's safe auto-fixes (used by GC).
14
+ *
15
+ * All operations are advisory. Adapters MUST handle CLI-not-installed gracefully by
16
+ * returning a `SlopBackendUnavailable` result (never throw) — callers route around the
17
+ * unavailability without aborting the pipeline.
18
+ */
19
+
20
+ import type { Platform } from "../../platform/types.js";
21
+ import type {
22
+ HarnessAntiSlopBackend,
23
+ HarnessSlopQueueEntry,
24
+ } from "../../types.js";
25
+
26
+ // ---------------------------------------------------------------------------
27
+ // Result shapes
28
+ // ---------------------------------------------------------------------------
29
+
30
+ /** A finding produced by a backend scan, before being normalized into a queue entry. */
31
+ export interface SlopFinding {
32
+ kind: HarnessSlopQueueEntry["kind"];
33
+ file: string;
34
+ range: HarnessSlopQueueEntry["range"];
35
+ severity: HarnessSlopQueueEntry["severity"];
36
+ source: HarnessSlopQueueEntry["source"];
37
+ message: string;
38
+ remediation?: string;
39
+ details?: Record<string, unknown>;
40
+ /** Hint to the queue layer: when these findings should cluster, the same key is shared. */
41
+ clusterKey?: string;
42
+ }
43
+
44
+ export interface SlopScanResult {
45
+ ok: true;
46
+ findings: SlopFinding[];
47
+ durationMs: number;
48
+ /** Free-form metadata (CLI version, stats, etc.). */
49
+ details?: Record<string, unknown>;
50
+ }
51
+
52
+ export interface SlopBackendUnavailable {
53
+ ok: false;
54
+ reason: "not-installed" | "version-too-old" | "execution-failed" | "timeout" | "config-missing";
55
+ message: string;
56
+ /** When `execution-failed`, exit code and stderr for diagnostics. */
57
+ exitCode?: number;
58
+ stderr?: string;
59
+ }
60
+
61
+ export type SlopBackendResult = SlopScanResult | SlopBackendUnavailable;
62
+
63
+ // ---------------------------------------------------------------------------
64
+ // Options shapes
65
+ // ---------------------------------------------------------------------------
66
+
67
+ export interface ScanOptions {
68
+ cwd: string;
69
+ /** When set, restricts the scan to the given subtree (relative to cwd). */
70
+ subtree?: string;
71
+ /** When true, only scan files changed since HEAD. */
72
+ changedSinceHead?: boolean;
73
+ /** Hard timeout in ms; the adapter aborts and returns `timeout` past this. */
74
+ timeoutMs?: number;
75
+ }
76
+
77
+ export interface DupesOptions extends ScanOptions {
78
+ /** Minimum similarity threshold (0–1). */
79
+ threshold?: number;
80
+ /** Minimum token count below which results are filtered out. */
81
+ minTokenCount?: number;
82
+ /** When set, only scan files within this list (relative paths). */
83
+ files?: string[];
84
+ /** When set, the proposed content is staged into a shadow copy before scanning. */
85
+ proposedWrite?: { file: string; content: string };
86
+ }
87
+
88
+ export type DeadCodeOptions = ScanOptions;
89
+ export type AuditOptions = ScanOptions;
90
+
91
+ export interface FixOptions extends ScanOptions {
92
+ /** Specific entry ids to fix. */
93
+ entryIds?: string[];
94
+ /** When true, the adapter applies fixes; when false, it dry-runs. */
95
+ apply: boolean;
96
+ }
97
+
98
+ export interface FixResult {
99
+ ok: boolean;
100
+ appliedIds: string[];
101
+ failedIds: { id: string; reason: string }[];
102
+ details?: Record<string, unknown>;
103
+ }
104
+
105
+ // ---------------------------------------------------------------------------
106
+ // Adapter interface
107
+ // ---------------------------------------------------------------------------
108
+
109
+ export interface SlopBackend {
110
+ /** Identifier surfaced to score reports and queue entries. */
111
+ readonly id: HarnessAntiSlopBackend;
112
+
113
+ /** Quick availability check. Adapters cache the result for the lifetime of the process. */
114
+ isAvailable(platform: Platform): Promise<boolean>;
115
+
116
+ /** Full scan: duplicates + dead code + layer + other findings the backend supports. */
117
+ scan(platform: Platform, opts: ScanOptions): Promise<SlopBackendResult>;
118
+
119
+ /** Duplicate-only scan (focused for pre-edit probe). */
120
+ dupes(platform: Platform, opts: DupesOptions): Promise<SlopBackendResult>;
121
+
122
+ /** Dead-code-only scan (focused for post-session sweep). */
123
+ deadCode(platform: Platform, opts: DeadCodeOptions): Promise<SlopBackendResult>;
124
+
125
+ /** Full audit (used by Validate). */
126
+ audit(platform: Platform, opts: AuditOptions): Promise<SlopBackendResult>;
127
+
128
+ /** Apply mechanical auto-fixes. Returns the list of entry ids the adapter handled. */
129
+ fix(platform: Platform, opts: FixOptions): Promise<FixResult>;
130
+ }
131
+
132
+ /**
133
+ * Helper for adapters: clamp a value to a 0..1 range and return the default when undefined.
134
+ */
135
+ export function clampThreshold(value: number | undefined, fallback: number): number {
136
+ if (typeof value !== "number" || !Number.isFinite(value)) return fallback;
137
+ if (value < 0) return 0;
138
+ if (value > 1) return 1;
139
+ return value;
140
+ }