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

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pstack/skills/poteto-mode/playbooks/worktree-cleanup.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
### Worktree and simulator cleanup

1. Snapshot and audit. Record `df -h /`, then run `scripts/worktree-audit.sh [-b <pr-base-ref>] [repo-path]` (principle-build-the-lever). Without `-b` it checks `MERGED` against `origin/HEAD`, so pass `-b origin/<branch>` when PRs target another branch. It reads paths from `git worktree list`, never hand-typed, since a hand-typed `myrepo-worktrees/x` misses one that lives at `.cursor/worktrees/myrepo/x` (principle-encode-lessons-in-structure). It classifies each worktree by size, age, merge state, uncommitted work, PR state, and the newest chat that touched it, then suggests a bucket. The transcript scan is slow, so background it.
1. Snapshot and audit. Record `df -h /`, then run `scripts/worktree-audit.sh [-b <pr-base-ref>] [repo-path]` (principle-build-the-lever). Without `-b` it checks `MERGED` against `origin/HEAD`, so pass `-b origin/<branch>` when PRs target another branch. It reads paths from `git worktree list`, never hand-typed, since a hand-typed `myrepo-worktrees/x` misses one that lives at `.cursor/worktrees/myrepo/x` (principle-encode-lessons-in-structure). It classifies each worktree by size, age, merge state, uncommitted work, PR state, and the newest chat that touched it, then suggests a bucket. `hold-unpushed` means the worktree holds commits that exist nowhere else (ahead of its remote, no remote branch, or detached and not on the base, and HEAD is not the head of one of its PRs); push them or get a decision before anything else. A closed-but-unmerged PR is not evidence the work is safe. The transcript scan is slow, so background it.
2. The bucket is advice, not permission. The pinned and active chats are the real artifact (principle-prove-it-works). Get that set from the user or sidebar and cross-check every candidate. The lever has marked `safe` a worktree the user had pinned, so the pinned set wins.
3. Verify usage before deleting. For every `verify-recent-chat` row, or anything you doubt, fan subagents out to read the transcripts and report whether the chat is pinned or ongoing and which worktrees it touches (principle-guard-the-context-window, transcripts are bulk). A pinned chat spawns arena and repro trees into sibling worktrees via background subagents, and those are in use even when their names never hit the sidebar. Pick each subagent's reasoning level and fast or standard speed for the job, per the Subagents section.
4. Pause on irreversible loss. `wip:N` is N tracked uncommitted edits. Show the diff and get a decision first, since removing a clean worktree is recoverable from its branch but uncommitted work is gone. `scratch:N` is untracked throwaway, safe to drop, but name the files. Per Autonomy, clean and merged and not-in-use proceeds. `wip` and in-use pause.
Expand Down
2 changes: 1 addition & 1 deletion pstack/skills/poteto-mode/scripts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"private": true,
"type": "module",
"scripts": {
"test": "bun test watch-pr check-plan.test.ts",
"test": "bun test watch-pr check-plan.test.ts worktree-audit",
"typecheck": "tsc --project watch-pr/tsconfig.json --noEmit --strict"
},
"dependencies": {
Expand Down
83 changes: 83 additions & 0 deletions pstack/skills/poteto-mode/scripts/worktree-audit.fixture.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { spawnSync } from "node:child_process";
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { basename, join } from "node:path";

type PullRequest = {
number: number;
state: string;
headRefName: string;
headRefOid: string;
};

type AuditRow = {
merged: string;
dirty: string;
remote: string;
pr: string;
lastChat: string;
bucket: string;
};

export function createAuditFixture() {
const root = mkdtempSync(join(tmpdir(), "worktree-audit-test-"));
const home = join(root, "home");
const bin = join(root, "bin");
const origin = join(root, "origin.git");
const repo = join(root, "repo");
const prs = join(root, "prs.json");
const env = {
...process.env,
HOME: home,
GIT_CONFIG_GLOBAL: join(root, "gitconfig"),
GIT_CONFIG_NOSYSTEM: "1",
PATH: `${bin}:${process.env.PATH}`,
AUDIT_FIXTURE_PRS: prs,
};
const cleanup = () => rmSync(root, { recursive: true, force: true });
const run = (directory: string, command: string, args: string[]) => {
const result = spawnSync(command, args, { cwd: directory, env, encoding: "utf8" });
if (result.status !== 0)
throw new Error(`${command} ${args.join(" ")} failed in ${directory}: ${result.error ?? result.stderr}`);
return result.stdout.trim();
};
const git = (directory: string, ...args: string[]) => run(directory, "git", args);
const setPrs = (value: PullRequest[]) => writeFileSync(prs, JSON.stringify(value));

try {
mkdirSync(home);
mkdirSync(bin);
setPrs([]);
writeFileSync(join(bin, "gh"), '#!/bin/sh\ncat "$AUDIT_FIXTURE_PRS"\n', { mode: 0o755 });
git(root, "config", "--global", "user.name", "Audit Test");
git(root, "config", "--global", "user.email", "[email protected]");
git(root, "config", "--global", "init.defaultBranch", "main");
git(root, "init", "--bare", origin);
git(root, "clone", origin, repo);
writeFileSync(join(repo, "base"), "base\n");
git(repo, "add", "base");
git(repo, "commit", "-m", "base");
git(repo, "push", "origin", "main");
git(repo, "remote", "set-head", "origin", "main");
} catch (error) {
cleanup();
throw error;
}

const audit = (extraEnv: NodeJS.ProcessEnv = {}) => {
const result = spawnSync("bash", [join(import.meta.dir, "worktree-audit.sh"), repo], {
cwd: repo,
env: { ...env, ...extraEnv },
encoding: "utf8",
});
const rows: Record<string, AuditRow> = {};
for (const line of result.stdout.trim().split("\n")) {
if (!line || line.startsWith("# base:") || line.startsWith("SIZE\t")) continue;
const [, , merged, dirty, remote, pr, lastChat, bucket, worktree] = line.split("\t");
rows[basename(worktree)] = { merged, dirty, remote, pr, lastChat, bucket };
}
return { status: result.status, stderr: result.stderr, rows };
};

return { root, home, origin, repo, env, run, git, setPrs, audit, cleanup };
}
Loading