supipowers 0.2.6 → 0.3.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 +21 -6
- package/skills/debugging/SKILL.md +54 -15
- package/skills/planning/SKILL.md +70 -10
- package/skills/receiving-code-review/SKILL.md +87 -0
- package/skills/tdd/SKILL.md +83 -0
- package/skills/verification/SKILL.md +54 -0
- package/src/commands/config.ts +53 -23
- package/src/commands/plan.ts +96 -31
- package/src/commands/qa.ts +150 -29
- package/src/commands/release.ts +1 -1
- package/src/commands/review.ts +2 -2
- package/src/commands/run.ts +52 -2
- package/src/commands/update.ts +2 -2
- package/src/discipline/debugging.ts +57 -0
- package/src/discipline/receiving-review.ts +65 -0
- package/src/discipline/tdd.ts +77 -0
- package/src/discipline/verification.ts +68 -0
- package/src/git/branch-finish.ts +101 -0
- package/src/git/worktree.ts +119 -0
- package/src/index.ts +11 -2
- package/src/lsp/detector.ts +2 -2
- package/src/orchestrator/agent-prompts.ts +282 -0
- package/src/orchestrator/dispatcher.ts +150 -1
- package/src/orchestrator/prompts.ts +17 -31
- package/src/planning/plan-reviewer.ts +49 -0
- package/src/planning/plan-writer-prompt.ts +173 -0
- package/src/planning/prompt-builder.ts +178 -0
- package/src/planning/spec-reviewer.ts +43 -0
- package/src/qa/phases/discovery.ts +34 -0
- package/src/qa/phases/execution.ts +65 -0
- package/src/qa/phases/matrix.ts +41 -0
- package/src/qa/phases/reporting.ts +71 -0
- package/src/qa/session.ts +104 -0
- package/src/storage/qa-sessions.ts +83 -0
- package/src/storage/specs.ts +36 -0
- package/src/types.ts +70 -0
- package/src/visual/companion.ts +115 -0
- package/src/visual/prompt-instructions.ts +102 -0
- package/src/visual/scripts/frame-template.html +201 -0
- package/src/visual/scripts/helper.js +88 -0
- package/src/visual/scripts/index.js +148 -0
- package/src/visual/scripts/package.json +10 -0
- package/src/visual/scripts/start-server.sh +98 -0
- package/src/visual/scripts/stop-server.sh +21 -0
- package/src/visual/types.ts +16 -0
package/src/commands/plan.ts
CHANGED
|
@@ -1,10 +1,29 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@oh-my-pi/pi-coding-agent";
|
|
2
2
|
import { loadConfig } from "../config/loader.js";
|
|
3
3
|
import { savePlan } from "../storage/plans.js";
|
|
4
|
-
import { notifySuccess, notifyInfo } from "../notifications/renderer.js";
|
|
4
|
+
import { notifySuccess, notifyInfo, notifyError } from "../notifications/renderer.js";
|
|
5
|
+
import {
|
|
6
|
+
generateVisualSessionId,
|
|
7
|
+
createSessionDir,
|
|
8
|
+
getScriptsDir,
|
|
9
|
+
parseServerInfo,
|
|
10
|
+
} from "../visual/companion.js";
|
|
11
|
+
import { buildVisualInstructions } from "../visual/prompt-instructions.js";
|
|
12
|
+
import { buildPlanningPrompt, buildQuickPlanPrompt } from "../planning/prompt-builder.js";
|
|
5
13
|
import * as fs from "node:fs";
|
|
6
14
|
import * as path from "node:path";
|
|
7
15
|
|
|
16
|
+
/** Module-level tracking for cleanup */
|
|
17
|
+
let activeSessionDir: string | null = null;
|
|
18
|
+
|
|
19
|
+
export function getActiveVisualSessionDir(): string | null {
|
|
20
|
+
return activeSessionDir;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function setActiveVisualSessionDir(dir: string | null): void {
|
|
24
|
+
activeSessionDir = dir;
|
|
25
|
+
}
|
|
26
|
+
|
|
8
27
|
export function registerPlanCommand(pi: ExtensionAPI): void {
|
|
9
28
|
pi.registerCommand("supi:plan", {
|
|
10
29
|
description: "Start collaborative planning for a feature or task",
|
|
@@ -24,39 +43,85 @@ export function registerPlanCommand(pi: ExtensionAPI): void {
|
|
|
24
43
|
const isQuick = args?.startsWith("--quick");
|
|
25
44
|
const quickDesc = isQuick ? args.replace("--quick", "").trim() : "";
|
|
26
45
|
|
|
46
|
+
// ── Visual companion consent ──────────────────────────────────
|
|
47
|
+
let visualUrl: string | null = null;
|
|
48
|
+
let visualSessionDir: string | null = null;
|
|
49
|
+
|
|
50
|
+
if (ctx.hasUI && !isQuick) {
|
|
51
|
+
const modeChoice = await ctx.ui.select(
|
|
52
|
+
"Planning mode",
|
|
53
|
+
[
|
|
54
|
+
"Terminal only",
|
|
55
|
+
"Terminal + Visual companion (opens browser)",
|
|
56
|
+
],
|
|
57
|
+
{ helpText: "Visual companion shows mockups and diagrams in a browser · Esc to cancel" },
|
|
58
|
+
);
|
|
59
|
+
if (!modeChoice) return;
|
|
60
|
+
|
|
61
|
+
if (modeChoice.startsWith("Terminal + Visual")) {
|
|
62
|
+
const sessionId = generateVisualSessionId();
|
|
63
|
+
visualSessionDir = createSessionDir(ctx.cwd, sessionId);
|
|
64
|
+
const scriptsDir = getScriptsDir();
|
|
65
|
+
|
|
66
|
+
// Install server dependencies if needed
|
|
67
|
+
const nodeModules = path.join(scriptsDir, "node_modules");
|
|
68
|
+
if (!fs.existsSync(nodeModules)) {
|
|
69
|
+
notifyInfo(ctx, "Installing visual companion dependencies...");
|
|
70
|
+
const installResult = await pi.exec("npm", ["install", "--production"], { cwd: scriptsDir });
|
|
71
|
+
if (installResult.code !== 0) {
|
|
72
|
+
notifyError(ctx, "Failed to install visual companion dependencies", installResult.stderr);
|
|
73
|
+
visualSessionDir = null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (visualSessionDir) {
|
|
78
|
+
// Stop any previous visual companion
|
|
79
|
+
if (activeSessionDir) {
|
|
80
|
+
const stopScript = path.join(scriptsDir, "stop-server.sh");
|
|
81
|
+
await pi.exec("bash", [stopScript, activeSessionDir], { cwd: scriptsDir });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Start the server (pass session dir via env command since ExecOptions has no env)
|
|
85
|
+
const startScript = path.join(scriptsDir, "start-server.sh");
|
|
86
|
+
const startResult = await pi.exec("env", [
|
|
87
|
+
`SUPI_VISUAL_DIR=${visualSessionDir}`,
|
|
88
|
+
"bash",
|
|
89
|
+
startScript,
|
|
90
|
+
], { cwd: scriptsDir });
|
|
91
|
+
|
|
92
|
+
if (startResult.code === 0) {
|
|
93
|
+
const serverInfo = parseServerInfo(startResult.stdout);
|
|
94
|
+
if (serverInfo) {
|
|
95
|
+
visualUrl = serverInfo.url;
|
|
96
|
+
activeSessionDir = visualSessionDir;
|
|
97
|
+
notifyInfo(ctx, "Visual companion ready", visualUrl);
|
|
98
|
+
} else {
|
|
99
|
+
notifyError(ctx, "Visual companion started but no connection info received");
|
|
100
|
+
visualSessionDir = null;
|
|
101
|
+
}
|
|
102
|
+
} else {
|
|
103
|
+
const errorMsg = startResult.stderr || startResult.stdout;
|
|
104
|
+
notifyError(ctx, "Failed to start visual companion", errorMsg);
|
|
105
|
+
visualSessionDir = null;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ── Build prompt ──────────────────────────────────────────────
|
|
27
112
|
let prompt: string;
|
|
28
113
|
if (isQuick && quickDesc) {
|
|
29
|
-
prompt =
|
|
30
|
-
"Generate a concise implementation plan for the following task.",
|
|
31
|
-
"Skip brainstorming — go straight to task breakdown.",
|
|
32
|
-
"",
|
|
33
|
-
`Task: ${quickDesc}`,
|
|
34
|
-
"",
|
|
35
|
-
"Format the plan as markdown with YAML frontmatter (name, created, tags).",
|
|
36
|
-
"Each task should have: name, [parallel-safe] or [sequential] annotation,",
|
|
37
|
-
"**files**, **criteria**, and **complexity** (small/medium/large).",
|
|
38
|
-
"",
|
|
39
|
-
skillContent ? "Follow these planning guidelines:\n" + skillContent : "",
|
|
40
|
-
"",
|
|
41
|
-
"After generating the plan, save it and confirm with the user.",
|
|
42
|
-
].join("\n");
|
|
114
|
+
prompt = buildQuickPlanPrompt(quickDesc, skillContent || undefined);
|
|
43
115
|
} else {
|
|
44
|
-
prompt =
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
"",
|
|
54
|
-
"Format the final plan as markdown with YAML frontmatter (name, created, tags).",
|
|
55
|
-
"Each task: name, [parallel-safe] or [sequential] annotation,",
|
|
56
|
-
"**files**, **criteria**, **complexity** (small/medium/large).",
|
|
57
|
-
"",
|
|
58
|
-
skillContent ? "Follow these planning guidelines:\n" + skillContent : "",
|
|
59
|
-
].join("\n");
|
|
116
|
+
prompt = buildPlanningPrompt({
|
|
117
|
+
topic: args || undefined,
|
|
118
|
+
skillContent: skillContent || undefined,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Append visual companion instructions if active
|
|
123
|
+
if (visualUrl && visualSessionDir) {
|
|
124
|
+
prompt += "\n\n" + buildVisualInstructions(visualUrl, visualSessionDir);
|
|
60
125
|
}
|
|
61
126
|
|
|
62
127
|
pi.sendMessage(
|
package/src/commands/qa.ts
CHANGED
|
@@ -1,11 +1,30 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@oh-my-pi/pi-coding-agent";
|
|
2
2
|
import { detectAndCache } from "../qa/detector.js";
|
|
3
|
-
import { buildQaRunPrompt } from "../qa/runner.js";
|
|
4
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
|
+
};
|
|
5
24
|
|
|
6
25
|
export function registerQaCommand(pi: ExtensionAPI): void {
|
|
7
26
|
pi.registerCommand("supi:qa", {
|
|
8
|
-
description: "Run QA pipeline (
|
|
27
|
+
description: "Run QA pipeline with session management (discovery → matrix → execution → reporting)",
|
|
9
28
|
async handler(args, ctx) {
|
|
10
29
|
const framework = detectAndCache(ctx.cwd);
|
|
11
30
|
|
|
@@ -13,48 +32,150 @@ export function registerQaCommand(pi: ExtensionAPI): void {
|
|
|
13
32
|
notifyError(
|
|
14
33
|
ctx,
|
|
15
34
|
"No test framework detected",
|
|
16
|
-
"Configure manually
|
|
35
|
+
"Configure manually via /supi:config"
|
|
17
36
|
);
|
|
18
37
|
return;
|
|
19
38
|
}
|
|
20
39
|
|
|
21
|
-
|
|
22
|
-
let
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
40
|
+
// ── Step 1: Session selection ──────────────────────────────────
|
|
41
|
+
let ledger: QaSessionLedger | null = null;
|
|
42
|
+
|
|
43
|
+
const activeSession = findActiveSession(ctx.cwd);
|
|
44
|
+
const failedSession = findSessionWithFailures(ctx.cwd);
|
|
45
|
+
|
|
46
|
+
if (ctx.hasUI && !args?.trim()) {
|
|
47
|
+
const sessionOptions: string[] = [];
|
|
48
|
+
|
|
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
|
+
}
|
|
56
|
+
|
|
57
|
+
sessionOptions.push("Start new session");
|
|
58
|
+
|
|
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;
|
|
66
|
+
|
|
67
|
+
if (choice.startsWith("Resume")) {
|
|
68
|
+
ledger = failedSession ?? activeSession;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
37
71
|
}
|
|
38
72
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
+
}
|
|
78
|
+
|
|
79
|
+
// ── Step 2: Phase selection ────────────────────────────────────
|
|
80
|
+
type PhaseAction =
|
|
81
|
+
| { type: "run-phase"; phase: QaPhase }
|
|
82
|
+
| { type: "rerun-failed" };
|
|
83
|
+
|
|
84
|
+
let action: PhaseAction | null = null;
|
|
85
|
+
const nextPhase = getNextPhase(ledger);
|
|
86
|
+
const failedTests = getFailedTests(ledger);
|
|
87
|
+
|
|
88
|
+
if (ctx.hasUI && !args?.trim()) {
|
|
89
|
+
const phaseOptions: string[] = [];
|
|
90
|
+
|
|
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`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Offer starting from next pending phase
|
|
97
|
+
if (nextPhase) {
|
|
98
|
+
phaseOptions.push(PHASE_LABELS[nextPhase]);
|
|
99
|
+
}
|
|
100
|
+
|
|
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
|
+
}
|
|
44
119
|
}
|
|
45
|
-
}
|
|
46
|
-
|
|
120
|
+
} else if (nextPhase) {
|
|
121
|
+
// Only one option — just run the next phase
|
|
122
|
+
action = { type: "run-phase", phase: nextPhase };
|
|
47
123
|
}
|
|
124
|
+
} else if (nextPhase) {
|
|
125
|
+
action = { type: "run-phase", phase: nextPhase };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (!action) {
|
|
129
|
+
notifyInfo(ctx, "QA pipeline complete", getPhaseStatusLine(ledger));
|
|
130
|
+
return;
|
|
48
131
|
}
|
|
49
132
|
|
|
50
|
-
|
|
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;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
notifyInfo(ctx, `QA phase: ${phase}`, `session: ${ledger.id}`);
|
|
160
|
+
}
|
|
51
161
|
|
|
52
|
-
|
|
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");
|
|
53
174
|
|
|
54
175
|
pi.sendMessage(
|
|
55
176
|
{
|
|
56
177
|
customType: "supi-qa",
|
|
57
|
-
content: [{ type: "text", text: prompt }],
|
|
178
|
+
content: [{ type: "text", text: prompt + sessionContext }],
|
|
58
179
|
display: "none",
|
|
59
180
|
},
|
|
60
181
|
{ deliverAs: "steer" }
|
package/src/commands/release.ts
CHANGED
|
@@ -12,7 +12,7 @@ export function registerReleaseCommand(pi: ExtensionAPI): void {
|
|
|
12
12
|
let lastTag: string | null = null;
|
|
13
13
|
try {
|
|
14
14
|
const result = await pi.exec("git", ["describe", "--tags", "--abbrev=0"], { cwd: ctx.cwd });
|
|
15
|
-
if (result.
|
|
15
|
+
if (result.code === 0) lastTag = result.stdout.trim();
|
|
16
16
|
} catch {
|
|
17
17
|
// no tags yet
|
|
18
18
|
}
|
package/src/commands/review.ts
CHANGED
|
@@ -49,7 +49,7 @@ export function registerReviewCommand(pi: ExtensionAPI): void {
|
|
|
49
49
|
let changedFiles: string[] = [];
|
|
50
50
|
try {
|
|
51
51
|
const result = await pi.exec("git", ["diff", "--name-only", "HEAD"], { cwd: ctx.cwd });
|
|
52
|
-
if (result.
|
|
52
|
+
if (result.code === 0) {
|
|
53
53
|
changedFiles = result.stdout
|
|
54
54
|
.split("\n")
|
|
55
55
|
.map((f) => f.trim())
|
|
@@ -62,7 +62,7 @@ export function registerReviewCommand(pi: ExtensionAPI): void {
|
|
|
62
62
|
if (changedFiles.length === 0) {
|
|
63
63
|
try {
|
|
64
64
|
const result = await pi.exec("git", ["diff", "--name-only", "--cached"], { cwd: ctx.cwd });
|
|
65
|
-
if (result.
|
|
65
|
+
if (result.code === 0) {
|
|
66
66
|
changedFiles = result.stdout
|
|
67
67
|
.split("\n")
|
|
68
68
|
.map((f) => f.trim())
|
package/src/commands/run.ts
CHANGED
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
loadAllAgentResults,
|
|
12
12
|
} from "../storage/runs.js";
|
|
13
13
|
import { scheduleBatches } from "../orchestrator/batch-scheduler.js";
|
|
14
|
-
import { dispatchAgent, dispatchFixAgent } from "../orchestrator/dispatcher.js";
|
|
14
|
+
import { dispatchAgent, dispatchAgentWithReview, dispatchFixAgent } from "../orchestrator/dispatcher.js";
|
|
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";
|
|
@@ -22,6 +22,8 @@ import {
|
|
|
22
22
|
notifyError,
|
|
23
23
|
notifySummary,
|
|
24
24
|
} from "../notifications/renderer.js";
|
|
25
|
+
import { buildWorktreePrompt } from "../git/worktree.js";
|
|
26
|
+
import { buildBranchFinishPrompt } from "../git/branch-finish.js";
|
|
25
27
|
import type { RunManifest, AgentResult } from "../types.js";
|
|
26
28
|
|
|
27
29
|
export function registerRunCommand(pi: ExtensionAPI): void {
|
|
@@ -32,6 +34,7 @@ export function registerRunCommand(pi: ExtensionAPI): void {
|
|
|
32
34
|
const profile = resolveProfile(ctx.cwd, config, args?.replace("--profile ", "") || undefined);
|
|
33
35
|
|
|
34
36
|
let manifest = findActiveRun(ctx.cwd);
|
|
37
|
+
let branchName: string | null = null;
|
|
35
38
|
|
|
36
39
|
if (!manifest) {
|
|
37
40
|
const plans = listPlans(ctx.cwd);
|
|
@@ -60,6 +63,36 @@ export function registerRunCommand(pi: ExtensionAPI): void {
|
|
|
60
63
|
};
|
|
61
64
|
createRun(ctx.cwd, manifest);
|
|
62
65
|
notifyInfo(ctx, `Run started: ${manifest.id}`, `${plan.tasks.length} tasks in ${batches.length} batches`);
|
|
66
|
+
|
|
67
|
+
// Offer worktree setup for isolated execution
|
|
68
|
+
if (ctx.hasUI) {
|
|
69
|
+
const useWorktree = await ctx.ui.select(
|
|
70
|
+
"Execution isolation",
|
|
71
|
+
[
|
|
72
|
+
"Run in current workspace",
|
|
73
|
+
"Create isolated worktree (recommended)",
|
|
74
|
+
],
|
|
75
|
+
{ helpText: "Worktrees prevent work-in-progress from polluting your workspace" },
|
|
76
|
+
);
|
|
77
|
+
if (!useWorktree) return;
|
|
78
|
+
|
|
79
|
+
if (useWorktree.startsWith("Create isolated")) {
|
|
80
|
+
branchName = `supi/${plan.name || manifest.id}`;
|
|
81
|
+
const worktreeInstructions = buildWorktreePrompt({
|
|
82
|
+
branchName,
|
|
83
|
+
cwd: ctx.cwd,
|
|
84
|
+
});
|
|
85
|
+
pi.sendMessage(
|
|
86
|
+
{
|
|
87
|
+
customType: "supi-worktree-setup",
|
|
88
|
+
content: [{ type: "text", text: worktreeInstructions }],
|
|
89
|
+
display: "none",
|
|
90
|
+
},
|
|
91
|
+
{ deliverAs: "steer" },
|
|
92
|
+
);
|
|
93
|
+
notifyInfo(ctx, "Setting up worktree", `Branch: ${branchName}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
63
96
|
} else {
|
|
64
97
|
notifyInfo(ctx, `Resuming run: ${manifest.id}`);
|
|
65
98
|
}
|
|
@@ -89,7 +122,7 @@ export function registerRunCommand(pi: ExtensionAPI): void {
|
|
|
89
122
|
const task = plan.tasks.find((t) => t.id === taskId);
|
|
90
123
|
if (!task) return Promise.resolve(null);
|
|
91
124
|
|
|
92
|
-
return
|
|
125
|
+
return dispatchAgentWithReview({
|
|
93
126
|
pi,
|
|
94
127
|
ctx,
|
|
95
128
|
task,
|
|
@@ -170,6 +203,23 @@ export function registerRunCommand(pi: ExtensionAPI): void {
|
|
|
170
203
|
`(${runSummary.done} clean, ${runSummary.doneWithConcerns} with concerns, ` +
|
|
171
204
|
`${runSummary.blocked} blocked) | ${runSummary.totalFilesChanged} files | ${durationSec}s`
|
|
172
205
|
);
|
|
206
|
+
|
|
207
|
+
// Offer branch finish options if we created a worktree branch
|
|
208
|
+
if (branchName && manifest.status === "completed") {
|
|
209
|
+
const finishInstructions = buildBranchFinishPrompt({
|
|
210
|
+
branchName,
|
|
211
|
+
baseBranch: "main",
|
|
212
|
+
});
|
|
213
|
+
pi.sendMessage(
|
|
214
|
+
{
|
|
215
|
+
customType: "supi-branch-finish",
|
|
216
|
+
content: [{ type: "text", text: finishInstructions }],
|
|
217
|
+
display: "none",
|
|
218
|
+
},
|
|
219
|
+
{ deliverAs: "steer" },
|
|
220
|
+
);
|
|
221
|
+
notifyInfo(ctx, "Run succeeded", "Follow branch finish instructions to integrate your work");
|
|
222
|
+
}
|
|
173
223
|
},
|
|
174
224
|
});
|
|
175
225
|
}
|
package/src/commands/update.ts
CHANGED
|
@@ -24,7 +24,7 @@ export function handleUpdate(pi: ExtensionAPI, ctx: ExtensionContext): void {
|
|
|
24
24
|
|
|
25
25
|
// Check latest version on npm
|
|
26
26
|
const checkResult = await pi.exec("npm", ["view", "supipowers", "version"], { cwd: tmpdir() });
|
|
27
|
-
if (checkResult.
|
|
27
|
+
if (checkResult.code !== 0) {
|
|
28
28
|
ctx.ui.notify("Failed to check for updates — npm view failed", "error");
|
|
29
29
|
return;
|
|
30
30
|
}
|
|
@@ -46,7 +46,7 @@ export function handleUpdate(pi: ExtensionAPI, ctx: ExtensionContext): void {
|
|
|
46
46
|
"npm", ["install", "--prefix", tempDir, `supipowers@${latestVersion}`],
|
|
47
47
|
{ cwd: tempDir },
|
|
48
48
|
);
|
|
49
|
-
if (installResult.
|
|
49
|
+
if (installResult.code !== 0) {
|
|
50
50
|
ctx.ui.notify("Failed to download latest version", "error");
|
|
51
51
|
return;
|
|
52
52
|
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Systematic debugging instructions for sub-agent prompts.
|
|
3
|
+
* Matches superpowers' systematic-debugging skill depth.
|
|
4
|
+
*/
|
|
5
|
+
export function buildDebuggingInstructions(): string {
|
|
6
|
+
return [
|
|
7
|
+
"## Systematic Debugging",
|
|
8
|
+
"",
|
|
9
|
+
"### Iron Law",
|
|
10
|
+
"",
|
|
11
|
+
"NO FIXES WITHOUT ROOT CAUSE INVESTIGATION FIRST.",
|
|
12
|
+
"Symptom fixes are failure. Find root cause before attempting any fix.",
|
|
13
|
+
"",
|
|
14
|
+
"### Phase 1: Root Cause Investigation",
|
|
15
|
+
"",
|
|
16
|
+
"Complete this phase before proposing fixes:",
|
|
17
|
+
"",
|
|
18
|
+
"1. **Read the error message carefully.** Don't skip it; it often contains the solution.",
|
|
19
|
+
"2. **Reproduce consistently.** Exact steps, every time.",
|
|
20
|
+
"3. **Check recent changes.** Run `git diff` to see what changed. Check new dependencies, config changes.",
|
|
21
|
+
"4. **Gather evidence** in multi-component systems: add diagnostic instrumentation at each boundary.",
|
|
22
|
+
"5. **Trace the data flow** backward through the call stack to find the original trigger.",
|
|
23
|
+
"",
|
|
24
|
+
"### Phase 2: Pattern Analysis",
|
|
25
|
+
"",
|
|
26
|
+
"1. Find a working example in the codebase that does something similar.",
|
|
27
|
+
"2. Compare against the reference completely (don't skim).",
|
|
28
|
+
"3. Identify differences between working and broken.",
|
|
29
|
+
"4. Understand dependencies and assumptions.",
|
|
30
|
+
"",
|
|
31
|
+
"### Phase 3: Hypothesis and Testing",
|
|
32
|
+
"",
|
|
33
|
+
"1. Form a single, specific hypothesis (not vague).",
|
|
34
|
+
"2. Test minimally: smallest possible change, one variable at a time.",
|
|
35
|
+
"3. Verify before continuing. If wrong → form a NEW hypothesis, not more fixes.",
|
|
36
|
+
"4. Admit uncertainty. Don't pretend to know.",
|
|
37
|
+
"",
|
|
38
|
+
"### Phase 4: Implementation",
|
|
39
|
+
"",
|
|
40
|
+
"1. Create a failing test case first (automated or manual).",
|
|
41
|
+
"2. Implement a single fix that addresses the root cause only.",
|
|
42
|
+
"3. Verify the fix: test passes, no other tests broken.",
|
|
43
|
+
"4. **If the fix doesn't work:**",
|
|
44
|
+
" - Fewer than 3 attempts → return to Phase 1 with new information",
|
|
45
|
+
" - 3 or more attempts → STOP and question the architecture. Discuss with human partner.",
|
|
46
|
+
"",
|
|
47
|
+
"### Red Flags — STOP and Follow the Process",
|
|
48
|
+
"",
|
|
49
|
+
"- \"Quick fix for now, investigate later\"",
|
|
50
|
+
"- \"Just try changing X and see if it works\"",
|
|
51
|
+
"- \"Skip the test, I'll manually verify\"",
|
|
52
|
+
"- \"It's probably X, let me fix that\"",
|
|
53
|
+
"- \"I don't fully understand but this might work\"",
|
|
54
|
+
"- \"One more fix attempt\" (when already tried 2+)",
|
|
55
|
+
"- Each fix reveals a new problem in a different place",
|
|
56
|
+
].join("\n");
|
|
57
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Receiving code review instructions for sub-agent prompts.
|
|
3
|
+
* Matches superpowers' receiving-code-review skill:
|
|
4
|
+
* technical rigor and verification, not performative agreement.
|
|
5
|
+
*/
|
|
6
|
+
export function buildReceivingReviewInstructions(): string {
|
|
7
|
+
return [
|
|
8
|
+
"## Receiving Code Review Feedback",
|
|
9
|
+
"",
|
|
10
|
+
"Code review requires technical evaluation, not emotional performance.",
|
|
11
|
+
"Verify before implementing. Ask before assuming. Technical correctness over social comfort.",
|
|
12
|
+
"",
|
|
13
|
+
"### The Response Pattern",
|
|
14
|
+
"",
|
|
15
|
+
"1. **READ:** Complete feedback without reacting.",
|
|
16
|
+
"2. **UNDERSTAND:** Restate the requirement in your own words, or ask for clarification.",
|
|
17
|
+
"3. **VERIFY:** Check against codebase reality.",
|
|
18
|
+
"4. **EVALUATE:** Is this technically sound for THIS codebase?",
|
|
19
|
+
"5. **RESPOND:** Technical acknowledgment or reasoned pushback.",
|
|
20
|
+
"6. **IMPLEMENT:** One at a time, test each change.",
|
|
21
|
+
"",
|
|
22
|
+
"### Forbidden Responses",
|
|
23
|
+
"",
|
|
24
|
+
"Never use performative agreement:",
|
|
25
|
+
"- \"You're absolutely right!\"",
|
|
26
|
+
"- \"Great point!\"",
|
|
27
|
+
"- \"Excellent catch!\"",
|
|
28
|
+
"",
|
|
29
|
+
"Instead: restate requirements, ask clarifying questions, take action.",
|
|
30
|
+
"",
|
|
31
|
+
"### Handling Unclear Feedback",
|
|
32
|
+
"",
|
|
33
|
+
"If any item is unclear, stop and ask for clarification before implementing anything.",
|
|
34
|
+
"Items may be related — clarify all unclear items before starting work.",
|
|
35
|
+
"",
|
|
36
|
+
"### Source-Specific Handling",
|
|
37
|
+
"",
|
|
38
|
+
"**From your human partner:** Trusted. Implement after understanding.",
|
|
39
|
+
"**From external reviewers:** Verify technically. Check for breaking changes.",
|
|
40
|
+
"Question whether the reviewer understands the full context.",
|
|
41
|
+
"",
|
|
42
|
+
"### YAGNI Check",
|
|
43
|
+
"",
|
|
44
|
+
"For suggested \"professional features\" — grep the codebase for actual usage.",
|
|
45
|
+
"If unused, suggest removal instead of implementing.",
|
|
46
|
+
"",
|
|
47
|
+
"### Implementation Order",
|
|
48
|
+
"",
|
|
49
|
+
"1. Clarify all unclear items first.",
|
|
50
|
+
"2. Then implement in order: blocking issues → simple fixes → complex fixes.",
|
|
51
|
+
"3. Test each change before moving to the next.",
|
|
52
|
+
"",
|
|
53
|
+
"### When to Push Back",
|
|
54
|
+
"",
|
|
55
|
+
"Push back when feedback would introduce bugs, break existing behavior,",
|
|
56
|
+
"add unnecessary complexity, or contradicts the codebase's established patterns.",
|
|
57
|
+
"Use technical reasoning, not defensiveness.",
|
|
58
|
+
"",
|
|
59
|
+
"### The Bottom Line",
|
|
60
|
+
"",
|
|
61
|
+
"External feedback = suggestions to evaluate, not orders to follow.",
|
|
62
|
+
"Verify before implementing. Question. Then implement.",
|
|
63
|
+
"No performative agreement. Technical rigor always.",
|
|
64
|
+
].join("\n");
|
|
65
|
+
}
|