Skip to content
Merged
2 changes: 1 addition & 1 deletion .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"name": "pstack",
"source": "./plugins/pstack",
"description": "if you want to go fast, go deep first. pstack helps you write less, but higher quality code. rigorous agent workflows you can parallelize with confidence.",
"version": "1.17.0",
"version": "1.18.0",
"author": {
"name": "Lauren Tan (original)"
},
Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/verify-open-pstack/features/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ The ownership table below says which features a changed path reaches. `SKILL.md`
| --- | --- |
| `skills` | One `skill-invocation:<name>` exercise per selected skill, in both harnesses |
| `shared` | All listed skills, setup, runner, shipped tools, and `hook:startup` in both harnesses, and Claude's `agent-preload:poteto-agent`; a version-only manifest change reaches less, as described below |
| `catalog` | The generated model catalog and native agents, and the registry sources: `catalog`, `setup`, and `runner`, and no skill check |
| `catalog` | The generated model catalog and native agents, and the registry sources: `catalog`, `setup`, and `runner`, and no skill check; a provider adapter under `runner/providers/` also reaches `writers` |
| `assets` | Installed UI/resource surface for each actual consumer found in the pinned plugin manifests |
| `setup` | Installed setup/model configuration with byte-exact daily Claude file restoration before publication |
| `runner` | Installed parent invoking real configured provider lanes |
Expand Down
1 change: 1 addition & 0 deletions .claude/skills/verify-open-pstack/features/isolation.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Verifier-changing PRs run from their reviewed candidate head, with publisher rev
- Claude uses normal operator login/config with `claude --plugin-dir <exact-head checkout>/plugins/pstack --settings '{"enabledPlugins":{"pstack@open-pstack":false}}'`. These flags load only the candidate for that session, without persistent installation or settings changes.
- Codex installs the exact-head marketplace with `codex plugin marketplace add … --ref <sha>` and `codex plugin add` in a run-owned `CODEX_HOME`. Symlink its `auth.json` to the operator's `${CODEX_HOME:-$HOME/.codex}/auth.json`, resolving the daily home before overriding the environment.
- If the daily home has no `auth.json`, fail closed: this tool needs file-based Codex auth. Do not read credentials, copy them, or start login. Native refresh writes through the symlink to the single existing login file.
- Symlink each of `settings.json` and `auth.json` that exists in the operator's `${XDG_CONFIG_HOME:-$HOME/.config}/muse/` into the run-owned `.config/muse/`, so Muse lanes keep the operator's transport and login; removing the run state removes only the links.
- Keep temporary, GitHub, and fixture state run-owned; disable global/system Git configuration and credential helpers for candidate commands. Provider descendants use the same native-login arrangement, never publisher credentials.

Do not claim Seatbelt enforcement, source-write protection, Keychain denial, daily-home denial, or OS-backed secrecy. Provenance rechecks detect changed candidate/installed files but are not prevention.
Expand Down
98 changes: 89 additions & 9 deletions .claude/skills/verify-open-pstack/features/recipes.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Database } from 'bun:sqlite';
import { createHash } from 'node:crypto';
import { lstat, mkdir, readFile, realpath, rm, writeFile } from 'node:fs/promises';
import { homedir } from 'node:os';
import { basename, join } from 'node:path';
Expand Down Expand Up @@ -33,7 +34,7 @@ export const ROUTE = /^([a-z][a-z0-9-]*):([A-Za-z0-9._/-]+)@([a-z]+)$/;
const SETUP_DESCRIPTOR = 'claude:opus@high';
const DESCRIPTOR = /\b[a-z][a-z0-9-]*:[A-Za-z0-9._/-]+@[a-z]+/g;
const SHEET_HEADER = '# pstack model configuration\n\n';
const SETTING = 'codex writer sandbox: ';
const SETTING = 'codex writer sandbox: ', ANY_SETTING = /^[a-z][a-z0-9-]* writer sandbox: /;
const FULL_ACCESS_SETTING = `${SETTING}full-access`, BOUNDED_SETTING = `${SETTING}bounded`;
const WRITER_ROLE = 'feature, refactoring', WRITER_LANE = 'codex:gpt-6.1-sol@max';

