supipowers 0.4.0 → 0.6.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/package.json +3 -3
- package/skills/context-mode/SKILL.md +38 -0
- package/skills/qa-strategy/SKILL.md +103 -21
- package/src/commands/config.ts +23 -2
- package/src/commands/fix-pr.ts +1 -1
- package/src/commands/plan.ts +1 -1
- package/src/commands/qa.ts +232 -148
- package/src/commands/release.ts +1 -1
- package/src/commands/review.ts +1 -1
- package/src/commands/run.ts +9 -4
- package/src/commands/supi.ts +1 -1
- package/src/config/defaults.ts +11 -0
- package/src/config/schema.ts +11 -0
- package/src/context-mode/compressor.ts +200 -0
- package/src/context-mode/detector.ts +43 -0
- package/src/context-mode/event-extractor.ts +170 -0
- package/src/context-mode/event-store.ts +168 -0
- package/src/context-mode/hooks.ts +176 -0
- package/src/context-mode/installer.ts +71 -0
- package/src/context-mode/snapshot-builder.ts +127 -0
- package/src/discipline/debugging.ts +7 -7
- package/src/discipline/receiving-review.ts +5 -5
- package/src/discipline/tdd.ts +2 -2
- package/src/discipline/verification.ts +9 -9
- package/src/git/base-branch.ts +30 -0
- package/src/git/branch-finish.ts +12 -3
- package/src/git/sanitize.ts +19 -0
- package/src/git/worktree.ts +38 -11
- package/src/index.ts +8 -1
- package/src/orchestrator/agent-prompts.ts +15 -7
- package/src/orchestrator/conflict-resolver.ts +3 -2
- package/src/orchestrator/dispatcher.ts +76 -21
- package/src/orchestrator/prompts.ts +46 -6
- package/src/planning/plan-reviewer.ts +1 -1
- package/src/planning/plan-writer-prompt.ts +6 -9
- package/src/planning/prompt-builder.ts +17 -16
- package/src/planning/spec-reviewer.ts +2 -2
- package/src/qa/config.ts +43 -0
- package/src/qa/matrix.ts +84 -0
- package/src/qa/prompt-builder.ts +212 -0
- package/src/qa/scripts/detect-app-type.sh +68 -0
- package/src/qa/scripts/discover-routes.sh +143 -0
- package/src/qa/scripts/ensure-playwright.sh +38 -0
- package/src/qa/scripts/run-e2e-tests.sh +99 -0
- package/src/qa/scripts/start-dev-server.sh +46 -0
- package/src/qa/scripts/stop-dev-server.sh +36 -0
- package/src/qa/session.ts +39 -55
- package/src/qa/types.ts +97 -0
- package/src/storage/qa-sessions.ts +9 -9
- package/src/types.ts +22 -70
- package/src/qa/detector.ts +0 -61
- package/src/qa/phases/discovery.ts +0 -34
- package/src/qa/phases/execution.ts +0 -65
- package/src/qa/phases/matrix.ts +0 -41
- package/src/qa/phases/reporting.ts +0 -71
- package/src/qa/report.ts +0 -22
- package/src/qa/runner.ts +0 -46
package/src/commands/qa.ts
CHANGED
|
@@ -1,184 +1,268 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@oh-my-pi/pi-coding-agent";
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
} from "../qa/session.js";
|
|
12
|
-
import { buildDiscoveryPrompt } from "../qa/phases/discovery.js";
|
|
13
|
-
import { buildMatrixPrompt } from "../qa/phases/matrix.js";
|
|
14
|
-
import { buildExecutionPrompt } from "../qa/phases/execution.js";
|
|
15
|
-
import { buildReportingPrompt } from "../qa/phases/reporting.js";
|
|
16
|
-
import type { QaPhase, QaSessionLedger } from "../types.js";
|
|
17
|
-
|
|
18
|
-
const PHASE_LABELS: Record<QaPhase, string> = {
|
|
19
|
-
discovery: "Discovery — Scan for test cases",
|
|
20
|
-
matrix: "Matrix — Build traceability matrix",
|
|
21
|
-
execution: "Execution — Run tests",
|
|
22
|
-
reporting: "Reporting — Generate summary",
|
|
23
|
-
};
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import { notifyInfo, notifyError, notifyWarning } from "../notifications/renderer.js";
|
|
5
|
+
import { loadE2eQaConfig, saveE2eQaConfig, DEFAULT_E2E_QA_CONFIG } from "../qa/config.js";
|
|
6
|
+
import { loadE2eMatrix } from "../qa/matrix.js";
|
|
7
|
+
import { createNewE2eSession } from "../qa/session.js";
|
|
8
|
+
import { buildE2eOrchestratorPrompt } from "../qa/prompt-builder.js";
|
|
9
|
+
import { findActiveSession, getSessionDir } from "../storage/qa-sessions.js";
|
|
10
|
+
import type { E2eQaConfig, AppType, E2eRegression } from "../qa/types.js";
|
|
24
11
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
async handler(args, ctx) {
|
|
29
|
-
const framework = detectAndCache(ctx.cwd);
|
|
30
|
-
|
|
31
|
-
if (!framework) {
|
|
32
|
-
notifyError(
|
|
33
|
-
ctx,
|
|
34
|
-
"No test framework detected",
|
|
35
|
-
"Configure manually via /supi:config"
|
|
36
|
-
);
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
12
|
+
function getScriptsDir(): string {
|
|
13
|
+
return path.join(path.dirname(new URL(import.meta.url).pathname), "..", "qa", "scripts");
|
|
14
|
+
}
|
|
39
15
|
|
|
40
|
-
|
|
41
|
-
|
|
16
|
+
function findSkillPath(skillName: string): string | null {
|
|
17
|
+
const candidates = [
|
|
18
|
+
path.join(process.cwd(), "skills", skillName, "SKILL.md"),
|
|
19
|
+
path.join(path.dirname(new URL(import.meta.url).pathname), "..", "..", "skills", skillName, "SKILL.md"),
|
|
20
|
+
];
|
|
21
|
+
for (const p of candidates) {
|
|
22
|
+
if (fs.existsSync(p)) return p;
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
42
26
|
|
|
43
|
-
|
|
44
|
-
|
|
27
|
+
const APP_TYPE_OPTIONS = [
|
|
28
|
+
"nextjs-app — Next.js App Router",
|
|
29
|
+
"nextjs-pages — Next.js Pages Router",
|
|
30
|
+
"react-router — React with React Router",
|
|
31
|
+
"vite — Vite-based app",
|
|
32
|
+
"express — Express.js server",
|
|
33
|
+
"generic — Other web app",
|
|
34
|
+
];
|
|
45
35
|
|
|
46
|
-
|
|
47
|
-
|
|
36
|
+
const BROWSER_OPTIONS = [
|
|
37
|
+
"chromium (recommended)",
|
|
38
|
+
"firefox",
|
|
39
|
+
"webkit",
|
|
40
|
+
];
|
|
48
41
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
sessionOptions.push(`Resume ${activeSession.id} (${next ?? "all phases done"} pending)`);
|
|
55
|
-
}
|
|
42
|
+
const RETRY_OPTIONS = [
|
|
43
|
+
"1",
|
|
44
|
+
"2 (recommended)",
|
|
45
|
+
"3",
|
|
46
|
+
];
|
|
56
47
|
|
|
57
|
-
|
|
48
|
+
async function runSetupWizard(
|
|
49
|
+
ctx: any,
|
|
50
|
+
detectedAppType: string | null,
|
|
51
|
+
detectedDevCommand: string | null,
|
|
52
|
+
detectedPort: number | null,
|
|
53
|
+
): Promise<E2eQaConfig | null> {
|
|
54
|
+
// 1. App type
|
|
55
|
+
const appTypeChoice = await ctx.ui.select(
|
|
56
|
+
"App type",
|
|
57
|
+
APP_TYPE_OPTIONS,
|
|
58
|
+
{ helpText: detectedAppType ? `Auto-detected: ${detectedAppType}` : "Select your web app framework" },
|
|
59
|
+
);
|
|
60
|
+
if (!appTypeChoice) return null;
|
|
61
|
+
const appType = appTypeChoice.split(" ")[0] as AppType;
|
|
58
62
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
63
|
+
// 2. Dev command
|
|
64
|
+
const defaultDev = detectedDevCommand || "npm run dev";
|
|
65
|
+
const devCommand = await ctx.ui.input(
|
|
66
|
+
"Dev server command",
|
|
67
|
+
defaultDev,
|
|
68
|
+
{ helpText: "Command to start your development server" },
|
|
69
|
+
);
|
|
70
|
+
if (devCommand === undefined) return null;
|
|
66
71
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
+
// 3. Port
|
|
73
|
+
const defaultPort = String(detectedPort || 3000);
|
|
74
|
+
const portStr = await ctx.ui.input(
|
|
75
|
+
"Dev server port",
|
|
76
|
+
defaultPort,
|
|
77
|
+
{ helpText: "Port your dev server runs on" },
|
|
78
|
+
);
|
|
79
|
+
if (portStr === undefined) return null;
|
|
80
|
+
const port = parseInt(portStr, 10) || 3000;
|
|
72
81
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
82
|
+
// 4. Browser
|
|
83
|
+
const browserChoice = await ctx.ui.select(
|
|
84
|
+
"Browser for E2E tests",
|
|
85
|
+
BROWSER_OPTIONS,
|
|
86
|
+
{ helpText: "Playwright browser to use" },
|
|
87
|
+
);
|
|
88
|
+
if (!browserChoice) return null;
|
|
89
|
+
const browser = browserChoice.split(" ")[0] as "chromium" | "firefox" | "webkit";
|
|
78
90
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
91
|
+
// 5. Max retries
|
|
92
|
+
const retryChoice = await ctx.ui.select(
|
|
93
|
+
"Max test retries",
|
|
94
|
+
RETRY_OPTIONS,
|
|
95
|
+
{ helpText: "How many times to retry failing tests" },
|
|
96
|
+
);
|
|
97
|
+
if (!retryChoice) return null;
|
|
98
|
+
const maxRetries = parseInt(retryChoice, 10);
|
|
83
99
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
100
|
+
return {
|
|
101
|
+
app: {
|
|
102
|
+
type: appType,
|
|
103
|
+
devCommand: devCommand || defaultDev,
|
|
104
|
+
port,
|
|
105
|
+
baseUrl: `http://localhost:${port}`,
|
|
106
|
+
},
|
|
107
|
+
playwright: {
|
|
108
|
+
browser,
|
|
109
|
+
headless: true,
|
|
110
|
+
timeout: 30000,
|
|
111
|
+
},
|
|
112
|
+
execution: {
|
|
113
|
+
maxRetries,
|
|
114
|
+
maxFlows: 20,
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
}
|
|
87
118
|
|
|
88
|
-
|
|
89
|
-
|
|
119
|
+
export function registerQaCommand(pi: ExtensionAPI): void {
|
|
120
|
+
pi.registerCommand("supi:qa", {
|
|
121
|
+
description: "Run autonomous E2E product testing pipeline with playwright",
|
|
122
|
+
async handler(args, ctx) {
|
|
123
|
+
const scriptsDir = getScriptsDir();
|
|
90
124
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
125
|
+
// ── Step 1: Detect app type ─────────────────────────────────────
|
|
126
|
+
let detectedType: string | null = null;
|
|
127
|
+
let detectedDevCommand: string | null = null;
|
|
128
|
+
let detectedPort: number | null = null;
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
const detectResult = await pi.exec("bash", [
|
|
132
|
+
path.join(scriptsDir, "detect-app-type.sh"),
|
|
133
|
+
ctx.cwd,
|
|
134
|
+
], { cwd: ctx.cwd });
|
|
135
|
+
|
|
136
|
+
if (detectResult.code === 0) {
|
|
137
|
+
const detected = JSON.parse(detectResult.stdout.trim());
|
|
138
|
+
detectedType = detected.type;
|
|
139
|
+
detectedDevCommand = detected.devCommand;
|
|
140
|
+
detectedPort = detected.port;
|
|
94
141
|
}
|
|
142
|
+
} catch { /* proceed without detection */ }
|
|
143
|
+
|
|
144
|
+
// ── Step 2: Ensure playwright ────────────────────────────────────
|
|
145
|
+
try {
|
|
146
|
+
const pwResult = await pi.exec("bash", [
|
|
147
|
+
path.join(scriptsDir, "ensure-playwright.sh"),
|
|
148
|
+
ctx.cwd,
|
|
149
|
+
], { cwd: ctx.cwd });
|
|
95
150
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
phaseOptions.push(PHASE_LABELS[nextPhase]);
|
|
151
|
+
if (pwResult.code !== 0) {
|
|
152
|
+
notifyWarning(ctx, "Playwright setup issue", "Could not verify playwright installation. The agent will handle it.");
|
|
99
153
|
}
|
|
154
|
+
} catch {
|
|
155
|
+
notifyWarning(ctx, "Playwright check skipped", "Will be handled during execution");
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// ── Step 3: Load or create config ────────────────────────────────
|
|
159
|
+
let config = loadE2eQaConfig(ctx.cwd);
|
|
160
|
+
|
|
161
|
+
if (!config && ctx.hasUI) {
|
|
162
|
+
config = await runSetupWizard(ctx, detectedType, detectedDevCommand, detectedPort);
|
|
163
|
+
if (!config) return; // user cancelled
|
|
164
|
+
saveE2eQaConfig(ctx.cwd, config);
|
|
165
|
+
ctx.ui.notify("E2E QA config saved to .omp/supipowers/e2e-qa.json", "info");
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (!config) {
|
|
169
|
+
// Use defaults with detected values
|
|
170
|
+
config = {
|
|
171
|
+
...DEFAULT_E2E_QA_CONFIG,
|
|
172
|
+
app: {
|
|
173
|
+
type: (detectedType as AppType) || "generic",
|
|
174
|
+
devCommand: detectedDevCommand || "npm run dev",
|
|
175
|
+
port: detectedPort || 3000,
|
|
176
|
+
baseUrl: `http://localhost:${detectedPort || 3000}`,
|
|
177
|
+
},
|
|
178
|
+
};
|
|
179
|
+
}
|
|
100
180
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
if (
|
|
117
|
-
|
|
118
|
-
|
|
181
|
+
// ── Step 4: Check for unresolved regressions ─────────────────────
|
|
182
|
+
const activeSession = findActiveSession(ctx.cwd);
|
|
183
|
+
if (activeSession && activeSession.regressions.length > 0 && ctx.hasUI) {
|
|
184
|
+
const unresolvedRegressions = activeSession.regressions.filter((r: E2eRegression) => !r.resolution);
|
|
185
|
+
if (unresolvedRegressions.length > 0) {
|
|
186
|
+
for (const regression of unresolvedRegressions) {
|
|
187
|
+
const choice = await ctx.ui.select(
|
|
188
|
+
`Regression: ${regression.flowName}`,
|
|
189
|
+
[
|
|
190
|
+
"This is a bug — needs fixing",
|
|
191
|
+
"Behavior changed intentionally — update the test",
|
|
192
|
+
"Skip for now",
|
|
193
|
+
],
|
|
194
|
+
{ helpText: `Was passing, now fails: ${regression.error}` },
|
|
195
|
+
);
|
|
196
|
+
if (!choice) continue;
|
|
197
|
+
if (choice.startsWith("This is a bug")) regression.resolution = "bug";
|
|
198
|
+
else if (choice.startsWith("Behavior changed")) regression.resolution = "intentional-change";
|
|
199
|
+
else regression.resolution = "skipped";
|
|
119
200
|
}
|
|
120
|
-
} else if (nextPhase) {
|
|
121
|
-
// Only one option — just run the next phase
|
|
122
|
-
action = { type: "run-phase", phase: nextPhase };
|
|
123
201
|
}
|
|
124
|
-
} else if (nextPhase) {
|
|
125
|
-
action = { type: "run-phase", phase: nextPhase };
|
|
126
202
|
}
|
|
127
203
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
204
|
+
// ── Step 5: Route discovery ──────────────────────────────────────
|
|
205
|
+
let discoveredRoutes = "";
|
|
206
|
+
try {
|
|
207
|
+
const routeResult = await pi.exec("bash", [
|
|
208
|
+
path.join(scriptsDir, "discover-routes.sh"),
|
|
209
|
+
ctx.cwd,
|
|
210
|
+
config.app.type,
|
|
211
|
+
], { cwd: ctx.cwd });
|
|
132
212
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
if (action.type === "rerun-failed") {
|
|
137
|
-
ledger = advancePhase(ctx.cwd, ledger, "execution", "running");
|
|
138
|
-
prompt = buildExecutionPrompt(ledger, { failedOnly: true, failedTests });
|
|
139
|
-
notifyInfo(ctx, "QA re-running failed tests", `${failedTests.length} test(s)`);
|
|
140
|
-
} else {
|
|
141
|
-
const phase = action.phase;
|
|
142
|
-
ledger = advancePhase(ctx.cwd, ledger, phase, "running");
|
|
143
|
-
|
|
144
|
-
switch (phase) {
|
|
145
|
-
case "discovery":
|
|
146
|
-
prompt = buildDiscoveryPrompt(framework, ctx.cwd);
|
|
147
|
-
break;
|
|
148
|
-
case "matrix":
|
|
149
|
-
prompt = buildMatrixPrompt(ledger);
|
|
150
|
-
break;
|
|
151
|
-
case "execution":
|
|
152
|
-
prompt = buildExecutionPrompt(ledger);
|
|
153
|
-
break;
|
|
154
|
-
case "reporting":
|
|
155
|
-
prompt = buildReportingPrompt(ledger);
|
|
156
|
-
break;
|
|
213
|
+
if (routeResult.code === 0 && routeResult.stdout.trim()) {
|
|
214
|
+
discoveredRoutes = routeResult.stdout.trim();
|
|
157
215
|
}
|
|
216
|
+
} catch { /* agent will discover routes manually */ }
|
|
158
217
|
|
|
159
|
-
|
|
218
|
+
if (!discoveredRoutes) {
|
|
219
|
+
discoveredRoutes = '{"path": "/", "file": "unknown", "type": "page", "hasForm": false}';
|
|
160
220
|
}
|
|
161
221
|
|
|
162
|
-
//
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
222
|
+
// ── Step 6: Load previous matrix ─────────────────────────────────
|
|
223
|
+
const previousMatrix = loadE2eMatrix(ctx.cwd);
|
|
224
|
+
const matrixJson = previousMatrix ? JSON.stringify(previousMatrix, null, 2) : null;
|
|
225
|
+
|
|
226
|
+
// ── Step 7: Create session ───────────────────────────────────────
|
|
227
|
+
const ledger = createNewE2eSession(ctx.cwd, config);
|
|
228
|
+
const sessionDir = getSessionDir(ctx.cwd, ledger.id);
|
|
229
|
+
|
|
230
|
+
// ── Step 8: Load skill ───────────────────────────────────────────
|
|
231
|
+
let skillContent = "";
|
|
232
|
+
const skillPath = findSkillPath("qa-strategy");
|
|
233
|
+
if (skillPath) {
|
|
234
|
+
try {
|
|
235
|
+
skillContent = fs.readFileSync(skillPath, "utf-8");
|
|
236
|
+
} catch { /* proceed without */ }
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// ── Step 9: Build and send prompt ────────────────────────────────
|
|
240
|
+
const routeCount = discoveredRoutes.split("\n").filter(Boolean).length;
|
|
241
|
+
|
|
242
|
+
const prompt = buildE2eOrchestratorPrompt({
|
|
243
|
+
cwd: ctx.cwd,
|
|
244
|
+
appType: config.app,
|
|
245
|
+
sessionDir,
|
|
246
|
+
scriptsDir,
|
|
247
|
+
config,
|
|
248
|
+
discoveredRoutes,
|
|
249
|
+
previousMatrix: matrixJson,
|
|
250
|
+
skillContent,
|
|
251
|
+
});
|
|
174
252
|
|
|
175
253
|
pi.sendMessage(
|
|
176
254
|
{
|
|
177
255
|
customType: "supi-qa",
|
|
178
|
-
content: [{ type: "text", text: prompt
|
|
256
|
+
content: [{ type: "text", text: prompt }],
|
|
179
257
|
display: "none",
|
|
180
258
|
},
|
|
181
|
-
{ deliverAs: "steer" }
|
|
259
|
+
{ deliverAs: "steer", triggerTurn: true },
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
notifyInfo(
|
|
263
|
+
ctx,
|
|
264
|
+
`E2E QA started: ${config.app.type}`,
|
|
265
|
+
`${routeCount} routes discovered | session ${ledger.id}`,
|
|
182
266
|
);
|
|
183
267
|
},
|
|
184
268
|
});
|
package/src/commands/release.ts
CHANGED
package/src/commands/review.ts
CHANGED
package/src/commands/run.ts
CHANGED
|
@@ -15,6 +15,7 @@ import { dispatchAgent, dispatchAgentWithReview, dispatchFixAgent } from "../orc
|
|
|
15
15
|
import { summarizeBatch, buildRunSummary } from "../orchestrator/result-collector.js";
|
|
16
16
|
import { analyzeConflicts } from "../orchestrator/conflict-resolver.js";
|
|
17
17
|
import { isLspAvailable } from "../lsp/detector.js";
|
|
18
|
+
import { detectContextMode } from "../context-mode/detector.js";
|
|
18
19
|
import {
|
|
19
20
|
notifyInfo,
|
|
20
21
|
notifySuccess,
|
|
@@ -24,6 +25,7 @@ import {
|
|
|
24
25
|
} from "../notifications/renderer.js";
|
|
25
26
|
import { buildWorktreePrompt } from "../git/worktree.js";
|
|
26
27
|
import { buildBranchFinishPrompt } from "../git/branch-finish.js";
|
|
28
|
+
import { detectBaseBranch } from "../git/base-branch.js";
|
|
27
29
|
import type { RunManifest, AgentResult } from "../types.js";
|
|
28
30
|
|
|
29
31
|
export function registerRunCommand(pi: ExtensionAPI): void {
|
|
@@ -88,7 +90,7 @@ export function registerRunCommand(pi: ExtensionAPI): void {
|
|
|
88
90
|
content: [{ type: "text", text: worktreeInstructions }],
|
|
89
91
|
display: "none",
|
|
90
92
|
},
|
|
91
|
-
{ deliverAs: "steer" },
|
|
93
|
+
{ deliverAs: "steer", triggerTurn: true },
|
|
92
94
|
);
|
|
93
95
|
notifyInfo(ctx, "Setting up worktree", `Branch: ${branchName}`);
|
|
94
96
|
}
|
|
@@ -104,6 +106,7 @@ export function registerRunCommand(pi: ExtensionAPI): void {
|
|
|
104
106
|
}
|
|
105
107
|
const plan = parsePlan(planContent, manifest.planRef);
|
|
106
108
|
const lsp = isLspAvailable(pi.getActiveTools());
|
|
109
|
+
const ctxMode = detectContextMode(pi.getActiveTools()).available;
|
|
107
110
|
|
|
108
111
|
for (const batch of manifest.batches) {
|
|
109
112
|
if (batch.status === "completed") continue;
|
|
@@ -129,6 +132,7 @@ export function registerRunCommand(pi: ExtensionAPI): void {
|
|
|
129
132
|
planContext: plan.context,
|
|
130
133
|
config,
|
|
131
134
|
lspAvailable: lsp,
|
|
135
|
+
contextModeAvailable: ctxMode,
|
|
132
136
|
});
|
|
133
137
|
});
|
|
134
138
|
|
|
@@ -140,7 +144,7 @@ export function registerRunCommand(pi: ExtensionAPI): void {
|
|
|
140
144
|
}
|
|
141
145
|
}
|
|
142
146
|
|
|
143
|
-
const conflicts = analyzeConflicts(batchResults, plan.tasks);
|
|
147
|
+
const conflicts = analyzeConflicts(batchResults, plan.tasks, ctxMode);
|
|
144
148
|
if (conflicts.hasConflicts) {
|
|
145
149
|
notifyWarning(
|
|
146
150
|
ctx,
|
|
@@ -164,6 +168,7 @@ export function registerRunCommand(pi: ExtensionAPI): void {
|
|
|
164
168
|
planContext: plan.context,
|
|
165
169
|
config,
|
|
166
170
|
lspAvailable: lsp,
|
|
171
|
+
contextModeAvailable: ctxMode,
|
|
167
172
|
previousOutput: failed.output,
|
|
168
173
|
failureReason: failed.output,
|
|
169
174
|
});
|
|
@@ -208,7 +213,7 @@ export function registerRunCommand(pi: ExtensionAPI): void {
|
|
|
208
213
|
if (branchName && manifest.status === "completed") {
|
|
209
214
|
const finishInstructions = buildBranchFinishPrompt({
|
|
210
215
|
branchName,
|
|
211
|
-
baseBranch:
|
|
216
|
+
baseBranch: await detectBaseBranch((cmd, args) => pi.exec(cmd, args)),
|
|
212
217
|
});
|
|
213
218
|
pi.sendMessage(
|
|
214
219
|
{
|
|
@@ -216,7 +221,7 @@ export function registerRunCommand(pi: ExtensionAPI): void {
|
|
|
216
221
|
content: [{ type: "text", text: finishInstructions }],
|
|
217
222
|
display: "none",
|
|
218
223
|
},
|
|
219
|
-
{ deliverAs: "steer" },
|
|
224
|
+
{ deliverAs: "steer", triggerTurn: true },
|
|
220
225
|
);
|
|
221
226
|
notifyInfo(ctx, "Run succeeded", "Follow branch finish instructions to integrate your work");
|
|
222
227
|
}
|
package/src/commands/supi.ts
CHANGED
|
@@ -15,7 +15,7 @@ export function handleSupi(pi: ExtensionAPI, ctx: ExtensionContext): void {
|
|
|
15
15
|
"/supi:plan — Start collaborative planning",
|
|
16
16
|
"/supi:run — Execute a plan with sub-agents",
|
|
17
17
|
"/supi:review — Run quality gates",
|
|
18
|
-
"/supi:qa —
|
|
18
|
+
"/supi:qa — E2E product testing with Playwright",
|
|
19
19
|
"/supi:fix-pr — Fix PR review comments",
|
|
20
20
|
"/supi:release — Release automation",
|
|
21
21
|
"/supi:config — Manage configuration",
|
package/src/config/defaults.ts
CHANGED
|
@@ -19,10 +19,21 @@ export const DEFAULT_CONFIG: SupipowersConfig = {
|
|
|
19
19
|
qa: {
|
|
20
20
|
framework: null,
|
|
21
21
|
command: null,
|
|
22
|
+
e2e: false,
|
|
22
23
|
},
|
|
23
24
|
release: {
|
|
24
25
|
pipeline: null,
|
|
25
26
|
},
|
|
27
|
+
contextMode: {
|
|
28
|
+
enabled: true,
|
|
29
|
+
compressionThreshold: 4096,
|
|
30
|
+
blockHttpCommands: true,
|
|
31
|
+
routingInstructions: true,
|
|
32
|
+
eventTracking: true,
|
|
33
|
+
compaction: true,
|
|
34
|
+
llmSummarization: false,
|
|
35
|
+
llmThreshold: 16384,
|
|
36
|
+
},
|
|
26
37
|
};
|
|
27
38
|
|
|
28
39
|
export const BUILTIN_PROFILES: Record<string, Profile> = {
|
package/src/config/schema.ts
CHANGED
|
@@ -25,10 +25,21 @@ const ConfigSchema = Type.Object({
|
|
|
25
25
|
qa: Type.Object({
|
|
26
26
|
framework: Type.Union([Type.String(), Type.Null()]),
|
|
27
27
|
command: Type.Union([Type.String(), Type.Null()]),
|
|
28
|
+
e2e: Type.Boolean(),
|
|
28
29
|
}),
|
|
29
30
|
release: Type.Object({
|
|
30
31
|
pipeline: Type.Union([Type.String(), Type.Null()]),
|
|
31
32
|
}),
|
|
33
|
+
contextMode: Type.Object({
|
|
34
|
+
enabled: Type.Boolean(),
|
|
35
|
+
compressionThreshold: Type.Number({ minimum: 1024 }),
|
|
36
|
+
blockHttpCommands: Type.Boolean(),
|
|
37
|
+
routingInstructions: Type.Boolean(),
|
|
38
|
+
eventTracking: Type.Boolean(),
|
|
39
|
+
compaction: Type.Boolean(),
|
|
40
|
+
llmSummarization: Type.Boolean(),
|
|
41
|
+
llmThreshold: Type.Number({ minimum: 4096 }),
|
|
42
|
+
}),
|
|
32
43
|
});
|
|
33
44
|
|
|
34
45
|
export function validateConfig(data: unknown): { valid: boolean; errors: string[] } {
|