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.
- package/README.md +14 -8
- package/bin/install.mjs +20 -5
- package/bin/install.ts +95 -0
- package/package.json +8 -4
- package/skills/context-mode/SKILL.md +17 -10
- package/skills/harness/SKILL.md +94 -0
- package/skills/ui-design/SKILL.md +63 -0
- package/skills/ui-design/sub-agent-templates/component-builder.md +29 -0
- package/skills/ui-design/sub-agent-templates/design-critic.md +46 -0
- package/skills/ui-design/sub-agent-templates/pencil/component-builder.md +29 -0
- package/skills/ui-design/sub-agent-templates/pencil/design-critic.md +42 -0
- package/skills/ui-design/sub-agent-templates/pencil/section-assembler.md +27 -0
- package/skills/ui-design/sub-agent-templates/section-assembler.md +27 -0
- package/skills/ultraplan-discover/SKILL.md +96 -0
- package/skills/ultraplan-intake/SKILL.md +89 -0
- package/skills/ultraplan-research/SKILL.md +129 -0
- package/skills/ultraplan-review/SKILL.md +86 -0
- package/skills/ultraplan-review-scope/SKILL.md +111 -0
- package/skills/ultraplan-review-structure/SKILL.md +120 -0
- package/skills/ultraplan-review-tdd/SKILL.md +142 -0
- package/skills/ultraplan-scout/SKILL.md +110 -0
- package/skills/ultraplan-synthesize/SKILL.md +124 -0
- package/src/{quality/ai-session.ts → ai/final-message.ts} +27 -0
- package/src/ai/schema-text.ts +129 -0
- package/src/ai/structured-output.ts +274 -0
- package/src/ai/template.ts +27 -0
- package/src/bootstrap.ts +63 -28
- package/src/commands/agents.ts +149 -45
- package/src/commands/ai-review.ts +251 -30
- package/src/commands/clear.ts +434 -0
- package/src/commands/commit.ts +1 -0
- package/src/commands/config.ts +242 -44
- package/src/commands/context.ts +55 -28
- package/src/commands/doctor.ts +234 -6
- package/src/commands/fix-pr.ts +306 -131
- package/src/commands/generate.ts +111 -21
- package/src/commands/memory.ts +192 -0
- package/src/commands/model-picker.ts +28 -21
- package/src/commands/model.ts +19 -9
- package/src/commands/optimize-context.ts +408 -29
- package/src/commands/plan.ts +2 -0
- package/src/commands/qa.ts +312 -137
- package/src/commands/release.ts +259 -76
- package/src/commands/review.ts +293 -59
- package/src/commands/status.ts +200 -13
- package/src/commands/supi.ts +3 -35
- package/src/commands/ui-design.ts +394 -0
- package/src/commands/ultraplan.ts +1518 -0
- package/src/commands/update.ts +86 -0
- package/src/config/defaults.ts +62 -0
- package/src/config/loader.ts +448 -60
- package/src/config/schema.ts +108 -2
- package/src/context/optimizer.ts +25 -33
- package/src/context/rule-renderer.ts +223 -0
- package/src/context/savings.ts +258 -0
- package/src/context/startup-check.ts +380 -0
- package/src/context/startup-optimizer.ts +355 -0
- package/src/context/tokenignore.ts +146 -0
- package/src/context-mode/cache-handle.ts +49 -0
- package/src/context-mode/cache-preview.ts +71 -0
- package/src/context-mode/cache-store.ts +738 -0
- package/src/context-mode/compressor.ts +131 -26
- package/src/context-mode/dedup.ts +108 -0
- package/src/context-mode/detector.ts +35 -4
- package/src/context-mode/event-extractor.ts +14 -12
- package/src/context-mode/event-store.ts +91 -36
- package/src/context-mode/hooks.ts +798 -56
- package/src/context-mode/knowledge/store.ts +255 -11
- package/src/context-mode/memory-store.ts +325 -0
- package/src/context-mode/metrics-recorder.ts +158 -0
- package/src/context-mode/metrics-store.ts +765 -0
- package/src/context-mode/model.ts +24 -0
- package/src/context-mode/processor-keys.ts +29 -0
- package/src/context-mode/processors/build.ts +66 -0
- package/src/context-mode/processors/docker.ts +57 -0
- package/src/context-mode/processors/git.ts +111 -0
- package/src/context-mode/processors/json.ts +112 -0
- package/src/context-mode/processors/k8s.ts +67 -0
- package/src/context-mode/processors/lint.ts +67 -0
- package/src/context-mode/processors/log.ts +86 -0
- package/src/context-mode/processors/registry.ts +116 -0
- package/src/context-mode/processors/test-runner.ts +102 -0
- package/src/context-mode/processors/types.ts +20 -0
- package/src/context-mode/repomap.ts +400 -0
- package/src/context-mode/routing.ts +97 -24
- package/src/context-mode/sandbox/runners.ts +5 -1
- package/src/context-mode/snapshot-builder.ts +106 -11
- package/src/context-mode/source-hash.ts +173 -0
- package/src/context-mode/tool-name.ts +11 -0
- package/src/context-mode/tools.ts +654 -22
- package/src/context-mode/web/fetcher.ts +31 -12
- package/src/debug/logger.ts +2 -1
- package/src/deps/registry.ts +1 -1
- package/src/discipline/failure-summarizer.ts +170 -0
- package/src/discipline/failure-taxonomy.ts +131 -0
- package/src/discipline/workflow-invariants.ts +125 -0
- package/src/discovery/index.ts +31 -0
- package/src/discovery/lsp.ts +87 -0
- package/src/discovery/rank.ts +144 -0
- package/src/discovery/sources.ts +89 -0
- package/src/discovery/workflow.ts +87 -0
- package/src/docs/contracts.ts +39 -0
- package/src/docs/drift.ts +117 -87
- package/src/fix-pr/assessment.ts +200 -0
- package/src/fix-pr/contracts.ts +47 -0
- package/src/fix-pr/fetch-comments.ts +80 -0
- package/src/fix-pr/prompt-builder.ts +58 -40
- package/src/fix-pr/scripts/exec.ts +34 -0
- package/src/fix-pr/scripts/trigger-review.ts +106 -0
- package/src/fix-pr/scripts/wait-and-check.ts +108 -0
- package/src/fix-pr/types.ts +4 -0
- package/src/git/branch-finish.ts +5 -0
- package/src/git/commit-contract.ts +83 -0
- package/src/git/commit.ts +121 -184
- package/src/git/status.ts +62 -8
- package/src/harness/anti_slop/architecture-parser.ts +210 -0
- package/src/harness/anti_slop/backend-factory.ts +30 -0
- package/src/harness/anti_slop/backend.ts +140 -0
- package/src/harness/anti_slop/desloppify-adapter.ts +319 -0
- package/src/harness/anti_slop/fallow-adapter.ts +305 -0
- package/src/harness/anti_slop/installer.ts +227 -0
- package/src/harness/anti_slop/queue.ts +216 -0
- package/src/harness/anti_slop/recommend.ts +84 -0
- package/src/harness/anti_slop/score.ts +180 -0
- package/src/harness/anti_slop/synthetic-edit-test.ts +128 -0
- package/src/harness/artifacts/agents-md.ts +88 -0
- package/src/harness/artifacts/checks-wiring.ts +57 -0
- package/src/harness/artifacts/docs-tree.ts +79 -0
- package/src/harness/artifacts/lint-configs.ts +136 -0
- package/src/harness/artifacts/review-agents.ts +67 -0
- package/src/harness/bare-entry.ts +108 -0
- package/src/harness/command.ts +1010 -0
- package/src/harness/default-agents/design.md +23 -0
- package/src/harness/default-agents/discover.md +18 -0
- package/src/harness/default-agents/implement.md +24 -0
- package/src/harness/default-agents/plan.md +19 -0
- package/src/harness/default-agents/research.md +21 -0
- package/src/harness/default-agents/validate.md +22 -0
- package/src/harness/gc/reporter.ts +28 -0
- package/src/harness/gc/runner.ts +136 -0
- package/src/harness/hooks/layer-context-inject.ts +155 -0
- package/src/harness/hooks/post-session-sweep.ts +130 -0
- package/src/harness/hooks/pre-edit-dupe-probe.ts +224 -0
- package/src/harness/hooks/register.ts +118 -0
- package/src/harness/model.ts +117 -0
- package/src/harness/pipeline.ts +348 -0
- package/src/harness/project-paths.ts +235 -0
- package/src/harness/stage-runner.ts +107 -0
- package/src/harness/stages/design.ts +386 -0
- package/src/harness/stages/discover.ts +454 -0
- package/src/harness/stages/implement.ts +162 -0
- package/src/harness/stages/plan.ts +335 -0
- package/src/harness/stages/research.ts +263 -0
- package/src/harness/stages/validate.ts +684 -0
- package/src/harness/storage.ts +467 -0
- package/src/harness/tools.ts +426 -0
- package/src/lsp/bridge.ts +56 -95
- package/src/lsp/capabilities.ts +108 -0
- package/src/lsp/contracts.ts +35 -0
- package/src/lsp/detector.ts +8 -12
- package/src/markdown-frontmatter.ts +68 -0
- package/src/mempalace/bridge.ts +129 -0
- package/src/mempalace/config.ts +75 -0
- package/src/mempalace/format.ts +163 -0
- package/src/mempalace/hooks.ts +370 -0
- package/src/mempalace/installer-helper.ts +194 -0
- package/src/mempalace/python/mempalace_bridge.py +440 -0
- package/src/mempalace/runtime.ts +565 -0
- package/src/mempalace/schema.ts +264 -0
- package/src/mempalace/session-summary.ts +198 -0
- package/src/mempalace/tool.ts +186 -0
- package/src/mempalace/uv.ts +256 -0
- package/src/migrate/runner.ts +354 -0
- package/src/planning/approval-flow.ts +206 -9
- package/src/planning/plan-writer-prompt.ts +4 -3
- package/src/planning/planning-ask-tool.ts +39 -0
- package/src/planning/render-markdown.ts +74 -0
- package/src/planning/spec.ts +42 -0
- package/src/planning/system-prompt.ts +11 -8
- package/src/planning/validate.ts +84 -0
- package/src/platform/omp.ts +15 -2
- package/src/platform/system-prompt.ts +37 -0
- package/src/platform/test-utils.ts +3 -0
- package/src/platform/types.ts +6 -1
- package/src/qa/config.ts +12 -6
- package/src/qa/detect-app-type.ts +13 -6
- package/src/qa/matrix.ts +12 -6
- package/src/qa/prompt-builder.ts +28 -30
- package/src/qa/scripts/dev-server-utils.ts +72 -0
- package/src/qa/scripts/run-e2e-tests.ts +226 -0
- package/src/qa/scripts/start-dev-server.ts +138 -0
- package/src/qa/scripts/stop-dev-server.ts +77 -0
- package/src/qa/session.ts +13 -7
- package/src/quality/ai-setup.ts +27 -25
- package/src/quality/contracts.ts +34 -0
- package/src/quality/gates/ai-review.ts +20 -58
- package/src/quality/gates/command.ts +249 -46
- package/src/quality/review-gates.ts +18 -2
- package/src/quality/runner.ts +63 -22
- package/src/quality/schemas.ts +37 -2
- package/src/quality/setup.ts +96 -16
- package/src/release/changelog.ts +1 -1
- package/src/release/channels/custom.ts +13 -3
- package/src/release/channels/types.ts +5 -0
- package/src/release/contracts.ts +90 -0
- package/src/release/executor.ts +122 -45
- package/src/release/prompt.ts +18 -2
- package/src/release/targets.ts +86 -0
- package/src/release/version.ts +96 -71
- package/src/review/agent-loader.ts +298 -127
- package/src/review/fixer.ts +10 -6
- package/src/review/multi-agent-runner.ts +115 -14
- package/src/review/output.ts +12 -139
- package/src/review/runner.ts +12 -6
- package/src/review/scope.ts +144 -24
- package/src/review/types.ts +11 -20
- package/src/review/validator.ts +12 -6
- package/src/storage/fix-pr-sessions.ts +21 -14
- package/src/storage/plans.ts +14 -5
- package/src/storage/qa-sessions.ts +25 -19
- package/src/storage/reliability-metrics.ts +180 -0
- package/src/storage/reports.ts +8 -7
- package/src/storage/review-sessions.ts +55 -20
- package/src/tool-catalog/active-tool-controller.ts +164 -0
- package/src/tool-catalog/active-tool-planner.ts +212 -0
- package/src/tool-catalog/tool-groups.ts +102 -0
- package/src/types.ts +1401 -5
- package/src/ui-design/backend-adapter.ts +78 -0
- package/src/ui-design/backends/local-html.ts +82 -0
- package/src/ui-design/backends/pencil-mcp.ts +111 -0
- package/src/ui-design/components-scanner.ts +124 -0
- package/src/ui-design/config.ts +55 -0
- package/src/ui-design/pen-scanner.ts +95 -0
- package/src/ui-design/pen-selector.ts +72 -0
- package/src/ui-design/prompt-builder.ts +73 -0
- package/src/ui-design/scanner.ts +136 -0
- package/src/ui-design/session.ts +974 -0
- package/src/ui-design/system-prompt.ts +312 -0
- package/src/ui-design/tokens-scanner.ts +181 -0
- package/src/ui-design/types.ts +96 -0
- package/src/ultraplan/agent-catalog.ts +522 -0
- package/src/ultraplan/authoring/agent-catalog.ts +310 -0
- package/src/ultraplan/authoring/authoring-tools.ts +552 -0
- package/src/ultraplan/authoring/command-handlers.ts +339 -0
- package/src/ultraplan/authoring/markdown.ts +510 -0
- package/src/ultraplan/authoring/model.ts +162 -0
- package/src/ultraplan/authoring/pipeline.ts +319 -0
- package/src/ultraplan/authoring/stage-runner.ts +141 -0
- package/src/ultraplan/authoring/stages/approve.ts +249 -0
- package/src/ultraplan/authoring/stages/discover.ts +289 -0
- package/src/ultraplan/authoring/stages/intake.ts +203 -0
- package/src/ultraplan/authoring/stages/research.ts +399 -0
- package/src/ultraplan/authoring/stages/review.ts +333 -0
- package/src/ultraplan/authoring/stages/scout.ts +188 -0
- package/src/ultraplan/authoring/stages/synthesize.ts +348 -0
- package/src/ultraplan/authoring/storage.ts +594 -0
- package/src/ultraplan/authoring/synth-gate.ts +165 -0
- package/src/ultraplan/authoring-draft.ts +653 -0
- package/src/ultraplan/authoring-persist.ts +180 -0
- package/src/ultraplan/authoring-tool.ts +608 -0
- package/src/ultraplan/authoring-wizard.ts +587 -0
- package/src/ultraplan/batch/merge.ts +98 -0
- package/src/ultraplan/batch/planner.ts +150 -0
- package/src/ultraplan/batch/presenter.ts +97 -0
- package/src/ultraplan/batch/storage.ts +420 -0
- package/src/ultraplan/batch/supervisor.ts +317 -0
- package/src/ultraplan/batch/worker.ts +26 -0
- package/src/ultraplan/batch/worktree.ts +110 -0
- package/src/ultraplan/contracts.ts +1593 -0
- package/src/ultraplan/default-agents/authoring/discoverer.md +12 -0
- package/src/ultraplan/default-agents/authoring/intake.md +12 -0
- package/src/ultraplan/default-agents/authoring/planner.md +12 -0
- package/src/ultraplan/default-agents/authoring/researcher.md +12 -0
- package/src/ultraplan/default-agents/authoring/scope-checker.md +12 -0
- package/src/ultraplan/default-agents/authoring/scout.md +12 -0
- package/src/ultraplan/default-agents/authoring/structure-checker.md +12 -0
- package/src/ultraplan/default-agents/authoring/tdd-checker.md +12 -0
- package/src/ultraplan/default-agents/backend-domain-reviewer.md +10 -0
- package/src/ultraplan/default-agents/backend-executor.md +10 -0
- package/src/ultraplan/default-agents/backend-stack-reviewer.md +10 -0
- package/src/ultraplan/default-agents/backend-tester.md +10 -0
- package/src/ultraplan/default-agents/frontend-domain-reviewer.md +10 -0
- package/src/ultraplan/default-agents/frontend-executor.md +10 -0
- package/src/ultraplan/default-agents/frontend-stack-reviewer.md +10 -0
- package/src/ultraplan/default-agents/frontend-tester.md +10 -0
- package/src/ultraplan/default-agents/infrastructure-domain-reviewer.md +10 -0
- package/src/ultraplan/default-agents/infrastructure-executor.md +10 -0
- package/src/ultraplan/default-agents/infrastructure-stack-reviewer.md +10 -0
- package/src/ultraplan/default-agents/infrastructure-tester.md +10 -0
- package/src/ultraplan/execution/contract.ts +71 -0
- package/src/ultraplan/execution/policy.ts +217 -0
- package/src/ultraplan/execution/runtime-tools.ts +107 -0
- package/src/ultraplan/execution/session-runner.ts +281 -0
- package/src/ultraplan/next-router.ts +85 -0
- package/src/ultraplan/presenter.ts +359 -0
- package/src/ultraplan/project-paths.ts +342 -0
- package/src/ultraplan/runtime/active-execution.ts +72 -0
- package/src/ultraplan/runtime/apply-mutation.ts +416 -0
- package/src/ultraplan/runtime/blockers.ts +243 -0
- package/src/ultraplan/runtime/hook-bridge.ts +486 -0
- package/src/ultraplan/runtime/launch-context.ts +207 -0
- package/src/ultraplan/runtime/migration.ts +524 -0
- package/src/ultraplan/runtime/normalize.ts +281 -0
- package/src/ultraplan/runtime/proof.ts +260 -0
- package/src/ultraplan/runtime/reducer.ts +416 -0
- package/src/ultraplan/runtime/repair.ts +251 -0
- package/src/ultraplan/runtime/tracker-storage.ts +368 -0
- package/src/ultraplan/session-selection.ts +291 -0
- package/src/ultraplan/storage.ts +374 -0
- package/src/utils/editor.ts +38 -0
- package/src/utils/executable.ts +80 -0
- package/src/utils/paths.ts +1 -20
- package/src/utils/shell.ts +31 -0
- package/src/visual/companion.ts +2 -1
- package/src/visual/scripts/frame-template.html +60 -0
- package/src/visual/scripts/index.js +59 -13
- package/src/visual/scripts/package.json +3 -0
- package/src/visual/start-server.ts +2 -1
- package/src/workspace/git-scope.ts +64 -0
- package/src/workspace/locks.ts +23 -0
- package/src/workspace/package-manager.ts +117 -0
- package/src/workspace/path-mapping.ts +75 -0
- package/src/workspace/project-slug.ts +92 -0
- package/src/workspace/repo-root.ts +137 -0
- package/src/workspace/selector.ts +115 -0
- package/src/workspace/state-paths.ts +118 -0
- package/src/workspace/targets.ts +313 -0
- package/src/fix-pr/scripts/diff-comments.sh +0 -33
- package/src/fix-pr/scripts/fetch-pr-comments.sh +0 -25
- package/src/fix-pr/scripts/trigger-review.sh +0 -36
- package/src/fix-pr/scripts/wait-and-check.sh +0 -37
- package/src/qa/scripts/detect-app-type.sh +0 -68
- package/src/qa/scripts/discover-routes.sh +0 -143
- package/src/qa/scripts/run-e2e-tests.sh +0 -131
- package/src/qa/scripts/start-dev-server.sh +0 -46
- package/src/qa/scripts/stop-dev-server.sh +0 -36
- package/src/review/prompts/fix-output-schema.md +0 -18
- package/src/review/prompts/review-output-schema.md +0 -38
- package/src/review/template.ts +0 -15
- /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
|
-
|
|
23
|
-
|
|
24
|
-
.
|
|
25
|
-
|
|
26
|
-
|
|
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
|
+
}
|