From 3c99056291abb27f25ba34a6018f66b0a3aed244 Mon Sep 17 00:00:00 2001 From: Rudy Celekli Date: Sun, 25 Oct 2026 11:32:24 -0400 Subject: [PATCH] fix(pstack): list reflect transcripts when a layout is missing zsh aborts the listing on the first unmatched pattern and the pipe to head still exits 0, so an existing transcript is skipped. --- .../claude-code/skills/reflect/SKILL.md | 4 +- .../reflect/scripts/test/locate.test.mjs | 48 +++++++++++++++++++ pstack/skills/reflect/SKILL.md | 4 +- .../reflect/scripts/test/locate.test.mjs | 48 +++++++++++++++++++ 4 files changed, 100 insertions(+), 4 deletions(-) create mode 100644 harness/pstack/claude-code/skills/reflect/scripts/test/locate.test.mjs create mode 100644 pstack/skills/reflect/scripts/test/locate.test.mjs diff --git a/harness/pstack/claude-code/skills/reflect/SKILL.md b/harness/pstack/claude-code/skills/reflect/SKILL.md index dcb6c9175..ded8fbbd4 100644 --- a/harness/pstack/claude-code/skills/reflect/SKILL.md +++ b/harness/pstack/claude-code/skills/reflect/SKILL.md @@ -17,10 +17,10 @@ Invoke when the user says "reflect" or "/reflect". Skip when the conversation is Before fanning out, find this conversation's transcript in the `agent-transcripts/` directory the system prompt names. Do not glob across `~/.cursor/projects/*/`. That crosses workspace boundaries and reads private chats from unrelated projects. ```bash -ls -t /*.jsonl /*/*.jsonl /*/subagents/*.jsonl 2>/dev/null | head -10 +bash -c 'shopt -s nullglob; files=("$1"/*.jsonl "$1"/*/*.jsonl "$1"/*/subagents/*.jsonl); if ((${#files[@]})); then ls -t "${files[@]}" | head -10; fi' bash "" ``` -That covers the legacy flat (`.jsonl`), current nested (`/.jsonl`), and subagent (`/subagents/.jsonl`) layouts. For each candidate, read the first JSONL line. Take the file whose `message.content[0].text` contains the conversation's opening user prompt. If none matches, pass a tight digest of the session instead. +`nullglob` drops a layout that is not on disk. Without it, zsh aborts on the first missing pattern, the pipe to `head` still exits 0, and a transcript that exists is left out. That covers the legacy flat (`.jsonl`), current nested (`/.jsonl`), and subagent (`/subagents/.jsonl`) layouts. For each candidate, read the first JSONL line. Take the file whose `message.content[0].text` contains the conversation's opening user prompt. If none matches, pass a tight digest of the session instead. ## 2. Spawn three reviewers in parallel diff --git a/harness/pstack/claude-code/skills/reflect/scripts/test/locate.test.mjs b/harness/pstack/claude-code/skills/reflect/scripts/test/locate.test.mjs new file mode 100644 index 000000000..8b0c9973d --- /dev/null +++ b/harness/pstack/claude-code/skills/reflect/scripts/test/locate.test.mjs @@ -0,0 +1,48 @@ +import assert from "node:assert/strict"; +import { spawnSync } from "node:child_process"; +import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import test from "node:test"; +import { fileURLToPath } from "node:url"; + +const skillPath = fileURLToPath(new URL("../../SKILL.md", import.meta.url)); + +function locateCommand(dir) { + const text = readFileSync(skillPath, "utf8"); + const fence = text.match(/```bash\n([\s\S]*?)```/); + assert.ok(fence, "reflect should show the transcript listing"); + return fence[1].trim().replaceAll("", dir); +} + +function run(shell, prefix, command) { + return spawnSync(shell, [...prefix, command], { encoding: "utf8" }); +} + +test("a missing layout does not hide the transcripts that exist", () => { + const parent = mkdtempSync(join(tmpdir(), "reflect-locate-")); + const dir = join(parent, "agent transcripts"); + mkdirSync(join(dir, "abc"), { recursive: true }); + writeFileSync(join(dir, "abc", "abc.jsonl"), "{\"k\":1}\n"); + const command = locateCommand(dir); + try { + const bash = run( + "bash", + ["--noprofile", "--norc", "-c"], + `shopt -s failglob; ${command}` + ); + assert.equal(bash.error, undefined, bash.stderr); + assert.equal(bash.status, 0, bash.stderr); + assert.match(bash.stdout, /abc\.jsonl/); + assert.doesNotMatch(bash.stderr, /no match/); + + const zsh = spawnSync("zsh", ["-f", "-c", "exit 0"]); + if (zsh.error) return; + const listed = run("zsh", ["-f", "-c"], command); + assert.equal(listed.status, 0, listed.stderr); + assert.match(listed.stdout, /abc\.jsonl/); + assert.doesNotMatch(listed.stderr, /no matches found/); + } finally { + rmSync(parent, { recursive: true, force: true }); + } +}); diff --git a/pstack/skills/reflect/SKILL.md b/pstack/skills/reflect/SKILL.md index 9bb153bd2..dcbe77477 100644 --- a/pstack/skills/reflect/SKILL.md +++ b/pstack/skills/reflect/SKILL.md @@ -15,10 +15,10 @@ Invoke when the user says "reflect" or "/reflect". Skip when the conversation is Before fanning out, find this conversation's transcript in the `agent-transcripts/` directory the system prompt names. Do not glob across `~/.cursor/projects/*/`. That crosses workspace boundaries and reads private chats from unrelated projects. ```bash -ls -t /*.jsonl /*/*.jsonl /*/subagents/*.jsonl 2>/dev/null | head -10 +bash -c 'shopt -s nullglob; files=("$1"/*.jsonl "$1"/*/*.jsonl "$1"/*/subagents/*.jsonl); if ((${#files[@]})); then ls -t "${files[@]}" | head -10; fi' bash "" ``` -That covers the legacy flat (`.jsonl`), current nested (`/.jsonl`), and subagent (`/subagents/.jsonl`) layouts. For each candidate, read the first JSONL line. Take the file whose `message.content[0].text` contains the conversation's opening user prompt. If none matches, pass a tight digest of the session instead. +`nullglob` drops a layout that is not on disk. Without it, zsh aborts on the first missing pattern, the pipe to `head` still exits 0, and a transcript that exists is left out. That covers the legacy flat (`.jsonl`), current nested (`/.jsonl`), and subagent (`/subagents/.jsonl`) layouts. For each candidate, read the first JSONL line. Take the file whose `message.content[0].text` contains the conversation's opening user prompt. If none matches, pass a tight digest of the session instead. ## 2. Spawn three reviewers in parallel diff --git a/pstack/skills/reflect/scripts/test/locate.test.mjs b/pstack/skills/reflect/scripts/test/locate.test.mjs new file mode 100644 index 000000000..8b0c9973d --- /dev/null +++ b/pstack/skills/reflect/scripts/test/locate.test.mjs @@ -0,0 +1,48 @@ +import assert from "node:assert/strict"; +import { spawnSync } from "node:child_process"; +import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import test from "node:test"; +import { fileURLToPath } from "node:url"; + +const skillPath = fileURLToPath(new URL("../../SKILL.md", import.meta.url)); + +function locateCommand(dir) { + const text = readFileSync(skillPath, "utf8"); + const fence = text.match(/```bash\n([\s\S]*?)```/); + assert.ok(fence, "reflect should show the transcript listing"); + return fence[1].trim().replaceAll("", dir); +} + +function run(shell, prefix, command) { + return spawnSync(shell, [...prefix, command], { encoding: "utf8" }); +} + +test("a missing layout does not hide the transcripts that exist", () => { + const parent = mkdtempSync(join(tmpdir(), "reflect-locate-")); + const dir = join(parent, "agent transcripts"); + mkdirSync(join(dir, "abc"), { recursive: true }); + writeFileSync(join(dir, "abc", "abc.jsonl"), "{\"k\":1}\n"); + const command = locateCommand(dir); + try { + const bash = run( + "bash", + ["--noprofile", "--norc", "-c"], + `shopt -s failglob; ${command}` + ); + assert.equal(bash.error, undefined, bash.stderr); + assert.equal(bash.status, 0, bash.stderr); + assert.match(bash.stdout, /abc\.jsonl/); + assert.doesNotMatch(bash.stderr, /no match/); + + const zsh = spawnSync("zsh", ["-f", "-c", "exit 0"]); + if (zsh.error) return; + const listed = run("zsh", ["-f", "-c"], command); + assert.equal(listed.status, 0, listed.stderr); + assert.match(listed.stdout, /abc\.jsonl/); + assert.doesNotMatch(listed.stderr, /no matches found/); + } finally { + rmSync(parent, { recursive: true, force: true }); + } +});