Expand Down Expand Up @@ -64,8 +65,8 @@ export async function readCatalog(location: string): Promise<Catalog> {
}

const sheetText = (lines: [string, string[]][]): string => SHEET_HEADER + lines.map(([role, lanes]) => `${role}: ${lanes.join(', ')}`).join('\n') + '\n';
const roleLines = (text: string): string[] => text.split('\n').filter(line => /^[a-z][^:]*: \S/.test(line) && !line.startsWith(SETTING));
const settingLines = (text: string): string[] => text.split('\n').filter(line => line.startsWith(SETTING));
const roleLines = (text: string): string[] => text.split('\n').filter(line => /^[a-z][^:]*: \S/.test(line) && !ANY_SETTING.test(line));
const settingLines = (text: string): string[] => text.split('\n').filter(line => ANY_SETTING.test(line));
Comment thread
ericlitman marked this conversation as resolved.
Resolved
Comment thread
github-actions[bot] marked this conversation as resolved.
Resolved
const sh = (text: string): string => `'${text.replaceAll("'", `'\\''`)}'`;

const rules = (dir: string): string => `Work only inside ${dir}. Do not push, post, open, merge, or modify pull requests, issues, or remote branches, and do not write outside ${dir}. Do not edit any skill, plugin, or configuration file outside ${dir}. Do not ask questions: where the skill would ask, choose its documented default and continue. This is a headless session that ends at your first final reply and stops any background work still running, so run every command and subagent in the foreground, even where the skill says to use the background, and finish all work before you reply.`;
Expand Down Expand Up @@ -361,6 +362,84 @@ function codexWriter(optIn: boolean): Case {
};
}

const PROVIDER_WRITERS: [descriptor: string, optIn: boolean][] = [
['devin:swe-2@medium', false], ['dsh:deepseek-flash@low', false], ['opencode:opencode/deepseek-v4.1-flash-free@low', true],
['agy:gemini-3.8-flash@low', true], ['cursor:composer-2.5@default', true],
];
const MUSE = { provider: 'muse', model: 'muse-spark-1.3', effort: 'low' };

async function laneWorktree(f: Fixture, name: string): Promise<string> {
const repo = join(f.dir, `${name}-repo`), lane = join(f.dir, `${name}-lane`);
await git(f, 'init', '-q', '-b', 'main', repo);
await git(f, '-C', repo, 'commit', '-q', '--allow-empty', '-m', 'init');
await git(f, '-C', repo, 'worktree', 'add', '-q', '-b', name, lane);
return lane;
}

function executedWriter(name: string, prompt: (f: Fixture, at: (file: string) => string, lane: string) => string, route: (f: Fixture) => Promise<{ provider: string; model: string; effort: string; optIn: boolean; sheet?: string }>): Case {
const at = (f: Fixture, file: string) => join(f.dir, `${name}.${file}`), lane = (f: Fixture) => join(f.dir, `${name}-lane`);
let nonce = '', expected: { provider: string; model: string; effort: string; optIn: boolean; sheet?: string } = { provider: '', model: '', effort: '', optIn: false };
return {
id: `${name}-writer`,
codexSandbox: 'danger-full-access',
async prepare(f) {
expected = await route(f);
nonce = crypto.randomUUID();
await laneWorktree(f, name);
if (expected.sheet !== undefined) await writeFile(at(f, 'sheet.md'), expected.sheet);
await writeFile(at(f, 'prompt.md'), `Using the shell, run exactly this command in the current directory, then reply DONE: python3 -c 'import hashlib; print(hashlib.sha256(b"${nonce}").hexdigest())' > result.txt\n`);
},
prompt: f => prompt(f, file => at(f, file), lane(f)),
async assert(record, f) {
if (!record.commands.some(c => c.includes('pstack-runner'))) throw new Error('runner-not-run');
const receipt = JSON.parse(await readFile(at(f, 'receipt.json'), 'utf8')), sandbox = expected.optIn ? 'full-access' : 'bounded';
if (receipt.status !== 'complete') throw new Error(`receipt-status:${receipt.status}`);
if (receipt.provider !== expected.provider || receipt.model !== expected.model || receipt.effort !== expected.effort || receipt.mode !== 'isolated-write' || receipt.cwd !== lane(f)) throw new Error('receipt-route-mismatch');
if (receipt.outputPath !== at(f, 'output.md') || !(Date.parse(receipt.startedAt) >= record.started)) throw new Error('receipt-not-this-session');
if (receipt.writerSandbox !== sandbox) throw new Error(`writer-sandbox:${receipt.writerSandbox}`);
const path = join(lane(f), 'result.txt'), effect = await written(record, path);
if ((await readFile(path, 'utf8')).trim() !== createHash('sha256').update(nonce).digest('hex')) throw new Error(`${name}-effect-mismatch`);
return { assertions: ['runner-ran', 'receipt-complete', `writer-sandbox:${sandbox}`, effect, 'executed'], files: [at(f, 'receipt.json'), path] };
},
};
}

function providerWriter(descriptor: string, optIn: boolean): Case {
const [, provider = '', model = '', effort = ''] = ROUTE.exec(descriptor)!;
return executedWriter(provider, (f, at, lane) => `${invoke(f.harness, 'poteto-mode', `Do no engineering work. For this task the pstack model sheet is ${at('sheet.md')}; use it in place of any configured sheet. Dispatch exactly one lane and nothing else: the first descriptor of its "${WRITER_ROLE}" role as an isolated-write writer, on the route and with the launcher arguments provider dispatch gives this parent. Use the prompt file ${at('prompt.md')}, the dedicated worktree ${lane} as its cwd, the output ${at('output.md')} and the receipt ${at('receipt.json')}, and wait for the lane to exit.`)} ${rules(f.dir)}`, async f => {
const installed = await readCatalog(f.location);
if (installed.route(f.harness, descriptor) !== 'external') throw new Error(`runner-route-not-external:${descriptor}`);
const sheet = sheetText([...installed.defaults].map(([role, lanes]) => [role, role === WRITER_ROLE ? [descriptor] : lanes])) + (optIn ? `${provider} writer sandbox: full-access\n` : '');
return { provider, model, effort, optIn, sheet };
});
}

const launcherArgs = (f: Fixture, args: string): string => `${invoke(f.harness, 'poteto-mode', `Dispatch exactly one external lane and nothing else. Run the installed launcher ${join(f.location, 'skills/poteto-mode/scripts/runner/pstack-runner')} with these arguments and wait for it to exit: ${args}`)}. ${rules(f.dir)}`;

const museWriter = executedWriter('muse', (f, at, lane) => launcherArgs(f, `--parent ${f.harness} --provider muse --model ${MUSE.model} --effort ${MUSE.effort} --mode isolated-write --writer-sandbox full-access --prompt ${at('prompt.md')} --cwd ${lane} --output ${at('output.md')} --receipt ${at('receipt.json')}`),
async () => ({ ...MUSE, optIn: true }));

const museReader: Case = {
id: 'muse-reader',
codexSandbox: 'danger-full-access',
async prepare(f) {
await mkdir(join(f.dir, 'muse-read'));
await writeFile(join(f.dir, 'muse-read/notes.txt'), `The marker is ${crypto.randomUUID()}.\n`);
await writeFile(join(f.dir, 'muse-read.prompt.md'), 'Read notes.txt in the current directory and reply with only the marker it names.\n');
},
prompt: f => launcherArgs(f, `--parent ${f.harness} --provider muse --model ${MUSE.model} --effort ${MUSE.effort} --mode read-only --prompt ${join(f.dir, 'muse-read.prompt.md')} --cwd ${join(f.dir, 'muse-read')} --output ${join(f.dir, 'muse-read.output.md')} --receipt ${join(f.dir, 'muse-read.receipt.json')}`),
async assert(record, f) {
if (!record.commands.some(c => c.includes('pstack-runner'))) throw new Error('runner-not-run');
const receipt = JSON.parse(await readFile(join(f.dir, 'muse-read.receipt.json'), 'utf8'));
if (receipt.status !== 'complete') throw new Error(`receipt-status:${receipt.status}`);
if (receipt.provider !== 'muse' || receipt.model !== MUSE.model || receipt.mode !== 'read-only' || receipt.modelEvidence !== 'pinned-argv') throw new Error('receipt-route-mismatch');
if (!(Date.parse(receipt.startedAt) >= record.started)) throw new Error('receipt-not-this-session');
const marker = /The marker is (\S+)\./.exec(await readFile(join(f.dir, 'muse-read/notes.txt'), 'utf8'))![1]!;
if (!(await readFile(join(f.dir, 'muse-read.output.md'), 'utf8')).includes(marker)) throw new Error('muse-reader-marker-missing');
return { assertions: ['runner-ran', 'receipt-complete', 'marker-read'], files: [join(f.dir, 'muse-read.receipt.json'), join(f.dir, 'muse-read.output.md')] };
},
};

function runner(harness: Harness, routes: string[]): Case[] {
const chosen = routes.filter(route => !route.startsWith(`${harness}:`));
const cases = (chosen.length ? chosen : ['']).map((fixed, index): Case => {
Expand Down Expand Up @@ -513,7 +592,7 @@ function newFamilies(): Case {
};
}

const READ_ONLY_ROLE = 'how explorer', READ_ONLY_LANE = 'devin:swe-2@max';
const READ_ONLY_ROLE = 'how explorer', READ_ONLY_LANE = 'opencode:opencode/deepseek-v4.1-flash-free@max';
function readOnlyProvider(): Case {
let expected: string[] = [];
return {
Expand All @@ -540,7 +619,7 @@ function readOnlyProvider(): Case {
};
}

const REFUSED_ROLE = 'bug-fix';
const REFUSED_ROLE = 'bug-fix', REFUSED_LANE = 'opencode:opencode/deepseek-v4.1-flash-free@max';
function writerRefusal(): Case {
let prepared = '';
const refusal = (f: Fixture) => join(f.dir, 'refusal.txt');
Expand All @@ -551,19 +630,19 @@ function writerRefusal(): Case {
async prepare(f) {
const roles = [...(await readCatalog(f.location)).defaults];
if (!roles.some(([role]) => role === REFUSED_ROLE)) throw new Error(`catalog-role-missing:${REFUSED_ROLE}`);
prepared = sheetText(roles.map(([role, lanes]) => [role, role === REFUSED_ROLE ? [READ_ONLY_LANE] : lanes]));
prepared = sheetText(roles.map(([role, lanes]) => [role, role === REFUSED_ROLE ? [REFUSED_LANE] : lanes]));
await writeFile(join(f.configHome, 'pstack-models.md'), prepared);
if (f.harness === 'codex') await rm(join(f.configHome, 'AGENTS.md'), { force: true });
await rm(refusal(f), { force: true });
},
prompt: f => `${invoke(f.harness, 'setup-pstack', `Use these answers and ask nothing else. Keep every role assignment and every family's current effort. If setup refuses a role assignment, write only setup's refusal message to ${refusal(f)} and stop without changing any configuration.`)} Do not push, post, or contact GitHub.`,
prompt: f => `${invoke(f.harness, 'setup-pstack', `Use these answers and ask nothing else. Keep every role assignment and every family's current effort. Decline every writer sandbox opt-in. If setup refuses a role assignment, write only setup's refusal message to ${refusal(f)} and stop without changing any configuration.`)} Do not push, post, or contact GitHub.`,
async assert(record, f) {
const assertions = [loaded(record, 'pstack:setup-pstack')];
if (await readFile(join(f.configHome, 'pstack-models.md'), 'utf8') !== prepared) throw new Error('writer-refusal-sheet-changed');
if (f.harness === 'codex' && await lstat(join(f.configHome, 'AGENTS.md')).then(() => true, () => false)) throw new Error('writer-refusal-integration-written');
const text = await readFile(refusal(f), 'utf8').catch(() => '');
if (!text.includes(REFUSED_ROLE) || !text.toLowerCase().includes('devin')) throw new Error('writer-refusal-missing');
return { assertions: [...assertions, `refused:${REFUSED_ROLE}:${READ_ONLY_LANE}`], files: [refusal(f)] };
if (!text.includes(REFUSED_ROLE) || !text.toLowerCase().includes('opencode')) throw new Error('writer-refusal-missing');
return { assertions: [...assertions, `refused:${REFUSED_ROLE}:${REFUSED_LANE}`], files: [refusal(f)] };
},
};
}
Expand Down Expand Up @@ -701,6 +780,7 @@ export function recipe(feature: string, harness: Harness, routes: string[] = [])
if (feature === 'project-skill') return [projectSkill];
if (feature === 'hook:startup') return [startupHook];
if (feature === 'runner') return runner(harness, routes);
if (feature === 'writers') return [...PROVIDER_WRITERS.map(([descriptor, optIn]) => providerWriter(descriptor, optIn)), museWriter, museReader];
if (feature === 'setup' || feature === 'skill-invocation:setup-pstack') return setup;
if (feature === 'catalog') return catalog(harness);
if (feature === 'shipped-tools') return [shippedTools];
Expand Down
2 changes: 2 additions & 0 deletions .claude/skills/verify-open-pstack/features/runner.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,5 @@ The runner recipe in `recipes.ts` has the installed `poteto-mode` parent launch
From Codex the recipe adds the `linked-worktree-writer` case: one `claude:opus@max` `isolated-write` lane whose cwd is a linked worktree of a run-owned repository. It asserts the receipt is `complete` with that route and cwd, the receipt argv's `--settings` sandbox denies writes to the repository's real shared `.git` directory, and the child wrote `WRITTEN.md` in the worktree during this session. Route overrides do not change this case.

From Claude the recipe adds the `full-access-writer` case. Its fixture sheet puts `feature, refactoring` on `codex:gpt-6.1-sol@max` and holds the line `codex writer sandbox: full-access`. The prompt names the sheet, the role and the lane's paths, never the runner flag. The child writes a per-run nonce to `.agents/result.json` in its cwd, and adds a second nonce that it reads from the DOM headless Chrome dumps for a fixture page (Playwright's Chromium when Chrome is absent). The page file holds that nonce only base64-encoded. The case asserts the receipt is `complete` for that route and cwd, records `writerSandbox: "full-access"` and carries `--sandbox danger-full-access` in its argv, and that the `.agents/` result written this session holds both nonces. Its counterpart, `bounded-writer`, uses the same dispatch from a fixture sheet that puts `feature, refactoring` on `codex:gpt-6.1-sol@low` and has no setting line. The child writes a per-run nonce to the plain file `result.json` in its cwd, which `workspace-write` allows; it uses no browser and no `.agents/` path. The case asserts the receipt is `complete` for that route and cwd, records `writerSandbox: "bounded"`, carries `--sandbox workspace-write` and never `danger-full-access` in its argv, and that `result.json` written this session holds the nonce. Docker is not exercised. Route overrides do not change these cases.

The `writers` feature is reachable when a path under `plugins/pstack/skills/poteto-mode/scripts/runner/providers/` changes, and runs only when named. Its recipe has one writer case each for `devin:swe-2@medium` and `dsh:deepseek-flash@low`, whose sheets hold no setting line, and for `opencode:opencode/deepseek-v4.1-flash-free@low`, `agy:gemini-3.8-flash@low` and `cursor:composer-2.5@default`, whose sheets hold `<provider> writer sandbox: full-access`. Each fixture sheet puts `feature, refactoring` on that descriptor, and the prompt names the sheet, the role and the lane's paths, never the runner flag. The lane's cwd is a linked worktree of a run-owned repository. The child runs a Python command that writes the SHA-256 of a per-run nonce to `result.txt`; the case asserts the receipt is `complete` for that route, cwd and writer sandbox, and that `result.txt`, written this session, holds the hash, which the model cannot produce without running the command. `muse-writer` runs the same task with launcher arguments that name `--writer-sandbox full-access`, because no Muse family is registered for a sheet to name. `muse-reader` runs a read-only `muse:muse-spark-1.3@low` lane that must reply with a per-run marker from a fixture file, with a `pinned-argv` receipt.
Loading
Loading