From 95c749b973ea9026fde1f6a9e4241ea2dc5fff02 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 25 May 2026 03:36:05 +0000 Subject: [PATCH 01/10] =?UTF-8?q?feat:=20implement=20checkpoint-resume=20p?= =?UTF-8?q?attern=20=E2=80=94=20checkpoint.mjs=20+=20/resume=20command?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://claude.ai/code/session_01Phehb45JRh3HpLp9mAobzu --- commands/issue-code-generation.md | 5 +++ commands/resume.md | 42 +++++++++++++++++++ scripts/checkpoint.mjs | 68 +++++++++++++++++++++++++++++++ 3 files changed, 115 insertions(+) create mode 100644 commands/resume.md create mode 100644 scripts/checkpoint.mjs diff --git a/commands/issue-code-generation.md b/commands/issue-code-generation.md index 61a63bb..b3af426 100644 --- a/commands/issue-code-generation.md +++ b/commands/issue-code-generation.md @@ -8,22 +8,27 @@ Full issue → code → AC validation pipeline. 2. Pass the full issue JSON to the `issue-router` agent - If status is `NEEDS_REVIEW`, stop and surface the classification uncertainty for human decision - The router returns a **type** (`bug`, `feature`, `refactor`, `security`) used in step 4 + - Write checkpoint: `node scripts/checkpoint.mjs write 01-router ` 3. Pass the full issue JSON to the `ticket-analyst` agent - If status is `BLOCKED` or `NEEDS_REVIEW`, stop and surface the output for human decision + - Write checkpoint: `node scripts/checkpoint.mjs write 02-brief ` 4. Pass the `### Brief` block from ticket-analyst to the code-builder variant selected by the router: - `bug` → `code-builder-bug` - `feature` → `code-builder-feature` - `refactor` → `code-builder-refactor` - `security` or unclassified → `code-builder` (generic fallback) - If status is `BLOCKED` or `NEEDS_REVIEW`, stop and surface the output + - Write checkpoint: `node scripts/checkpoint.mjs write 03-patch ` 5. **If `--strict` flag is present**, pass to the `code-challenger` agent: the `### Patch` block, the original acceptance criteria, and the issue type - If status is `BLOCKED` or `NEEDS_REVIEW`, stop and surface the output for human decision before continuing - On `DONE`, carry the `### Handoff` block (critical points list) forward to step 6 - Skip this step entirely if `--strict` was not passed + - Write checkpoint: `node scripts/checkpoint.mjs write 04-challenges ` 6. Pass to the `code-reviewer` agent: the `### Patch` block, the original acceptance criteria, and any type-specific evidence blocks produced by the builder: - `bug` → include `### Reproduction` - `refactor` → include `### Non-regression evidence` - `feature` / `security` / fallback → `### Patch` only - If `--strict` was used, also include the `### Handoff` block from code-challenger so the reviewer is aware of pre-identified risks + - Write checkpoint: `node scripts/checkpoint.mjs write 05-review ` 7. If code-reviewer status is `NEEDS_REVIEW`, write the review report and stop — present blocking issues to the user 8. If code-reviewer status is `DONE`, present the full patch to the user for review and merge approval diff --git a/commands/resume.md b/commands/resume.md new file mode 100644 index 0000000..12a9407 --- /dev/null +++ b/commands/resume.md @@ -0,0 +1,42 @@ +# /resume + +Resume an interrupted `/issue-code-generation` pipeline from the first missing checkpoint. + +## Invocation + +``` +/resume +``` + +## Steps + +1. Run `node scripts/checkpoint.mjs list ` to get the list of already-completed steps +2. Display the completed steps to the user so they can confirm the resume point +3. Determine the first missing step from the ordered sequence: + - `01-router` + - `02-brief` + - `03-patch` + - `04-challenges` (only if `--strict` was originally used — infer from presence of its checkpoint) + - `05-review` +4. For each step **before** the first missing one, load the saved output via `node scripts/checkpoint.mjs read ` and treat it as the agent's output — **do not re-run the agent** +5. Resume the pipeline from the first missing step, following the same logic as `/issue-code-generation`: + + **`01-router` missing** — fetch the issue with `node scripts/gh-get-issue.mjs `, run `issue-router`, write checkpoint `01-router`, then continue from `02-brief` + + **`02-brief` missing** — load `01-router` checkpoint, run `ticket-analyst` with the original issue JSON, write checkpoint `02-brief`, then continue from `03-patch` + + **`03-patch` missing** — load `01-router` and `02-brief` checkpoints, run the code-builder variant indicated by the router type, write checkpoint `03-patch`; if `04-challenges` was part of the original run (checkpoint exists or `--strict` is passed), continue to `04-challenges`; otherwise continue to `05-review` + + **`04-challenges` missing** — load `03-patch` checkpoint, run `code-challenger` with the patch and original acceptance criteria, write checkpoint `04-challenges`, then continue to `05-review` + + **`05-review` missing** — load `03-patch` checkpoint (and `04-challenges` if present), run `code-reviewer`, write checkpoint `05-review` + +6. After each newly-run agent, call `node scripts/checkpoint.mjs write ` before proceeding to the next step +7. Apply the same stop conditions as `/issue-code-generation`: halt on `NEEDS_REVIEW` or `BLOCKED` and surface the output for human decision +8. On completion, present the final patch to the user for review and merge approval + +## Notes + +- `listCheckpoints` returns steps sorted lexicographically — the `01-` … `05-` numeric prefixes ensure correct order +- If all five steps (or four, when `--strict` was not used) are already checkpointed, inform the user that the pipeline is complete and show the `05-review` checkpoint content +- If no checkpoints exist for ``, inform the user and suggest running `/issue-code-generation ` instead diff --git a/scripts/checkpoint.mjs b/scripts/checkpoint.mjs new file mode 100644 index 0000000..91b0b89 --- /dev/null +++ b/scripts/checkpoint.mjs @@ -0,0 +1,68 @@ +#!/usr/bin/env node +// Checkpoint read/write for the checkpoint-resume pattern. +// Files land in ~/dev/checkpoints//-.md +// Usage: node scripts/checkpoint.mjs write +// node scripts/checkpoint.mjs read +// node scripts/checkpoint.mjs list + +import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs'; +import { homedir } from 'node:os'; +import { join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const CHECKPOINTS_ROOT = join(homedir(), 'dev', 'checkpoints'); + +function checkpointDir(issueId) { + return join(CHECKPOINTS_ROOT, String(issueId)); +} + +function findFile(dir, step) { + if (!existsSync(dir)) return null; + const prefix = `${step}-`; + const match = readdirSync(dir).find(f => f.startsWith(prefix) && f.endsWith('.md')); + return match ? join(dir, match) : null; +} + +export function writeCheckpoint(issueId, step, content) { + const dir = checkpointDir(issueId); + mkdirSync(dir, { recursive: true }); + // Derive agent slug from step label (e.g. "01-router" → "router") + const agentSlug = step.replace(/^\d+-/, ''); + const filePath = join(dir, `${step}-${agentSlug}.md`); + writeFileSync(filePath, content, 'utf8'); + return filePath; +} + +export function readCheckpoint(issueId, step) { + const file = findFile(checkpointDir(issueId), step); + if (!file) return null; + return readFileSync(file, 'utf8'); +} + +export function listCheckpoints(issueId) { + const dir = checkpointDir(issueId); + if (!existsSync(dir)) return []; + return readdirSync(dir) + .filter(f => f.endsWith('.md')) + .map(f => f.replace(/\.md$/, '').replace(/-[^-]+$/, '')) + .sort(); +} + +if (process.argv[1] === fileURLToPath(import.meta.url)) { + const [, , cmd, issueId, step, ...rest] = process.argv; + + if (cmd === 'write') { + const content = rest.join(' '); + const path = writeCheckpoint(issueId, step, content); + console.log(`Checkpoint written: ${path}`); + } else if (cmd === 'read') { + const content = readCheckpoint(issueId, step); + if (content === null) { console.log('null'); } else { process.stdout.write(content); } + } else if (cmd === 'list') { + const steps = listCheckpoints(issueId); + console.log(steps.length ? steps.join('\n') : '(none)'); + } else { + console.error('Usage: node scripts/checkpoint.mjs write|read|list [step] [content]'); + process.exit(1); + } +} From 28c378723f81356277058165175ce071edcce3e5 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 25 May 2026 03:42:05 +0000 Subject: [PATCH 02/10] fix: preserve strict-mode flag and multiline content in checkpoint-resume MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - /resume now requires explicit --strict to match original run; step sequence is derived from the flag, not inferred from checkpoint presence — prevents code-challenger being skipped on strict runs interrupted after 03-patch - checkpoint.mjs write reads content from stdin instead of argv tokens so multiline Markdown (### Brief, ### Patch, …) is preserved exactly - issue-code-generation.md updated to use pipe syntax consistently https://claude.ai/code/session_01Phehb45JRh3HpLp9mAobzu --- commands/issue-code-generation.md | 10 ++++----- commands/resume.md | 34 +++++++++++++++---------------- scripts/checkpoint.mjs | 12 ++++++++--- 3 files changed, 31 insertions(+), 25 deletions(-) diff --git a/commands/issue-code-generation.md b/commands/issue-code-generation.md index b3af426..3a755f9 100644 --- a/commands/issue-code-generation.md +++ b/commands/issue-code-generation.md @@ -8,27 +8,27 @@ Full issue → code → AC validation pipeline. 2. Pass the full issue JSON to the `issue-router` agent - If status is `NEEDS_REVIEW`, stop and surface the classification uncertainty for human decision - The router returns a **type** (`bug`, `feature`, `refactor`, `security`) used in step 4 - - Write checkpoint: `node scripts/checkpoint.mjs write 01-router ` + - Write checkpoint: pipe agent output to `node scripts/checkpoint.mjs write 01-router` 3. Pass the full issue JSON to the `ticket-analyst` agent - If status is `BLOCKED` or `NEEDS_REVIEW`, stop and surface the output for human decision - - Write checkpoint: `node scripts/checkpoint.mjs write 02-brief ` + - Write checkpoint: pipe agent output to `node scripts/checkpoint.mjs write 02-brief` 4. Pass the `### Brief` block from ticket-analyst to the code-builder variant selected by the router: - `bug` → `code-builder-bug` - `feature` → `code-builder-feature` - `refactor` → `code-builder-refactor` - `security` or unclassified → `code-builder` (generic fallback) - If status is `BLOCKED` or `NEEDS_REVIEW`, stop and surface the output - - Write checkpoint: `node scripts/checkpoint.mjs write 03-patch ` + - Write checkpoint: pipe agent output to `node scripts/checkpoint.mjs write 03-patch` 5. **If `--strict` flag is present**, pass to the `code-challenger` agent: the `### Patch` block, the original acceptance criteria, and the issue type - If status is `BLOCKED` or `NEEDS_REVIEW`, stop and surface the output for human decision before continuing - On `DONE`, carry the `### Handoff` block (critical points list) forward to step 6 - Skip this step entirely if `--strict` was not passed - - Write checkpoint: `node scripts/checkpoint.mjs write 04-challenges ` + - Write checkpoint: pipe agent output to `node scripts/checkpoint.mjs write 04-challenges` 6. Pass to the `code-reviewer` agent: the `### Patch` block, the original acceptance criteria, and any type-specific evidence blocks produced by the builder: - `bug` → include `### Reproduction` - `refactor` → include `### Non-regression evidence` - `feature` / `security` / fallback → `### Patch` only - If `--strict` was used, also include the `### Handoff` block from code-challenger so the reviewer is aware of pre-identified risks - - Write checkpoint: `node scripts/checkpoint.mjs write 05-review ` + - Write checkpoint: pipe agent output to `node scripts/checkpoint.mjs write 05-review` 7. If code-reviewer status is `NEEDS_REVIEW`, write the review report and stop — present blocking issues to the user 8. If code-reviewer status is `DONE`, present the full patch to the user for review and merge approval diff --git a/commands/resume.md b/commands/resume.md index 12a9407..b105df7 100644 --- a/commands/resume.md +++ b/commands/resume.md @@ -5,38 +5,38 @@ Resume an interrupted `/issue-code-generation` pipeline from the first missing c ## Invocation ``` -/resume +/resume [--strict] ``` +Pass `--strict` if the original `/issue-code-generation` run used `--strict`. This flag is **required** to ensure `code-challenger` is not skipped when resuming a strict run interrupted before `04-challenges` was written. + ## Steps 1. Run `node scripts/checkpoint.mjs list ` to get the list of already-completed steps 2. Display the completed steps to the user so they can confirm the resume point -3. Determine the first missing step from the ordered sequence: - - `01-router` - - `02-brief` - - `03-patch` - - `04-challenges` (only if `--strict` was originally used — infer from presence of its checkpoint) - - `05-review` -4. For each step **before** the first missing one, load the saved output via `node scripts/checkpoint.mjs read ` and treat it as the agent's output — **do not re-run the agent** +3. Determine the full expected step sequence based on flags: + - Without `--strict`: `01-router`, `02-brief`, `03-patch`, `05-review` + - With `--strict`: `01-router`, `02-brief`, `03-patch`, `04-challenges`, `05-review` + - The first step absent from the checkpoint list is the resume point +4. For each step **before** the resume point, load the saved output via `node scripts/checkpoint.mjs read ` and treat it as the agent's output — **do not re-run the agent** 5. Resume the pipeline from the first missing step, following the same logic as `/issue-code-generation`: - **`01-router` missing** — fetch the issue with `node scripts/gh-get-issue.mjs `, run `issue-router`, write checkpoint `01-router`, then continue from `02-brief` + **`01-router` missing** — fetch the issue with `node scripts/gh-get-issue.mjs `, run `issue-router`, pipe output to `node scripts/checkpoint.mjs write 01-router`, then continue from `02-brief` - **`02-brief` missing** — load `01-router` checkpoint, run `ticket-analyst` with the original issue JSON, write checkpoint `02-brief`, then continue from `03-patch` + **`02-brief` missing** — load `01-router` checkpoint, run `ticket-analyst` with the original issue JSON, pipe output to `node scripts/checkpoint.mjs write 02-brief`, then continue from `03-patch` - **`03-patch` missing** — load `01-router` and `02-brief` checkpoints, run the code-builder variant indicated by the router type, write checkpoint `03-patch`; if `04-challenges` was part of the original run (checkpoint exists or `--strict` is passed), continue to `04-challenges`; otherwise continue to `05-review` + **`03-patch` missing** — load `01-router` and `02-brief` checkpoints, run the code-builder variant indicated by the router type, pipe output to `node scripts/checkpoint.mjs write 03-patch`; if `--strict` was passed continue to `04-challenges`, otherwise continue to `05-review` - **`04-challenges` missing** — load `03-patch` checkpoint, run `code-challenger` with the patch and original acceptance criteria, write checkpoint `04-challenges`, then continue to `05-review` + **`04-challenges` missing** *(only reached when `--strict` was passed)* — load `03-patch` checkpoint, run `code-challenger` with the patch and original acceptance criteria, pipe output to `node scripts/checkpoint.mjs write 04-challenges`, then continue to `05-review` - **`05-review` missing** — load `03-patch` checkpoint (and `04-challenges` if present), run `code-reviewer`, write checkpoint `05-review` + **`05-review` missing** — load `03-patch` checkpoint (and `04-challenges` if `--strict` was passed), run `code-reviewer`, pipe output to `node scripts/checkpoint.mjs write 05-review` -6. After each newly-run agent, call `node scripts/checkpoint.mjs write ` before proceeding to the next step -7. Apply the same stop conditions as `/issue-code-generation`: halt on `NEEDS_REVIEW` or `BLOCKED` and surface the output for human decision -8. On completion, present the final patch to the user for review and merge approval +6. Apply the same stop conditions as `/issue-code-generation`: halt on `NEEDS_REVIEW` or `BLOCKED` and surface the output for human decision +7. On completion, present the final patch to the user for review and merge approval ## Notes - `listCheckpoints` returns steps sorted lexicographically — the `01-` … `05-` numeric prefixes ensure correct order -- If all five steps (or four, when `--strict` was not used) are already checkpointed, inform the user that the pipeline is complete and show the `05-review` checkpoint content +- `node scripts/checkpoint.mjs write` reads content from stdin to preserve multiline Markdown structure +- If all expected steps are already checkpointed, inform the user that the pipeline is complete and show the `05-review` checkpoint content - If no checkpoints exist for ``, inform the user and suggest running `/issue-code-generation ` instead diff --git a/scripts/checkpoint.mjs b/scripts/checkpoint.mjs index 91b0b89..3c72795 100644 --- a/scripts/checkpoint.mjs +++ b/scripts/checkpoint.mjs @@ -1,7 +1,7 @@ #!/usr/bin/env node // Checkpoint read/write for the checkpoint-resume pattern. // Files land in ~/dev/checkpoints//-.md -// Usage: node scripts/checkpoint.mjs write +// Usage: node scripts/checkpoint.mjs write (content read from stdin) // node scripts/checkpoint.mjs read // node scripts/checkpoint.mjs list @@ -48,11 +48,17 @@ export function listCheckpoints(issueId) { .sort(); } +async function readStdin() { + const chunks = []; + for await (const chunk of process.stdin) chunks.push(chunk); + return Buffer.concat(chunks).toString('utf8'); +} + if (process.argv[1] === fileURLToPath(import.meta.url)) { - const [, , cmd, issueId, step, ...rest] = process.argv; + const [, , cmd, issueId, step] = process.argv; if (cmd === 'write') { - const content = rest.join(' '); + const content = await readStdin(); const path = writeCheckpoint(issueId, step, content); console.log(`Checkpoint written: ${path}`); } else if (cmd === 'read') { From 214878471c5a094d0d3e0c3b9ab5c701b77606d0 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 25 May 2026 03:50:10 +0000 Subject: [PATCH 03/10] fix: guard against path traversal and missing args in checkpoint.mjs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add STEP_RE allowlist (/^\d{2}-[a-z][a-z0-9-]*$/) enforced by assertStep() called in both writeCheckpoint and readCheckpoint — rejects any step containing path separators or traversal sequences - Add requireArgs() in the CLI handler to validate issueId/step before calling any function, replacing TypeError stack traces with a controlled usage error and non-zero exit https://claude.ai/code/session_01Phehb45JRh3HpLp9mAobzu --- scripts/checkpoint.mjs | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/scripts/checkpoint.mjs b/scripts/checkpoint.mjs index 3c72795..44aff1a 100644 --- a/scripts/checkpoint.mjs +++ b/scripts/checkpoint.mjs @@ -12,6 +12,16 @@ import { fileURLToPath } from 'node:url'; const CHECKPOINTS_ROOT = join(homedir(), 'dev', 'checkpoints'); +// Allowlist: exactly "NN-slug" where slug is lowercase letters/digits/hyphens. +// Rejects any input containing path separators or other traversal characters. +const STEP_RE = /^\d{2}-[a-z][a-z0-9-]*$/; + +function assertStep(step) { + if (!STEP_RE.test(step)) { + throw new Error(`Invalid step "${step}" — must match NN-slug (e.g. "01-router")`); + } +} + function checkpointDir(issueId) { return join(CHECKPOINTS_ROOT, String(issueId)); } @@ -24,9 +34,9 @@ function findFile(dir, step) { } export function writeCheckpoint(issueId, step, content) { + assertStep(step); const dir = checkpointDir(issueId); mkdirSync(dir, { recursive: true }); - // Derive agent slug from step label (e.g. "01-router" → "router") const agentSlug = step.replace(/^\d+-/, ''); const filePath = join(dir, `${step}-${agentSlug}.md`); writeFileSync(filePath, content, 'utf8'); @@ -34,6 +44,7 @@ export function writeCheckpoint(issueId, step, content) { } export function readCheckpoint(issueId, step) { + assertStep(step); const file = findFile(checkpointDir(issueId), step); if (!file) return null; return readFileSync(file, 'utf8'); @@ -57,18 +68,31 @@ async function readStdin() { if (process.argv[1] === fileURLToPath(import.meta.url)) { const [, , cmd, issueId, step] = process.argv; + function requireArgs(...names) { + const vals = { cmd, issueId, step }; + const missing = names.filter(n => !vals[n]); + if (missing.length) { + console.error(`Missing required argument(s): ${missing.join(', ')}`); + console.error('Usage: node scripts/checkpoint.mjs write|read|list [step]'); + process.exit(1); + } + } + if (cmd === 'write') { + requireArgs('issueId', 'step'); const content = await readStdin(); const path = writeCheckpoint(issueId, step, content); console.log(`Checkpoint written: ${path}`); } else if (cmd === 'read') { + requireArgs('issueId', 'step'); const content = readCheckpoint(issueId, step); if (content === null) { console.log('null'); } else { process.stdout.write(content); } } else if (cmd === 'list') { + requireArgs('issueId'); const steps = listCheckpoints(issueId); console.log(steps.length ? steps.join('\n') : '(none)'); } else { - console.error('Usage: node scripts/checkpoint.mjs write|read|list [step] [content]'); + console.error('Usage: node scripts/checkpoint.mjs write|read|list [step]'); process.exit(1); } } From 9165680d81ee4610039b8fdf60e8ebd582defdc3 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 25 May 2026 03:55:56 +0000 Subject: [PATCH 04/10] fix: validate issueId to prevent path traversal in checkpointDir MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add ISSUE_ID_RE (/^\d+$/) and assertIssueId() enforced at the top of writeCheckpoint, readCheckpoint, and listCheckpoints — rejects any issueId containing path separators or traversal sequences before path.join is called, keeping all operations inside ~/dev/checkpoints. https://claude.ai/code/session_01Phehb45JRh3HpLp9mAobzu --- scripts/checkpoint.mjs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/scripts/checkpoint.mjs b/scripts/checkpoint.mjs index 44aff1a..0e889ff 100644 --- a/scripts/checkpoint.mjs +++ b/scripts/checkpoint.mjs @@ -12,10 +12,16 @@ import { fileURLToPath } from 'node:url'; const CHECKPOINTS_ROOT = join(homedir(), 'dev', 'checkpoints'); -// Allowlist: exactly "NN-slug" where slug is lowercase letters/digits/hyphens. -// Rejects any input containing path separators or other traversal characters. +// Allowlist patterns — reject anything containing path separators or traversal sequences. +const ISSUE_ID_RE = /^\d+$/; const STEP_RE = /^\d{2}-[a-z][a-z0-9-]*$/; +function assertIssueId(issueId) { + if (!ISSUE_ID_RE.test(String(issueId))) { + throw new Error(`Invalid issueId "${issueId}" — must be a positive integer`); + } +} + function assertStep(step) { if (!STEP_RE.test(step)) { throw new Error(`Invalid step "${step}" — must match NN-slug (e.g. "01-router")`); @@ -34,6 +40,7 @@ function findFile(dir, step) { } export function writeCheckpoint(issueId, step, content) { + assertIssueId(issueId); assertStep(step); const dir = checkpointDir(issueId); mkdirSync(dir, { recursive: true }); @@ -44,6 +51,7 @@ export function writeCheckpoint(issueId, step, content) { } export function readCheckpoint(issueId, step) { + assertIssueId(issueId); assertStep(step); const file = findFile(checkpointDir(issueId), step); if (!file) return null; @@ -51,6 +59,7 @@ export function readCheckpoint(issueId, step) { } export function listCheckpoints(issueId) { + assertIssueId(issueId); const dir = checkpointDir(issueId); if (!existsSync(dir)) return []; return readdirSync(dir) From 3a7a3aa7bc53bc156a39dbeb6b050bd9801eb6a6 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 25 May 2026 04:05:37 +0000 Subject: [PATCH 05/10] =?UTF-8?q?fix:=20simplify=20checkpoint=20filename?= =?UTF-8?q?=20to=20.md=20=E2=80=94=20fixes=20listing=20of=20multi-hy?= =?UTF-8?q?phen=20steps?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The old -.md format caused listCheckpoints to return a corrupted slug for any step with more than one hyphen (e.g. 04-code-challenges became 04-code-challenges-code in the listing), making completed steps appear missing and triggering unintended re-runs. Replace the redundant suffix with a plain .md filename. readCheckpoint builds the path directly; listCheckpoints strips .md and filters by STEP_RE, which also excludes stray non-checkpoint files. findFile is removed as it is no longer needed. https://claude.ai/code/session_01Phehb45JRh3HpLp9mAobzu --- scripts/checkpoint.mjs | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/scripts/checkpoint.mjs b/scripts/checkpoint.mjs index 0e889ff..0a71a19 100644 --- a/scripts/checkpoint.mjs +++ b/scripts/checkpoint.mjs @@ -1,6 +1,6 @@ #!/usr/bin/env node // Checkpoint read/write for the checkpoint-resume pattern. -// Files land in ~/dev/checkpoints//-.md +// Files land in ~/dev/checkpoints//.md // Usage: node scripts/checkpoint.mjs write (content read from stdin) // node scripts/checkpoint.mjs read // node scripts/checkpoint.mjs list @@ -32,20 +32,12 @@ function checkpointDir(issueId) { return join(CHECKPOINTS_ROOT, String(issueId)); } -function findFile(dir, step) { - if (!existsSync(dir)) return null; - const prefix = `${step}-`; - const match = readdirSync(dir).find(f => f.startsWith(prefix) && f.endsWith('.md')); - return match ? join(dir, match) : null; -} - export function writeCheckpoint(issueId, step, content) { assertIssueId(issueId); assertStep(step); const dir = checkpointDir(issueId); mkdirSync(dir, { recursive: true }); - const agentSlug = step.replace(/^\d+-/, ''); - const filePath = join(dir, `${step}-${agentSlug}.md`); + const filePath = join(dir, `${step}.md`); writeFileSync(filePath, content, 'utf8'); return filePath; } @@ -53,9 +45,9 @@ export function writeCheckpoint(issueId, step, content) { export function readCheckpoint(issueId, step) { assertIssueId(issueId); assertStep(step); - const file = findFile(checkpointDir(issueId), step); - if (!file) return null; - return readFileSync(file, 'utf8'); + const filePath = join(checkpointDir(issueId), `${step}.md`); + if (!existsSync(filePath)) return null; + return readFileSync(filePath, 'utf8'); } export function listCheckpoints(issueId) { @@ -63,8 +55,8 @@ export function listCheckpoints(issueId) { const dir = checkpointDir(issueId); if (!existsSync(dir)) return []; return readdirSync(dir) - .filter(f => f.endsWith('.md')) - .map(f => f.replace(/\.md$/, '').replace(/-[^-]+$/, '')) + .filter(f => f.endsWith('.md') && STEP_RE.test(f.slice(0, -3))) + .map(f => f.slice(0, -3)) .sort(); } From 8fda63be863810a6c66af0de4c872b0392b3bfa5 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 25 May 2026 04:15:57 +0000 Subject: [PATCH 06/10] fix: re-fetch issue JSON on resume and restrict checkpoint file permissions - resume.md: issue JSON is never checkpointed, so any resume point at 02-brief or later now explicitly re-fetches via gh-get-issue.mjs before running ticket-analyst or code-builder; removes the silent dependency on context that no longer exists after interruption - checkpoint.mjs: writeCheckpoint passes mode 0o700 to mkdirSync and mode 0o600 to writeFileSync so checkpoint dirs and files are owner-only; prevents other local users from reading sensitive agent output on shared runners or workstations https://claude.ai/code/session_01Phehb45JRh3HpLp9mAobzu --- commands/resume.md | 13 +++++++------ scripts/checkpoint.mjs | 4 ++-- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/commands/resume.md b/commands/resume.md index b105df7..ab03289 100644 --- a/commands/resume.md +++ b/commands/resume.md @@ -19,20 +19,21 @@ Pass `--strict` if the original `/issue-code-generation` run used `--strict`. Th - With `--strict`: `01-router`, `02-brief`, `03-patch`, `04-challenges`, `05-review` - The first step absent from the checkpoint list is the resume point 4. For each step **before** the resume point, load the saved output via `node scripts/checkpoint.mjs read ` and treat it as the agent's output — **do not re-run the agent** -5. Resume the pipeline from the first missing step, following the same logic as `/issue-code-generation`: +5. The issue JSON is never checkpointed. Whenever the resume point is `02-brief` or later, re-fetch it with `node scripts/gh-get-issue.mjs ` before running any agent that needs it +6. Resume the pipeline from the first missing step, following the same logic as `/issue-code-generation`: - **`01-router` missing** — fetch the issue with `node scripts/gh-get-issue.mjs `, run `issue-router`, pipe output to `node scripts/checkpoint.mjs write 01-router`, then continue from `02-brief` + **`01-router` missing** — fetch the issue JSON with `node scripts/gh-get-issue.mjs `, run `issue-router`, pipe output to `node scripts/checkpoint.mjs write 01-router`, then continue from `02-brief` - **`02-brief` missing** — load `01-router` checkpoint, run `ticket-analyst` with the original issue JSON, pipe output to `node scripts/checkpoint.mjs write 02-brief`, then continue from `03-patch` + **`02-brief` missing** — re-fetch the issue JSON with `node scripts/gh-get-issue.mjs `, load `01-router` checkpoint, run `ticket-analyst` with the fresh issue JSON, pipe output to `node scripts/checkpoint.mjs write 02-brief`, then continue from `03-patch` - **`03-patch` missing** — load `01-router` and `02-brief` checkpoints, run the code-builder variant indicated by the router type, pipe output to `node scripts/checkpoint.mjs write 03-patch`; if `--strict` was passed continue to `04-challenges`, otherwise continue to `05-review` + **`03-patch` missing** — re-fetch the issue JSON with `node scripts/gh-get-issue.mjs `, load `01-router` and `02-brief` checkpoints, run the code-builder variant indicated by the router type, pipe output to `node scripts/checkpoint.mjs write 03-patch`; if `--strict` was passed continue to `04-challenges`, otherwise continue to `05-review` **`04-challenges` missing** *(only reached when `--strict` was passed)* — load `03-patch` checkpoint, run `code-challenger` with the patch and original acceptance criteria, pipe output to `node scripts/checkpoint.mjs write 04-challenges`, then continue to `05-review` **`05-review` missing** — load `03-patch` checkpoint (and `04-challenges` if `--strict` was passed), run `code-reviewer`, pipe output to `node scripts/checkpoint.mjs write 05-review` -6. Apply the same stop conditions as `/issue-code-generation`: halt on `NEEDS_REVIEW` or `BLOCKED` and surface the output for human decision -7. On completion, present the final patch to the user for review and merge approval +7. Apply the same stop conditions as `/issue-code-generation`: halt on `NEEDS_REVIEW` or `BLOCKED` and surface the output for human decision +8. On completion, present the final patch to the user for review and merge approval ## Notes diff --git a/scripts/checkpoint.mjs b/scripts/checkpoint.mjs index 0a71a19..c78edeb 100644 --- a/scripts/checkpoint.mjs +++ b/scripts/checkpoint.mjs @@ -36,9 +36,9 @@ export function writeCheckpoint(issueId, step, content) { assertIssueId(issueId); assertStep(step); const dir = checkpointDir(issueId); - mkdirSync(dir, { recursive: true }); + mkdirSync(dir, { recursive: true, mode: 0o700 }); const filePath = join(dir, `${step}.md`); - writeFileSync(filePath, content, 'utf8'); + writeFileSync(filePath, content, { encoding: 'utf8', mode: 0o600 }); return filePath; } From fd5dbbba71dcd008d9054f0afadb67b5207978c9 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 25 May 2026 04:23:22 +0000 Subject: [PATCH 07/10] fix: exit non-zero when read checkpoint is missing Previously the CLI printed "null" and exited 0, making a missing checkpoint indistinguishable from valid content in shell pipelines. Now it emits an error to stderr and exits 1, so /resume fails fast on a typoed or deleted checkpoint instead of feeding invalid data to the next agent. https://claude.ai/code/session_01Phehb45JRh3HpLp9mAobzu --- scripts/checkpoint.mjs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/scripts/checkpoint.mjs b/scripts/checkpoint.mjs index c78edeb..7664f3b 100644 --- a/scripts/checkpoint.mjs +++ b/scripts/checkpoint.mjs @@ -87,7 +87,11 @@ if (process.argv[1] === fileURLToPath(import.meta.url)) { } else if (cmd === 'read') { requireArgs('issueId', 'step'); const content = readCheckpoint(issueId, step); - if (content === null) { console.log('null'); } else { process.stdout.write(content); } + if (content === null) { + console.error(`Checkpoint not found: ${issueId}/${step}`); + process.exit(1); + } + process.stdout.write(content); } else if (cmd === 'list') { requireArgs('issueId'); const steps = listCheckpoints(issueId); From 2cda19d646b8152446d9cdba5e5c0b5b3c2a1789 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 25 May 2026 04:29:56 +0000 Subject: [PATCH 08/10] fix: namespace checkpoints by repository to prevent cross-repo collisions Checkpoint paths are now ~/dev/checkpoints////.md. All three exported functions (writeCheckpoint, readCheckpoint, listCheckpoints) take repo as their first argument and validate it against REPO_RE before building the path; assertRepo rejects anything that is not owner/repo. The CLI requires --repo on every subcommand. Both commands updated to pass --repo on every checkpoint call. https://claude.ai/code/session_01Phehb45JRh3HpLp9mAobzu --- commands/issue-code-generation.md | 10 ++--- commands/resume.md | 20 +++++----- scripts/checkpoint.mjs | 62 +++++++++++++++++++------------ 3 files changed, 55 insertions(+), 37 deletions(-) diff --git a/commands/issue-code-generation.md b/commands/issue-code-generation.md index 3a755f9..e922f04 100644 --- a/commands/issue-code-generation.md +++ b/commands/issue-code-generation.md @@ -8,27 +8,27 @@ Full issue → code → AC validation pipeline. 2. Pass the full issue JSON to the `issue-router` agent - If status is `NEEDS_REVIEW`, stop and surface the classification uncertainty for human decision - The router returns a **type** (`bug`, `feature`, `refactor`, `security`) used in step 4 - - Write checkpoint: pipe agent output to `node scripts/checkpoint.mjs write 01-router` + - Write checkpoint: pipe agent output to `node scripts/checkpoint.mjs write --repo 01-router` 3. Pass the full issue JSON to the `ticket-analyst` agent - If status is `BLOCKED` or `NEEDS_REVIEW`, stop and surface the output for human decision - - Write checkpoint: pipe agent output to `node scripts/checkpoint.mjs write 02-brief` + - Write checkpoint: pipe agent output to `node scripts/checkpoint.mjs write --repo 02-brief` 4. Pass the `### Brief` block from ticket-analyst to the code-builder variant selected by the router: - `bug` → `code-builder-bug` - `feature` → `code-builder-feature` - `refactor` → `code-builder-refactor` - `security` or unclassified → `code-builder` (generic fallback) - If status is `BLOCKED` or `NEEDS_REVIEW`, stop and surface the output - - Write checkpoint: pipe agent output to `node scripts/checkpoint.mjs write 03-patch` + - Write checkpoint: pipe agent output to `node scripts/checkpoint.mjs write --repo 03-patch` 5. **If `--strict` flag is present**, pass to the `code-challenger` agent: the `### Patch` block, the original acceptance criteria, and the issue type - If status is `BLOCKED` or `NEEDS_REVIEW`, stop and surface the output for human decision before continuing - On `DONE`, carry the `### Handoff` block (critical points list) forward to step 6 - Skip this step entirely if `--strict` was not passed - - Write checkpoint: pipe agent output to `node scripts/checkpoint.mjs write 04-challenges` + - Write checkpoint: pipe agent output to `node scripts/checkpoint.mjs write --repo 04-challenges` 6. Pass to the `code-reviewer` agent: the `### Patch` block, the original acceptance criteria, and any type-specific evidence blocks produced by the builder: - `bug` → include `### Reproduction` - `refactor` → include `### Non-regression evidence` - `feature` / `security` / fallback → `### Patch` only - If `--strict` was used, also include the `### Handoff` block from code-challenger so the reviewer is aware of pre-identified risks - - Write checkpoint: pipe agent output to `node scripts/checkpoint.mjs write 05-review` + - Write checkpoint: pipe agent output to `node scripts/checkpoint.mjs write --repo 05-review` 7. If code-reviewer status is `NEEDS_REVIEW`, write the review report and stop — present blocking issues to the user 8. If code-reviewer status is `DONE`, present the full patch to the user for review and merge approval diff --git a/commands/resume.md b/commands/resume.md index ab03289..80a964f 100644 --- a/commands/resume.md +++ b/commands/resume.md @@ -5,32 +5,34 @@ Resume an interrupted `/issue-code-generation` pipeline from the first missing c ## Invocation ``` -/resume [--strict] +/resume --repo [--strict] ``` -Pass `--strict` if the original `/issue-code-generation` run used `--strict`. This flag is **required** to ensure `code-challenger` is not skipped when resuming a strict run interrupted before `04-challenges` was written. +`--repo` is required and must match the repository used in the original `/issue-code-generation` run — checkpoints are namespaced by repo so runs against different repositories with the same issue number do not collide. + +Pass `--strict` if the original run used `--strict`. This flag is **required** to ensure `code-challenger` is not skipped when resuming a strict run interrupted before `04-challenges` was written. ## Steps -1. Run `node scripts/checkpoint.mjs list ` to get the list of already-completed steps +1. Run `node scripts/checkpoint.mjs list --repo ` to get the list of already-completed steps 2. Display the completed steps to the user so they can confirm the resume point 3. Determine the full expected step sequence based on flags: - Without `--strict`: `01-router`, `02-brief`, `03-patch`, `05-review` - With `--strict`: `01-router`, `02-brief`, `03-patch`, `04-challenges`, `05-review` - The first step absent from the checkpoint list is the resume point -4. For each step **before** the resume point, load the saved output via `node scripts/checkpoint.mjs read ` and treat it as the agent's output — **do not re-run the agent** +4. For each step **before** the resume point, load the saved output via `node scripts/checkpoint.mjs read --repo ` and treat it as the agent's output — **do not re-run the agent** 5. The issue JSON is never checkpointed. Whenever the resume point is `02-brief` or later, re-fetch it with `node scripts/gh-get-issue.mjs ` before running any agent that needs it 6. Resume the pipeline from the first missing step, following the same logic as `/issue-code-generation`: - **`01-router` missing** — fetch the issue JSON with `node scripts/gh-get-issue.mjs `, run `issue-router`, pipe output to `node scripts/checkpoint.mjs write 01-router`, then continue from `02-brief` + **`01-router` missing** — fetch the issue JSON with `node scripts/gh-get-issue.mjs `, run `issue-router`, pipe output to `node scripts/checkpoint.mjs write --repo 01-router`, then continue from `02-brief` - **`02-brief` missing** — re-fetch the issue JSON with `node scripts/gh-get-issue.mjs `, load `01-router` checkpoint, run `ticket-analyst` with the fresh issue JSON, pipe output to `node scripts/checkpoint.mjs write 02-brief`, then continue from `03-patch` + **`02-brief` missing** — re-fetch the issue JSON with `node scripts/gh-get-issue.mjs `, load `01-router` checkpoint, run `ticket-analyst` with the fresh issue JSON, pipe output to `node scripts/checkpoint.mjs write --repo 02-brief`, then continue from `03-patch` - **`03-patch` missing** — re-fetch the issue JSON with `node scripts/gh-get-issue.mjs `, load `01-router` and `02-brief` checkpoints, run the code-builder variant indicated by the router type, pipe output to `node scripts/checkpoint.mjs write 03-patch`; if `--strict` was passed continue to `04-challenges`, otherwise continue to `05-review` + **`03-patch` missing** — re-fetch the issue JSON with `node scripts/gh-get-issue.mjs `, load `01-router` and `02-brief` checkpoints, run the code-builder variant indicated by the router type, pipe output to `node scripts/checkpoint.mjs write --repo 03-patch`; if `--strict` was passed continue to `04-challenges`, otherwise continue to `05-review` - **`04-challenges` missing** *(only reached when `--strict` was passed)* — load `03-patch` checkpoint, run `code-challenger` with the patch and original acceptance criteria, pipe output to `node scripts/checkpoint.mjs write 04-challenges`, then continue to `05-review` + **`04-challenges` missing** *(only reached when `--strict` was passed)* — load `03-patch` checkpoint, run `code-challenger` with the patch and original acceptance criteria, pipe output to `node scripts/checkpoint.mjs write --repo 04-challenges`, then continue to `05-review` - **`05-review` missing** — load `03-patch` checkpoint (and `04-challenges` if `--strict` was passed), run `code-reviewer`, pipe output to `node scripts/checkpoint.mjs write 05-review` + **`05-review` missing** — load `03-patch` checkpoint (and `04-challenges` if `--strict` was passed), run `code-reviewer`, pipe output to `node scripts/checkpoint.mjs write --repo 05-review` 7. Apply the same stop conditions as `/issue-code-generation`: halt on `NEEDS_REVIEW` or `BLOCKED` and surface the output for human decision 8. On completion, present the final patch to the user for review and merge approval diff --git a/scripts/checkpoint.mjs b/scripts/checkpoint.mjs index 7664f3b..5838ea0 100644 --- a/scripts/checkpoint.mjs +++ b/scripts/checkpoint.mjs @@ -1,9 +1,9 @@ #!/usr/bin/env node // Checkpoint read/write for the checkpoint-resume pattern. -// Files land in ~/dev/checkpoints//.md -// Usage: node scripts/checkpoint.mjs write (content read from stdin) -// node scripts/checkpoint.mjs read -// node scripts/checkpoint.mjs list +// Files land in ~/dev/checkpoints////.md +// Usage: node scripts/checkpoint.mjs write --repo (content from stdin) +// node scripts/checkpoint.mjs read --repo +// node scripts/checkpoint.mjs list --repo import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs'; import { homedir } from 'node:os'; @@ -13,9 +13,16 @@ import { fileURLToPath } from 'node:url'; const CHECKPOINTS_ROOT = join(homedir(), 'dev', 'checkpoints'); // Allowlist patterns — reject anything containing path separators or traversal sequences. +const REPO_RE = /^[a-zA-Z0-9][a-zA-Z0-9._-]*\/[a-zA-Z0-9][a-zA-Z0-9._-]*$/; const ISSUE_ID_RE = /^\d+$/; const STEP_RE = /^\d{2}-[a-z][a-z0-9-]*$/; +function assertRepo(repo) { + if (!REPO_RE.test(String(repo))) { + throw new Error(`Invalid repo "${repo}" — must be owner/repo (e.g. "acme/my-app")`); + } +} + function assertIssueId(issueId) { if (!ISSUE_ID_RE.test(String(issueId))) { throw new Error(`Invalid issueId "${issueId}" — must be a positive integer`); @@ -28,31 +35,35 @@ function assertStep(step) { } } -function checkpointDir(issueId) { - return join(CHECKPOINTS_ROOT, String(issueId)); +function checkpointDir(repo, issueId) { + const [owner, repoName] = repo.split('/'); + return join(CHECKPOINTS_ROOT, owner, repoName, String(issueId)); } -export function writeCheckpoint(issueId, step, content) { +export function writeCheckpoint(repo, issueId, step, content) { + assertRepo(repo); assertIssueId(issueId); assertStep(step); - const dir = checkpointDir(issueId); + const dir = checkpointDir(repo, issueId); mkdirSync(dir, { recursive: true, mode: 0o700 }); const filePath = join(dir, `${step}.md`); writeFileSync(filePath, content, { encoding: 'utf8', mode: 0o600 }); return filePath; } -export function readCheckpoint(issueId, step) { +export function readCheckpoint(repo, issueId, step) { + assertRepo(repo); assertIssueId(issueId); assertStep(step); - const filePath = join(checkpointDir(issueId), `${step}.md`); + const filePath = join(checkpointDir(repo, issueId), `${step}.md`); if (!existsSync(filePath)) return null; return readFileSync(filePath, 'utf8'); } -export function listCheckpoints(issueId) { +export function listCheckpoints(repo, issueId) { + assertRepo(repo); assertIssueId(issueId); - const dir = checkpointDir(issueId); + const dir = checkpointDir(repo, issueId); if (!existsSync(dir)) return []; return readdirSync(dir) .filter(f => f.endsWith('.md') && STEP_RE.test(f.slice(0, -3))) @@ -67,37 +78,42 @@ async function readStdin() { } if (process.argv[1] === fileURLToPath(import.meta.url)) { - const [, , cmd, issueId, step] = process.argv; + const args = process.argv.slice(2); + const cmd = args[0]; + const repoIdx = args.indexOf('--repo'); + const repo = repoIdx !== -1 ? args[repoIdx + 1] : undefined; + const positional = args.filter((_, i) => i !== 0 && i !== repoIdx && i !== repoIdx + 1); + const [issueId, step] = positional; function requireArgs(...names) { - const vals = { cmd, issueId, step }; + const vals = { cmd, repo, issueId, step }; const missing = names.filter(n => !vals[n]); if (missing.length) { console.error(`Missing required argument(s): ${missing.join(', ')}`); - console.error('Usage: node scripts/checkpoint.mjs write|read|list [step]'); + console.error('Usage: node scripts/checkpoint.mjs write|read|list --repo [step]'); process.exit(1); } } if (cmd === 'write') { - requireArgs('issueId', 'step'); + requireArgs('repo', 'issueId', 'step'); const content = await readStdin(); - const path = writeCheckpoint(issueId, step, content); + const path = writeCheckpoint(repo, issueId, step, content); console.log(`Checkpoint written: ${path}`); } else if (cmd === 'read') { - requireArgs('issueId', 'step'); - const content = readCheckpoint(issueId, step); + requireArgs('repo', 'issueId', 'step'); + const content = readCheckpoint(repo, issueId, step); if (content === null) { - console.error(`Checkpoint not found: ${issueId}/${step}`); + console.error(`Checkpoint not found: ${repo}/${issueId}/${step}`); process.exit(1); } process.stdout.write(content); } else if (cmd === 'list') { - requireArgs('issueId'); - const steps = listCheckpoints(issueId); + requireArgs('repo', 'issueId'); + const steps = listCheckpoints(repo, issueId); console.log(steps.length ? steps.join('\n') : '(none)'); } else { - console.error('Usage: node scripts/checkpoint.mjs write|read|list [step]'); + console.error('Usage: node scripts/checkpoint.mjs write|read|list --repo [step]'); process.exit(1); } } From 90918a1977bfe4ef635ffcab3ef5ea5e6f3069e8 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 25 May 2026 04:34:12 +0000 Subject: [PATCH 09/10] fix: pass --repo to gh-get-issue.mjs on every resume re-fetch All node scripts/gh-get-issue.mjs calls in resume.md now include --repo so the fetched issue JSON always comes from the intended repository, matching the namespace used for checkpoint storage. https://claude.ai/code/session_01Phehb45JRh3HpLp9mAobzu --- commands/resume.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/commands/resume.md b/commands/resume.md index 80a964f..67ebcba 100644 --- a/commands/resume.md +++ b/commands/resume.md @@ -21,14 +21,14 @@ Pass `--strict` if the original run used `--strict`. This flag is **required** t - With `--strict`: `01-router`, `02-brief`, `03-patch`, `04-challenges`, `05-review` - The first step absent from the checkpoint list is the resume point 4. For each step **before** the resume point, load the saved output via `node scripts/checkpoint.mjs read --repo ` and treat it as the agent's output — **do not re-run the agent** -5. The issue JSON is never checkpointed. Whenever the resume point is `02-brief` or later, re-fetch it with `node scripts/gh-get-issue.mjs ` before running any agent that needs it +5. The issue JSON is never checkpointed. Whenever the resume point is `02-brief` or later, re-fetch it with `node scripts/gh-get-issue.mjs --repo ` before running any agent that needs it 6. Resume the pipeline from the first missing step, following the same logic as `/issue-code-generation`: - **`01-router` missing** — fetch the issue JSON with `node scripts/gh-get-issue.mjs `, run `issue-router`, pipe output to `node scripts/checkpoint.mjs write --repo 01-router`, then continue from `02-brief` + **`01-router` missing** — fetch the issue JSON with `node scripts/gh-get-issue.mjs --repo `, run `issue-router`, pipe output to `node scripts/checkpoint.mjs write --repo 01-router`, then continue from `02-brief` - **`02-brief` missing** — re-fetch the issue JSON with `node scripts/gh-get-issue.mjs `, load `01-router` checkpoint, run `ticket-analyst` with the fresh issue JSON, pipe output to `node scripts/checkpoint.mjs write --repo 02-brief`, then continue from `03-patch` + **`02-brief` missing** — re-fetch the issue JSON with `node scripts/gh-get-issue.mjs --repo `, load `01-router` checkpoint, run `ticket-analyst` with the fresh issue JSON, pipe output to `node scripts/checkpoint.mjs write --repo 02-brief`, then continue from `03-patch` - **`03-patch` missing** — re-fetch the issue JSON with `node scripts/gh-get-issue.mjs `, load `01-router` and `02-brief` checkpoints, run the code-builder variant indicated by the router type, pipe output to `node scripts/checkpoint.mjs write --repo 03-patch`; if `--strict` was passed continue to `04-challenges`, otherwise continue to `05-review` + **`03-patch` missing** — re-fetch the issue JSON with `node scripts/gh-get-issue.mjs --repo `, load `01-router` and `02-brief` checkpoints, run the code-builder variant indicated by the router type, pipe output to `node scripts/checkpoint.mjs write --repo 03-patch`; if `--strict` was passed continue to `04-challenges`, otherwise continue to `05-review` **`04-challenges` missing** *(only reached when `--strict` was passed)* — load `03-patch` checkpoint, run `code-challenger` with the patch and original acceptance criteria, pipe output to `node scripts/checkpoint.mjs write --repo 04-challenges`, then continue to `05-review` From 1d9a242cc9264c3d9e4db2a615d26bad7c2e3972 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 25 May 2026 04:39:38 +0000 Subject: [PATCH 10/10] fix: reject issue number 0 in assertIssueId Change ISSUE_ID_RE from /^\d+$/ to /^[1-9]\d*$/ so that 0 is rejected as an invalid GitHub issue number, matching the error message that already says "positive integer". https://claude.ai/code/session_01Phehb45JRh3HpLp9mAobzu --- scripts/checkpoint.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/checkpoint.mjs b/scripts/checkpoint.mjs index 5838ea0..41ccdf2 100644 --- a/scripts/checkpoint.mjs +++ b/scripts/checkpoint.mjs @@ -14,7 +14,7 @@ const CHECKPOINTS_ROOT = join(homedir(), 'dev', 'checkpoints'); // Allowlist patterns — reject anything containing path separators or traversal sequences. const REPO_RE = /^[a-zA-Z0-9][a-zA-Z0-9._-]*\/[a-zA-Z0-9][a-zA-Z0-9._-]*$/; -const ISSUE_ID_RE = /^\d+$/; +const ISSUE_ID_RE = /^[1-9]\d*$/; const STEP_RE = /^\d{2}-[a-z][a-z0-9-]*$/; function assertRepo(repo) {