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.
Files changed (57) hide show
  1. package/package.json +3 -3
  2. package/skills/context-mode/SKILL.md +38 -0
  3. package/skills/qa-strategy/SKILL.md +103 -21
  4. package/src/commands/config.ts +23 -2
  5. package/src/commands/fix-pr.ts +1 -1
  6. package/src/commands/plan.ts +1 -1
  7. package/src/commands/qa.ts +232 -148
  8. package/src/commands/release.ts +1 -1
  9. package/src/commands/review.ts +1 -1
  10. package/src/commands/run.ts +9 -4
  11. package/src/commands/supi.ts +1 -1
  12. package/src/config/defaults.ts +11 -0
  13. package/src/config/schema.ts +11 -0
  14. package/src/context-mode/compressor.ts +200 -0
  15. package/src/context-mode/detector.ts +43 -0
  16. package/src/context-mode/event-extractor.ts +170 -0
  17. package/src/context-mode/event-store.ts +168 -0
  18. package/src/context-mode/hooks.ts +176 -0
  19. package/src/context-mode/installer.ts +71 -0
  20. package/src/context-mode/snapshot-builder.ts +127 -0
  21. package/src/discipline/debugging.ts +7 -7
  22. package/src/discipline/receiving-review.ts +5 -5
  23. package/src/discipline/tdd.ts +2 -2
  24. package/src/discipline/verification.ts +9 -9
  25. package/src/git/base-branch.ts +30 -0
  26. package/src/git/branch-finish.ts +12 -3
  27. package/src/git/sanitize.ts +19 -0
  28. package/src/git/worktree.ts +38 -11
  29. package/src/index.ts +8 -1
  30. package/src/orchestrator/agent-prompts.ts +15 -7
  31. package/src/orchestrator/conflict-resolver.ts +3 -2
  32. package/src/orchestrator/dispatcher.ts +76 -21
  33. package/src/orchestrator/prompts.ts +46 -6
  34. package/src/planning/plan-reviewer.ts +1 -1
  35. package/src/planning/plan-writer-prompt.ts +6 -9
  36. package/src/planning/prompt-builder.ts +17 -16
  37. package/src/planning/spec-reviewer.ts +2 -2
  38. package/src/qa/config.ts +43 -0
  39. package/src/qa/matrix.ts +84 -0
  40. package/src/qa/prompt-builder.ts +212 -0
  41. package/src/qa/scripts/detect-app-type.sh +68 -0
  42. package/src/qa/scripts/discover-routes.sh +143 -0
  43. package/src/qa/scripts/ensure-playwright.sh +38 -0
  44. package/src/qa/scripts/run-e2e-tests.sh +99 -0
  45. package/src/qa/scripts/start-dev-server.sh +46 -0
  46. package/src/qa/scripts/stop-dev-server.sh +36 -0
  47. package/src/qa/session.ts +39 -55
  48. package/src/qa/types.ts +97 -0
  49. package/src/storage/qa-sessions.ts +9 -9
  50. package/src/types.ts +22 -70
  51. package/src/qa/detector.ts +0 -61
  52. package/src/qa/phases/discovery.ts +0 -34
  53. package/src/qa/phases/execution.ts +0 -65
  54. package/src/qa/phases/matrix.ts +0 -41
  55. package/src/qa/phases/reporting.ts +0 -71
  56. package/src/qa/report.ts +0 -22
  57. package/src/qa/runner.ts +0 -46
@@ -1,184 +1,268 @@
1
1
  import type { ExtensionAPI } from "@oh-my-pi/pi-coding-agent";
