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,684 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VALIDATE stage runner.
|
|
3
|
+
*
|
|
4
|
+
* Runs every sub-check the harness installed:
|
|
5
|
+
* - lint (delegates to the configured tool via platform.exec; structured pass/fail),
|
|
6
|
+
* - structural test (placeholder — emitter-supplied; harness only records the result),
|
|
7
|
+
* - eval (placeholder),
|
|
8
|
+
* - cross-link check (every artifact referenced from AGENTS.md / docs/ exists),
|
|
9
|
+
* - schema check (Discover / Design / Plan artifacts validate),
|
|
10
|
+
* - discover-drift (re-run Discover and diff against the saved artifact),
|
|
11
|
+
* - anti-slop scan (selected backend audit),
|
|
12
|
+
* - score computation,
|
|
13
|
+
* - synthetic edit test (hooks fire).
|
|
14
|
+
*
|
|
15
|
+
* Returns a `HarnessValidateReport` and persists it. The command handler owns the
|
|
16
|
+
* user-accept gate.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import * as fs from "node:fs";
|
|
20
|
+
import * as path from "node:path";
|
|
21
|
+
|
|
22
|
+
import type { Platform } from "../../platform/types.js";
|
|
23
|
+
import type {
|
|
24
|
+
HarnessAntiSlopBackend,
|
|
25
|
+
HarnessLayerRule,
|
|
26
|
+
HarnessSlopQueueEntry,
|
|
27
|
+
HarnessValidateCheck,
|
|
28
|
+
HarnessValidateFinding,
|
|
29
|
+
HarnessValidateReport,
|
|
30
|
+
} from "../../types.js";
|
|
31
|
+
import { parseArchitectureMarkdown } from "../anti_slop/architecture-parser.js";
|
|
32
|
+
import type { SlopBackend, SlopFinding } from "../anti_slop/backend.js";
|
|
33
|
+
import { computeQueueEntryId } from "../anti_slop/queue.js";
|
|
34
|
+
import { computeScore, scoreFloorPassed } from "../anti_slop/score.js";
|
|
35
|
+
import { runSyntheticEditTest } from "../anti_slop/synthetic-edit-test.js";
|
|
36
|
+
import {
|
|
37
|
+
type HarnessStageRunResult,
|
|
38
|
+
type HarnessStageRunner,
|
|
39
|
+
type HarnessStageRunnerContext,
|
|
40
|
+
nowIso,
|
|
41
|
+
} from "../stage-runner.js";
|
|
42
|
+
import {
|
|
43
|
+
appendScoreHistory,
|
|
44
|
+
appendSlopQueueEntry,
|
|
45
|
+
loadHarnessDesignSpecJson,
|
|
46
|
+
loadHarnessDiscover,
|
|
47
|
+
saveHarnessRepoScore,
|
|
48
|
+
saveHarnessValidateReport,
|
|
49
|
+
readSlopQueue,
|
|
50
|
+
} from "../storage.js";
|
|
51
|
+
import { buildDiscoverArtifact } from "./discover.js";
|
|
52
|
+
import {
|
|
53
|
+
getHarnessAgentsMdPath,
|
|
54
|
+
getHarnessArchitectureDocPath,
|
|
55
|
+
getHarnessGoldenPrinciplesPath,
|
|
56
|
+
} from "../project-paths.js";
|
|
57
|
+
|
|
58
|
+
export interface ValidateStageInput {
|
|
59
|
+
/** Selected backend (from the design spec). */
|
|
60
|
+
backend: HarnessAntiSlopBackend;
|
|
61
|
+
/** Backend adapter (only consulted when `backend !== "supi-native"`). */
|
|
62
|
+
adapter?: SlopBackend;
|
|
63
|
+
/** Score floor configuration. */
|
|
64
|
+
scoreFloor: { strict: number; lenient: number; release_blocking: boolean };
|
|
65
|
+
/** Hook config snapshot for the synthetic-edit test. */
|
|
66
|
+
hooks: {
|
|
67
|
+
pre_edit_dupe_probe: { enabled: boolean };
|
|
68
|
+
post_session_sweep: { enabled: boolean };
|
|
69
|
+
layer_context_inject: { enabled: boolean; addendum_max_chars: number };
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
interface CheckResult {
|
|
74
|
+
name: string;
|
|
75
|
+
passed: boolean;
|
|
76
|
+
summary: string;
|
|
77
|
+
findings: HarnessValidateFinding[];
|
|
78
|
+
durationMs?: number;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
type CheckContract = Pick<HarnessValidateCheck, "invariant" | "proves" | "doesNotProve" | "artifact" | "failSafe">;
|
|
82
|
+
|
|
83
|
+
const CHECK_CONTRACTS: Readonly<Record<string, CheckContract>> = {
|
|
84
|
+
"cross-link-check": {
|
|
85
|
+
invariant: "Agent-facing harness docs must exist and point maintainers to the architecture and golden-principles contracts.",
|
|
86
|
+
proves: "AGENTS.md, docs/architecture.md, and docs/golden-principles.md exist, and AGENTS.md references the core docs.",
|
|
87
|
+
doesNotProve: "The docs are semantically complete, current, or sufficient for every agent client.",
|
|
88
|
+
artifact: "validate-report.json findings plus filesystem state",
|
|
89
|
+
failSafe: "Missing or unreadable docs produce error findings and block validation.",
|
|
90
|
+
},
|
|
91
|
+
"schema-check": {
|
|
92
|
+
invariant: "Persisted pipeline artifacts must remain readable before later stages claim continuity.",
|
|
93
|
+
proves: "discover.json can be loaded for the active harness session.",
|
|
94
|
+
doesNotProve: "The discovered facts are fresh or semantically exhaustive.",
|
|
95
|
+
artifact: "validate-report.json schema-check entry",
|
|
96
|
+
failSafe: "Missing or invalid JSON produces an error finding and blocks validation.",
|
|
97
|
+
},
|
|
98
|
+
"discover-drift": {
|
|
99
|
+
invariant: "The saved discovery artifact should not silently diverge from the current repository profile.",
|
|
100
|
+
proves: "A fresh discovery pass did not reveal language-level drift that invalidates the saved artifact.",
|
|
101
|
+
doesNotProve: "Every framework, script, dependency, or architecture convention is unchanged.",
|
|
102
|
+
artifact: "validate-report.json discover-drift entry",
|
|
103
|
+
failSafe: "Missing discovery data blocks; detected drift is recorded as warning/info with remediation.",
|
|
104
|
+
},
|
|
105
|
+
"anti-slop-scan": {
|
|
106
|
+
invariant: "New duplicate, dead-code, and layer-drift findings must be visible in the persistent slop queue.",
|
|
107
|
+
proves: "The selected backend either ran and returned findings, or an explicitly soft-failed optional backend state was recorded.",
|
|
108
|
+
doesNotProve: "The selected backend can detect semantic bugs, product regressions, or unsupported language patterns.",
|
|
109
|
+
artifact: "validate-report.json, queue.jsonl, score.json",
|
|
110
|
+
failSafe: "Missing optional backend configuration warns; backend runtime errors become blocking findings.",
|
|
111
|
+
},
|
|
112
|
+
"synthetic-edit-test": {
|
|
113
|
+
invariant: "Installed anti-slop hooks must fire against a controlled representative edit before the harness claims runtime guardrails work.",
|
|
114
|
+
proves: "The enabled in-process hook handlers respond to the synthetic edit fixture without reported failures.",
|
|
115
|
+
doesNotProve: "Hook latency, all real editor events, or every repository-specific layer edge case.",
|
|
116
|
+
artifact: "validate-report.json synthetic-edit-test entry",
|
|
117
|
+
failSafe: "Hook failures are emitted as error findings and block validation.",
|
|
118
|
+
},
|
|
119
|
+
"ci-local-wiring": {
|
|
120
|
+
invariant: "Every harness validation gate must have one local command and CI must invoke that command instead of relying on human memory.",
|
|
121
|
+
proves: "The configured local command exists and the configured CI workflow calls it on the selected PR trigger.",
|
|
122
|
+
doesNotProve: "The command's inner toolchain is installed, every gate is semantically sufficient, or CI provider secrets/runners are available.",
|
|
123
|
+
artifact: "validate-report.json plus package.json and CI workflow contents",
|
|
124
|
+
failSafe: "Missing package scripts or workflow files emit error findings and block validation.",
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
function attachCheckContract(check: CheckResult): HarnessValidateCheck {
|
|
129
|
+
const contract = CHECK_CONTRACTS[check.name] ?? {
|
|
130
|
+
invariant: `The ${check.name} gate must state the rule it protects.`,
|
|
131
|
+
proves: check.summary,
|
|
132
|
+
doesNotProve: "No explicit blind spot was registered for this check.",
|
|
133
|
+
artifact: "validate-report.json",
|
|
134
|
+
failSafe: "Unknown checks should be given an explicit contract before release.",
|
|
135
|
+
};
|
|
136
|
+
return { ...check, ...contract };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async function checkCrossLinks(cwd: string): Promise<CheckResult> {
|
|
140
|
+
const startedAt = Date.now();
|
|
141
|
+
const findings: HarnessValidateFinding[] = [];
|
|
142
|
+
|
|
143
|
+
const agentsPath = path.join(cwd, "AGENTS.md");
|
|
144
|
+
const architecturePath = path.join(cwd, "docs", "architecture.md");
|
|
145
|
+
const goldenPath = path.join(cwd, "docs", "golden-principles.md");
|
|
146
|
+
|
|
147
|
+
const expected = [
|
|
148
|
+
{ file: "AGENTS.md", path: agentsPath },
|
|
149
|
+
{ file: "docs/architecture.md", path: architecturePath },
|
|
150
|
+
{ file: "docs/golden-principles.md", path: goldenPath },
|
|
151
|
+
];
|
|
152
|
+
for (const target of expected) {
|
|
153
|
+
if (!fs.existsSync(target.path)) {
|
|
154
|
+
findings.push({
|
|
155
|
+
severity: "error",
|
|
156
|
+
file: target.file,
|
|
157
|
+
message: `${target.file} is missing`,
|
|
158
|
+
remediation: `Run /supi:harness implement (or rebuild) to regenerate ${target.file}.`,
|
|
159
|
+
source: "cross-link-check",
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// AGENTS.md must reference both core docs.
|
|
165
|
+
if (fs.existsSync(agentsPath)) {
|
|
166
|
+
try {
|
|
167
|
+
const agents = fs.readFileSync(agentsPath, "utf8");
|
|
168
|
+
if (!agents.includes("docs/architecture.md")) {
|
|
169
|
+
findings.push({
|
|
170
|
+
severity: "warning",
|
|
171
|
+
file: "AGENTS.md",
|
|
172
|
+
message: "AGENTS.md does not reference docs/architecture.md",
|
|
173
|
+
remediation: "Add a link to docs/architecture.md from AGENTS.md.",
|
|
174
|
+
source: "cross-link-check",
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
if (!agents.includes("docs/golden-principles.md")) {
|
|
178
|
+
findings.push({
|
|
179
|
+
severity: "warning",
|
|
180
|
+
file: "AGENTS.md",
|
|
181
|
+
message: "AGENTS.md does not reference docs/golden-principles.md",
|
|
182
|
+
remediation: "Add a link to docs/golden-principles.md from AGENTS.md.",
|
|
183
|
+
source: "cross-link-check",
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
} catch {
|
|
187
|
+
findings.push({
|
|
188
|
+
severity: "error",
|
|
189
|
+
file: "AGENTS.md",
|
|
190
|
+
message: "AGENTS.md exists but is unreadable",
|
|
191
|
+
remediation: "Inspect filesystem permissions on AGENTS.md.",
|
|
192
|
+
source: "cross-link-check",
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const passed = !findings.some((f) => f.severity === "error");
|
|
198
|
+
return {
|
|
199
|
+
name: "cross-link-check",
|
|
200
|
+
passed,
|
|
201
|
+
summary: passed
|
|
202
|
+
? "All required artifacts exist and AGENTS.md references core docs."
|
|
203
|
+
: `${findings.length} issue(s) detected.`,
|
|
204
|
+
findings,
|
|
205
|
+
durationMs: Date.now() - startedAt,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async function checkSchema(
|
|
210
|
+
paths: HarnessStageRunnerContext["paths"],
|
|
211
|
+
cwd: string,
|
|
212
|
+
sessionId: string,
|
|
213
|
+
): Promise<CheckResult> {
|
|
214
|
+
const startedAt = Date.now();
|
|
215
|
+
const findings: HarnessValidateFinding[] = [];
|
|
216
|
+
|
|
217
|
+
const discover = loadHarnessDiscover(paths, cwd, sessionId);
|
|
218
|
+
if (!discover.ok) {
|
|
219
|
+
findings.push({
|
|
220
|
+
severity: "error",
|
|
221
|
+
file: "discover.json",
|
|
222
|
+
message: `discover.json is missing or invalid: ${discover.error.message}`,
|
|
223
|
+
remediation: "Re-run /supi:harness discover.",
|
|
224
|
+
source: "schema-check",
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return {
|
|
229
|
+
name: "schema-check",
|
|
230
|
+
passed: findings.length === 0,
|
|
231
|
+
summary: findings.length === 0 ? "All schemas valid." : "Schema validation failed.",
|
|
232
|
+
findings,
|
|
233
|
+
durationMs: Date.now() - startedAt,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function scriptNameFromLocalCommand(command: string): string | null {
|
|
238
|
+
const trimmed = command.trim();
|
|
239
|
+
const runMatch = /^(?:bun|npm|pnpm|yarn)\s+run\s+([^\s]+)$/.exec(trimmed);
|
|
240
|
+
if (runMatch) return runMatch[1];
|
|
241
|
+
const pnpmOrYarnMatch = /^(?:pnpm|yarn)\s+([^\s]+)$/.exec(trimmed);
|
|
242
|
+
if (pnpmOrYarnMatch) return pnpmOrYarnMatch[1];
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async function checkCiLocalWiring(
|
|
247
|
+
paths: HarnessStageRunnerContext["paths"],
|
|
248
|
+
cwd: string,
|
|
249
|
+
sessionId: string,
|
|
250
|
+
): Promise<CheckResult> {
|
|
251
|
+
const startedAt = Date.now();
|
|
252
|
+
const findings: HarnessValidateFinding[] = [];
|
|
253
|
+
const spec = loadHarnessDesignSpecJson(paths, cwd, sessionId);
|
|
254
|
+
if (!spec.ok) {
|
|
255
|
+
return {
|
|
256
|
+
name: "ci-local-wiring",
|
|
257
|
+
passed: true,
|
|
258
|
+
summary: "Skipped: no design-spec.json available for CI wiring validation.",
|
|
259
|
+
findings,
|
|
260
|
+
durationMs: Date.now() - startedAt,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (!spec.value.ci) {
|
|
265
|
+
return {
|
|
266
|
+
name: "ci-local-wiring",
|
|
267
|
+
passed: false,
|
|
268
|
+
summary: "design-spec.json does not contain CI/local wiring configuration.",
|
|
269
|
+
findings: [{
|
|
270
|
+
severity: "error",
|
|
271
|
+
file: "design-spec.json",
|
|
272
|
+
message: "Design spec is missing ci configuration.",
|
|
273
|
+
remediation: "Re-run /supi:harness design so CI trigger and local quality command are recorded.",
|
|
274
|
+
source: "ci-local-wiring",
|
|
275
|
+
}],
|
|
276
|
+
durationMs: Date.now() - startedAt,
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
const scriptName = scriptNameFromLocalCommand(spec.value.ci.localCommand);
|
|
282
|
+
const packageJsonPath = path.join(cwd, "package.json");
|
|
283
|
+
if (scriptName) {
|
|
284
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
285
|
+
findings.push({
|
|
286
|
+
severity: "error",
|
|
287
|
+
file: "package.json",
|
|
288
|
+
message: `Local quality command ${spec.value.ci.localCommand} requires package.json, but package.json is missing.`,
|
|
289
|
+
remediation: `Create package.json with a scripts.${scriptName} entry or choose a non-package local command.`,
|
|
290
|
+
source: "ci-local-wiring",
|
|
291
|
+
});
|
|
292
|
+
} else {
|
|
293
|
+
try {
|
|
294
|
+
const parsed = JSON.parse(fs.readFileSync(packageJsonPath, "utf8")) as { scripts?: Record<string, unknown> };
|
|
295
|
+
if (typeof parsed.scripts?.[scriptName] !== "string") {
|
|
296
|
+
findings.push({
|
|
297
|
+
severity: "error",
|
|
298
|
+
file: "package.json",
|
|
299
|
+
message: `package.json does not define scripts.${scriptName} for ${spec.value.ci.localCommand}.`,
|
|
300
|
+
remediation: `Add scripts.${scriptName} and make it run the validation gates from design-spec.json.`,
|
|
301
|
+
source: "ci-local-wiring",
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
} catch (error) {
|
|
305
|
+
findings.push({
|
|
306
|
+
severity: "error",
|
|
307
|
+
file: "package.json",
|
|
308
|
+
message: `package.json is unreadable or invalid JSON: ${error instanceof Error ? error.message : String(error)}`,
|
|
309
|
+
remediation: "Fix package.json before validating CI/local quality wiring.",
|
|
310
|
+
source: "ci-local-wiring",
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const workflowPath = path.join(cwd, spec.value.ci.workflowPath);
|
|
317
|
+
if (!fs.existsSync(workflowPath)) {
|
|
318
|
+
findings.push({
|
|
319
|
+
severity: "error",
|
|
320
|
+
file: spec.value.ci.workflowPath,
|
|
321
|
+
message: `CI workflow ${spec.value.ci.workflowPath} is missing.`,
|
|
322
|
+
remediation: `Create the workflow and have it run ${spec.value.ci.localCommand}.`,
|
|
323
|
+
source: "ci-local-wiring",
|
|
324
|
+
});
|
|
325
|
+
} else {
|
|
326
|
+
try {
|
|
327
|
+
const workflow = fs.readFileSync(workflowPath, "utf8");
|
|
328
|
+
if (!workflow.includes(spec.value.ci.localCommand)) {
|
|
329
|
+
findings.push({
|
|
330
|
+
severity: "error",
|
|
331
|
+
file: spec.value.ci.workflowPath,
|
|
332
|
+
message: `CI workflow does not invoke ${spec.value.ci.localCommand}.`,
|
|
333
|
+
remediation: "Call the local harness quality command from CI instead of duplicating gate commands inline.",
|
|
334
|
+
source: "ci-local-wiring",
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
if (spec.value.ci.trigger.mode === "branches") {
|
|
338
|
+
for (const branch of spec.value.ci.trigger.branches) {
|
|
339
|
+
if (!workflow.includes(branch)) {
|
|
340
|
+
findings.push({
|
|
341
|
+
severity: "error",
|
|
342
|
+
file: spec.value.ci.workflowPath,
|
|
343
|
+
message: `CI workflow does not mention configured PR target branch ${branch}.`,
|
|
344
|
+
remediation: `Add ${branch} to the pull_request.branches trigger or update the design spec.`,
|
|
345
|
+
source: "ci-local-wiring",
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
} catch (error) {
|
|
351
|
+
findings.push({
|
|
352
|
+
severity: "error",
|
|
353
|
+
file: spec.value.ci.workflowPath,
|
|
354
|
+
message: `CI workflow is unreadable: ${error instanceof Error ? error.message : String(error)}`,
|
|
355
|
+
remediation: "Fix workflow file permissions or contents.",
|
|
356
|
+
source: "ci-local-wiring",
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return {
|
|
362
|
+
name: "ci-local-wiring",
|
|
363
|
+
passed: !findings.some((finding) => finding.severity === "error"),
|
|
364
|
+
summary: findings.length === 0
|
|
365
|
+
? "CI workflow invokes the local harness quality command."
|
|
366
|
+
: `${findings.length} CI/local wiring issue(s) detected.`,
|
|
367
|
+
findings,
|
|
368
|
+
durationMs: Date.now() - startedAt,
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
async function checkDiscoverDrift(
|
|
373
|
+
paths: HarnessStageRunnerContext["paths"],
|
|
374
|
+
cwd: string,
|
|
375
|
+
sessionId: string,
|
|
376
|
+
now: string,
|
|
377
|
+
): Promise<CheckResult> {
|
|
378
|
+
const startedAt = Date.now();
|
|
379
|
+
const findings: HarnessValidateFinding[] = [];
|
|
380
|
+
|
|
381
|
+
const saved = loadHarnessDiscover(paths, cwd, sessionId);
|
|
382
|
+
if (!saved.ok) {
|
|
383
|
+
findings.push({
|
|
384
|
+
severity: "error",
|
|
385
|
+
file: "discover.json",
|
|
386
|
+
message: "Cannot drift-check: saved discover artifact missing",
|
|
387
|
+
remediation: "Re-run /supi:harness discover.",
|
|
388
|
+
source: "discover-drift",
|
|
389
|
+
});
|
|
390
|
+
return {
|
|
391
|
+
name: "discover-drift",
|
|
392
|
+
passed: false,
|
|
393
|
+
summary: "discover.json missing",
|
|
394
|
+
findings,
|
|
395
|
+
durationMs: Date.now() - startedAt,
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
const fresh = buildDiscoverArtifact({ cwd, sessionId, now });
|
|
399
|
+
const savedLangs = new Set(saved.value.languages);
|
|
400
|
+
const freshLangs = new Set(fresh.languages);
|
|
401
|
+
for (const lang of freshLangs) {
|
|
402
|
+
if (!savedLangs.has(lang)) {
|
|
403
|
+
findings.push({
|
|
404
|
+
severity: "warning",
|
|
405
|
+
file: "discover.json",
|
|
406
|
+
message: `New language detected since saved discover: ${lang}`,
|
|
407
|
+
remediation: "Re-run /supi:harness discover to refresh the artifact.",
|
|
408
|
+
source: "discover-drift",
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
for (const lang of savedLangs) {
|
|
413
|
+
if (!freshLangs.has(lang)) {
|
|
414
|
+
findings.push({
|
|
415
|
+
severity: "info",
|
|
416
|
+
file: "discover.json",
|
|
417
|
+
message: `Language no longer present: ${lang}`,
|
|
418
|
+
remediation: "Re-run /supi:harness discover to refresh the artifact.",
|
|
419
|
+
source: "discover-drift",
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
const passed = !findings.some((f) => f.severity === "error");
|
|
425
|
+
return {
|
|
426
|
+
name: "discover-drift",
|
|
427
|
+
passed,
|
|
428
|
+
summary: passed ? "Discover artifact reflects current repo state." : "Discover artifact is stale.",
|
|
429
|
+
findings,
|
|
430
|
+
durationMs: Date.now() - startedAt,
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
async function checkAntiSlopScan(
|
|
435
|
+
platform: Platform,
|
|
436
|
+
cwd: string,
|
|
437
|
+
input: ValidateStageInput,
|
|
438
|
+
): Promise<{
|
|
439
|
+
result: CheckResult;
|
|
440
|
+
scanFindings: SlopFinding[];
|
|
441
|
+
slopCounts: { duplicates: number; deadCode: number; layerViolations: number; other: number };
|
|
442
|
+
}> {
|
|
443
|
+
const startedAt = Date.now();
|
|
444
|
+
const findings: HarnessValidateFinding[] = [];
|
|
445
|
+
const slopCounts = { duplicates: 0, deadCode: 0, layerViolations: 0, other: 0 };
|
|
446
|
+
|
|
447
|
+
if (input.backend === "supi-native" || !input.adapter) {
|
|
448
|
+
return {
|
|
449
|
+
result: {
|
|
450
|
+
name: "anti-slop-scan",
|
|
451
|
+
passed: true,
|
|
452
|
+
summary: "Skipped (supi-native backend has no external scan).",
|
|
453
|
+
findings,
|
|
454
|
+
durationMs: Date.now() - startedAt,
|
|
455
|
+
},
|
|
456
|
+
scanFindings: [],
|
|
457
|
+
slopCounts,
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
const scan = await input.adapter.audit(platform, { cwd });
|
|
462
|
+
if (!scan.ok) {
|
|
463
|
+
findings.push({
|
|
464
|
+
severity: scan.reason === "not-installed" || scan.reason === "config-missing" ? "warning" : "error",
|
|
465
|
+
file: ".",
|
|
466
|
+
message: `${input.backend} ${scan.reason}: ${scan.message}`,
|
|
467
|
+
remediation: "Install the backend or fall back to supi-native in the design spec.",
|
|
468
|
+
source: "anti-slop-scan",
|
|
469
|
+
});
|
|
470
|
+
return {
|
|
471
|
+
result: {
|
|
472
|
+
name: "anti-slop-scan",
|
|
473
|
+
passed: scan.reason === "not-installed" || scan.reason === "config-missing", // soft-fail
|
|
474
|
+
summary: scan.message,
|
|
475
|
+
findings,
|
|
476
|
+
durationMs: Date.now() - startedAt,
|
|
477
|
+
},
|
|
478
|
+
scanFindings: [],
|
|
479
|
+
slopCounts,
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
for (const finding of scan.findings) {
|
|
484
|
+
if (finding.kind === "duplicate") slopCounts.duplicates += 1;
|
|
485
|
+
else if (finding.kind === "dead-code") slopCounts.deadCode += 1;
|
|
486
|
+
else if (finding.kind === "layer-violation") slopCounts.layerViolations += 1;
|
|
487
|
+
else slopCounts.other += 1;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
return {
|
|
491
|
+
result: {
|
|
492
|
+
name: "anti-slop-scan",
|
|
493
|
+
passed: scan.findings.length === 0,
|
|
494
|
+
summary:
|
|
495
|
+
scan.findings.length === 0
|
|
496
|
+
? "No slop findings."
|
|
497
|
+
: `${scan.findings.length} slop finding(s) recorded; see queue.`,
|
|
498
|
+
findings,
|
|
499
|
+
durationMs: Date.now() - startedAt,
|
|
500
|
+
},
|
|
501
|
+
scanFindings: scan.findings,
|
|
502
|
+
slopCounts,
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
function checkSyntheticEdit(input: ValidateStageInput, layerRules: readonly HarnessLayerRule[]): CheckResult {
|
|
507
|
+
const startedAt = Date.now();
|
|
508
|
+
const test = runSyntheticEditTest({
|
|
509
|
+
layerRules,
|
|
510
|
+
hooks: input.hooks,
|
|
511
|
+
});
|
|
512
|
+
const findings: HarnessValidateFinding[] = test.failures.map((failure) => ({
|
|
513
|
+
severity: "error" as const,
|
|
514
|
+
file: "synthetic-edit-test",
|
|
515
|
+
message: failure,
|
|
516
|
+
remediation: "Inspect the failing hook implementation; test runs against in-process handlers.",
|
|
517
|
+
source: "synthetic-edit-test",
|
|
518
|
+
}));
|
|
519
|
+
return {
|
|
520
|
+
name: "synthetic-edit-test",
|
|
521
|
+
passed: test.failures.length === 0,
|
|
522
|
+
summary:
|
|
523
|
+
test.failures.length === 0
|
|
524
|
+
? `${test.hooksFired.length} hook(s) fired as expected.`
|
|
525
|
+
: `${test.failures.length} synthetic-edit failure(s).`,
|
|
526
|
+
findings,
|
|
527
|
+
durationMs: Date.now() - startedAt,
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
function loadLayerRules(cwd: string): HarnessLayerRule[] {
|
|
532
|
+
const archPath = path.join(cwd, "docs", "architecture.md");
|
|
533
|
+
if (!fs.existsSync(archPath)) return [];
|
|
534
|
+
try {
|
|
535
|
+
const md = fs.readFileSync(archPath, "utf8");
|
|
536
|
+
return parseArchitectureMarkdown(md);
|
|
537
|
+
} catch {
|
|
538
|
+
return [];
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* Run every sub-check and assemble the validate report. Pure-ish: side effects are limited
|
|
544
|
+
* to the optional anti-slop scan (which itself is fenced behind the adapter's
|
|
545
|
+
* `isAvailable`).
|
|
546
|
+
*/
|
|
547
|
+
export async function runValidate(
|
|
548
|
+
ctx: HarnessStageRunnerContext,
|
|
549
|
+
input: ValidateStageInput,
|
|
550
|
+
): Promise<HarnessValidateReport> {
|
|
551
|
+
const recordedAt = nowIso(ctx);
|
|
552
|
+
const layerRules = loadLayerRules(ctx.cwd);
|
|
553
|
+
|
|
554
|
+
const checks: CheckResult[] = [];
|
|
555
|
+
const slopFindings: SlopFinding[] = [];
|
|
556
|
+
let slopCounts = { duplicates: 0, deadCode: 0, layerViolations: 0, other: 0 };
|
|
557
|
+
|
|
558
|
+
checks.push(await checkCrossLinks(ctx.cwd));
|
|
559
|
+
checks.push(await checkSchema(ctx.paths, ctx.cwd, ctx.sessionId));
|
|
560
|
+
checks.push(await checkDiscoverDrift(ctx.paths, ctx.cwd, ctx.sessionId, recordedAt));
|
|
561
|
+
checks.push(await checkCiLocalWiring(ctx.paths, ctx.cwd, ctx.sessionId));
|
|
562
|
+
|
|
563
|
+
const scanResult = await checkAntiSlopScan(ctx.platform, ctx.cwd, input);
|
|
564
|
+
checks.push(scanResult.result);
|
|
565
|
+
slopFindings.push(...scanResult.scanFindings);
|
|
566
|
+
slopCounts = scanResult.slopCounts;
|
|
567
|
+
|
|
568
|
+
const synthetic = checkSyntheticEdit(input, layerRules);
|
|
569
|
+
checks.push(synthetic);
|
|
570
|
+
|
|
571
|
+
// Persist scan findings to the queue so future runs see them.
|
|
572
|
+
for (const finding of slopFindings) {
|
|
573
|
+
const id = computeQueueEntryId({
|
|
574
|
+
kind: finding.kind,
|
|
575
|
+
file: finding.file,
|
|
576
|
+
range: finding.range,
|
|
577
|
+
ruleHint: typeof finding.details?.rule === "string" ? finding.details.rule : undefined,
|
|
578
|
+
});
|
|
579
|
+
const entry: HarnessSlopQueueEntry = {
|
|
580
|
+
id,
|
|
581
|
+
kind: finding.kind,
|
|
582
|
+
file: finding.file,
|
|
583
|
+
range: finding.range,
|
|
584
|
+
severity: finding.severity,
|
|
585
|
+
source: finding.source,
|
|
586
|
+
state: "open",
|
|
587
|
+
message: finding.message,
|
|
588
|
+
remediation: finding.remediation,
|
|
589
|
+
ts: recordedAt,
|
|
590
|
+
...(finding.clusterKey ? { clusters: [finding.clusterKey] } : {}),
|
|
591
|
+
...(finding.details ? { details: finding.details } : {}),
|
|
592
|
+
};
|
|
593
|
+
appendSlopQueueEntry(ctx.paths, ctx.cwd, entry);
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// Score = pulled from the queue (post-write so this run's findings are reflected).
|
|
597
|
+
const queue = readSlopQueue(ctx.paths, ctx.cwd);
|
|
598
|
+
const score = computeScore({
|
|
599
|
+
computedAt: recordedAt,
|
|
600
|
+
entries: queue.ok ? queue.value : [],
|
|
601
|
+
});
|
|
602
|
+
const floor = scoreFloorPassed(score, input.scoreFloor);
|
|
603
|
+
|
|
604
|
+
const passed = checks.every((c) => c.passed) && floor.passed;
|
|
605
|
+
const report: HarnessValidateReport = {
|
|
606
|
+
sessionId: ctx.sessionId,
|
|
607
|
+
recordedAt,
|
|
608
|
+
passed,
|
|
609
|
+
checks: checks.map(attachCheckContract),
|
|
610
|
+
slopScan: {
|
|
611
|
+
backend: input.backend,
|
|
612
|
+
duplicates: slopCounts.duplicates,
|
|
613
|
+
deadCode: slopCounts.deadCode,
|
|
614
|
+
layerViolations: slopCounts.layerViolations,
|
|
615
|
+
other: slopCounts.other,
|
|
616
|
+
},
|
|
617
|
+
score,
|
|
618
|
+
scoreFloorPassed: floor.passed,
|
|
619
|
+
syntheticEditTest: {
|
|
620
|
+
ran: synthetic.passed || synthetic.findings.length > 0,
|
|
621
|
+
hooksFired: [],
|
|
622
|
+
failures: synthetic.findings.map((f) => f.message),
|
|
623
|
+
},
|
|
624
|
+
};
|
|
625
|
+
|
|
626
|
+
// Persist the score snapshot + history.
|
|
627
|
+
saveHarnessRepoScore(ctx.paths, ctx.cwd, score);
|
|
628
|
+
appendScoreHistory(ctx.paths, ctx.cwd, {
|
|
629
|
+
recordedAt,
|
|
630
|
+
sessionId: ctx.sessionId,
|
|
631
|
+
lenient: score.lenient,
|
|
632
|
+
strict: score.strict,
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
return report;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
export class HarnessValidateStage implements HarnessStageRunner {
|
|
639
|
+
readonly stage = "validate" as const;
|
|
640
|
+
|
|
641
|
+
constructor(private readonly input: ValidateStageInput) {}
|
|
642
|
+
|
|
643
|
+
async isReady(ctx: HarnessStageRunnerContext): Promise<boolean> {
|
|
644
|
+
return loadHarnessDiscover(ctx.paths, ctx.cwd, ctx.sessionId).ok;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
async isComplete(ctx: HarnessStageRunnerContext): Promise<boolean> {
|
|
648
|
+
const reportPath = path.join(
|
|
649
|
+
path.dirname(getHarnessArchitectureDocPath(ctx.paths, ctx.cwd)),
|
|
650
|
+
"..",
|
|
651
|
+
"validate-report.json",
|
|
652
|
+
);
|
|
653
|
+
return fs.existsSync(reportPath);
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
async run(ctx: HarnessStageRunnerContext): Promise<HarnessStageRunResult> {
|
|
657
|
+
const report = await runValidate(ctx, this.input);
|
|
658
|
+
const persisted = saveHarnessValidateReport(ctx.paths, ctx.cwd, ctx.sessionId, report);
|
|
659
|
+
if (!persisted.ok) {
|
|
660
|
+
return {
|
|
661
|
+
status: "failed",
|
|
662
|
+
stage: this.stage,
|
|
663
|
+
artifactPaths: [],
|
|
664
|
+
error: `failed to persist validate report: ${persisted.error.message}`,
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
return {
|
|
668
|
+
status: report.passed ? "awaiting-user" : "blocked",
|
|
669
|
+
stage: this.stage,
|
|
670
|
+
artifactPaths: ["validate-report.json"],
|
|
671
|
+
blocker: report.passed ? undefined : { code: "validate-failed", message: "Validate report contains failures." },
|
|
672
|
+
details: {
|
|
673
|
+
passed: report.passed,
|
|
674
|
+
scoreLenient: report.score.lenient,
|
|
675
|
+
scoreStrict: report.score.strict,
|
|
676
|
+
},
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
// Suppress static-analysis "imported but unused" — these are referenced via path
|
|
682
|
+
// composition in nested helpers and we keep the imports for clarity.
|
|
683
|
+
void getHarnessAgentsMdPath;
|
|
684
|
+
void getHarnessGoldenPrinciplesPath;
|