supipowers 2.2.1 → 2.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -0
- package/package.json +10 -9
- package/skills/context-mode/SKILL.md +1 -1
- package/src/bootstrap.ts +2 -1
- package/src/ci.ts +47 -0
- package/src/commands/doctor.ts +4 -2
- package/src/commands/memory.ts +138 -5
- package/src/commands/plan.ts +2 -1
- package/src/commands/update.ts +7 -5
- package/src/config/defaults.ts +2 -0
- package/src/config/schema.ts +2 -0
- package/src/context-mode/sandbox/executor.ts +30 -1
- package/src/fix-pr/scripts/exec.ts +32 -1
- package/src/harness/anti_slop/fallow-adapter.ts +4 -3
- package/src/harness/command.ts +12 -7
- package/src/harness/pipeline.ts +2 -8
- package/src/harness/stage-runner.ts +3 -0
- package/src/harness/stages/docs.ts +82 -0
- package/src/harness/stages/implement-apply.ts +1 -0
- package/src/harness/storage.ts +2 -1
- package/src/mempalace/contract.ts +32 -0
- package/src/mempalace/git-hook.ts +443 -0
- package/src/mempalace/hooks.ts +10 -15
- package/src/mempalace/tool.ts +2 -2
- package/src/mempalace/uv.ts +51 -8
- package/src/planning/approval-flow.ts +15 -17
- package/src/planning/planning-ask-tool.ts +4 -10
- package/src/release/executor.ts +13 -3
- package/src/storage/plans.ts +2 -2
- package/src/text.ts +7 -2
- package/src/types.ts +12 -0
- package/src/utils/editor.ts +31 -5
- package/src/utils/exec-cli.ts +106 -0
- package/src/visual/scripts/npm-shrinkwrap.json +878 -0
- package/src/visual/scripts/package-lock.json +878 -0
- package/src/visual/scripts/package.json +1 -1
package/README.md
CHANGED
|
@@ -36,6 +36,9 @@ The installer detects Pi (`~/.pi`) and OMP (`~/.omp`) — when both are present
|
|
|
36
36
|
| [Bun](https://bun.sh) | Runtime — required for installation and the built-in SQLite FTS index |
|
|
37
37
|
| [Git](https://git-scm.com) | Used by the installer and git-based workflows |
|
|
38
38
|
|
|
39
|
+
> [!TIP]
|
|
40
|
+
> OMP ≥15.1.7 is recommended for best reliability with supipowers command-driven agent handoffs and accurate provider-scoped `/fast` status indicators. Older compatible OMP versions can run supipowers but lack those runtime fixes.
|
|
41
|
+
|
|
39
42
|
### Optional dependencies
|
|
40
43
|
|
|
41
44
|
The installer scans for these and offers to install missing tooling where it can. Everything works without them, but each one unlocks additional capabilities.
|
package/package.json
CHANGED
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "supipowers",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.3",
|
|
4
4
|
"description": "Workflow extension for OMP coding agents.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
|
-
"test": "bun test --timeout
|
|
7
|
+
"test": "bun test --timeout 60000 --parallel tests/",
|
|
8
|
+
"test:windows": "bun test --timeout 60000 --parallel tests/platform/ tests/utils/editor.test.ts tests/utils/exec-cli.test.ts tests/fix-pr/scripts/exec.test.ts tests/mempalace/uv.test.ts tests/mempalace/git-hook.test.ts",
|
|
8
9
|
"typecheck": "tsc --noEmit",
|
|
9
|
-
"test:watch": "bun test --timeout
|
|
10
|
-
"test:evals": "bun test --timeout
|
|
10
|
+
"test:watch": "bun test --timeout 60000 --parallel --watch tests/",
|
|
11
|
+
"test:evals": "bun test --timeout 60000 --parallel tests/evals/",
|
|
11
12
|
"build": "tsc -p tsconfig.build.json",
|
|
12
|
-
"ci": "bun run
|
|
13
|
+
"ci": "bun run src/ci.ts",
|
|
13
14
|
"install:visual-server": "npm --prefix src/visual/scripts ci --ignore-scripts --no-audit --no-fund",
|
|
14
15
|
"postinstall": "bun run install:visual-server",
|
|
15
16
|
"prepare": "git config core.hooksPath hooks || true",
|
|
16
17
|
"dev-install": "bun run bin/dev-install.ts"
|
|
17
18
|
},
|
|
18
19
|
"engines": {
|
|
19
|
-
"bun": ">=1.3.
|
|
20
|
+
"bun": ">=1.3.13"
|
|
20
21
|
},
|
|
21
22
|
"keywords": [
|
|
22
23
|
"omp",
|
|
@@ -65,7 +66,7 @@
|
|
|
65
66
|
]
|
|
66
67
|
},
|
|
67
68
|
"dependencies": {
|
|
68
|
-
"@clack/prompts": "^
|
|
69
|
+
"@clack/prompts": "^1.4.0",
|
|
69
70
|
"handlebars": "^4.7.8",
|
|
70
71
|
"yaml": "^2.8.3",
|
|
71
72
|
"zod": "^4.3.0"
|
|
@@ -90,8 +91,8 @@
|
|
|
90
91
|
"@oh-my-pi/pi-ai": "latest",
|
|
91
92
|
"@oh-my-pi/pi-coding-agent": "latest",
|
|
92
93
|
"@oh-my-pi/pi-tui": "latest",
|
|
93
|
-
"@types/node": "^
|
|
94
|
+
"@types/node": "^25.8.0",
|
|
94
95
|
"bun-types": "^1.3.11",
|
|
95
|
-
"typescript": "^
|
|
96
|
+
"typescript": "^6.0.3"
|
|
96
97
|
}
|
|
97
98
|
}
|
|
@@ -63,7 +63,7 @@ When OMP's `shellMinimizer` is active, large bash output ends with a `[raw outpu
|
|
|
63
63
|
|
|
64
64
|
### Read
|
|
65
65
|
|
|
66
|
-
Reads are never blocked — OMP's native open/read tool preserves hashline anchors (e.g., `120th|content` after 14.4.1) for the edit contract. Large reads (>110 lines) are auto-compressed to head (80) + tail (30) with a `sel` hint.
|
|
66
|
+
Reads are never blocked — OMP's native open/read tool preserves hashline anchors (e.g., `120th|content` after 14.4.1) for the edit contract. Copy edit anchors exactly, without the `|content` body, and never fabricate anchors. Edit payload lines must start with `~` immediately followed by intended file content; avoid a readability space after `~` unless that space is intentional file content. Large reads (>110 lines) are auto-compressed to head (80) + tail (30) with a `sel` hint.
|
|
67
67
|
|
|
68
68
|
For analysis-only reads where anchors are not needed, prefer `ctx_execute_file(path, language, code)` — only your printed summary enters context.
|
|
69
69
|
|
package/src/bootstrap.ts
CHANGED
|
@@ -12,6 +12,7 @@ import { registerAiReviewCommand, handleAiReview } from "./commands/ai-review.js
|
|
|
12
12
|
import { registerQaCommand } from "./commands/qa.js";
|
|
13
13
|
import { registerReleaseCommand, handleRelease } from "./commands/release.js";
|
|
14
14
|
import { registerUpdateCommand, handleUpdate } from "./commands/update.js";
|
|
15
|
+
import { execCli } from "./utils/exec-cli.js";
|
|
15
16
|
import { registerDoctorCommand, handleDoctor } from "./commands/doctor.js";
|
|
16
17
|
import { registerModelCommand, handleModel } from "./commands/model.js";
|
|
17
18
|
import { registerFixPrCommand } from "./commands/fix-pr.js";
|
|
@@ -175,7 +176,7 @@ export function bootstrap(platform: Platform): void {
|
|
|
175
176
|
const currentVersion = getInstalledVersion(platform);
|
|
176
177
|
if (!currentVersion) return;
|
|
177
178
|
|
|
178
|
-
platform.exec("npm", ["view", "supipowers", "version"], { cwd: tmpdir() })
|
|
179
|
+
execCli((cmd, args, opts) => platform.exec(cmd, args, opts), "npm", ["view", "supipowers", "version"], { cwd: tmpdir() })
|
|
179
180
|
.then((result) => {
|
|
180
181
|
if (result.code !== 0) return;
|
|
181
182
|
const latest = result.stdout.trim();
|
package/src/ci.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export type CiProfile = "default" | "windows-fast";
|
|
2
|
+
|
|
3
|
+
export interface CiCommand {
|
|
4
|
+
label: string;
|
|
5
|
+
args: string[];
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function resolveCiProfile(value: string | undefined): CiProfile {
|
|
9
|
+
if (value === undefined || value === "" || value === "default") return "default";
|
|
10
|
+
if (value === "windows-fast") return "windows-fast";
|
|
11
|
+
throw new Error(`Unsupported SUPIPOWERS_CI_PROFILE: ${value}`);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function getCiPlan(profile: CiProfile): CiCommand[] {
|
|
15
|
+
const typecheck = { label: "Typecheck", args: ["bun", "run", "typecheck"] };
|
|
16
|
+
if (profile === "windows-fast") {
|
|
17
|
+
return [
|
|
18
|
+
typecheck,
|
|
19
|
+
{ label: "Windows portability tests", args: ["bun", "run", "test:windows"] },
|
|
20
|
+
];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return [
|
|
24
|
+
typecheck,
|
|
25
|
+
{ label: "Test", args: ["bun", "run", "test"] },
|
|
26
|
+
];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function runCi(profileValue: string | undefined = process.env.SUPIPOWERS_CI_PROFILE): number {
|
|
30
|
+
const profile = resolveCiProfile(profileValue);
|
|
31
|
+
for (const command of getCiPlan(profile)) {
|
|
32
|
+
console.log(`\n> ${command.label}`);
|
|
33
|
+
const result = Bun.spawnSync({
|
|
34
|
+
cmd: command.args,
|
|
35
|
+
stdin: "inherit",
|
|
36
|
+
stdout: "inherit",
|
|
37
|
+
stderr: "inherit",
|
|
38
|
+
env: process.env,
|
|
39
|
+
});
|
|
40
|
+
if (!result.success) return result.exitCode;
|
|
41
|
+
}
|
|
42
|
+
return 0;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (import.meta.main) {
|
|
46
|
+
process.exitCode = runCi();
|
|
47
|
+
}
|
package/src/commands/doctor.ts
CHANGED
|
@@ -11,6 +11,7 @@ import { formatReliabilitySection, loadReliabilitySummaries } from "../storage/r
|
|
|
11
11
|
import { getMetricsStore, getSessionId } from "../context-mode/hooks.js";
|
|
12
12
|
import { getProjectStatePath, getProjectStateDir } from "../workspace/state-paths.js";
|
|
13
13
|
import { basename } from "node:path";
|
|
14
|
+
import { execCli } from "../utils/exec-cli.js";
|
|
14
15
|
|
|
15
16
|
export interface CheckResult {
|
|
16
17
|
name: string;
|
|
@@ -435,14 +436,14 @@ export function checkMetrics(
|
|
|
435
436
|
|
|
436
437
|
export async function checkNpm(platform: Platform): Promise<CheckResult> {
|
|
437
438
|
try {
|
|
438
|
-
const vResult = await platform.exec("npm", ["--version"]);
|
|
439
|
+
const vResult = await execCli((cmd, args, opts) => platform.exec(cmd, args, opts), "npm", ["--version"]);
|
|
439
440
|
if (vResult.code !== 0) {
|
|
440
441
|
return { name: "npm", presence: { ok: false, detail: "npm not found" } };
|
|
441
442
|
}
|
|
442
443
|
const version = vResult.stdout.trim();
|
|
443
444
|
const presence = { ok: true, detail: `v${version}` };
|
|
444
445
|
|
|
445
|
-
const pingResult = await platform.exec("npm", ["ping"]);
|
|
446
|
+
const pingResult = await execCli((cmd, args, opts) => platform.exec(cmd, args, opts), "npm", ["ping"]);
|
|
446
447
|
if (pingResult.code === 0) {
|
|
447
448
|
return { name: "npm", presence, functional: { ok: true, detail: "Registry reachable" } };
|
|
448
449
|
}
|
|
@@ -501,6 +502,7 @@ export function getDoctorRecommendations(): string[] {
|
|
|
501
502
|
return [
|
|
502
503
|
"Set `tools.elideFileMutationInputs: true` (OMP ≥14.7.0) — elides `write`/`edit`/`apply_patch` payloads from history after success. Saves significant context on long sessions like `/supi:ultraplan execute` and `/supi:harness implement`.",
|
|
503
504
|
"Update to OMP ≥14.7.2 — fixes the `Working…` spinner staying active after read-only commands such as `/supi:status`, `/supi:doctor`, `/supi:context`, and `/supi:clear`. (oh-my-pi#927)",
|
|
505
|
+
"Use OMP ≥15.1.7 for best reliability — includes ACP fixes for command-driven agent handoffs and permission prompts, plus accurate provider-scoped `/fast` status-line indicators.",
|
|
504
506
|
];
|
|
505
507
|
}
|
|
506
508
|
|
package/src/commands/memory.ts
CHANGED
|
@@ -6,17 +6,33 @@ import {
|
|
|
6
6
|
snapshotMempalaceInstall,
|
|
7
7
|
steerMempalaceInitialization,
|
|
8
8
|
} from "../mempalace/installer-helper.js";
|
|
9
|
+
import {
|
|
10
|
+
getMempalacePostCommitHookStatus,
|
|
11
|
+
installMempalacePostCommitHook,
|
|
12
|
+
uninstallMempalacePostCommitHook,
|
|
13
|
+
type MempalacePostCommitHookStatusResult,
|
|
14
|
+
} from "../mempalace/git-hook.js";
|
|
9
15
|
|
|
10
16
|
const SUBCOMMANDS = [
|
|
11
17
|
{ name: "status", description: "Show palace path, managed venv, and install status" },
|
|
12
18
|
{ name: "setup", description: "Install or repair the managed Python environment and MemPalace package" },
|
|
19
|
+
{ name: "git-hook", description: "Manage the opt-in post-commit MemPalace reindex hook" },
|
|
20
|
+
] as const;
|
|
21
|
+
|
|
22
|
+
const GIT_HOOK_ACTIONS = [
|
|
23
|
+
{ name: "status", description: "Show post-commit reindex hook status" },
|
|
24
|
+
{ name: "install", description: "Install or update the post-commit reindex hook" },
|
|
25
|
+
{ name: "uninstall", description: "Remove the managed hook and restore a chained user hook" },
|
|
13
26
|
] as const;
|
|
14
27
|
|
|
15
28
|
const HELP = [
|
|
16
29
|
"/supi:memory — native MemPalace integration",
|
|
17
30
|
"",
|
|
18
31
|
"Subcommands:",
|
|
19
|
-
...SUBCOMMANDS.map((subcommand) => ` ${subcommand.name.padEnd(
|
|
32
|
+
...SUBCOMMANDS.map((subcommand) => ` ${subcommand.name.padEnd(10)} ${subcommand.description}`),
|
|
33
|
+
"",
|
|
34
|
+
"Git hook:",
|
|
35
|
+
" git-hook status|install|uninstall",
|
|
20
36
|
"",
|
|
21
37
|
"Memory APIs are exposed to the agent via the `mempalace` tool.",
|
|
22
38
|
].join("\n");
|
|
@@ -43,6 +59,80 @@ function statusReport(platform: Platform, cwd: string): string {
|
|
|
43
59
|
return lines.join("\n");
|
|
44
60
|
}
|
|
45
61
|
|
|
62
|
+
function gitHookStatusReport(result: MempalacePostCommitHookStatusResult): string {
|
|
63
|
+
if (!result.ok) {
|
|
64
|
+
return [
|
|
65
|
+
"/supi:memory git-hook status",
|
|
66
|
+
"",
|
|
67
|
+
`status: unavailable (${result.code})`,
|
|
68
|
+
result.message,
|
|
69
|
+
].join("\n");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return [
|
|
73
|
+
"/supi:memory git-hook status",
|
|
74
|
+
"",
|
|
75
|
+
`repo root: ${result.repoRoot}`,
|
|
76
|
+
`hooks dir: ${result.hooksDir}`,
|
|
77
|
+
`core.hooksPath: ${result.coreHooksPath ?? "(default .git/hooks)"}`,
|
|
78
|
+
`post-commit hook: ${result.installed ? "present" : "missing"}`,
|
|
79
|
+
`managed by supipowers: ${result.managed}`,
|
|
80
|
+
`chained user hook: ${result.userHookPresent ? result.userHookPath : "none"}`,
|
|
81
|
+
`reindex runner: ${result.runnerPresent ? result.runnerPath : `${result.runnerPath} (missing)`}`,
|
|
82
|
+
].join("\n");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function handleGitHook(platform: Platform, ctx: PlatformContext, action: string): Promise<void> {
|
|
86
|
+
const config = loadConfig(platform.paths, ctx.cwd);
|
|
87
|
+
const command = action || "status";
|
|
88
|
+
|
|
89
|
+
if (command === "status") {
|
|
90
|
+
const status = await getMempalacePostCommitHookStatus({
|
|
91
|
+
paths: platform.paths,
|
|
92
|
+
cwd: ctx.cwd,
|
|
93
|
+
config,
|
|
94
|
+
exec: platform.exec,
|
|
95
|
+
});
|
|
96
|
+
ctx.ui.notify(gitHookStatusReport(status), status.ok ? "info" : "warning");
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (command === "install") {
|
|
101
|
+
const result = await installMempalacePostCommitHook({
|
|
102
|
+
paths: platform.paths,
|
|
103
|
+
cwd: ctx.cwd,
|
|
104
|
+
config,
|
|
105
|
+
exec: platform.exec,
|
|
106
|
+
});
|
|
107
|
+
if (result.ok) {
|
|
108
|
+
ctx.ui.notify(
|
|
109
|
+
`MemPalace post-commit reindex hook ${result.action}: ${result.hookPath}`,
|
|
110
|
+
"info",
|
|
111
|
+
);
|
|
112
|
+
} else {
|
|
113
|
+
ctx.ui.notify(`MemPalace post-commit hook install failed (${result.code}): ${result.message}`, "warning");
|
|
114
|
+
}
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (command === "uninstall") {
|
|
119
|
+
const result = await uninstallMempalacePostCommitHook({
|
|
120
|
+
paths: platform.paths,
|
|
121
|
+
cwd: ctx.cwd,
|
|
122
|
+
config,
|
|
123
|
+
exec: platform.exec,
|
|
124
|
+
});
|
|
125
|
+
if (result.ok) {
|
|
126
|
+
ctx.ui.notify(`MemPalace post-commit reindex hook ${result.action}.`, "info");
|
|
127
|
+
} else {
|
|
128
|
+
ctx.ui.notify(`MemPalace post-commit hook uninstall failed (${result.code}): ${result.message}`, "warning");
|
|
129
|
+
}
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
ctx.ui.notify(`Unknown /supi:memory git-hook action: ${command}\n\n${HELP}`, "warning");
|
|
134
|
+
}
|
|
135
|
+
|
|
46
136
|
async function runSetup(platform: Platform, ctx: PlatformContext): Promise<void> {
|
|
47
137
|
const config = loadConfig(platform.paths, ctx.cwd);
|
|
48
138
|
if (!config.mempalace.enabled) {
|
|
@@ -105,6 +195,26 @@ async function runSetup(platform: Platform, ctx: PlatformContext): Promise<void>
|
|
|
105
195
|
"info",
|
|
106
196
|
);
|
|
107
197
|
|
|
198
|
+
|
|
199
|
+
if (config.mempalace.hooks.postCommitReindex) {
|
|
200
|
+
const hookResult = await installMempalacePostCommitHook({
|
|
201
|
+
paths: platform.paths,
|
|
202
|
+
cwd: ctx.cwd,
|
|
203
|
+
config,
|
|
204
|
+
exec: platform.exec,
|
|
205
|
+
});
|
|
206
|
+
if (hookResult.ok) {
|
|
207
|
+
ctx.ui.notify(
|
|
208
|
+
`MemPalace post-commit reindex hook ${hookResult.action}: ${hookResult.hookPath}`,
|
|
209
|
+
"info",
|
|
210
|
+
);
|
|
211
|
+
} else {
|
|
212
|
+
ctx.ui.notify(
|
|
213
|
+
`MemPalace setup completed, but post-commit hook install failed (${hookResult.code}): ${hookResult.message}`,
|
|
214
|
+
"warning",
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
108
218
|
// Check if the current project's wing is already initialized; if not, steer
|
|
109
219
|
// the model to run init + mine through the mempalace tool.
|
|
110
220
|
const initState = await checkMempalaceProjectInitialized({
|
|
@@ -141,8 +251,8 @@ async function runSetup(platform: Platform, ctx: PlatformContext): Promise<void>
|
|
|
141
251
|
export function handleMemory(platform: Platform, ctx: PlatformContext, args?: string): void {
|
|
142
252
|
if (!ctx.hasUI) return;
|
|
143
253
|
|
|
144
|
-
const
|
|
145
|
-
|
|
254
|
+
const parts = (args ?? "").trim().split(/\s+/).filter(Boolean);
|
|
255
|
+
const sub = parts[0] ?? "";
|
|
146
256
|
if (sub === "" || sub === "help" || sub === "--help" || sub === "-h") {
|
|
147
257
|
ctx.ui.notify(HELP, "info");
|
|
148
258
|
return;
|
|
@@ -168,14 +278,37 @@ export function handleMemory(platform: Platform, ctx: PlatformContext, args?: st
|
|
|
168
278
|
return;
|
|
169
279
|
}
|
|
170
280
|
|
|
281
|
+
if (sub === "git-hook") {
|
|
282
|
+
void (async () => {
|
|
283
|
+
try {
|
|
284
|
+
await handleGitHook(platform, ctx, parts[1] ?? "status");
|
|
285
|
+
} catch (err) {
|
|
286
|
+
ctx.ui.notify(`MemPalace git-hook command crashed: ${(err as Error).message}`, "error");
|
|
287
|
+
}
|
|
288
|
+
})();
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
|
|
171
292
|
ctx.ui.notify(`Unknown /supi:memory subcommand: ${sub}\n\n${HELP}`, "warning");
|
|
172
293
|
}
|
|
173
294
|
|
|
174
295
|
export function registerMemoryCommand(platform: Platform): void {
|
|
175
296
|
platform.registerCommand("supi:memory", {
|
|
176
|
-
description: "Manage native MemPalace integration (status, setup)",
|
|
297
|
+
description: "Manage native MemPalace integration (status, setup, git-hook)",
|
|
177
298
|
getArgumentCompletions(prefix: string) {
|
|
178
|
-
const
|
|
299
|
+
const rawLower = prefix.toLowerCase();
|
|
300
|
+
const lower = rawLower.trim();
|
|
301
|
+
if (rawLower.startsWith("git-hook ")) {
|
|
302
|
+
const actionPrefix = rawLower.slice("git-hook ".length).trimStart();
|
|
303
|
+
const matches = GIT_HOOK_ACTIONS
|
|
304
|
+
.filter((action) => action.name.startsWith(actionPrefix))
|
|
305
|
+
.map((action) => ({
|
|
306
|
+
value: `git-hook ${action.name} `,
|
|
307
|
+
label: action.name,
|
|
308
|
+
description: action.description,
|
|
309
|
+
}));
|
|
310
|
+
return matches.length > 0 ? matches : null;
|
|
311
|
+
}
|
|
179
312
|
const matches = SUBCOMMANDS
|
|
180
313
|
.filter((subcommand) => subcommand.name.startsWith(lower))
|
|
181
314
|
.map((subcommand) => ({
|
package/src/commands/plan.ts
CHANGED
|
@@ -19,6 +19,7 @@ import { loadModelConfig } from "../config/model-config.js";
|
|
|
19
19
|
import { getProjectStatePath } from "../workspace/state-paths.js";
|
|
20
20
|
import { cancelPlanTracking, startPlanTracking } from "../planning/approval-flow.js";
|
|
21
21
|
import { stopVisualServer } from "../visual/stop-server.js";
|
|
22
|
+
import { execCli } from "../utils/exec-cli.js";
|
|
22
23
|
|
|
23
24
|
modelRegistry.register({
|
|
24
25
|
id: "plan",
|
|
@@ -111,7 +112,7 @@ export function registerPlanCommand(platform: Platform): void {
|
|
|
111
112
|
const nodeModules = path.join(scriptsDir, "node_modules");
|
|
112
113
|
if (!fs.existsSync(nodeModules)) {
|
|
113
114
|
notifyInfo(ctx, "Installing visual companion dependencies...");
|
|
114
|
-
const installResult = await platform.exec("npm", ["install", "--production"], { cwd: scriptsDir });
|
|
115
|
+
const installResult = await execCli((cmd, args, opts) => platform.exec(cmd, args, opts), "npm", ["install", "--production"], { cwd: scriptsDir });
|
|
115
116
|
if (installResult.code !== 0) {
|
|
116
117
|
notifyError(ctx, "Failed to install visual companion dependencies", installResult.stderr);
|
|
117
118
|
debugLogger.log("visual_companion_dependency_install_failed", {
|
package/src/commands/update.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
steerMempalaceInitialization,
|
|
12
12
|
} from "../mempalace/installer-helper.js";
|
|
13
13
|
import { loadConfig } from "../config/loader.js";
|
|
14
|
+
import { execCli, wrapExecForCli } from "../utils/exec-cli.js";
|
|
14
15
|
|
|
15
16
|
// ── Options builder ──────────────────────────────────────
|
|
16
17
|
|
|
@@ -59,7 +60,7 @@ async function updateSupipowers(
|
|
|
59
60
|
ctx.ui.notify(`Current version: v${currentVersion}`, "info");
|
|
60
61
|
|
|
61
62
|
// Check latest version on npm
|
|
62
|
-
const checkResult = await platform.exec("npm", ["view", "supipowers", "version"], { cwd: tmpdir() });
|
|
63
|
+
const checkResult = await execCli((cmd, args, opts) => platform.exec(cmd, args, opts), "npm", ["view", "supipowers", "version"], { cwd: tmpdir() });
|
|
63
64
|
if (checkResult.code !== 0) {
|
|
64
65
|
ctx.ui.notify("Failed to check for updates — npm view failed", "error");
|
|
65
66
|
return null;
|
|
@@ -78,7 +79,8 @@ async function updateSupipowers(
|
|
|
78
79
|
mkdirSync(tempDir, { recursive: true });
|
|
79
80
|
|
|
80
81
|
try {
|
|
81
|
-
const installResult = await
|
|
82
|
+
const installResult = await execCli(
|
|
83
|
+
(cmd, args, opts) => platform.exec(cmd, args, opts),
|
|
82
84
|
"npm", ["install", "--prefix", tempDir, `supipowers@${latestVersion}`],
|
|
83
85
|
{ cwd: tempDir },
|
|
84
86
|
);
|
|
@@ -130,10 +132,10 @@ async function updateSupipowers(
|
|
|
130
132
|
// Install runtime dependencies (handlebars, etc.)
|
|
131
133
|
// Without this, the extension fails to load because node_modules/ was deleted above.
|
|
132
134
|
ctx.ui.notify("Installing dependencies...", "info");
|
|
133
|
-
const bunInstall = await platform.exec("bun", ["install", "--production"], { cwd: extDir });
|
|
135
|
+
const bunInstall = await execCli((cmd, args, opts) => platform.exec(cmd, args, opts), "bun", ["install", "--production"], { cwd: extDir });
|
|
134
136
|
if (bunInstall.code !== 0) {
|
|
135
137
|
// Fallback to npm if bun is not available (e.g. Windows without global bun)
|
|
136
|
-
const npmInstall = await platform.exec("npm", ["install", "--omit=dev"], { cwd: extDir });
|
|
138
|
+
const npmInstall = await execCli((cmd, args, opts) => platform.exec(cmd, args, opts), "npm", ["install", "--omit=dev"], { cwd: extDir });
|
|
137
139
|
if (npmInstall.code !== 0) {
|
|
138
140
|
ctx.ui.notify(
|
|
139
141
|
"Could not install extension dependencies.\n" +
|
|
@@ -177,7 +179,7 @@ async function updateSupipowers(
|
|
|
177
179
|
|
|
178
180
|
export function handleUpdate(platform: Platform, ctx: PlatformContext): void {
|
|
179
181
|
void (async () => {
|
|
180
|
-
const exec = (cmd: string, args: string[]) => platform.exec(cmd, args);
|
|
182
|
+
const exec = wrapExecForCli((cmd: string, args: string[]) => platform.exec(cmd, args));
|
|
181
183
|
|
|
182
184
|
// 1. Scan all dependencies
|
|
183
185
|
const allStatuses = await scanAll(exec);
|
package/src/config/defaults.ts
CHANGED
|
@@ -74,9 +74,11 @@ export const DEFAULT_CONFIG: SupipowersConfig = {
|
|
|
74
74
|
hooks: {
|
|
75
75
|
wakeUp: true,
|
|
76
76
|
searchGuidance: true,
|
|
77
|
+
writeGuidance: true,
|
|
77
78
|
autoSearchOnPrompt: true,
|
|
78
79
|
compactionCheckpoint: true,
|
|
79
80
|
shutdownDiary: true,
|
|
81
|
+
postCommitReindex: false,
|
|
80
82
|
},
|
|
81
83
|
budgets: {
|
|
82
84
|
wakeUpTokens: 1200,
|
package/src/config/schema.ts
CHANGED
|
@@ -109,9 +109,11 @@ export const ConfigSchema = z.object(
|
|
|
109
109
|
{
|
|
110
110
|
wakeUp: z.boolean(),
|
|
111
111
|
searchGuidance: z.boolean(),
|
|
112
|
+
writeGuidance: z.boolean(),
|
|
112
113
|
autoSearchOnPrompt: z.boolean(),
|
|
113
114
|
compactionCheckpoint: z.boolean(),
|
|
114
115
|
shutdownDiary: z.boolean(),
|
|
116
|
+
postCommitReindex: z.boolean(),
|
|
115
117
|
},
|
|
116
118
|
).strict(),
|
|
117
119
|
budgets: z.object(
|
|
@@ -20,6 +20,31 @@ export interface ExecuteResult {
|
|
|
20
20
|
|
|
21
21
|
const DEFAULT_TIMEOUT = 30_000;
|
|
22
22
|
|
|
23
|
+
const MISSING_BASH_EXIT_CODE = 127;
|
|
24
|
+
function buildMissingBashMessage(platform: NodeJS.Platform): string {
|
|
25
|
+
const base = "bash is required to execute shell snippets.";
|
|
26
|
+
switch (platform) {
|
|
27
|
+
case "win32":
|
|
28
|
+
return `${base} Install Git for Windows or WSL, then retry.`;
|
|
29
|
+
case "darwin":
|
|
30
|
+
return `${base} Install bash via Homebrew (\`brew install bash\`) or Xcode Command Line Tools, then retry.`;
|
|
31
|
+
case "linux":
|
|
32
|
+
return `${base} Install bash via your distro's package manager (apt/yum/pacman), then retry.`;
|
|
33
|
+
default:
|
|
34
|
+
return `${base} Install bash for your platform and retry.`;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const MISSING_BASH_MESSAGE = buildMissingBashMessage(process.platform);
|
|
38
|
+
|
|
39
|
+
function missingBashResult(startedAt: number): ExecuteResult {
|
|
40
|
+
return {
|
|
41
|
+
stdout: "",
|
|
42
|
+
stderr: MISSING_BASH_MESSAGE,
|
|
43
|
+
exitCode: MISSING_BASH_EXIT_CODE,
|
|
44
|
+
duration: performance.now() - startedAt,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
23
48
|
export async function executeCode(
|
|
24
49
|
language: string,
|
|
25
50
|
code: string,
|
|
@@ -29,6 +54,11 @@ export async function executeCode(
|
|
|
29
54
|
const opts = options ?? {};
|
|
30
55
|
const timeout = opts.timeout ?? DEFAULT_TIMEOUT;
|
|
31
56
|
const cwd = opts.cwd ?? process.cwd();
|
|
57
|
+
const start = performance.now();
|
|
58
|
+
|
|
59
|
+
if (language === "shell" && Bun.which("bash") === null) {
|
|
60
|
+
return missingBashResult(start);
|
|
61
|
+
}
|
|
32
62
|
|
|
33
63
|
const id = randomUUID();
|
|
34
64
|
const srcPath = path.join(os.tmpdir(), `ctx-exec-${id}${runner.fileExt}`);
|
|
@@ -37,7 +67,6 @@ export async function executeCode(
|
|
|
37
67
|
: undefined;
|
|
38
68
|
|
|
39
69
|
fs.writeFileSync(srcPath, code);
|
|
40
|
-
const start = performance.now();
|
|
41
70
|
|
|
42
71
|
try {
|
|
43
72
|
// Compile step for compiled languages
|
|
@@ -2,6 +2,35 @@ import { spawnSync } from "node:child_process";
|
|
|
2
2
|
import type { ExecOptions, ExecResult, Platform } from "../../platform/types.js";
|
|
3
3
|
import { findExecutable } from "../../utils/executable.js";
|
|
4
4
|
|
|
5
|
+
export interface CliInvocation {
|
|
6
|
+
cmd: string;
|
|
7
|
+
args: string[];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function quoteCmdArgument(arg: string): string {
|
|
11
|
+
return `"${arg.replace(/"/g, '""')}"`;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function buildCliInvocation(
|
|
15
|
+
resolvedCommand: string,
|
|
16
|
+
args: string[],
|
|
17
|
+
platform: NodeJS.Platform = process.platform,
|
|
18
|
+
): CliInvocation {
|
|
19
|
+
if (platform === "win32" && /\.(cmd|bat)$/i.test(resolvedCommand)) {
|
|
20
|
+
return {
|
|
21
|
+
cmd: "cmd.exe",
|
|
22
|
+
args: [
|
|
23
|
+
"/d",
|
|
24
|
+
"/s",
|
|
25
|
+
"/c",
|
|
26
|
+
`"${[resolvedCommand, ...args].map(quoteCmdArgument).join(" ")}"`,
|
|
27
|
+
],
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return { cmd: resolvedCommand, args };
|
|
32
|
+
}
|
|
33
|
+
|
|
5
34
|
export function runCliCommand(
|
|
6
35
|
command: string,
|
|
7
36
|
args: string[],
|
|
@@ -13,7 +42,9 @@ export function runCliCommand(
|
|
|
13
42
|
pathext: env.PATHEXT,
|
|
14
43
|
}) ?? command;
|
|
15
44
|
|
|
16
|
-
const
|
|
45
|
+
const invocation = buildCliInvocation(resolvedCommand, args);
|
|
46
|
+
|
|
47
|
+
const result = spawnSync(invocation.cmd, invocation.args, {
|
|
17
48
|
cwd: options?.cwd,
|
|
18
49
|
env,
|
|
19
50
|
encoding: "utf8",
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
type SlopBackendResult,
|
|
26
26
|
type SlopFinding,
|
|
27
27
|
} from "./backend.js";
|
|
28
|
+
import { execCli } from "../../utils/exec-cli.js";
|
|
28
29
|
|
|
29
30
|
const DEFAULT_TIMEOUT_MS = 60_000;
|
|
30
31
|
|
|
@@ -149,7 +150,7 @@ async function resolveInvocation(
|
|
|
149
150
|
}
|
|
150
151
|
|
|
151
152
|
try {
|
|
152
|
-
const probe = await platform.exec("npx", ["--no-install", "fallow", "--version"], { timeout: 5000 });
|
|
153
|
+
const probe = await execCli((cmd, args, opts) => platform.exec(cmd, args, opts), "npx", ["--no-install", "fallow", "--version"], { timeout: 5000 });
|
|
153
154
|
if (probe.code === 0) {
|
|
154
155
|
availabilityCache = { ok: true, via: "npx" };
|
|
155
156
|
return { ok: true, cmd: "npx", baseArgs: ["--no-install", "fallow"], via: "npx" };
|
|
@@ -187,7 +188,7 @@ async function runFallow(
|
|
|
187
188
|
const startedAt = Date.now();
|
|
188
189
|
let result;
|
|
189
190
|
try {
|
|
190
|
-
result = await platform.exec(invocation.cmd, args, {
|
|
191
|
+
result = await execCli((cmd, args, opts) => platform.exec(cmd, args, opts), invocation.cmd, args, {
|
|
191
192
|
cwd: opts.cwd,
|
|
192
193
|
timeout: opts.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
193
194
|
});
|
|
@@ -269,7 +270,7 @@ export class FallowAdapter implements SlopBackend {
|
|
|
269
270
|
if (opts.subtree) args.push("--path", opts.subtree);
|
|
270
271
|
|
|
271
272
|
try {
|
|
272
|
-
const result = await platform.exec(invocation.cmd, args, {
|
|
273
|
+
const result = await execCli((cmd, args, opts) => platform.exec(cmd, args, opts), invocation.cmd, args, {
|
|
273
274
|
cwd: opts.cwd,
|
|
274
275
|
timeout: opts.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
275
276
|
});
|
package/src/harness/command.ts
CHANGED
|
@@ -45,7 +45,6 @@ import {
|
|
|
45
45
|
import { computeScore } from "./anti_slop/score.js";
|
|
46
46
|
import {
|
|
47
47
|
type BuildRunnerInput,
|
|
48
|
-
type HarnessPipelineProgressEvent,
|
|
49
48
|
type PipelineRunOutcome,
|
|
50
49
|
HARNESS_STAGE_ORDER,
|
|
51
50
|
runHarnessPipelineUntilGate,
|
|
@@ -58,7 +57,7 @@ import { DEFAULT_HARNESS_CONFIG } from "./hooks/register.js";
|
|
|
58
57
|
import { handlePrComment } from "./pr-comment/handler.js";
|
|
59
58
|
import { runGitVerificationQa } from "./git-verify-qa.js";
|
|
60
59
|
import { getHarnessSessionDir } from "./project-paths.js";
|
|
61
|
-
import type { HarnessDesignSpec, HarnessGateMode, HarnessSession, HarnessStage } from "../types.js";
|
|
60
|
+
import type { HarnessDesignSpec, HarnessGateMode, HarnessPipelineProgressEvent, HarnessSession, HarnessStage } from "../types.js";
|
|
62
61
|
|
|
63
62
|
modelRegistry.register({
|
|
64
63
|
id: "harness",
|
|
@@ -133,9 +132,11 @@ function createHarnessProgress(ctx: HarnessCommandContext) {
|
|
|
133
132
|
let done = 0;
|
|
134
133
|
let cur: HarnessStage | null = null;
|
|
135
134
|
const completed: string[] = [];
|
|
135
|
+
let liveDetail: string | null = null;
|
|
136
136
|
|
|
137
137
|
function refresh() {
|
|
138
|
-
const
|
|
138
|
+
const baseLabel = cur ? HARNESS_STAGE_LABELS[cur] : "Complete";
|
|
139
|
+
const label = cur && liveDetail ? `${baseLabel} — ${liveDetail}` : baseLabel;
|
|
139
140
|
const spinner = cur ? "\u25cc" : "\u2713";
|
|
140
141
|
(ctx.ui as any).setStatus?.("supi-harness", ` ${spinner} harness: ${label} (${done}/${SO.length})`);
|
|
141
142
|
}
|
|
@@ -147,22 +148,26 @@ function createHarnessProgress(ctx: HarnessCommandContext) {
|
|
|
147
148
|
case "stage-started":
|
|
148
149
|
cur = event.stage;
|
|
149
150
|
break;
|
|
151
|
+
case "stage-progress":
|
|
152
|
+
cur = event.stage;
|
|
153
|
+
liveDetail = event.detail;
|
|
154
|
+
break;
|
|
150
155
|
case "stage-completed": {
|
|
151
|
-
done += 1; cur = null;
|
|
156
|
+
done += 1; cur = null; liveDetail = null;
|
|
152
157
|
const mark = "\u2713";
|
|
153
158
|
completed.push(`${mark} ${HARNESS_STAGE_LABELS[event.stage]}: ${event.detail || "done"}`);
|
|
154
159
|
break;
|
|
155
160
|
}
|
|
156
161
|
case "stage-skipped":
|
|
157
|
-
done += 1; cur = null;
|
|
162
|
+
done += 1; cur = null; liveDetail = null;
|
|
158
163
|
completed.push(`\u2013 ${HARNESS_STAGE_LABELS[event.stage]}: skipped`);
|
|
159
164
|
break;
|
|
160
165
|
case "awaiting-user":
|
|
161
|
-
done += 1; cur = null;
|
|
166
|
+
done += 1; cur = null; liveDetail = null;
|
|
162
167
|
completed.push(`\u25cb ${HARNESS_STAGE_LABELS[event.stage]}: ${event.detail || "awaiting review"}`);
|
|
163
168
|
break;
|
|
164
169
|
case "stage-failed": case "stage-blocked":
|
|
165
|
-
cur = null;
|
|
170
|
+
cur = null; liveDetail = null;
|
|
166
171
|
completed.push(`\u2717 ${HARNESS_STAGE_LABELS[event.stage]}: ${event.detail || "failed"}`);
|
|
167
172
|
break;
|
|
168
173
|
}
|