2
- import { detectAndCache } from "../qa/detector.js";
3
- import { notifyInfo, notifyError } from "../notifications/renderer.js";
4
- import { findActiveSession, findSessionWithFailures } from "../storage/qa-sessions.js";
5
- import {
6
- createNewSession,
7
- advancePhase,
8
- getFailedTests,
9
- getNextPhase,
10
- getPhaseStatusLine,
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
- export function registerQaCommand(pi: ExtensionAPI): void {
26
- pi.registerCommand("supi:qa", {
27
- description: "Run QA pipeline with session management (discovery → matrix → execution → reporting)",
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
- // ── Step 1: Session selection ──────────────────────────────────
41
- let ledger: QaSessionLedger | null = null;
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
- const activeSession = findActiveSession(ctx.cwd);
44
- const failedSession = findSessionWithFailures(ctx.cwd);
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
- if (ctx.hasUI && !args?.trim()) {
47
- const sessionOptions: string[] = [];
36
+ const BROWSER_OPTIONS = [
37
+ "chromium (recommended)",
38
+ "firefox",
39
+ "webkit",
40
+ ];
48
41
 
49
- if (failedSession) {
50
- const failCount = failedSession.results.filter((r) => r.status === "fail").length;
51
- sessionOptions.push(`Resume ${failedSession.id} (${failCount} failed test${failCount !== 1 ? "s" : ""})`);
52
- } else if (activeSession) {
53
- const next = getNextPhase(activeSession);
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
- sessionOptions.push("Start new session");
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
- if (sessionOptions.length > 1) {
60
- const choice = await ctx.ui.select(
61
- "QA Session",
62
- sessionOptions,
63
- { helpText: "Select session · Esc to cancel" },
64
- );
65
- if (!choice) return;
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
- if (choice.startsWith("Resume")) {
68
- ledger = failedSession ?? activeSession;
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
- // Create new session if none selected
74
- if (!ledger) {
75
- ledger = createNewSession(ctx.cwd, framework.name);
76
- notifyInfo(ctx, "QA session created", ledger.id);
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
- // ── Step 2: Phase selection ────────────────────────────────────
80
- type PhaseAction =
81
- | { type: "run-phase"; phase: QaPhase }
82
- | { type: "rerun-failed" };
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
- let action: PhaseAction | null = null;
85
- const nextPhase = getNextPhase(ledger);
86
- const failedTests = getFailedTests(ledger);
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
- if (ctx.hasUI && !args?.trim()) {
89
- const phaseOptions: string[] = [];
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
- // Offer re-run failed if there are failures
92
- if (failedTests.length > 0) {
93
- phaseOptions.push(`Re-run ${failedTests.length} failed test${failedTests.length !== 1 ? "s" : ""} only`);
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
- // Offer starting from next pending phase
97
- if (nextPhase) {
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
- if (phaseOptions.length > 1) {
102
- const statusLine = getPhaseStatusLine(ledger);
103
- const choice = await ctx.ui.select(
104
- `QA Phase · ${statusLine}`,
105
- phaseOptions,
106
- { helpText: "Select action · Esc to cancel" },
107
- );
108
- if (!choice) return;
109
-
110
- if (choice.startsWith("Re-run")) {
111
- action = { type: "rerun-failed" };
112
- } else {
113
- // Extract phase from label
114
- const selectedPhase = (Object.entries(PHASE_LABELS) as [QaPhase, string][])
115
- .find(([, label]) => label === choice)?.[0];
116
- if (selectedPhase) {
117
- action = { type: "run-phase", phase: selectedPhase };
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
- if (!action) {
129
- notifyInfo(ctx, "QA pipeline complete", getPhaseStatusLine(ledger));
130
- return;
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
- // ── Step 3: Execute ────────────────────────────────────────────
134
- let prompt: string;
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
- notifyInfo(ctx, `QA phase: ${phase}`, `session: ${ledger.id}`);
218
+ if (!discoveredRoutes) {
219
+ discoveredRoutes = '{"path": "/", "file": "unknown", "type": "page", "hasForm": false}';
160
220
  }
161
221
 
162
- // Include session context for the sub-agent
163
- const sessionContext = [
164
- `\n\n## QA Session Context`,
165
- ``,
166
- `Session ID: ${ledger.id}`,
167
- `Session ledger path: .omp/supipowers/qa-sessions/${ledger.id}/ledger.json`,
168
- ``,
169
- `Current ledger state:`,
170
- "```json",
171
- JSON.stringify(ledger, null, 2),
172
- "```",
173
- ].join("\n");
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 + sessionContext }],
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
  });
@@ -40,7 +40,7 @@ export function registerReleaseCommand(pi: ExtensionAPI): void {
40
40
  content: [{ type: "text", text: prompt }],
41
41
  display: "none",
42
42
  },
43
- { deliverAs: "steer" }
43
+ { deliverAs: "steer", triggerTurn: true }
44
44
  );
45
45
  },
46
46
  });
@@ -92,7 +92,7 @@ export function registerReviewCommand(pi: ExtensionAPI): void {
92
92
  content: [{ type: "text", text: reviewPrompt }],
93
93
  display: "none",
94
94
  },
95
- { deliverAs: "steer" }
95
+ { deliverAs: "steer", triggerTurn: true }
96
96
  );
97
97
  },
98
98
  });
@@ -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: "main",
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
  }
@@ -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 — Run QA pipeline",
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",
@@ -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> = {
@@ -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[] } {