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.
- 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 +131 -42
- 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 +18 -8
- 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 +135 -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 +268 -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 +221 -109
- package/src/review/fixer.ts +10 -6
- package/src/review/multi-agent-runner.ts +114 -13
- 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 +1 -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 +1399 -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
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adapter that wraps the `desloppify` CLI. desloppify is a Python tool, so the adapter
|
|
3
|
+
* verifies a usable Python interpreter is on PATH before any call. When Python is
|
|
4
|
+
* unavailable we surface a structured `SlopBackendUnavailable` result with `not-installed`
|
|
5
|
+
* so the Design phase can offer a fallback (fallow + supi-native).
|
|
6
|
+
*
|
|
7
|
+
* desloppify exposes `scan` (full multi-language scan), `next`/`resolve` (queue
|
|
8
|
+
* management), and `update-skill` (agent-skill distribution). We bridge `next/resolve` to
|
|
9
|
+
* our supi-native queue so the user-facing UX is the harness queue, regardless of backend.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { Platform } from "../../platform/types.js";
|
|
13
|
+
import type { HarnessAntiSlopBackend } from "../../types.js";
|
|
14
|
+
import {
|
|
15
|
+
type AuditOptions,
|
|
16
|
+
type DeadCodeOptions,
|
|
17
|
+
type DupesOptions,
|
|
18
|
+
type FixOptions,
|
|
19
|
+
type FixResult,
|
|
20
|
+
type ScanOptions,
|
|
21
|
+
type SlopBackend,
|
|
22
|
+
type SlopBackendResult,
|
|
23
|
+
type SlopFinding,
|
|
24
|
+
} from "./backend.js";
|
|
25
|
+
|
|
26
|
+
const DEFAULT_TIMEOUT_MS = 90_000;
|
|
27
|
+
const PYTHON_MIN_MAJOR = 3;
|
|
28
|
+
const PYTHON_MIN_MINOR = 11;
|
|
29
|
+
|
|
30
|
+
let availabilityCache:
|
|
31
|
+
| { ok: true; cmd: string; baseArgs: string[] }
|
|
32
|
+
| { ok: false; reason: "not-installed" | "version-too-old"; message: string }
|
|
33
|
+
| null = null;
|
|
34
|
+
|
|
35
|
+
interface DesloppifyFinding {
|
|
36
|
+
id?: string;
|
|
37
|
+
kind?: string;
|
|
38
|
+
file?: string;
|
|
39
|
+
start_line?: number;
|
|
40
|
+
end_line?: number;
|
|
41
|
+
severity?: string;
|
|
42
|
+
message?: string;
|
|
43
|
+
remediation?: string;
|
|
44
|
+
cluster?: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
interface DesloppifyJsonOutput {
|
|
48
|
+
findings?: DesloppifyFinding[];
|
|
49
|
+
score?: { lenient?: number; strict?: number };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function mapKind(kind?: string): SlopFinding["kind"] {
|
|
53
|
+
switch ((kind ?? "").toLowerCase()) {
|
|
54
|
+
case "duplicate":
|
|
55
|
+
case "near-duplicate":
|
|
56
|
+
return "duplicate";
|
|
57
|
+
case "dead-code":
|
|
58
|
+
case "unused":
|
|
59
|
+
return "dead-code";
|
|
60
|
+
case "layer":
|
|
61
|
+
case "layer-violation":
|
|
62
|
+
return "layer-violation";
|
|
63
|
+
case "naming":
|
|
64
|
+
return "naming";
|
|
65
|
+
case "complexity":
|
|
66
|
+
return "complexity";
|
|
67
|
+
case "circular":
|
|
68
|
+
return "circular-dependency";
|
|
69
|
+
case "file-too-large":
|
|
70
|
+
return "file-too-large";
|
|
71
|
+
default:
|
|
72
|
+
return "other";
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function mapSeverity(severity?: string): SlopFinding["severity"] {
|
|
77
|
+
switch ((severity ?? "").toLowerCase()) {
|
|
78
|
+
case "blocker":
|
|
79
|
+
case "error":
|
|
80
|
+
return "blocker";
|
|
81
|
+
case "warning":
|
|
82
|
+
return "warning";
|
|
83
|
+
case "info":
|
|
84
|
+
return "info";
|
|
85
|
+
default:
|
|
86
|
+
return "warning";
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function parseFindings(raw: string): SlopFinding[] {
|
|
91
|
+
if (!raw.trim()) return [];
|
|
92
|
+
let parsed: DesloppifyJsonOutput;
|
|
93
|
+
try {
|
|
94
|
+
parsed = JSON.parse(raw) as DesloppifyJsonOutput;
|
|
95
|
+
} catch {
|
|
96
|
+
return [];
|
|
97
|
+
}
|
|
98
|
+
const findings = Array.isArray(parsed.findings) ? parsed.findings : [];
|
|
99
|
+
return findings.map<SlopFinding>((f) => ({
|
|
100
|
+
kind: mapKind(f.kind),
|
|
101
|
+
file: f.file ?? "",
|
|
102
|
+
range:
|
|
103
|
+
typeof f.start_line === "number"
|
|
104
|
+
? { startLine: f.start_line, endLine: f.end_line ?? f.start_line }
|
|
105
|
+
: null,
|
|
106
|
+
severity: mapSeverity(f.severity),
|
|
107
|
+
source: "desloppify",
|
|
108
|
+
message: f.message ?? "(no message)",
|
|
109
|
+
remediation: f.remediation,
|
|
110
|
+
details: { ...(f.id ? { desloppifyId: f.id } : {}) },
|
|
111
|
+
clusterKey: f.cluster,
|
|
112
|
+
}));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function isPythonAcceptable(versionLine: string): boolean {
|
|
116
|
+
const match = versionLine.match(/Python\s+(\d+)\.(\d+)/);
|
|
117
|
+
if (!match) return false;
|
|
118
|
+
const major = Number(match[1]);
|
|
119
|
+
const minor = Number(match[2]);
|
|
120
|
+
if (major > PYTHON_MIN_MAJOR) return true;
|
|
121
|
+
if (major < PYTHON_MIN_MAJOR) return false;
|
|
122
|
+
return minor >= PYTHON_MIN_MINOR;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function detectPython(platform: Platform): Promise<{ cmd: string; version: string } | null> {
|
|
126
|
+
for (const cmd of ["python3", "python"]) {
|
|
127
|
+
try {
|
|
128
|
+
const probe = await platform.exec(cmd, ["--version"], { timeout: 3000 });
|
|
129
|
+
if (probe.code !== 0) continue;
|
|
130
|
+
// Python 2 prints --version to stderr; merge.
|
|
131
|
+
const stdoutVersion = probe.stdout.trim();
|
|
132
|
+
const stderrVersion = probe.stderr.trim();
|
|
133
|
+
const version = stdoutVersion || stderrVersion;
|
|
134
|
+
if (version && isPythonAcceptable(version)) {
|
|
135
|
+
return { cmd, version };
|
|
136
|
+
}
|
|
137
|
+
} catch {
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async function resolveInvocation(
|
|
145
|
+
platform: Platform,
|
|
146
|
+
): Promise<
|
|
147
|
+
| { ok: true; cmd: string; baseArgs: string[] }
|
|
148
|
+
| { ok: false; reason: "not-installed" | "version-too-old"; message: string }
|
|
149
|
+
> {
|
|
150
|
+
if (availabilityCache) return availabilityCache;
|
|
151
|
+
|
|
152
|
+
// desloppify itself may be on PATH directly.
|
|
153
|
+
try {
|
|
154
|
+
const probe = await platform.exec("desloppify", ["--version"], { timeout: 5000 });
|
|
155
|
+
if (probe.code === 0) {
|
|
156
|
+
availabilityCache = { ok: true, cmd: "desloppify", baseArgs: [] };
|
|
157
|
+
return availabilityCache;
|
|
158
|
+
}
|
|
159
|
+
} catch {
|
|
160
|
+
// fallthrough to python -m
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const python = await detectPython(platform);
|
|
164
|
+
if (!python) {
|
|
165
|
+
availabilityCache = {
|
|
166
|
+
ok: false,
|
|
167
|
+
reason: "not-installed",
|
|
168
|
+
message: `Python ${PYTHON_MIN_MAJOR}.${PYTHON_MIN_MINOR}+ not found on PATH; install desloppify via \`pip install --upgrade "desloppify[full]"\` after upgrading Python`,
|
|
169
|
+
};
|
|
170
|
+
return availabilityCache;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Try `python -m desloppify --version` to confirm the package is importable.
|
|
174
|
+
try {
|
|
175
|
+
const probe = await platform.exec(python.cmd, ["-m", "desloppify", "--version"], { timeout: 8000 });
|
|
176
|
+
if (probe.code === 0) {
|
|
177
|
+
availabilityCache = { ok: true, cmd: python.cmd, baseArgs: ["-m", "desloppify"] };
|
|
178
|
+
return availabilityCache;
|
|
179
|
+
}
|
|
180
|
+
} catch {
|
|
181
|
+
// fallthrough
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
availabilityCache = {
|
|
185
|
+
ok: false,
|
|
186
|
+
reason: "not-installed",
|
|
187
|
+
message: `desloppify not installed under ${python.cmd}; run \`${python.cmd} -m pip install --upgrade "desloppify[full]"\``,
|
|
188
|
+
};
|
|
189
|
+
return availabilityCache;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async function runDesloppify(
|
|
193
|
+
platform: Platform,
|
|
194
|
+
subcommand: string,
|
|
195
|
+
extraArgs: string[],
|
|
196
|
+
opts: ScanOptions,
|
|
197
|
+
): Promise<SlopBackendResult> {
|
|
198
|
+
const invocation = await resolveInvocation(platform);
|
|
199
|
+
if (!invocation.ok) {
|
|
200
|
+
return { ok: false, reason: invocation.reason, message: invocation.message };
|
|
201
|
+
}
|
|
202
|
+
const args = [
|
|
203
|
+
...invocation.baseArgs,
|
|
204
|
+
subcommand,
|
|
205
|
+
"--format",
|
|
206
|
+
"json",
|
|
207
|
+
...extraArgs,
|
|
208
|
+
];
|
|
209
|
+
if (opts.subtree) args.push("--path", opts.subtree);
|
|
210
|
+
if (opts.changedSinceHead) args.push("--changed-since", "HEAD");
|
|
211
|
+
|
|
212
|
+
const startedAt = Date.now();
|
|
213
|
+
let result;
|
|
214
|
+
try {
|
|
215
|
+
result = await platform.exec(invocation.cmd, args, {
|
|
216
|
+
cwd: opts.cwd,
|
|
217
|
+
timeout: opts.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
218
|
+
});
|
|
219
|
+
} catch (error) {
|
|
220
|
+
return {
|
|
221
|
+
ok: false,
|
|
222
|
+
reason: "execution-failed",
|
|
223
|
+
message: error instanceof Error ? error.message : "desloppify execution threw",
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const durationMs = Date.now() - startedAt;
|
|
228
|
+
if (result.killed) {
|
|
229
|
+
return { ok: false, reason: "timeout", message: `desloppify ${subcommand} timed out after ${opts.timeoutMs ?? DEFAULT_TIMEOUT_MS} ms` };
|
|
230
|
+
}
|
|
231
|
+
if (result.code >= 2) {
|
|
232
|
+
return {
|
|
233
|
+
ok: false,
|
|
234
|
+
reason: "execution-failed",
|
|
235
|
+
message: `desloppify ${subcommand} exited with code ${result.code}`,
|
|
236
|
+
exitCode: result.code,
|
|
237
|
+
stderr: result.stderr,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
return {
|
|
241
|
+
ok: true,
|
|
242
|
+
findings: parseFindings(result.stdout),
|
|
243
|
+
durationMs,
|
|
244
|
+
details: { exitCode: result.code },
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export class DesloppifyAdapter implements SlopBackend {
|
|
249
|
+
readonly id: HarnessAntiSlopBackend = "desloppify";
|
|
250
|
+
|
|
251
|
+
async isAvailable(platform: Platform): Promise<boolean> {
|
|
252
|
+
const invocation = await resolveInvocation(platform);
|
|
253
|
+
return invocation.ok;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
async scan(platform: Platform, opts: ScanOptions): Promise<SlopBackendResult> {
|
|
257
|
+
return runDesloppify(platform, "scan", [], opts);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
async dupes(platform: Platform, opts: DupesOptions): Promise<SlopBackendResult> {
|
|
261
|
+
const args: string[] = ["--only", "duplicate"];
|
|
262
|
+
if (typeof opts.threshold === "number") args.push("--threshold", String(opts.threshold));
|
|
263
|
+
return runDesloppify(platform, "scan", args, opts);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
async deadCode(platform: Platform, opts: DeadCodeOptions): Promise<SlopBackendResult> {
|
|
267
|
+
return runDesloppify(platform, "scan", ["--only", "dead-code"], opts);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
async audit(platform: Platform, opts: AuditOptions): Promise<SlopBackendResult> {
|
|
271
|
+
return runDesloppify(platform, "scan", [], opts);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
async fix(platform: Platform, opts: FixOptions): Promise<FixResult> {
|
|
275
|
+
const invocation = await resolveInvocation(platform);
|
|
276
|
+
if (!invocation.ok) {
|
|
277
|
+
return {
|
|
278
|
+
ok: false,
|
|
279
|
+
appliedIds: [],
|
|
280
|
+
failedIds: (opts.entryIds ?? []).map((id) => ({ id, reason: invocation.message })),
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
const args = [...invocation.baseArgs, "resolve"];
|
|
284
|
+
for (const id of opts.entryIds ?? []) args.push("--id", id);
|
|
285
|
+
if (!opts.apply) args.push("--dry-run");
|
|
286
|
+
try {
|
|
287
|
+
const result = await platform.exec(invocation.cmd, args, {
|
|
288
|
+
cwd: opts.cwd,
|
|
289
|
+
timeout: opts.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
290
|
+
});
|
|
291
|
+
if (result.code !== 0) {
|
|
292
|
+
return {
|
|
293
|
+
ok: false,
|
|
294
|
+
appliedIds: [],
|
|
295
|
+
failedIds: (opts.entryIds ?? []).map((id) => ({ id, reason: `desloppify resolve exited ${result.code}: ${result.stderr.trim()}` })),
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
return {
|
|
299
|
+
ok: true,
|
|
300
|
+
appliedIds: opts.entryIds ?? [],
|
|
301
|
+
failedIds: [],
|
|
302
|
+
details: { stdout: result.stdout },
|
|
303
|
+
};
|
|
304
|
+
} catch (error) {
|
|
305
|
+
return {
|
|
306
|
+
ok: false,
|
|
307
|
+
appliedIds: [],
|
|
308
|
+
failedIds: (opts.entryIds ?? []).map((id) => ({
|
|
309
|
+
id,
|
|
310
|
+
reason: error instanceof Error ? error.message : "desloppify resolve threw",
|
|
311
|
+
})),
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
export function _resetDesloppifyAvailabilityCacheForTests(): void {
|
|
318
|
+
availabilityCache = null;
|
|
319
|
+
}
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adapter that wraps the `fallow` CLI for TS/JS code-health scanning.
|
|
3
|
+
*
|
|
4
|
+
* Contract:
|
|
5
|
+
* - We invoke fallow with explicit JSON output flags so parsing is deterministic.
|
|
6
|
+
* - Exit code 0 = no findings; exit code 1 = findings present (NOT a process failure);
|
|
7
|
+
* exit code ≥2 = execution failure.
|
|
8
|
+
* - We never invoke `fallow watch` from the adapter — the harness owns its own scheduling
|
|
9
|
+
* via hooks.
|
|
10
|
+
* - Cross-platform binary detection: try `fallow` first, fall back to `npx --no-install
|
|
11
|
+
* fallow` when the native binary is missing on PATH (Windows installs without `bun
|
|
12
|
+
* install -g`).
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type { Platform } from "../../platform/types.js";
|
|
16
|
+
import type { HarnessAntiSlopBackend } from "../../types.js";
|
|
17
|
+
import {
|
|
18
|
+
type AuditOptions,
|
|
19
|
+
type DeadCodeOptions,
|
|
20
|
+
type DupesOptions,
|
|
21
|
+
type FixOptions,
|
|
22
|
+
type FixResult,
|
|
23
|
+
type ScanOptions,
|
|
24
|
+
type SlopBackend,
|
|
25
|
+
type SlopBackendResult,
|
|
26
|
+
type SlopFinding,
|
|
27
|
+
} from "./backend.js";
|
|
28
|
+
|
|
29
|
+
const DEFAULT_TIMEOUT_MS = 60_000;
|
|
30
|
+
|
|
31
|
+
/** Cache availability per process so repeated probes don't re-run version checks. */
|
|
32
|
+
let availabilityCache: { ok: boolean; via: "binary" | "npx" } | null = null;
|
|
33
|
+
|
|
34
|
+
interface FallowFinding {
|
|
35
|
+
kind?: string;
|
|
36
|
+
rule?: string;
|
|
37
|
+
file?: string;
|
|
38
|
+
startLine?: number;
|
|
39
|
+
endLine?: number;
|
|
40
|
+
severity?: string;
|
|
41
|
+
message?: string;
|
|
42
|
+
remediation?: string;
|
|
43
|
+
partner?: { file: string; startLine: number; endLine: number };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface FallowJsonOutput {
|
|
47
|
+
version?: string;
|
|
48
|
+
findings?: FallowFinding[];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function mapKind(kind?: string): SlopFinding["kind"] {
|
|
52
|
+
switch ((kind ?? "").toLowerCase()) {
|
|
53
|
+
case "duplicate":
|
|
54
|
+
case "dupe":
|
|
55
|
+
case "near-duplicate":
|
|
56
|
+
return "duplicate";
|
|
57
|
+
case "dead":
|
|
58
|
+
case "dead-code":
|
|
59
|
+
case "unused":
|
|
60
|
+
case "unused-export":
|
|
61
|
+
return "dead-code";
|
|
62
|
+
case "layer":
|
|
63
|
+
case "layer-violation":
|
|
64
|
+
case "boundary":
|
|
65
|
+
return "layer-violation";
|
|
66
|
+
case "naming":
|
|
67
|
+
return "naming";
|
|
68
|
+
case "complexity":
|
|
69
|
+
return "complexity";
|
|
70
|
+
case "circular":
|
|
71
|
+
case "circular-dependency":
|
|
72
|
+
return "circular-dependency";
|
|
73
|
+
case "file-too-large":
|
|
74
|
+
return "file-too-large";
|
|
75
|
+
default:
|
|
76
|
+
return "other";
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function mapSeverity(severity?: string): SlopFinding["severity"] {
|
|
81
|
+
switch ((severity ?? "").toLowerCase()) {
|
|
82
|
+
case "error":
|
|
83
|
+
case "blocker":
|
|
84
|
+
return "blocker";
|
|
85
|
+
case "warn":
|
|
86
|
+
case "warning":
|
|
87
|
+
return "warning";
|
|
88
|
+
case "info":
|
|
89
|
+
case "note":
|
|
90
|
+
return "info";
|
|
91
|
+
default:
|
|
92
|
+
return "warning";
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function parseFindings(raw: string): SlopFinding[] {
|
|
97
|
+
if (!raw.trim()) return [];
|
|
98
|
+
let parsed: FallowJsonOutput;
|
|
99
|
+
try {
|
|
100
|
+
parsed = JSON.parse(raw) as FallowJsonOutput;
|
|
101
|
+
} catch {
|
|
102
|
+
return [];
|
|
103
|
+
}
|
|
104
|
+
const findings = Array.isArray(parsed.findings) ? parsed.findings : [];
|
|
105
|
+
return findings.map<SlopFinding>((f) => ({
|
|
106
|
+
kind: mapKind(f.kind),
|
|
107
|
+
file: f.file ?? "",
|
|
108
|
+
range:
|
|
109
|
+
typeof f.startLine === "number"
|
|
110
|
+
? { startLine: f.startLine, endLine: f.endLine ?? f.startLine }
|
|
111
|
+
: null,
|
|
112
|
+
severity: mapSeverity(f.severity),
|
|
113
|
+
source: "fallow",
|
|
114
|
+
message: f.message ?? "(no message)",
|
|
115
|
+
remediation: f.remediation,
|
|
116
|
+
details: {
|
|
117
|
+
...(f.rule ? { rule: f.rule } : {}),
|
|
118
|
+
...(f.partner ? { partner: f.partner } : {}),
|
|
119
|
+
},
|
|
120
|
+
clusterKey: f.partner ? `${f.file}:${f.startLine}-${f.partner.file}:${f.partner.startLine}` : undefined,
|
|
121
|
+
}));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Decide how to invoke fallow. Returns the command + args. Caches the choice between
|
|
126
|
+
* calls so the cost of probing PATH is paid once per process.
|
|
127
|
+
*/
|
|
128
|
+
async function resolveInvocation(
|
|
129
|
+
platform: Platform,
|
|
130
|
+
): Promise<{ ok: true; cmd: string; baseArgs: string[]; via: "binary" | "npx" } | { ok: false; reason: "not-installed"; message: string }> {
|
|
131
|
+
if (availabilityCache?.ok) {
|
|
132
|
+
return availabilityCache.via === "npx"
|
|
133
|
+
? { ok: true, cmd: "npx", baseArgs: ["--no-install", "fallow"], via: "npx" }
|
|
134
|
+
: { ok: true, cmd: "fallow", baseArgs: [], via: "binary" };
|
|
135
|
+
}
|
|
136
|
+
if (availabilityCache?.ok === false) {
|
|
137
|
+
return { ok: false, reason: "not-installed", message: "fallow CLI not on PATH and `npx fallow` unavailable" };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Probe native binary first.
|
|
141
|
+
try {
|
|
142
|
+
const probe = await platform.exec("fallow", ["--version"], { timeout: 3000 });
|
|
143
|
+
if (probe.code === 0) {
|
|
144
|
+
availabilityCache = { ok: true, via: "binary" };
|
|
145
|
+
return { ok: true, cmd: "fallow", baseArgs: [], via: "binary" };
|
|
146
|
+
}
|
|
147
|
+
} catch {
|
|
148
|
+
// fallthrough to npx probe
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
try {
|
|
152
|
+
const probe = await platform.exec("npx", ["--no-install", "fallow", "--version"], { timeout: 5000 });
|
|
153
|
+
if (probe.code === 0) {
|
|
154
|
+
availabilityCache = { ok: true, via: "npx" };
|
|
155
|
+
return { ok: true, cmd: "npx", baseArgs: ["--no-install", "fallow"], via: "npx" };
|
|
156
|
+
}
|
|
157
|
+
} catch {
|
|
158
|
+
// fallthrough
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
availabilityCache = { ok: false, via: "binary" };
|
|
162
|
+
return { ok: false, reason: "not-installed", message: "fallow CLI not on PATH and `npx --no-install fallow` failed" };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async function runFallow(
|
|
166
|
+
platform: Platform,
|
|
167
|
+
subcommand: string,
|
|
168
|
+
extraArgs: string[],
|
|
169
|
+
opts: ScanOptions,
|
|
170
|
+
): Promise<SlopBackendResult> {
|
|
171
|
+
const invocation = await resolveInvocation(platform);
|
|
172
|
+
if (!invocation.ok) {
|
|
173
|
+
return { ok: false, reason: invocation.reason, message: invocation.message };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const args = [
|
|
177
|
+
...invocation.baseArgs,
|
|
178
|
+
subcommand,
|
|
179
|
+
"--format",
|
|
180
|
+
"json",
|
|
181
|
+
"--quiet",
|
|
182
|
+
...extraArgs,
|
|
183
|
+
];
|
|
184
|
+
if (opts.changedSinceHead) args.push("--changed-since", "HEAD");
|
|
185
|
+
if (opts.subtree) args.push("--path", opts.subtree);
|
|
186
|
+
|
|
187
|
+
const startedAt = Date.now();
|
|
188
|
+
let result;
|
|
189
|
+
try {
|
|
190
|
+
result = await platform.exec(invocation.cmd, args, {
|
|
191
|
+
cwd: opts.cwd,
|
|
192
|
+
timeout: opts.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
193
|
+
});
|
|
194
|
+
} catch (error) {
|
|
195
|
+
return {
|
|
196
|
+
ok: false,
|
|
197
|
+
reason: "execution-failed",
|
|
198
|
+
message: error instanceof Error ? error.message : "fallow execution threw",
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const durationMs = Date.now() - startedAt;
|
|
203
|
+
if (result.killed) {
|
|
204
|
+
return { ok: false, reason: "timeout", message: `fallow ${subcommand} timed out after ${opts.timeoutMs ?? DEFAULT_TIMEOUT_MS} ms` };
|
|
205
|
+
}
|
|
206
|
+
if (result.code >= 2) {
|
|
207
|
+
return {
|
|
208
|
+
ok: false,
|
|
209
|
+
reason: "execution-failed",
|
|
210
|
+
message: `fallow ${subcommand} exited with code ${result.code}`,
|
|
211
|
+
exitCode: result.code,
|
|
212
|
+
stderr: result.stderr,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// 0 or 1 = clean run; 1 just means findings were reported.
|
|
217
|
+
return {
|
|
218
|
+
ok: true,
|
|
219
|
+
findings: parseFindings(result.stdout),
|
|
220
|
+
durationMs,
|
|
221
|
+
details: { exitCode: result.code, via: invocation.via },
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export class FallowAdapter implements SlopBackend {
|
|
226
|
+
readonly id: HarnessAntiSlopBackend = "fallow";
|
|
227
|
+
|
|
228
|
+
async isAvailable(platform: Platform): Promise<boolean> {
|
|
229
|
+
const invocation = await resolveInvocation(platform);
|
|
230
|
+
return invocation.ok;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
async scan(platform: Platform, opts: ScanOptions): Promise<SlopBackendResult> {
|
|
234
|
+
return runFallow(platform, "audit", [], opts);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
async dupes(platform: Platform, opts: DupesOptions): Promise<SlopBackendResult> {
|
|
238
|
+
const args: string[] = [];
|
|
239
|
+
if (typeof opts.threshold === "number") args.push("--threshold", String(opts.threshold));
|
|
240
|
+
if (typeof opts.minTokenCount === "number") args.push("--min-tokens", String(opts.minTokenCount));
|
|
241
|
+
return runFallow(platform, "dupes", args, opts);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
async deadCode(platform: Platform, opts: DeadCodeOptions): Promise<SlopBackendResult> {
|
|
245
|
+
return runFallow(platform, "dead-code", [], opts);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async audit(platform: Platform, opts: AuditOptions): Promise<SlopBackendResult> {
|
|
249
|
+
return runFallow(platform, "audit", [], opts);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
async fix(platform: Platform, opts: FixOptions): Promise<FixResult> {
|
|
253
|
+
const invocation = await resolveInvocation(platform);
|
|
254
|
+
if (!invocation.ok) {
|
|
255
|
+
return {
|
|
256
|
+
ok: false,
|
|
257
|
+
appliedIds: [],
|
|
258
|
+
failedIds: (opts.entryIds ?? []).map((id) => ({ id, reason: invocation.message })),
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
const args = [
|
|
262
|
+
...invocation.baseArgs,
|
|
263
|
+
"fix",
|
|
264
|
+
"--format",
|
|
265
|
+
"json",
|
|
266
|
+
];
|
|
267
|
+
if (opts.apply) args.push("--yes");
|
|
268
|
+
else args.push("--dry-run");
|
|
269
|
+
if (opts.subtree) args.push("--path", opts.subtree);
|
|
270
|
+
|
|
271
|
+
try {
|
|
272
|
+
const result = await platform.exec(invocation.cmd, args, {
|
|
273
|
+
cwd: opts.cwd,
|
|
274
|
+
timeout: opts.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
275
|
+
});
|
|
276
|
+
if (result.code >= 2) {
|
|
277
|
+
return {
|
|
278
|
+
ok: false,
|
|
279
|
+
appliedIds: [],
|
|
280
|
+
failedIds: (opts.entryIds ?? []).map((id) => ({ id, reason: `fallow fix exited ${result.code}: ${result.stderr.trim()}` })),
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
return {
|
|
284
|
+
ok: true,
|
|
285
|
+
appliedIds: opts.entryIds ?? [],
|
|
286
|
+
failedIds: [],
|
|
287
|
+
details: { stdout: result.stdout },
|
|
288
|
+
};
|
|
289
|
+
} catch (error) {
|
|
290
|
+
return {
|
|
291
|
+
ok: false,
|
|
292
|
+
appliedIds: [],
|
|
293
|
+
failedIds: (opts.entryIds ?? []).map((id) => ({
|
|
294
|
+
id,
|
|
295
|
+
reason: error instanceof Error ? error.message : "fallow fix threw",
|
|
296
|
+
})),
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/** Reset the in-process availability cache. Tests use this to re-probe without restarting. */
|
|
303
|
+
export function _resetFallowAvailabilityCacheForTests(): void {
|
|
304
|
+
availabilityCache = null;
|
|
305
|
+
}
|