Merge pull request #1501 from Tibsfox/fix/discuss-incremental-saves-1485

fix(discuss): incremental checkpoint saves to prevent answer loss on interrupt
This commit is contained in:
Tom Boucher
2026-04-01 12:17:54 -04:00
committed by GitHub
2 changed files with 139 additions and 0 deletions

View File

@@ -182,6 +182,26 @@ If "Skip": Exit workflow
**If doesn't exist:**
**Check for interrupted discussion checkpoint:**
```bash
ls ${phase_dir}/*-DISCUSS-CHECKPOINT.json 2>/dev/null || true
```
If a checkpoint file exists (previous session was interrupted before CONTEXT.md was written):
**If `--auto`:** Auto-select "Resume" — load checkpoint and continue from last completed area.
**Otherwise:** Use AskUserQuestion:
- header: "Resume"
- question: "Found interrupted discussion checkpoint ({N} areas completed out of {M}). Resume from where you left off?"
- options:
- "Resume" — Load checkpoint, skip completed areas, continue discussion
- "Start fresh" — Delete checkpoint, start discussion from scratch
If "Resume": Parse the checkpoint JSON. Load `decisions` into the internal accumulator. Set `areas_completed` to skip those areas. Continue to `present_gray_areas` with only the remaining areas.
If "Start fresh": Delete the checkpoint file. Continue as if no checkpoint existed.
Check `has_plans` and `plan_count` from init. **If `has_plans` is true:**
**If `--auto`:** Auto-select "Continue and replan after". Log: `[auto] Plans exist — continuing with context capture, will replan after.`
@@ -719,6 +739,44 @@ Back to [current area]: [return to current question]"
Track deferred ideas internally.
**Incremental checkpoint — save after each area completes:**
After each area is resolved (user says "Next area" or area auto-resolves in `--auto` mode), immediately write a checkpoint file with all decisions captured so far. This prevents data loss if the session is interrupted mid-discussion.
**Checkpoint file:** `${phase_dir}/${padded_phase}-DISCUSS-CHECKPOINT.json`
Write after each area:
```json
{
"phase": "{PHASE_NUM}",
"phase_name": "{phase_name}",
"timestamp": "{ISO timestamp}",
"areas_completed": ["Area 1", "Area 2"],
"areas_remaining": ["Area 3", "Area 4"],
"decisions": {
"Area 1": [
{"question": "...", "answer": "...", "options_presented": ["..."]},
{"question": "...", "answer": "...", "options_presented": ["..."]}
],
"Area 2": [
{"question": "...", "answer": "...", "options_presented": ["..."]}
]
},
"deferred_ideas": ["..."],
"canonical_refs": ["..."]
}
```
This is a structured checkpoint, not the final CONTEXT.md — the `write_context` step still produces the canonical output. But if the session dies, the next `gsd:discuss-phase` invocation can detect this checkpoint and offer to resume from it instead of starting from scratch.
**On session resume:** In the `check_existing` step, also check for `*-DISCUSS-CHECKPOINT.json`. If found and no CONTEXT.md exists:
- Display: "Found interrupted discussion checkpoint ({N} areas completed). Resume from checkpoint?"
- Options: "Resume" / "Start fresh"
- On "Resume": Load the checkpoint, skip completed areas, continue from where it left off
- On "Start fresh": Delete the checkpoint, proceed as normal
**After write_context completes successfully:** Delete the checkpoint file — the canonical CONTEXT.md now has all decisions.
**Track discussion log data internally:**
For each question asked, accumulate:
- Area name
@@ -933,6 +991,12 @@ Created: .planning/phases/${PADDED_PHASE}-${SLUG}/${PADDED_PHASE}-CONTEXT.md
Write file.
**Clean up checkpoint file** — CONTEXT.md is now the canonical record:
```bash
rm -f "${phase_dir}/${padded_phase}-DISCUSS-CHECKPOINT.json"
```
Commit phase context and discussion log:
```bash
@@ -1046,4 +1110,7 @@ Route to `confirm_creation` step (existing behavior — show manual next steps).
- Deferred ideas preserved for future phases
- STATE.md updated with session info
- User knows next steps
- Checkpoint file written after each area completes (incremental save)
- Interrupted sessions can be resumed from checkpoint (no re-answering completed areas)
- Checkpoint file cleaned up after successful CONTEXT.md write
</success_criteria>

View File

@@ -0,0 +1,72 @@
/**
* GSD Tools Tests - discuss-phase incremental checkpoint saves
*
* Validates that the discuss-phase workflow includes incremental
* checkpoint logic to prevent answer loss on session interruption.
*
* Closes: #1485
*/
const { test, describe } = require('node:test');
const assert = require('node:assert');
const fs = require('fs');
const path = require('path');
describe('discuss-phase incremental checkpoint saves (#1485)', () => {
const workflowPath = path.join(__dirname, '..', 'get-shit-done', 'workflows', 'discuss-phase.md');
test('workflow writes checkpoint file after each area completes', () => {
const content = fs.readFileSync(workflowPath, 'utf8');
assert.ok(
content.includes('DISCUSS-CHECKPOINT.json'),
'workflow should reference checkpoint JSON file'
);
assert.ok(
content.includes('Incremental checkpoint') || content.includes('incremental checkpoint'),
'workflow should describe incremental checkpoint saves'
);
});
test('checkpoint includes decisions, areas completed, and areas remaining', () => {
const content = fs.readFileSync(workflowPath, 'utf8');
assert.ok(content.includes('areas_completed'), 'checkpoint should track completed areas');
assert.ok(content.includes('areas_remaining'), 'checkpoint should track remaining areas');
assert.ok(content.includes('"decisions"'), 'checkpoint should include decisions object');
});
test('check_existing step detects checkpoint for session resume', () => {
const content = fs.readFileSync(workflowPath, 'utf8');
// The check_existing step should look for checkpoint files
assert.ok(
content.includes('DISCUSS-CHECKPOINT.json') && content.includes('Resume'),
'check_existing should detect checkpoint and offer resume'
);
});
test('checkpoint is cleaned up after successful CONTEXT.md write', () => {
const content = fs.readFileSync(workflowPath, 'utf8');
assert.ok(
content.includes('rm -f') && content.includes('DISCUSS-CHECKPOINT'),
'checkpoint file should be deleted after successful write_context'
);
});
test('success criteria include checkpoint requirements', () => {
const content = fs.readFileSync(workflowPath, 'utf8');
const criteriaMatch = content.match(/<success_criteria>([\s\S]*?)<\/success_criteria>/);
const criteria = criteriaMatch ? criteriaMatch[1] : '';
assert.ok(criteria.includes('checkpoint') || criteria.includes('Checkpoint'),
'success criteria should mention checkpoints');
assert.ok(criteria.includes('resume') || criteria.includes('Resume'),
'success criteria should mention session resume capability');
});
test('auto mode also writes checkpoints', () => {
const content = fs.readFileSync(workflowPath, 'utf8');
// The checkpoint section should mention auto mode
assert.ok(
content.includes('auto-resolves') || content.includes('--auto'),
'checkpoint logic should apply to both interactive and auto modes'
);
});
});