Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

This port applies the Cursor → Claude Code substitutions in skill bodies. Earlier drafts left them flagged; this revision resolves them. A later pass added a Codex build that shares the same skills; see [Codex port](#codex-port) below.

## Unreleased isolates runner startup from project-controlled Bun configuration

`pstack-runner` now starts Bun through a small POSIX shell boundary that disables automatic environment-file loading, ignores the repository's `bunfig.toml`, and removes inherited Bun and Node startup options before trusted TypeScript runs. It still inherits ordinary environment variables that the parent explicitly passes, but a repository can no longer inject credentials or preload code before the runner performs its subscription preflight. A boundary regression test executes the shipped wrapper from a fixture repository and covers `.env`, `bunfig.toml`, `BUN_OPTIONS`, and `NODE_OPTIONS`.

## 1.2.0 adds verified multi-PR plans, earlier runtime diagnostics, and shared review-bot triage

Plans with several stages now use one checklist instead of an overview and separate files for each stage. It has one ordered section for every pull request and keeps all ten ways of testing the real product, unit tests, live and performance proof, checks for how changes work together, merge rules, and supporting details in one place. A Node-based checker with no extra dependencies rejects missing or out-of-order sections, fake screenshots, empty definitions of success, incomplete performance proof, incorrectly written review checks, unsupported punctuation, and incorrect command use. Claude Code and Codex use the same installed skill and checker through their existing parent-controlled setup. If a provider fails, it is identified by name and treated as a dropout. No backup provider or hidden time limit was added.
Expand Down
5 changes: 5 additions & 0 deletions plugins/pstack/skills/poteto-mode/scripts/runner/entry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export {};

const startedAt = Date.now();
const { main } = await import("./cli.ts");
process.exitCode = await main(process.argv.slice(2), startedAt);
14 changes: 10 additions & 4 deletions plugins/pstack/skills/poteto-mode/scripts/runner/pstack-runner
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
#!/usr/bin/env bun
const startedAt = Date.now();
const { main } = await import("./cli.ts");
process.exitCode = await main(process.argv.slice(2), startedAt);
#!/bin/sh
set -eu

runner_dir=$(CDPATH= cd -P "$(dirname "$0")" && pwd)

# Bun reads these before application code runs. Do not let an untrusted project
# select an env file or preload code into the trusted runner process.
unset BUN_OPTIONS NODE_OPTIONS

exec bun --no-env-file --config=/dev/null "$runner_dir/entry.ts" "$@"
67 changes: 65 additions & 2 deletions plugins/pstack/skills/poteto-mode/scripts/runner/run.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ function receipt(path: string): RunnerReceipt {

function runnerArgs(input: RunnerOptions): string[] {
const args = [
join(import.meta.dir, "pstack-runner"),
join(import.meta.dir, "entry.ts"),
"--parent", input.parent,
"--provider", input.provider,
"--model", input.model,
Expand Down Expand Up @@ -668,14 +668,77 @@ describe("runLane", () => {
expect(receipt(input.receiptPath).status).toBe("complete");
});

it("isolates Bun startup from project configuration at the shipped executable boundary", async () => {
writeFileSync(
join(scratch, ".env"),
[
"PSTACK_PROJECT_ENV_SENTINEL=loaded-from-project-dotenv",
"PSTACK_INHERITED_ENV_SENTINEL=dotenv-must-not-override-parent",
"",
].join("\n")
);
const preloadCapture = join(scratch, "preload-ran.txt");
writeFileSync(
join(scratch, "bunfig.toml"),
'preload = ["./preload.ts"]\n'
);
writeFileSync(
join(scratch, "preload.ts"),
'await Bun.write(process.env.PSTACK_PRELOAD_CAPTURE!, "project preload ran");\n'
);
const capturedEnv = join(scratch, "captured-env.txt");
const codex = join(bin, "codex");
writeFileSync(
codex,
`#!/bin/sh
if [ "$1" = "login" ]; then
echo "Logged in using ChatGPT"
exit 0
fi
printf '%s\n%s' "\${PSTACK_PROJECT_ENV_SENTINEL-unset}" "\${PSTACK_INHERITED_ENV_SENTINEL-unset}" > "$PSTACK_ENV_CAPTURE"
echo '{"type":"thread.started","thread_id":"o1"}'
echo '{"type":"item.completed","item":{"type":"agent_message","text":"CODEX_OK"}}'
echo '{"type":"turn.completed","usage":{"input_tokens":20,"cached_input_tokens":5,"output_tokens":3,"reasoning_output_tokens":1}}'
`
);
chmodSync(codex, 0o755);

const input = options("codex", "project-env-boundary");
const runner = Bun.spawn([
join(import.meta.dir, "pstack-runner"),
...runnerArgs(input).slice(1),
], {
cwd: scratch,
env: {
...process.env,
BUN_OPTIONS: "--env-file=.env",
NODE_OPTIONS: "--require=./missing-project-preload.cjs",
PSTACK_ENV_CAPTURE: capturedEnv,
PSTACK_INHERITED_ENV_SENTINEL: "inherited-from-parent",
PSTACK_PRELOAD_CAPTURE: preloadCapture,
},
stdout: "pipe",
stderr: "pipe",
});
const stdout = new Response(runner.stdout).text();
const stderr = new Response(runner.stderr).text();

expect(await runner.exited).toBe(0);
await Promise.all([stdout, stderr]);
expect(readFileSync(capturedEnv, "utf8")).toBe(
"unset\ninherited-from-parent"
);
expect(existsSync(preloadCapture)).toBe(false);
expect(receipt(input.receiptPath).status).toBe("complete");
});

it("cancels a preflight with SIGINT and writes a terminal receipt", async () => {
const input = options("claude", "preflight-cancelled");
const started = join(scratch, "preflight-child.started");
const terminated = join(scratch, "preflight-child.terminated");
const isolatedRunner = join(scratch, "isolated-runner");
cpSync(import.meta.dir, isolatedRunner, { recursive: true });
const runner = Bun.spawn([
process.execPath,
join(isolatedRunner, "pstack-runner"),
...runnerArgs(input).slice(1),
], {
Expand Down