supipowers 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/commands/config.ts +140 -54
- package/src/commands/qa.ts +14 -2
- package/src/commands/release.ts +10 -23
- package/src/commands/review.ts +16 -1
- package/src/commands/status.ts +8 -16
- package/src/commands/supi.ts +28 -22
package/package.json
CHANGED
package/src/commands/config.ts
CHANGED
|
@@ -1,70 +1,156 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@oh-my-pi/pi-coding-agent";
|
|
2
2
|
import { loadConfig, updateConfig } from "../config/loader.js";
|
|
3
|
-
import { listProfiles
|
|
4
|
-
import {
|
|
3
|
+
import { listProfiles } from "../config/profiles.js";
|
|
4
|
+
import type { SupipowersConfig } from "../types.js";
|
|
5
|
+
|
|
6
|
+
interface SettingDef {
|
|
7
|
+
label: string;
|
|
8
|
+
key: string;
|
|
9
|
+
type: "select" | "toggle" | "number" | "text";
|
|
10
|
+
options?: string[];
|
|
11
|
+
get: (config: SupipowersConfig) => string;
|
|
12
|
+
set: (cwd: string, value: unknown) => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function buildSettings(cwd: string): SettingDef[] {
|
|
16
|
+
return [
|
|
17
|
+
{
|
|
18
|
+
label: "Default profile",
|
|
19
|
+
key: "defaultProfile",
|
|
20
|
+
type: "select",
|
|
21
|
+
options: listProfiles(cwd),
|
|
22
|
+
get: (c) => c.defaultProfile,
|
|
23
|
+
set: (d, v) => updateConfig(d, { defaultProfile: v }),
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
label: "Max parallel agents",
|
|
27
|
+
key: "orchestration.maxParallelAgents",
|
|
28
|
+
type: "select",
|
|
29
|
+
options: ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"],
|
|
30
|
+
get: (c) => String(c.orchestration.maxParallelAgents),
|
|
31
|
+
set: (d, v) => updateConfig(d, { orchestration: { maxParallelAgents: Number(v) } }),
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
label: "Max fix retries",
|
|
35
|
+
key: "orchestration.maxFixRetries",
|
|
36
|
+
type: "select",
|
|
37
|
+
options: ["0", "1", "2", "3", "4", "5"],
|
|
38
|
+
get: (c) => String(c.orchestration.maxFixRetries),
|
|
39
|
+
set: (d, v) => updateConfig(d, { orchestration: { maxFixRetries: Number(v) } }),
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
label: "Max nesting depth",
|
|
43
|
+
key: "orchestration.maxNestingDepth",
|
|
44
|
+
type: "select",
|
|
45
|
+
options: ["0", "1", "2", "3", "4", "5"],
|
|
46
|
+
get: (c) => String(c.orchestration.maxNestingDepth),
|
|
47
|
+
set: (d, v) => updateConfig(d, { orchestration: { maxNestingDepth: Number(v) } }),
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
label: "Model preference",
|
|
51
|
+
key: "orchestration.modelPreference",
|
|
52
|
+
type: "select",
|
|
53
|
+
options: ["auto", "fast", "balanced", "quality"],
|
|
54
|
+
get: (c) => c.orchestration.modelPreference,
|
|
55
|
+
set: (d, v) => updateConfig(d, { orchestration: { modelPreference: v } }),
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
label: "LSP setup guide",
|
|
59
|
+
key: "lsp.setupGuide",
|
|
60
|
+
type: "toggle",
|
|
61
|
+
get: (c) => c.lsp.setupGuide ? "on" : "off",
|
|
62
|
+
set: (d, v) => updateConfig(d, { lsp: { setupGuide: v === "on" } }),
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
label: "Notification verbosity",
|
|
66
|
+
key: "notifications.verbosity",
|
|
67
|
+
type: "select",
|
|
68
|
+
options: ["quiet", "normal", "verbose"],
|
|
69
|
+
get: (c) => c.notifications.verbosity,
|
|
70
|
+
set: (d, v) => updateConfig(d, { notifications: { verbosity: v } }),
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
label: "QA framework",
|
|
74
|
+
key: "qa.framework",
|
|
75
|
+
type: "text",
|
|
76
|
+
get: (c) => c.qa.framework ?? "not set",
|
|
77
|
+
set: (d, v) => updateConfig(d, { qa: { framework: v || null } }),
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
label: "QA command",
|
|
81
|
+
key: "qa.command",
|
|
82
|
+
type: "text",
|
|
83
|
+
get: (c) => c.qa.command ?? "not set",
|
|
84
|
+
set: (d, v) => updateConfig(d, { qa: { command: v || null } }),
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
label: "Release pipeline",
|
|
88
|
+
key: "release.pipeline",
|
|
89
|
+
type: "text",
|
|
90
|
+
get: (c) => c.release.pipeline ?? "not set",
|
|
91
|
+
set: (d, v) => updateConfig(d, { release: { pipeline: v || null } }),
|
|
92
|
+
},
|
|
93
|
+
];
|
|
94
|
+
}
|
|
5
95
|
|
|
6
96
|
export function registerConfigCommand(pi: ExtensionAPI): void {
|
|
7
97
|
pi.registerCommand("supi:config", {
|
|
8
|
-
description: "View and manage Supipowers configuration
|
|
9
|
-
async handler(
|
|
10
|
-
|
|
98
|
+
description: "View and manage Supipowers configuration",
|
|
99
|
+
async handler(_args, ctx) {
|
|
100
|
+
if (!ctx.hasUI) {
|
|
101
|
+
ctx.ui.notify("Config UI requires interactive mode", "warning");
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
11
104
|
|
|
12
|
-
|
|
13
|
-
const profiles = listProfiles(ctx.cwd);
|
|
14
|
-
const activeProfile = resolveProfile(ctx.cwd, config);
|
|
105
|
+
const settings = buildSettings(ctx.cwd);
|
|
15
106
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
`Profile: ${config.defaultProfile}`,
|
|
20
|
-
`Max parallel agents: ${config.orchestration.maxParallelAgents}`,
|
|
21
|
-
`Max fix retries: ${config.orchestration.maxFixRetries}`,
|
|
22
|
-
`Max nesting depth: ${config.orchestration.maxNestingDepth}`,
|
|
23
|
-
`Model preference: ${config.orchestration.modelPreference}`,
|
|
24
|
-
`LSP setup guide: ${config.lsp.setupGuide}`,
|
|
25
|
-
`Notification verbosity: ${config.notifications.verbosity}`,
|
|
26
|
-
`QA framework: ${config.qa.framework ?? "not detected"}`,
|
|
27
|
-
`Release pipeline: ${config.release.pipeline ?? "not configured"}`,
|
|
28
|
-
"",
|
|
29
|
-
`Available profiles: ${profiles.join(", ")}`,
|
|
30
|
-
"",
|
|
31
|
-
"To update: /supi:config set <key> <value>",
|
|
32
|
-
"Example: /supi:config set orchestration.maxParallelAgents 5",
|
|
33
|
-
];
|
|
107
|
+
// Main settings loop
|
|
108
|
+
while (true) {
|
|
109
|
+
const config = loadConfig(ctx.cwd);
|
|
34
110
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
});
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
111
|
+
const options = settings.map(
|
|
112
|
+
(s) => `${s.label}: ${s.get(config)}`
|
|
113
|
+
);
|
|
114
|
+
options.push("Done");
|
|
42
115
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
116
|
+
const choice = await ctx.ui.select(
|
|
117
|
+
"Supipowers Settings",
|
|
118
|
+
options,
|
|
119
|
+
{ helpText: "Select a setting to change · Esc to close" },
|
|
120
|
+
);
|
|
48
121
|
|
|
49
|
-
if (
|
|
50
|
-
else if (rawValue === "false") value = false;
|
|
51
|
-
else if (rawValue === "null") value = null;
|
|
52
|
-
else if (!isNaN(Number(rawValue))) value = Number(rawValue);
|
|
122
|
+
if (choice === undefined || choice === "Done") break;
|
|
53
123
|
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
current[keys[i]] = {};
|
|
58
|
-
current = current[keys[i]] as Record<string, unknown>;
|
|
59
|
-
}
|
|
60
|
-
current[keys[keys.length - 1]] = value;
|
|
124
|
+
const index = options.indexOf(choice);
|
|
125
|
+
const setting = settings[index];
|
|
126
|
+
if (!setting) break;
|
|
61
127
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
128
|
+
if (setting.type === "select" && setting.options) {
|
|
129
|
+
const value = await ctx.ui.select(
|
|
130
|
+
setting.label,
|
|
131
|
+
setting.options,
|
|
132
|
+
{ initialIndex: setting.options.indexOf(setting.get(config)) },
|
|
133
|
+
);
|
|
134
|
+
if (value !== undefined) {
|
|
135
|
+
setting.set(ctx.cwd, value);
|
|
136
|
+
ctx.ui.notify(`${setting.label} → ${value}`, "info");
|
|
137
|
+
}
|
|
138
|
+
} else if (setting.type === "toggle") {
|
|
139
|
+
const current = setting.get(config);
|
|
140
|
+
const newValue = current === "on" ? "off" : "on";
|
|
141
|
+
setting.set(ctx.cwd, newValue);
|
|
142
|
+
ctx.ui.notify(`${setting.label} → ${newValue}`, "info");
|
|
143
|
+
} else if (setting.type === "text") {
|
|
144
|
+
const value = await ctx.ui.input(
|
|
145
|
+
setting.label,
|
|
146
|
+
setting.get(config) === "not set" ? undefined : setting.get(config),
|
|
147
|
+
);
|
|
148
|
+
if (value !== undefined) {
|
|
149
|
+
setting.set(ctx.cwd, value);
|
|
150
|
+
ctx.ui.notify(`${setting.label} → ${value || "cleared"}`, "info");
|
|
151
|
+
}
|
|
152
|
+
}
|
|
65
153
|
}
|
|
66
|
-
|
|
67
|
-
notifyInfo(ctx, "Usage", "/supi:config or /supi:config set <key> <value>");
|
|
68
154
|
},
|
|
69
155
|
});
|
|
70
156
|
}
|
package/src/commands/qa.ts
CHANGED
|
@@ -23,6 +23,20 @@ export function registerQaCommand(pi: ExtensionAPI): void {
|
|
|
23
23
|
|
|
24
24
|
if (args?.includes("--changed")) {
|
|
25
25
|
scope = "changed";
|
|
26
|
+
} else if (args?.includes("--e2e")) {
|
|
27
|
+
scope = "e2e";
|
|
28
|
+
} else if (ctx.hasUI && !args?.trim()) {
|
|
29
|
+
// No flag provided — let the user pick
|
|
30
|
+
const choice = await ctx.ui.select(
|
|
31
|
+
"QA scope",
|
|
32
|
+
["all — Run all tests", "changed — Only changed files", "e2e — E2E / Playwright only"],
|
|
33
|
+
{ helpText: "Select test scope · Esc to cancel" },
|
|
34
|
+
);
|
|
35
|
+
if (!choice) return;
|
|
36
|
+
scope = choice.split(" — ")[0] as "all" | "changed" | "e2e";
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (scope === "changed") {
|
|
26
40
|
try {
|
|
27
41
|
const result = await pi.exec("git", ["diff", "--name-only", "HEAD"], { cwd: ctx.cwd });
|
|
28
42
|
if (result.exitCode === 0) {
|
|
@@ -31,8 +45,6 @@ export function registerQaCommand(pi: ExtensionAPI): void {
|
|
|
31
45
|
} catch {
|
|
32
46
|
scope = "all";
|
|
33
47
|
}
|
|
34
|
-
} else if (args?.includes("--e2e")) {
|
|
35
|
-
scope = "e2e";
|
|
36
48
|
}
|
|
37
49
|
|
|
38
50
|
notifyInfo(ctx, "QA started", `${framework.name} | scope: ${scope}`);
|
package/src/commands/release.ts
CHANGED
|
@@ -18,32 +18,19 @@ export function registerReleaseCommand(pi: ExtensionAPI): void {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
if (!config.release.pipeline) {
|
|
21
|
-
const
|
|
22
|
-
"
|
|
23
|
-
"",
|
|
24
|
-
"
|
|
25
|
-
"",
|
|
26
|
-
"1. **npm** — npm publish to registry",
|
|
27
|
-
"2. **github** — GitHub Release with gh CLI",
|
|
28
|
-
"3. **manual** — I'll handle publishing myself",
|
|
29
|
-
"",
|
|
30
|
-
"Tell me which option, and I'll save it for future releases.",
|
|
31
|
-
"",
|
|
32
|
-
"After you answer, I'll analyze commits and prepare the release.",
|
|
33
|
-
].join("\n");
|
|
34
|
-
|
|
35
|
-
pi.sendMessage(
|
|
36
|
-
{
|
|
37
|
-
customType: "supi-release-setup",
|
|
38
|
-
content: [{ type: "text", text: prompt }],
|
|
39
|
-
display: "none",
|
|
40
|
-
},
|
|
41
|
-
{ deliverAs: "steer" }
|
|
21
|
+
const choice = await ctx.ui.select(
|
|
22
|
+
"Release Setup — How do you publish?",
|
|
23
|
+
["npm — npm publish to registry", "github — GitHub Release with gh CLI", "manual — I'll handle publishing myself"],
|
|
24
|
+
{ helpText: "Select your release pipeline" },
|
|
42
25
|
);
|
|
43
|
-
|
|
26
|
+
|
|
27
|
+
if (!choice) return;
|
|
28
|
+
const pipeline = choice.split(" — ")[0];
|
|
29
|
+
updateConfig(ctx.cwd, { release: { pipeline } });
|
|
30
|
+
ctx.ui.notify(`Release pipeline set to: ${pipeline}`, "info");
|
|
44
31
|
}
|
|
45
32
|
|
|
46
|
-
notifyInfo(ctx, "Release started", `Pipeline: ${config.release.pipeline}`);
|
|
33
|
+
notifyInfo(ctx, "Release started", `Pipeline: ${config.release.pipeline || "just configured"}`);
|
|
47
34
|
|
|
48
35
|
const prompt = buildAnalyzerPrompt(lastTag);
|
|
49
36
|
|
package/src/commands/review.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@oh-my-pi/pi-coding-agent";
|
|
2
2
|
import { loadConfig } from "../config/loader.js";
|
|
3
|
-
import { resolveProfile } from "../config/profiles.js";
|
|
3
|
+
import { listProfiles, resolveProfile } from "../config/profiles.js";
|
|
4
4
|
import { buildReviewPrompt } from "../quality/gate-runner.js";
|
|
5
5
|
import { isLspAvailable } from "../lsp/detector.js";
|
|
6
6
|
import { notifyInfo, notifyWarning } from "../notifications/renderer.js";
|
|
@@ -20,6 +20,21 @@ export function registerReviewCommand(pi: ExtensionAPI): void {
|
|
|
20
20
|
if (match) profileOverride = match[1];
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
// If no flag provided and UI is available, let the user pick
|
|
24
|
+
if (!profileOverride && ctx.hasUI) {
|
|
25
|
+
const profiles = listProfiles(ctx.cwd);
|
|
26
|
+
const choice = await ctx.ui.select(
|
|
27
|
+
"Review profile",
|
|
28
|
+
profiles,
|
|
29
|
+
{
|
|
30
|
+
initialIndex: profiles.indexOf(config.defaultProfile),
|
|
31
|
+
helpText: "Select review depth · Esc to cancel",
|
|
32
|
+
},
|
|
33
|
+
);
|
|
34
|
+
if (!choice) return;
|
|
35
|
+
profileOverride = choice;
|
|
36
|
+
}
|
|
37
|
+
|
|
23
38
|
const profile = resolveProfile(ctx.cwd, config, profileOverride);
|
|
24
39
|
const lsp = isLspAvailable(pi.getActiveTools());
|
|
25
40
|
|
package/src/commands/status.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@oh-my-pi/pi-coding-agent";
|
|
2
2
|
import { findActiveRun, loadAllAgentResults } from "../storage/runs.js";
|
|
3
|
-
import { notifyInfo } from "../notifications/renderer.js";
|
|
4
3
|
|
|
5
4
|
export function registerStatusCommand(pi: ExtensionAPI): void {
|
|
6
5
|
pi.registerCommand("supi:status", {
|
|
@@ -9,42 +8,35 @@ export function registerStatusCommand(pi: ExtensionAPI): void {
|
|
|
9
8
|
const activeRun = findActiveRun(ctx.cwd);
|
|
10
9
|
|
|
11
10
|
if (!activeRun) {
|
|
12
|
-
|
|
11
|
+
ctx.ui.notify("No active runs — use /supi:run to execute a plan", "info");
|
|
13
12
|
return;
|
|
14
13
|
}
|
|
15
14
|
|
|
16
15
|
const results = loadAllAgentResults(ctx.cwd, activeRun.id);
|
|
17
|
-
const completedIds = new Set(results.map((r) => r.taskId));
|
|
18
16
|
const totalTasks = activeRun.batches.reduce(
|
|
19
17
|
(sum, b) => sum + b.taskIds.length,
|
|
20
18
|
0
|
|
21
19
|
);
|
|
22
|
-
const completedCount = results.length;
|
|
23
20
|
const doneCount = results.filter((r) => r.status === "done").length;
|
|
24
21
|
const concernCount = results.filter((r) => r.status === "done_with_concerns").length;
|
|
25
22
|
const blockedCount = results.filter((r) => r.status === "blocked").length;
|
|
26
|
-
|
|
27
23
|
const currentBatch = activeRun.batches.find((b) => b.status !== "completed");
|
|
28
24
|
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
"",
|
|
25
|
+
const options = [
|
|
26
|
+
`Run: ${activeRun.id}`,
|
|
32
27
|
`Status: ${activeRun.status}`,
|
|
33
28
|
`Plan: ${activeRun.planRef}`,
|
|
34
29
|
`Profile: ${activeRun.profile}`,
|
|
35
|
-
`Progress: ${
|
|
36
|
-
"",
|
|
30
|
+
`Progress: ${results.length}/${totalTasks} tasks`,
|
|
37
31
|
` Done: ${doneCount}`,
|
|
38
32
|
` With concerns: ${concernCount}`,
|
|
39
33
|
` Blocked: ${blockedCount}`,
|
|
40
|
-
""
|
|
41
|
-
|
|
34
|
+
`Batch: ${currentBatch ? `#${currentBatch.index} (${currentBatch.status})` : "all complete"}`,
|
|
35
|
+
"Close",
|
|
42
36
|
];
|
|
43
37
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
content: [{ type: "text", text: lines.join("\n") }],
|
|
47
|
-
display: "inline",
|
|
38
|
+
await ctx.ui.select("Supipowers Status", options, {
|
|
39
|
+
helpText: "Esc to close",
|
|
48
40
|
});
|
|
49
41
|
},
|
|
50
42
|
});
|
package/src/commands/supi.ts
CHANGED
|
@@ -13,30 +13,36 @@ export function registerSupiCommand(pi: ExtensionAPI): void {
|
|
|
13
13
|
const latestReport = loadLatestReport(ctx.cwd);
|
|
14
14
|
const plans = listPlans(ctx.cwd);
|
|
15
15
|
|
|
16
|
-
const
|
|
17
|
-
"
|
|
18
|
-
"",
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
" /supi:release — Release automation",
|
|
25
|
-
" /supi:config — Manage configuration",
|
|
26
|
-
" /supi:status — Check running tasks",
|
|
27
|
-
"",
|
|
28
|
-
"## Project Status",
|
|
29
|
-
` Profile: ${config.defaultProfile}`,
|
|
30
|
-
` Plans: ${plans.length}`,
|
|
31
|
-
` Active run: ${activeRun ? activeRun.id : "none"}`,
|
|
32
|
-
` Last review: ${latestReport ? `${latestReport.timestamp.slice(0, 10)} (${latestReport.passed ? "passed" : "failed"})` : "none"}`,
|
|
16
|
+
const commands = [
|
|
17
|
+
"/supi:plan — Start collaborative planning",
|
|
18
|
+
"/supi:run — Execute a plan with sub-agents",
|
|
19
|
+
"/supi:review — Run quality gates",
|
|
20
|
+
"/supi:qa — Run QA pipeline",
|
|
21
|
+
"/supi:release — Release automation",
|
|
22
|
+
"/supi:config — Manage configuration",
|
|
23
|
+
"/supi:status — Check running tasks",
|
|
33
24
|
];
|
|
34
25
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
26
|
+
const status = [
|
|
27
|
+
`Profile: ${config.defaultProfile}`,
|
|
28
|
+
`Plans: ${plans.length}`,
|
|
29
|
+
`Active run: ${activeRun ? activeRun.id : "none"}`,
|
|
30
|
+
`Last review: ${latestReport ? `${latestReport.timestamp.slice(0, 10)} (${latestReport.passed ? "passed" : "failed"})` : "none"}`,
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
const choice = await ctx.ui.select(
|
|
34
|
+
"Supipowers",
|
|
35
|
+
[...commands, "", ...status, "", "Close"],
|
|
36
|
+
{ helpText: "Select a command to run · Esc to close" },
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
if (choice && choice.startsWith("/supi:")) {
|
|
40
|
+
const cmdName = choice.split(" ")[0].slice(1); // remove leading /
|
|
41
|
+
const handler = pi.getCommands().find((c) => c.name === cmdName);
|
|
42
|
+
if (handler) {
|
|
43
|
+
await handler.handler("", ctx);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
40
46
|
},
|
|
41
47
|
});
|
|
42
48
|
}
|