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
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { findExecutable } from "../../utils/executable.js";
|
|
6
|
+
|
|
7
|
+
interface PlaywrightResultEntry {
|
|
8
|
+
test: string;
|
|
9
|
+
file: string;
|
|
10
|
+
status: string;
|
|
11
|
+
duration: number;
|
|
12
|
+
error: string | null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface PlaywrightSummary {
|
|
16
|
+
total: number;
|
|
17
|
+
passed: number;
|
|
18
|
+
failed: number;
|
|
19
|
+
skipped: number;
|
|
20
|
+
duration: number;
|
|
21
|
+
failures: Array<{
|
|
22
|
+
test: string;
|
|
23
|
+
file: string;
|
|
24
|
+
error: string;
|
|
25
|
+
}>;
|
|
26
|
+
error?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function quoteCmdArgument(arg: string): string {
|
|
30
|
+
return `"${arg.replace(/"/g, '""')}"`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function spawnPlaywright(
|
|
34
|
+
playwrightBinary: string,
|
|
35
|
+
playwrightArgs: string[],
|
|
36
|
+
cwd: string,
|
|
37
|
+
baseUrl: string,
|
|
38
|
+
env: NodeJS.ProcessEnv,
|
|
39
|
+
encoding: BufferEncoding,
|
|
40
|
+
) {
|
|
41
|
+
const spawnOptions = {
|
|
42
|
+
cwd,
|
|
43
|
+
env: { ...env, BASE_URL: baseUrl },
|
|
44
|
+
encoding,
|
|
45
|
+
} as const;
|
|
46
|
+
|
|
47
|
+
if (process.platform === "win32" && /\.(cmd|bat)$/i.test(playwrightBinary)) {
|
|
48
|
+
const commandLine = [playwrightBinary, ...playwrightArgs].map(quoteCmdArgument).join(" ");
|
|
49
|
+
return spawnSync("cmd.exe", ["/d", "/s", "/c", commandLine], spawnOptions);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return spawnSync(playwrightBinary, playwrightArgs, spawnOptions);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function resolvePlaywrightBinary(cwd: string): string | null {
|
|
56
|
+
return findExecutable("playwright-cli", {
|
|
57
|
+
cwd,
|
|
58
|
+
localDirs: [path.join("node_modules", ".bin")],
|
|
59
|
+
preferLocal: true,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function collectTestResults(suite: any, parentTitle: string, results: PlaywrightResultEntry[]): void {
|
|
64
|
+
const suiteTitle = typeof suite?.title === "string" && suite.title.length > 0
|
|
65
|
+
? (parentTitle ? `${parentTitle} > ${suite.title}` : suite.title)
|
|
66
|
+
: parentTitle;
|
|
67
|
+
|
|
68
|
+
for (const spec of Array.isArray(suite?.specs) ? suite.specs : []) {
|
|
69
|
+
const specTitle = typeof spec?.title === "string" && spec.title.length > 0
|
|
70
|
+
? `${suiteTitle} > ${spec.title}`
|
|
71
|
+
: suiteTitle;
|
|
72
|
+
const file = `${spec?.file ?? ""}${spec?.line ? `:${spec.line}` : ""}`;
|
|
73
|
+
|
|
74
|
+
for (const test of Array.isArray(spec?.tests) ? spec.tests : []) {
|
|
75
|
+
for (const result of Array.isArray(test?.results) ? test.results : []) {
|
|
76
|
+
results.push({
|
|
77
|
+
test: specTitle,
|
|
78
|
+
file,
|
|
79
|
+
status: String(result?.status ?? "unknown"),
|
|
80
|
+
duration: Number(result?.duration ?? 0),
|
|
81
|
+
error: result?.error?.message ?? null,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
for (const child of Array.isArray(suite?.suites) ? suite.suites : []) {
|
|
88
|
+
collectTestResults(child, suiteTitle, results);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function summarizePlaywrightOutput(raw: string): PlaywrightSummary {
|
|
93
|
+
const parsed = JSON.parse(raw);
|
|
94
|
+
const results: PlaywrightResultEntry[] = [];
|
|
95
|
+
|
|
96
|
+
for (const suite of Array.isArray(parsed?.suites) ? parsed.suites : []) {
|
|
97
|
+
collectTestResults(suite, "", results);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const passed = results.filter((result) => result.status === "passed").length;
|
|
101
|
+
const failed = results.filter((result) => result.status === "failed" || result.status === "timedOut").length;
|
|
102
|
+
const skipped = results.filter((result) => result.status === "skipped").length;
|
|
103
|
+
const duration = results.reduce((sum, result) => sum + result.duration, 0);
|
|
104
|
+
const failures = results
|
|
105
|
+
.filter((result) => result.status === "failed" || result.status === "timedOut")
|
|
106
|
+
.map((result) => ({
|
|
107
|
+
test: result.test,
|
|
108
|
+
file: result.file,
|
|
109
|
+
error: result.error ?? "Unknown error",
|
|
110
|
+
}));
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
total: results.length,
|
|
114
|
+
passed,
|
|
115
|
+
failed,
|
|
116
|
+
skipped,
|
|
117
|
+
duration,
|
|
118
|
+
failures,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function buildErrorSummary(message: string): PlaywrightSummary {
|
|
123
|
+
return {
|
|
124
|
+
total: 0,
|
|
125
|
+
passed: 0,
|
|
126
|
+
failed: 0,
|
|
127
|
+
skipped: 0,
|
|
128
|
+
duration: 0,
|
|
129
|
+
failures: [],
|
|
130
|
+
error: message,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function runE2eTests(
|
|
135
|
+
testDir: string,
|
|
136
|
+
baseUrl: string,
|
|
137
|
+
testFilter: string,
|
|
138
|
+
cwd: string,
|
|
139
|
+
): { exitCode: number; stdout: string } {
|
|
140
|
+
const resultsDir = path.resolve(testDir, "..", "results");
|
|
141
|
+
fs.mkdirSync(resultsDir, { recursive: true });
|
|
142
|
+
|
|
143
|
+
const rawOutputPath = path.join(resultsDir, "raw-results.json");
|
|
144
|
+
const playwrightStderrPath = path.join(resultsDir, "playwright-stderr.log");
|
|
145
|
+
const summaryPath = path.join(resultsDir, "summary.json");
|
|
146
|
+
const nodeParseStderrPath = path.join(resultsDir, "node-parse-stderr.log");
|
|
147
|
+
|
|
148
|
+
const playwrightBinary = resolvePlaywrightBinary(cwd);
|
|
149
|
+
if (!playwrightBinary) {
|
|
150
|
+
const stdout = JSON.stringify(
|
|
151
|
+
buildErrorSummary("playwright not found. Install with: npm install -g @playwright/cli@latest"),
|
|
152
|
+
);
|
|
153
|
+
return { exitCode: 1, stdout };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const playwrightArgs = [
|
|
157
|
+
"test",
|
|
158
|
+
testDir,
|
|
159
|
+
"--reporter=json",
|
|
160
|
+
`--output=${resultsDir}`,
|
|
161
|
+
];
|
|
162
|
+
|
|
163
|
+
if (testFilter.length > 0) {
|
|
164
|
+
playwrightArgs.push("--grep", testFilter);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const result = spawnPlaywright(
|
|
168
|
+
playwrightBinary,
|
|
169
|
+
playwrightArgs,
|
|
170
|
+
cwd,
|
|
171
|
+
baseUrl,
|
|
172
|
+
process.env,
|
|
173
|
+
"utf8",
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
const rawOutput = result.stdout ?? "";
|
|
177
|
+
const stderrOutput = result.stderr ?? "";
|
|
178
|
+
fs.writeFileSync(rawOutputPath, rawOutput);
|
|
179
|
+
fs.writeFileSync(playwrightStderrPath, stderrOutput);
|
|
180
|
+
|
|
181
|
+
if (rawOutput.trim().length === 0) {
|
|
182
|
+
const stdout = JSON.stringify(
|
|
183
|
+
buildErrorSummary(
|
|
184
|
+
`playwright produced no output (exit code: ${result.status ?? 1}). See ${playwrightStderrPath} for details.`,
|
|
185
|
+
),
|
|
186
|
+
);
|
|
187
|
+
return { exitCode: 1, stdout };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
try {
|
|
191
|
+
const summary = summarizePlaywrightOutput(rawOutput);
|
|
192
|
+
const stdout = JSON.stringify(summary);
|
|
193
|
+
fs.writeFileSync(summaryPath, stdout);
|
|
194
|
+
return {
|
|
195
|
+
exitCode: summary.failed > 0 ? 2 : 0,
|
|
196
|
+
stdout,
|
|
197
|
+
};
|
|
198
|
+
} catch (error) {
|
|
199
|
+
const message = error instanceof Error ? error.stack ?? error.message : String(error);
|
|
200
|
+
fs.writeFileSync(nodeParseStderrPath, message);
|
|
201
|
+
const stdout = JSON.stringify(
|
|
202
|
+
buildErrorSummary(`Failed to parse playwright output. See ${nodeParseStderrPath} for details.`),
|
|
203
|
+
);
|
|
204
|
+
return { exitCode: 1, stdout };
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function main(): void {
|
|
209
|
+
const [testDir, baseUrl, testFilter = ""] = process.argv.slice(2);
|
|
210
|
+
if (!testDir || !baseUrl) {
|
|
211
|
+
const stdout = JSON.stringify(
|
|
212
|
+
buildErrorSummary("Usage: run-e2e-tests.ts <test_dir> <base_url> [test_filter]"),
|
|
213
|
+
);
|
|
214
|
+
console.log(stdout);
|
|
215
|
+
process.exit(1);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const result = runE2eTests(testDir, baseUrl, testFilter, process.cwd());
|
|
219
|
+
console.log(result.stdout);
|
|
220
|
+
process.exit(result.exitCode);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const isMain = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
224
|
+
if (isMain) {
|
|
225
|
+
main();
|
|
226
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { spawn } from "node:child_process";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import {
|
|
6
|
+
DEV_SERVER_PID_FILENAME,
|
|
7
|
+
getShellInvocation,
|
|
8
|
+
isServerReachable,
|
|
9
|
+
killProcessTree,
|
|
10
|
+
} from "./dev-server-utils.js";
|
|
11
|
+
|
|
12
|
+
export interface StartDevServerResult {
|
|
13
|
+
pid: number | null;
|
|
14
|
+
url: string;
|
|
15
|
+
ready: boolean;
|
|
16
|
+
note?: string;
|
|
17
|
+
error?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function buildResult(result: StartDevServerResult): string {
|
|
21
|
+
return JSON.stringify(result);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function startDevServer(
|
|
25
|
+
cwd: string,
|
|
26
|
+
devCommand: string,
|
|
27
|
+
port: number,
|
|
28
|
+
timeoutSeconds: number,
|
|
29
|
+
sessionDir: string,
|
|
30
|
+
): Promise<{ exitCode: number; output: string }> {
|
|
31
|
+
const url = `http://localhost:${port}`;
|
|
32
|
+
if (await isServerReachable(port)) {
|
|
33
|
+
return {
|
|
34
|
+
exitCode: 0,
|
|
35
|
+
output: buildResult({
|
|
36
|
+
pid: null,
|
|
37
|
+
url,
|
|
38
|
+
ready: true,
|
|
39
|
+
note: "Server already running",
|
|
40
|
+
}),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
fs.mkdirSync(sessionDir, { recursive: true });
|
|
45
|
+
const pidFile = path.join(sessionDir, DEV_SERVER_PID_FILENAME);
|
|
46
|
+
const logPath = path.join(sessionDir, "dev-server.log");
|
|
47
|
+
const logFd = fs.openSync(logPath, "a");
|
|
48
|
+
const shell = getShellInvocation(devCommand);
|
|
49
|
+
const child = spawn(shell.command, shell.args, {
|
|
50
|
+
cwd,
|
|
51
|
+
detached: true,
|
|
52
|
+
stdio: ["ignore", logFd, logFd],
|
|
53
|
+
env: process.env,
|
|
54
|
+
});
|
|
55
|
+
child.unref();
|
|
56
|
+
fs.closeSync(logFd);
|
|
57
|
+
|
|
58
|
+
if (!child.pid) {
|
|
59
|
+
return {
|
|
60
|
+
exitCode: 1,
|
|
61
|
+
output: buildResult({
|
|
62
|
+
pid: null,
|
|
63
|
+
url,
|
|
64
|
+
ready: false,
|
|
65
|
+
error: "Dev server failed to start",
|
|
66
|
+
}),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
fs.writeFileSync(pidFile, String(child.pid));
|
|
71
|
+
|
|
72
|
+
for (let attempt = 0; attempt < timeoutSeconds; attempt += 1) {
|
|
73
|
+
if (child.exitCode !== null) {
|
|
74
|
+
fs.rmSync(pidFile, { force: true });
|
|
75
|
+
return {
|
|
76
|
+
exitCode: 1,
|
|
77
|
+
output: buildResult({
|
|
78
|
+
pid: child.pid,
|
|
79
|
+
url,
|
|
80
|
+
ready: false,
|
|
81
|
+
error: "Server process exited",
|
|
82
|
+
}),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (await isServerReachable(port)) {
|
|
87
|
+
return {
|
|
88
|
+
exitCode: 0,
|
|
89
|
+
output: buildResult({
|
|
90
|
+
pid: child.pid,
|
|
91
|
+
url,
|
|
92
|
+
ready: true,
|
|
93
|
+
}),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
await new Promise((resolve) => setTimeout(resolve, 1_000));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
await killProcessTree(child.pid);
|
|
101
|
+
fs.rmSync(pidFile, { force: true });
|
|
102
|
+
return {
|
|
103
|
+
exitCode: 1,
|
|
104
|
+
output: buildResult({
|
|
105
|
+
pid: child.pid,
|
|
106
|
+
url,
|
|
107
|
+
ready: false,
|
|
108
|
+
error: `Timeout after ${timeoutSeconds}s`,
|
|
109
|
+
}),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function main(): Promise<void> {
|
|
114
|
+
const [cwd, devCommand, portArg, timeoutArg = "60", sessionDir = "."] = process.argv.slice(2);
|
|
115
|
+
const port = Number.parseInt(portArg ?? "", 10);
|
|
116
|
+
const timeoutSeconds = Number.parseInt(timeoutArg, 10);
|
|
117
|
+
|
|
118
|
+
if (!cwd || !devCommand || !Number.isInteger(port) || !Number.isInteger(timeoutSeconds)) {
|
|
119
|
+
console.log(
|
|
120
|
+
buildResult({
|
|
121
|
+
pid: null,
|
|
122
|
+
url: Number.isInteger(port) ? `http://localhost:${port}` : "http://localhost:0",
|
|
123
|
+
ready: false,
|
|
124
|
+
error: "Usage: start-dev-server.ts <cwd> <dev_command> <port> <timeout_seconds> <session_dir>",
|
|
125
|
+
}),
|
|
126
|
+
);
|
|
127
|
+
process.exit(1);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const result = await startDevServer(cwd, devCommand, port, timeoutSeconds, sessionDir);
|
|
131
|
+
console.log(result.output);
|
|
132
|
+
process.exit(result.exitCode);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const isMain = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
136
|
+
if (isMain) {
|
|
137
|
+
void main();
|
|
138
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import {
|
|
5
|
+
DEV_SERVER_PID_FILENAME,
|
|
6
|
+
killProcessTree,
|
|
7
|
+
processExists,
|
|
8
|
+
} from "./dev-server-utils.js";
|
|
9
|
+
|
|
10
|
+
export interface StopDevServerResult {
|
|
11
|
+
stopped: boolean;
|
|
12
|
+
pid?: number;
|
|
13
|
+
note?: string;
|
|
14
|
+
error?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function buildResult(result: StopDevServerResult): string {
|
|
18
|
+
return JSON.stringify(result);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function stopDevServer(sessionDir: string): Promise<{ exitCode: number; output: string }> {
|
|
22
|
+
const pidFile = path.join(sessionDir, DEV_SERVER_PID_FILENAME);
|
|
23
|
+
if (!fs.existsSync(pidFile)) {
|
|
24
|
+
return {
|
|
25
|
+
exitCode: 0,
|
|
26
|
+
output: buildResult({ stopped: false, error: "No PID file found" }),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const pidText = fs.readFileSync(pidFile, "utf8").trim();
|
|
31
|
+
if (pidText.length === 0) {
|
|
32
|
+
return {
|
|
33
|
+
exitCode: 0,
|
|
34
|
+
output: buildResult({ stopped: false, error: "Empty PID file" }),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const pid = Number.parseInt(pidText, 10);
|
|
39
|
+
if (!Number.isInteger(pid) || pid <= 0) {
|
|
40
|
+
fs.rmSync(pidFile, { force: true });
|
|
41
|
+
return {
|
|
42
|
+
exitCode: 0,
|
|
43
|
+
output: buildResult({ stopped: false, error: `Invalid PID file: ${pidText}` }),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (!processExists(pid)) {
|
|
48
|
+
fs.rmSync(pidFile, { force: true });
|
|
49
|
+
return {
|
|
50
|
+
exitCode: 0,
|
|
51
|
+
output: buildResult({
|
|
52
|
+
stopped: true,
|
|
53
|
+
pid,
|
|
54
|
+
note: "Process was already dead",
|
|
55
|
+
}),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
await killProcessTree(pid);
|
|
60
|
+
fs.rmSync(pidFile, { force: true });
|
|
61
|
+
return {
|
|
62
|
+
exitCode: 0,
|
|
63
|
+
output: buildResult({ stopped: true, pid }),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function main(): Promise<void> {
|
|
68
|
+
const [sessionDir = "."] = process.argv.slice(2);
|
|
69
|
+
const result = await stopDevServer(sessionDir);
|
|
70
|
+
console.log(result.output);
|
|
71
|
+
process.exit(result.exitCode);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const isMain = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
75
|
+
if (isMain) {
|
|
76
|
+
void main();
|
|
77
|
+
}
|
package/src/qa/session.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import type { E2ePhase, E2ePhaseStatus, E2eSessionLedger, E2eQaConfig } from "./types.js";
|
|
4
3
|
import type { PlatformPaths } from "../platform/types.js";
|
|
5
|
-
import {
|
|
4
|
+
import type { WorkspaceTarget } from "../types.js";
|
|
5
|
+
import { createSession, generateSessionId, getSessionDir, updateSession } from "../storage/qa-sessions.js";
|
|
6
|
+
import type { E2ePhase, E2ePhaseStatus, E2eQaConfig, E2eSessionLedger } from "./types.js";
|
|
6
7
|
|
|
7
8
|
const PHASE_ORDER: E2ePhase[] = ["flow-discovery", "test-generation", "execution", "reporting"];
|
|
8
9
|
|
|
@@ -14,7 +15,12 @@ const PHASE_LABELS: Record<E2ePhase, string> = {
|
|
|
14
15
|
};
|
|
15
16
|
|
|
16
17
|
/** Create a new E2E QA session with all phases pending */
|
|
17
|
-
export function createNewE2eSession(
|
|
18
|
+
export function createNewE2eSession(
|
|
19
|
+
paths: PlatformPaths,
|
|
20
|
+
cwd: string,
|
|
21
|
+
config: E2eQaConfig,
|
|
22
|
+
target?: WorkspaceTarget,
|
|
23
|
+
): E2eSessionLedger {
|
|
18
24
|
const now = new Date().toISOString();
|
|
19
25
|
const ledger: E2eSessionLedger = {
|
|
20
26
|
id: generateSessionId(),
|
|
@@ -34,10 +40,9 @@ export function createNewE2eSession(paths: PlatformPaths, cwd: string, config: E
|
|
|
34
40
|
config,
|
|
35
41
|
};
|
|
36
42
|
|
|
37
|
-
|
|
38
|
-
createSession(paths, cwd, ledger);
|
|
43
|
+
createSession(paths, cwd, ledger, target);
|
|
39
44
|
|
|
40
|
-
const sessionDir = paths
|
|
45
|
+
const sessionDir = getSessionDir(paths, cwd, ledger.id, target);
|
|
41
46
|
fs.mkdirSync(path.join(sessionDir, "tests"), { recursive: true });
|
|
42
47
|
fs.mkdirSync(path.join(sessionDir, "screenshots"), { recursive: true });
|
|
43
48
|
|
|
@@ -51,6 +56,7 @@ export function advanceE2ePhase(
|
|
|
51
56
|
ledger: E2eSessionLedger,
|
|
52
57
|
phase: E2ePhase,
|
|
53
58
|
status: E2ePhaseStatus,
|
|
59
|
+
target?: WorkspaceTarget,
|
|
54
60
|
): E2eSessionLedger {
|
|
55
61
|
const now = new Date().toISOString();
|
|
56
62
|
const record = { ...ledger.phases[phase] };
|
|
@@ -68,7 +74,7 @@ export function advanceE2ePhase(
|
|
|
68
74
|
updatedAt: now,
|
|
69
75
|
phases: { ...ledger.phases, [phase]: record },
|
|
70
76
|
};
|
|
71
|
-
updateSession(paths, cwd, updated);
|
|
77
|
+
updateSession(paths, cwd, updated, target);
|
|
72
78
|
return updated;
|
|
73
79
|
}
|
|
74
80
|
|
package/src/quality/ai-setup.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { loadModelConfig } from "../config/model-config.js";
|
|
2
2
|
import { modelRegistry } from "../config/model-registry-instance.js";
|
|
3
3
|
import { createModelBridge, resolveModelForAction } from "../config/model-resolver.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import { renderSchemaText } from "../ai/schema-text.js";
|
|
5
|
+
import { parseStructuredOutput, runWithOutputValidation } from "../ai/structured-output.js";
|
|
6
|
+
import { QualityGatesSchema } from "./schemas.js";
|
|
6
7
|
import type { Platform } from "../platform/types.js";
|
|
7
8
|
import type { ProjectFacts, QualityGatesConfig, SetupProposal } from "../types.js";
|
|
8
|
-
import { runStructuredAgentSession } from "./ai-session.js";
|
|
9
9
|
|
|
10
10
|
modelRegistry.register({
|
|
11
11
|
id: "quality-gate-setup",
|
|
@@ -14,21 +14,22 @@ modelRegistry.register({
|
|
|
14
14
|
harnessRoleHint: "slow",
|
|
15
15
|
});
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
const QUALITY_GATES_SCHEMA_TEXT = renderSchemaText(QualityGatesSchema);
|
|
18
|
+
|
|
19
|
+
export function buildAiSetupPrompt(projectFacts: ProjectFacts, proposal: SetupProposal): string {
|
|
18
20
|
return [
|
|
19
21
|
"You are configuring Supipowers review quality gates for a repository.",
|
|
20
|
-
"Return JSON only as a QualityGatesConfig object
|
|
21
|
-
|
|
22
|
-
"Available gates and shapes:",
|
|
23
|
-
'- "lsp-diagnostics": {"enabled": true|false}',
|
|
24
|
-
'- "lint": {"enabled": true, "command": "..."} or {"enabled": false}',
|
|
25
|
-
'- "typecheck": {"enabled": true, "command": "..."} or {"enabled": false}',
|
|
26
|
-
'- "format": {"enabled": true, "command": "..."} or {"enabled": false}',
|
|
27
|
-
'- "test-suite": {"enabled": true, "command": "..."} or {"enabled": false}',
|
|
28
|
-
'- "build": {"enabled": true, "command": "..."} or {"enabled": false}',
|
|
22
|
+
"Return JSON only as a QualityGatesConfig object matching this schema:",
|
|
23
|
+
QUALITY_GATES_SCHEMA_TEXT,
|
|
29
24
|
"",
|
|
30
25
|
"Rules:",
|
|
31
26
|
"- Prefer commands that already exist in package.json scripts.",
|
|
27
|
+
"- Command-driven gates must use canonical target-aware runs. When enabled, return runs with command + target selectors instead of a single command string.",
|
|
28
|
+
"- packageScripts contains only commands shared across every discovered target; use targets to inspect package-specific scripts.",
|
|
29
|
+
"- /supi:checks All must cover the root target and every workspace target deterministically.",
|
|
30
|
+
"- /supi:checks runs commands from each target's package root, so add target-specific runs when root/shared commands do not cover a package.",
|
|
31
|
+
"- Use target.scope all-targets only when the same command truly works for every target.",
|
|
32
|
+
"- Use target.scope root, all-workspaces, or workspace with relativeDir when coverage differs by target.",
|
|
32
33
|
"- Never invent a mutating format command such as --write or lint --fix.",
|
|
33
34
|
"- Prefer checks that verify correctness during review: lsp, lint, typecheck, format check, tests, and build.",
|
|
34
35
|
"- Keep deterministic suggestions unless you have repository evidence to improve them.",
|
|
@@ -40,17 +41,11 @@ function buildAiSetupPrompt(projectFacts: ProjectFacts, proposal: SetupProposal)
|
|
|
40
41
|
"",
|
|
41
42
|
"Deterministic baseline proposal:",
|
|
42
43
|
JSON.stringify(proposal.gates, null, 2),
|
|
44
|
+
...(proposal.notes && proposal.notes.length > 0
|
|
45
|
+
? ["", "Deterministic baseline notes:", ...proposal.notes.map((note) => `- ${note}`)]
|
|
46
|
+
: []),
|
|
43
47
|
].join("\n");
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function parseAiSetupSuggestion(raw: string): QualityGatesConfig {
|
|
47
|
-
const parsed = JSON.parse(stripMarkdownCodeFence(raw)) as QualityGatesConfig;
|
|
48
|
-
const validation = validateQualityGates(parsed);
|
|
49
|
-
if (!validation.valid) {
|
|
50
|
-
throw new Error(validation.errors.join("\n"));
|
|
51
|
-
}
|
|
52
48
|
|
|
53
|
-
return parsed;
|
|
54
49
|
}
|
|
55
50
|
|
|
56
51
|
export async function suggestQualityGatesWithAi(input: {
|
|
@@ -67,20 +62,27 @@ export async function suggestQualityGatesWithAi(input: {
|
|
|
67
62
|
createModelBridge(input.platform),
|
|
68
63
|
);
|
|
69
64
|
|
|
70
|
-
const result = await
|
|
65
|
+
const result = await runWithOutputValidation<QualityGatesConfig>(
|
|
71
66
|
input.platform.createAgentSession.bind(input.platform),
|
|
72
67
|
{
|
|
73
68
|
cwd: input.cwd,
|
|
74
69
|
prompt: buildAiSetupPrompt(input.projectFacts, input.proposal),
|
|
70
|
+
schema: QUALITY_GATES_SCHEMA_TEXT,
|
|
71
|
+
parse: (raw) => parseStructuredOutput<QualityGatesConfig>(raw, QualityGatesSchema),
|
|
75
72
|
model: resolvedModel.model,
|
|
76
73
|
thinkingLevel: resolvedModel.thinkingLevel,
|
|
77
74
|
timeoutMs: 120_000,
|
|
75
|
+
reliability: {
|
|
76
|
+
paths: input.platform.paths,
|
|
77
|
+
cwd: input.cwd,
|
|
78
|
+
command: "quality-setup",
|
|
79
|
+
},
|
|
78
80
|
},
|
|
79
81
|
);
|
|
80
82
|
|
|
81
|
-
if (result.status
|
|
83
|
+
if (result.status === "blocked") {
|
|
82
84
|
throw new Error(result.error);
|
|
83
85
|
}
|
|
84
86
|
|
|
85
|
-
return
|
|
87
|
+
return result.output;
|
|
86
88
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// src/quality/contracts.ts
|
|
2
|
+
//
|
|
3
|
+
// TypeBox contracts for AI-driven quality-gate workflows. Embedded into
|
|
4
|
+
// prompts via ai/schema-text.ts and used by ai/structured-output.ts to
|
|
5
|
+
// validate model output with retry-on-invalid feedback.
|
|
6
|
+
|
|
7
|
+
import { Type, type Static } from "@sinclair/typebox";
|
|
8
|
+
|
|
9
|
+
const ISSUE_SEVERITIES = ["error", "warning", "info"] as const;
|
|
10
|
+
const RECOMMENDED_STATUSES = ["passed", "failed", "blocked"] as const;
|
|
11
|
+
|
|
12
|
+
export const AiReviewIssueSchema = Type.Object(
|
|
13
|
+
{
|
|
14
|
+
severity: Type.Union(ISSUE_SEVERITIES.map((value) => Type.Literal(value))),
|
|
15
|
+
message: Type.String({ minLength: 1 }),
|
|
16
|
+
file: Type.Optional(Type.String()),
|
|
17
|
+
line: Type.Optional(Type.Number()),
|
|
18
|
+
detail: Type.Optional(Type.String()),
|
|
19
|
+
},
|
|
20
|
+
{ additionalProperties: false },
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
export const AiReviewOutputSchema = Type.Object(
|
|
24
|
+
{
|
|
25
|
+
summary: Type.String(),
|
|
26
|
+
issues: Type.Array(AiReviewIssueSchema),
|
|
27
|
+
recommendedStatus: Type.Union(
|
|
28
|
+
RECOMMENDED_STATUSES.map((value) => Type.Literal(value)),
|
|
29
|
+
),
|
|
30
|
+
},
|
|
31
|
+
{ additionalProperties: false },
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
export type AiReviewOutput = Static<typeof AiReviewOutputSchema>;
|