mirror of
https://github.com/glittercowboy/get-shit-done
synced 2026-04-25 17:25:23 +02:00
feat(plan-phase): chunked mode + filesystem fallback for Windows stdio hang (#2499)
* feat(plan-phase): chunked mode + filesystem fallback for Windows stdio hang (#2310) Addresses the 2026-04-16 Windows incident where gsd-planner wrote all 5 PLAN.md files to disk but Task() never returned, hanging the orchestrator for 30+ minutes. Two mitigations: 1. Filesystem fallback (steps 9a, 11a): when Task() returns with an empty/truncated response but PLAN.md files exist on disk, surface a recoverable prompt (Accept plans / Retry planner / Stop) instead of silently failing. Directly addresses the post-restart recovery path. 2. Chunked mode (--chunked flag / workflow.plan_chunked config): splits the single long-lived planner Task into a short outline Task (~2 min) followed by N short per-plan Tasks (~3-5 min each). Each plan is committed individually for crash resilience. A hang loses one plan, not all of them. Resume detection skips plans already on disk on re-run. RCA confirmed: task state mtime 14:29 vs PLAN.md writes 14:32-14:52 = subagent completed normally, IPC return was dropped by Windows stdio deadlock. Neither mitigation fixes the root cause (requires upstream Task() timeout support); both bound damage and enable recovery. New reference file planner-chunked.md keeps OUTLINE COMPLETE / PLAN COMPLETE return formats out of gsd-planner.md (which sits at 46K near its size limit). Closes #2310 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(plan-phase): address CodeRabbit review comments on #2499 - docs/CONFIGURATION.md: add workflow.plan_chunked to full JSON schema example - plan-phase.md step 8.5.1: validate PLAN-OUTLINE.md with grep for OUTLINE COMPLETE marker before reusing (not just file existence) - plan-phase.md step 8.5.2: validate per-plan PLAN.md has YAML frontmatter (head -1 grep for ---) before skipping in resume path - plan-phase.md: add language tags (text/javascript/bash) to bare fenced code blocks in steps 8.5, 9a, 11a (markdownlint MD040) - Rejected: commit_docs gate on per-plan commits (gsd-sdk query commit already respects commit_docs internally — comment was a false positive) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(plan-phase): route Accept-plans through step 9 PLANNING COMPLETE handling Honors --skip-verify / plan_checker_enabled=false in 9a fallback path. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1201,6 +1201,10 @@ Execute: `/gsd-execute-phase {phase} --gaps-only`
|
||||
|
||||
Follow templates in checkpoints and revision_mode sections respectively.
|
||||
|
||||
## Chunked Mode Returns
|
||||
|
||||
See @~/.claude/get-shit-done/references/planner-chunked.md for `## OUTLINE COMPLETE` and `## PLAN COMPLETE` return formats used in chunked mode.
|
||||
|
||||
</structured_returns>
|
||||
|
||||
<critical_rules>
|
||||
|
||||
@@ -43,6 +43,7 @@ GSD stores project settings in `.planning/config.json`. Created during `/gsd-new
|
||||
"plan_bounce": false,
|
||||
"plan_bounce_script": null,
|
||||
"plan_bounce_passes": 2,
|
||||
"plan_chunked": false,
|
||||
"code_review_command": null,
|
||||
"cross_ai_execution": false,
|
||||
"cross_ai_command": null,
|
||||
@@ -150,6 +151,7 @@ All workflow toggles follow the **absent = enabled** pattern. If a key is missin
|
||||
| `workflow.plan_bounce` | boolean | `false` | Run external validation script against generated plans. When enabled, the plan-phase orchestrator pipes each PLAN.md through the script specified by `plan_bounce_script` and blocks on non-zero exit. Added in v1.36 |
|
||||
| `workflow.plan_bounce_script` | string | (none) | Path to the external script invoked for plan bounce validation. Receives the PLAN.md path as its first argument. Required when `plan_bounce` is `true`. Added in v1.36 |
|
||||
| `workflow.plan_bounce_passes` | number | `2` | Number of sequential bounce passes to run. Each pass feeds the previous pass's output back into the validator. Higher values increase rigor at the cost of latency. Added in v1.36 |
|
||||
| `workflow.plan_chunked` | boolean | `false` | Enable chunked planning mode. When `true` (or when `--chunked` flag is passed to `/gsd-plan-phase`), the orchestrator splits the single long-lived planner Task into a short outline Task followed by N short per-plan Tasks (~3-5 min each). Each plan is committed individually for crash resilience. If a Task hangs and the terminal is force-killed, rerunning with `--chunked` resumes from the last completed plan. Particularly useful on Windows where long-lived Tasks may hang on stdio. Added in v1.38 |
|
||||
| `workflow.code_review_command` | string | (none) | Shell command for external code review integration in `/gsd-ship`. Receives changed file paths via stdin. Non-zero exit blocks the ship workflow. Added in v1.36 |
|
||||
| `workflow.tdd_mode` | boolean | `false` | Enable TDD pipeline as a first-class execution mode. When `true`, the planner aggressively applies `type: tdd` to eligible tasks (business logic, APIs, validations, algorithms) and the executor enforces RED/GREEN/REFACTOR gate sequence. An end-of-phase collaborative review checkpoint verifies gate compliance. Added in v1.36 |
|
||||
| `workflow.cross_ai_execution` | boolean | `false` | Delegate phase execution to an external AI CLI instead of spawning local executor agents. Useful for leveraging a different model's strengths for specific phases. Added in v1.36 |
|
||||
|
||||
@@ -229,6 +229,7 @@
|
||||
"model-profiles.md",
|
||||
"phase-argument-parsing.md",
|
||||
"planner-antipatterns.md",
|
||||
"planner-chunked.md",
|
||||
"planner-gap-closure.md",
|
||||
"planner-reviews.md",
|
||||
"planner-revision.md",
|
||||
|
||||
@@ -265,7 +265,7 @@ Full roster at `get-shit-done/workflows/*.md`. Workflows are thin orchestrators
|
||||
|
||||
---
|
||||
|
||||
## References (49 shipped)
|
||||
## References (50 shipped)
|
||||
|
||||
Full roster at `get-shit-done/references/*.md`. References are shared knowledge documents that workflows and agents `@-reference`. The groupings below match [`docs/ARCHITECTURE.md`](ARCHITECTURE.md#references-get-shit-donereferencesmd) — core, workflow, thinking-model clusters, and the modular planner decomposition.
|
||||
|
||||
@@ -344,12 +344,13 @@ The `gsd-planner` agent is decomposed into a core agent plus reference modules t
|
||||
| Reference | Role |
|
||||
|-----------|------|
|
||||
| `planner-antipatterns.md` | Planner anti-patterns and specificity examples. |
|
||||
| `planner-chunked.md` | Chunked mode return formats (`## OUTLINE COMPLETE`, `## PLAN COMPLETE`) for Windows stdio hang mitigation. |
|
||||
| `planner-gap-closure.md` | Gap-closure mode behavior (reads VERIFICATION.md, targeted replanning). |
|
||||
| `planner-reviews.md` | Cross-AI review integration (reads REVIEWS.md from `/gsd-review`). |
|
||||
| `planner-revision.md` | Plan revision patterns for iterative refinement. |
|
||||
| `planner-source-audit.md` | Planner source-audit and authority-limit rules. |
|
||||
|
||||
> **Subdirectory:** `get-shit-done/references/few-shot-examples/` contains additional few-shot examples (`plan-checker.md`, `verifier.md`) that are referenced from specific agents. These are not counted in the 49 top-level references.
|
||||
> **Subdirectory:** `get-shit-done/references/few-shot-examples/` contains additional few-shot examples (`plan-checker.md`, `verifier.md`) that are referenced from specific agents. These are not counted in the 50 top-level references.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ const VALID_CONFIG_KEYS = new Set([
|
||||
'workflow.plan_bounce',
|
||||
'workflow.plan_bounce_script',
|
||||
'workflow.plan_bounce_passes',
|
||||
'workflow.plan_chunked',
|
||||
'workflow.security_enforcement',
|
||||
'workflow.security_asvs_level',
|
||||
'workflow.security_block_on',
|
||||
|
||||
49
get-shit-done/references/planner-chunked.md
Normal file
49
get-shit-done/references/planner-chunked.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# Chunked Mode Return Formats
|
||||
|
||||
Used when `plan-phase` spawns `gsd-planner` with `CHUNKED_MODE=true` (triggered by `--chunked`
|
||||
flag or `workflow.plan_chunked: true` config). Splits the single long-lived planner Task into
|
||||
shorter-lived Tasks to bound the blast radius of Windows stdio hangs.
|
||||
|
||||
## Modes
|
||||
|
||||
### outline-only
|
||||
|
||||
Write **only** `{PHASE_DIR}/{PADDED_PHASE}-PLAN-OUTLINE.md`. Do not write any PLAN.md files.
|
||||
Return:
|
||||
|
||||
```markdown
|
||||
## OUTLINE COMPLETE
|
||||
|
||||
**Phase:** {phase-name}
|
||||
**Plans:** {N} plan(s) in {M} wave(s)
|
||||
|
||||
| Plan ID | Objective | Wave | Depends On | Requirements |
|
||||
|---------|-----------|------|-----------|-------------|
|
||||
| {padded_phase}-01 | [brief objective] | 1 | none | REQ-001, REQ-002 |
|
||||
| {padded_phase}-02 | [brief objective] | 1 | none | REQ-003 |
|
||||
```
|
||||
|
||||
The orchestrator reads this table, then spawns one single-plan Task per row.
|
||||
|
||||
### single-plan
|
||||
|
||||
Write **exactly one** `{PHASE_DIR}/{plan_id}-PLAN.md`. Do not write any other plan files.
|
||||
Return:
|
||||
|
||||
```markdown
|
||||
## PLAN COMPLETE
|
||||
|
||||
**Plan:** {plan-id}
|
||||
**Objective:** {brief}
|
||||
**File:** {PHASE_DIR}/{plan-id}-PLAN.md
|
||||
**Tasks:** {N}
|
||||
```
|
||||
|
||||
The orchestrator verifies the file exists on disk after each return, commits it, then moves
|
||||
to the next plan entry from the outline.
|
||||
|
||||
## Resume Behaviour
|
||||
|
||||
If the orchestrator detects that `PLAN-OUTLINE.md` already exists (from a prior interrupted
|
||||
run), it skips the outline-only Task and goes directly to single-plan Tasks, skipping any
|
||||
`{plan_id}-PLAN.md` files that already exist on disk.
|
||||
@@ -54,7 +54,7 @@ Parse JSON for: `researcher_model`, `planner_model`, `checker_model`, `research_
|
||||
|
||||
## 2. Parse and Normalize Arguments
|
||||
|
||||
Extract from $ARGUMENTS: phase number (integer or decimal like `2.1`), flags (`--research`, `--skip-research`, `--gaps`, `--skip-verify`, `--skip-ui`, `--prd <filepath>`, `--reviews`, `--text`, `--bounce`, `--skip-bounce`).
|
||||
Extract from $ARGUMENTS: phase number (integer or decimal like `2.1`), flags (`--research`, `--skip-research`, `--gaps`, `--skip-verify`, `--skip-ui`, `--prd <filepath>`, `--reviews`, `--text`, `--bounce`, `--skip-bounce`, `--chunked`).
|
||||
|
||||
Set `TEXT_MODE=true` if `--text` is present in $ARGUMENTS OR `text_mode` from init JSON is `true`. When `TEXT_MODE` is active, replace every `AskUserQuestion` call with a plain-text numbered list and ask the user to type their choice number. This is required for Claude Code remote sessions (`/rc` mode) where TUI menus don't work through the Claude App.
|
||||
|
||||
@@ -69,6 +69,15 @@ mkdir -p ".planning/phases/${padded_phase}-${phase_slug}"
|
||||
|
||||
**Existing artifacts from init:** `has_research`, `has_plans`, `plan_count`.
|
||||
|
||||
Set `CHUNKED_MODE` from flag or config:
|
||||
```bash
|
||||
CHUNKED_CFG=$(gsd-sdk query config-get workflow.plan_chunked 2>/dev/null || echo "false")
|
||||
CHUNKED_MODE=false
|
||||
if [[ "$ARGUMENTS" =~ --chunked ]] || [[ "$CHUNKED_CFG" == "true" ]]; then
|
||||
CHUNKED_MODE=true
|
||||
fi
|
||||
```
|
||||
|
||||
## 2.5. Validate `--reviews` Prerequisite
|
||||
|
||||
**Skip if:** No `--reviews` flag.
|
||||
@@ -795,6 +804,8 @@ Every task MUST include these fields — they are NOT optional:
|
||||
</quality_gate>
|
||||
```
|
||||
|
||||
**If `CHUNKED_MODE` is `false` (default):** Spawn the planner as a single long-lived Task:
|
||||
|
||||
```
|
||||
Task(
|
||||
prompt=filled_prompt,
|
||||
@@ -804,6 +815,112 @@ Task(
|
||||
)
|
||||
```
|
||||
|
||||
**If `CHUNKED_MODE` is `true`:** Skip the Task() call above — proceed to step 8.5 instead.
|
||||
|
||||
## 8.5. Chunked Planning Mode
|
||||
|
||||
**Skip if `CHUNKED_MODE` is `false`.**
|
||||
|
||||
Chunked mode splits the single long-lived planner Task into a short outline Task followed by
|
||||
N short per-plan Tasks. Each Task is bounded to ~3–5 min; each plan is committed individually
|
||||
for crash resilience. If any Task hangs and the terminal is force-killed, rerunning
|
||||
`/gsd-plan-phase {N} --chunked` resumes from the last successfully committed plan.
|
||||
|
||||
**Intended for new or in-progress chunked runs.** To recover plans already written by a prior
|
||||
*non-chunked* run, use step 6's "Add more plans" or proceed directly to `/gsd-execute-phase`
|
||||
— don't start a fresh chunked run over existing non-chunked plans.
|
||||
|
||||
### 8.5.1 Outline Phase (outline-only mode, ~2 min)
|
||||
|
||||
**Resume detection:** If `${PHASE_DIR}/${PADDED_PHASE}-PLAN-OUTLINE.md` already exists **and
|
||||
is valid** (contains the `## OUTLINE COMPLETE` marker), skip this sub-step — the outline
|
||||
already exists from a previous run. Proceed directly to 8.5.2.
|
||||
|
||||
```bash
|
||||
OUTLINE_FILE="${PHASE_DIR}/${PADDED_PHASE}-PLAN-OUTLINE.md"
|
||||
if [[ -f "$OUTLINE_FILE" ]] && grep -q "^## OUTLINE COMPLETE" "$OUTLINE_FILE"; then
|
||||
# reuse existing outline — skip to 8.5.2
|
||||
fi
|
||||
```
|
||||
|
||||
Display:
|
||||
```text
|
||||
◆ Chunked mode: spawning outline planner...
|
||||
```
|
||||
|
||||
Spawn the planner in **outline-only** mode — it must write only the outline manifest, not any
|
||||
PLAN.md files:
|
||||
|
||||
```javascript
|
||||
Task(
|
||||
prompt="{same planning_context as step 8, plus:}
|
||||
|
||||
**Chunked mode: outline-only.**
|
||||
Do NOT write any PLAN.md files in this Task.
|
||||
Write only: {PHASE_DIR}/{PADDED_PHASE}-PLAN-OUTLINE.md
|
||||
|
||||
The outline must be a markdown table with columns:
|
||||
Plan ID | Objective | Wave | Depends On | Requirements
|
||||
|
||||
Return: ## OUTLINE COMPLETE with plan count.",
|
||||
subagent_type="gsd-planner",
|
||||
model="{planner_model}",
|
||||
description="Outline Phase {phase} (chunked)"
|
||||
)
|
||||
```
|
||||
|
||||
Handle return:
|
||||
- **`## OUTLINE COMPLETE`:** Read `PLAN-OUTLINE.md`, extract plan list. Continue to 8.5.2.
|
||||
- **Any other return or empty:** Display error. Offer: 1) Retry outline, 2) Stop.
|
||||
|
||||
### 8.5.2 Per-Plan Tasks (single-plan mode, ~3-5 min each)
|
||||
|
||||
For each plan entry extracted from `PLAN-OUTLINE.md`:
|
||||
|
||||
1. **Resume check:** If `${PHASE_DIR}/{plan_id}-PLAN.md` already exists on disk **and has
|
||||
valid YAML frontmatter** (opening `---` delimiter present), skip this plan (do not
|
||||
overwrite completed work — resume safety).
|
||||
|
||||
```bash
|
||||
PLAN_FILE="${PHASE_DIR}/${plan_id}-PLAN.md"
|
||||
if [[ -f "$PLAN_FILE" ]] && head -1 "$PLAN_FILE" | grep -q '^---'; then
|
||||
continue # plan already written, skip
|
||||
fi
|
||||
```
|
||||
|
||||
2. Display:
|
||||
```text
|
||||
◆ Chunked mode: planning {plan_id} ({k}/{N})...
|
||||
```
|
||||
|
||||
3. Spawn the planner in **single-plan** mode — it must write exactly one PLAN.md file:
|
||||
```javascript
|
||||
Task(
|
||||
prompt="{same planning_context as step 8, plus:}
|
||||
|
||||
**Chunked mode: single-plan.**
|
||||
Write exactly ONE plan file: {PHASE_DIR}/{plan_id}-PLAN.md
|
||||
Plan to write: {plan_id} — {objective}
|
||||
Wave: {wave} | Depends on: {depends_on}
|
||||
Phase requirement IDs to cover in this plan: {plan_requirements}
|
||||
|
||||
Return: ## PLAN COMPLETE with the plan ID.",
|
||||
subagent_type="gsd-planner",
|
||||
model="{planner_model}",
|
||||
description="Plan {plan_id} (chunked {k}/{N})"
|
||||
)
|
||||
```
|
||||
|
||||
4. **Verify disk:** Check `${PHASE_DIR}/{plan_id}-PLAN.md` exists. If missing: offer 1) Retry, 2) Stop.
|
||||
|
||||
5. **Commit per-plan:**
|
||||
```bash
|
||||
gsd-sdk query commit "docs(${PADDED_PHASE}): plan ${plan_id} (chunked)" "${PHASE_DIR}/${plan_id}-PLAN.md"
|
||||
```
|
||||
|
||||
After all N plans are written and committed, treat this as `## PLANNING COMPLETE` and continue
|
||||
to step 9.
|
||||
|
||||
## 9. Handle Planner Return
|
||||
|
||||
- **`## PLANNING COMPLETE`:** Display plan count. If `--skip-verify` or `plan_checker_enabled` is false (from init): skip to step 13. Otherwise: step 10.
|
||||
@@ -811,6 +928,35 @@ Task(
|
||||
- **`## ⚠ Source Audit: Unplanned Items Found`:** The planner's multi-source coverage audit found items from REQUIREMENTS.md, RESEARCH.md, ROADMAP goal, or CONTEXT.md decisions that are not covered by any plan. Handle in step 9c.
|
||||
- **`## CHECKPOINT REACHED`:** Present to user, get response, spawn continuation (step 12)
|
||||
- **`## PLANNING INCONCLUSIVE`:** Show attempts, offer: Add context / Retry / Manual
|
||||
- **Empty / truncated / no recognized marker:** → Filesystem fallback (step 9a).
|
||||
|
||||
## 9a. Filesystem Fallback (Planner)
|
||||
|
||||
**Triggered when:** Task() returns but the return contains no recognized marker (`## PLANNING COMPLETE`, `## PHASE SPLIT RECOMMENDED`, `## ⚠ Source Audit`, `## CHECKPOINT REACHED`, `## PLANNING INCONCLUSIVE`).
|
||||
|
||||
```bash
|
||||
DISK_PLANS=$(ls "${PHASE_DIR}"/*-PLAN.md 2>/dev/null | wc -l | tr -d ' ')
|
||||
```
|
||||
|
||||
**If `DISK_PLANS` > 0:** The planner wrote plans to disk but the Task() return was empty or
|
||||
truncated (the Windows stdio hang pattern — the subagent finished but the return never
|
||||
arrived). Display:
|
||||
|
||||
```text
|
||||
◆ Planner wrote {DISK_PLANS} plan(s) to disk but did not emit a PLANNING COMPLETE marker.
|
||||
This is a known Windows stdio hang pattern — work is likely recoverable.
|
||||
|
||||
Plans found on disk:
|
||||
{ls output of *-PLAN.md}
|
||||
```
|
||||
|
||||
Offer 3 options:
|
||||
1. **Accept plans** — treat as `## PLANNING COMPLETE` and continue through step 9 `## PLANNING COMPLETE` handling (so `--skip-verify` / `plan_checker_enabled=false` are honored — may skip to step 13 rather than step 10)
|
||||
2. **Retry planner** — re-spawn the planner with the same prompt (return to step 8)
|
||||
3. **Stop** — exit; user can re-run `/gsd-plan-phase {N}` to resume
|
||||
|
||||
**If `DISK_PLANS` is 0 and no marker:** The planner produced no output. Treat as
|
||||
`## PLANNING INCONCLUSIVE` and handle accordingly.
|
||||
|
||||
## 9b. Handle Phase Split Recommendation
|
||||
|
||||
@@ -925,6 +1071,7 @@ Task(
|
||||
|
||||
- **`## VERIFICATION PASSED`:** Display confirmation, proceed to step 13.
|
||||
- **`## ISSUES FOUND`:** Display issues, check iteration count, proceed to step 12.
|
||||
- **Empty / truncated / no recognized marker:** → Filesystem fallback (step 11a).
|
||||
|
||||
**Thinking partner for architectural tradeoffs (conditional):**
|
||||
If `features.thinking_partner` is enabled, scan the checker's issues for architectural tradeoff keywords
|
||||
@@ -945,6 +1092,29 @@ Apply this to the revision? [Yes] / [No, I'll decide]
|
||||
If yes: include the recommendation in the revision prompt. If no: proceed to revision loop as normal.
|
||||
If thinking_partner disabled: skip this block entirely.
|
||||
|
||||
## 11a. Filesystem Fallback (Checker)
|
||||
|
||||
**Triggered when:** Checker Task() returns but the return contains neither `## VERIFICATION PASSED` nor `## ISSUES FOUND`.
|
||||
|
||||
```bash
|
||||
DISK_PLANS=$(ls "${PHASE_DIR}"/*-PLAN.md 2>/dev/null | wc -l | tr -d ' ')
|
||||
```
|
||||
|
||||
**If `DISK_PLANS` > 0:** Plans exist on disk; the checker return was empty or truncated (the
|
||||
Windows stdio hang pattern — the subagent finished but the return never arrived). Display:
|
||||
|
||||
```text
|
||||
◆ Checker return was empty or truncated. {DISK_PLANS} plan(s) exist on disk.
|
||||
This is a known Windows stdio hang pattern — checker may have completed without returning.
|
||||
```
|
||||
|
||||
Offer 3 options:
|
||||
1. **Accept verification** — treat as `## VERIFICATION PASSED` and continue to step 13
|
||||
2. **Retry checker** — re-spawn the checker with the same prompt (return to step 10)
|
||||
3. **Stop** — exit; user can re-run `/gsd-plan-phase {N}` to resume
|
||||
|
||||
**If `DISK_PLANS` is 0:** No plans on disk — something is seriously wrong. Display error and stop.
|
||||
|
||||
## 12. Revision Loop (Max 3 Iterations)
|
||||
|
||||
Track `iteration_count` (starts at 1 after initial plan + check).
|
||||
|
||||
236
tests/enh-2310-chunked-plan-phase.test.cjs
Normal file
236
tests/enh-2310-chunked-plan-phase.test.cjs
Normal file
@@ -0,0 +1,236 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Tests for #2310: plan-phase chunked mode + filesystem fallback.
|
||||
*
|
||||
* Context: on Windows (and occasionally other platforms), gsd-planner's
|
||||
* Task() call may never return even though the subagent finished writing all
|
||||
* PLAN.md files to disk. The orchestrator hangs indefinitely. Two mitigations:
|
||||
*
|
||||
* 1. Filesystem fallback (steps 9a, 11a): if the Task() return lacks the
|
||||
* expected marker but PLAN.md files exist on disk, surface a recoverable
|
||||
* prompt instead of hanging/failing silently.
|
||||
*
|
||||
* 2. Chunked mode (step 8.5): --chunked flag / workflow.plan_chunked config
|
||||
* splits the single long planner Task into (a) a short outline Task and
|
||||
* (b) N short single-plan Tasks. Each Task is shorter-lived, the
|
||||
* orchestrator can commit work incrementally, and a hang loses only one
|
||||
* plan instead of the entire phase.
|
||||
*/
|
||||
|
||||
const { test, describe } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
const PLAN_PHASE = path.join(
|
||||
__dirname, '..', 'get-shit-done', 'workflows', 'plan-phase.md'
|
||||
);
|
||||
|
||||
const PLANNER_AGENT = path.join(__dirname, '..', 'agents', 'gsd-planner.md');
|
||||
const PLANNER_CHUNKED_REF = path.join(__dirname, '..', 'get-shit-done', 'references', 'planner-chunked.md');
|
||||
const CONFIG_SCHEMA = path.join(__dirname, '..', 'get-shit-done', 'bin', 'lib', 'config-schema.cjs');
|
||||
const CONFIGURATION_MD = path.join(__dirname, '..', 'docs', 'CONFIGURATION.md');
|
||||
|
||||
describe('plan-phase.md — filesystem fallback (#2310)', () => {
|
||||
const content = fs.readFileSync(PLAN_PHASE, 'utf-8');
|
||||
|
||||
test('step 9 checks PLAN.md count on disk when planner return lacks completion marker', () => {
|
||||
assert.ok(
|
||||
content.includes('DISK_PLANS=$(ls "${PHASE_DIR}"/*-PLAN.md'),
|
||||
'step 9a must check disk for PLAN.md files via DISK_PLANS variable'
|
||||
);
|
||||
});
|
||||
|
||||
test('step 9a fallback section exists', () => {
|
||||
assert.ok(
|
||||
content.includes('## 9a. Filesystem Fallback'),
|
||||
'plan-phase.md must have a ## 9a. Filesystem Fallback section for planner hang recovery'
|
||||
);
|
||||
});
|
||||
|
||||
test('step 9a fallback offers Accept plans option', () => {
|
||||
assert.ok(
|
||||
content.includes('Accept plans'),
|
||||
'step 9a must offer "Accept plans" as a recovery option'
|
||||
);
|
||||
});
|
||||
|
||||
test('step 9a fallback offers Retry planner option', () => {
|
||||
assert.ok(
|
||||
content.includes('Retry planner'),
|
||||
'step 9a must offer "Retry planner" as a recovery option'
|
||||
);
|
||||
});
|
||||
|
||||
test('step 11 has filesystem fallback section', () => {
|
||||
assert.ok(
|
||||
content.includes('## 11a. Filesystem Fallback'),
|
||||
'plan-phase.md must have a ## 11a. Filesystem Fallback section for checker hang recovery'
|
||||
);
|
||||
});
|
||||
|
||||
test('step 11a fallback offers Accept verification option', () => {
|
||||
assert.ok(
|
||||
content.includes('Accept verification'),
|
||||
'step 11a must offer "Accept verification" as a recovery option'
|
||||
);
|
||||
});
|
||||
|
||||
test('step 11a fallback offers Retry checker option', () => {
|
||||
assert.ok(
|
||||
content.includes('Retry checker'),
|
||||
'step 11a must offer "Retry checker" as a recovery option'
|
||||
);
|
||||
});
|
||||
|
||||
test('step 9 routes to step 9a when no recognized marker found', () => {
|
||||
assert.ok(
|
||||
content.includes('step 9a') || content.includes('9a.'),
|
||||
'step 9 handle-return must reference the filesystem fallback path (step 9a)'
|
||||
);
|
||||
});
|
||||
|
||||
test('step 11 routes to step 11a when no recognized marker found', () => {
|
||||
assert.ok(
|
||||
content.includes('step 11a') || content.includes('11a.'),
|
||||
'step 11 handle-return must reference the filesystem fallback path (step 11a)'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('plan-phase.md — chunked mode flag and config (#2310)', () => {
|
||||
const content = fs.readFileSync(PLAN_PHASE, 'utf-8');
|
||||
|
||||
test('step 2 parses --chunked flag', () => {
|
||||
assert.ok(
|
||||
content.includes('--chunked'),
|
||||
'step 2 must parse --chunked flag from $ARGUMENTS'
|
||||
);
|
||||
});
|
||||
|
||||
test('step 2 reads workflow.plan_chunked config', () => {
|
||||
assert.ok(
|
||||
content.includes('workflow.plan_chunked'),
|
||||
'step 2 must read workflow.plan_chunked config key'
|
||||
);
|
||||
});
|
||||
|
||||
test('step 2 sets CHUNKED_MODE variable', () => {
|
||||
assert.ok(
|
||||
content.includes('CHUNKED_MODE'),
|
||||
'step 2 must set CHUNKED_MODE from flag or config'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('plan-phase.md — chunked mode implementation (#2310)', () => {
|
||||
const content = fs.readFileSync(PLAN_PHASE, 'utf-8');
|
||||
|
||||
test('step 8.5 chunked planning section exists', () => {
|
||||
assert.ok(
|
||||
content.includes('## 8.5.'),
|
||||
'plan-phase.md must have a step 8.5 section for chunked planning mode'
|
||||
);
|
||||
});
|
||||
|
||||
test('chunked mode produces PLAN-OUTLINE.md', () => {
|
||||
assert.ok(
|
||||
content.includes('PLAN-OUTLINE.md'),
|
||||
'chunked mode outline step must produce a *-PLAN-OUTLINE.md file'
|
||||
);
|
||||
});
|
||||
|
||||
test('chunked outline step uses outline-only mode', () => {
|
||||
assert.ok(
|
||||
content.includes('outline-only'),
|
||||
'chunked step 8.5.1 must spawn the planner in outline-only mode'
|
||||
);
|
||||
});
|
||||
|
||||
test('chunked per-plan step uses single-plan mode', () => {
|
||||
assert.ok(
|
||||
content.includes('single-plan'),
|
||||
'chunked step 8.5.2 must spawn the planner in single-plan mode for each plan'
|
||||
);
|
||||
});
|
||||
|
||||
test('chunked mode checks for existing outline to enable resume', () => {
|
||||
// The resume check skips the outline Task if PLAN-OUTLINE.md already exists
|
||||
assert.ok(
|
||||
content.includes('PLAN-OUTLINE.md') && content.includes('already exists'),
|
||||
'chunked mode must detect existing PLAN-OUTLINE.md and skip outline generation (resume safety)'
|
||||
);
|
||||
});
|
||||
|
||||
test('chunked mode commits each plan individually', () => {
|
||||
assert.ok(
|
||||
content.includes('chunked'),
|
||||
'chunked mode must commit each individual plan for crash resilience'
|
||||
);
|
||||
});
|
||||
|
||||
test('step 8 routes to chunked path when CHUNKED_MODE is true', () => {
|
||||
assert.ok(
|
||||
content.includes('CHUNKED_MODE') && content.includes('8.5'),
|
||||
'step 8 must route to step 8.5 when CHUNKED_MODE is true'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('gsd-planner.md — references planner-chunked.md (#2310)', () => {
|
||||
const plannerContent = fs.readFileSync(PLANNER_AGENT, 'utf-8');
|
||||
|
||||
test('gsd-planner.md references planner-chunked.md for chunked return formats', () => {
|
||||
assert.ok(
|
||||
plannerContent.includes('planner-chunked.md'),
|
||||
'gsd-planner.md must reference planner-chunked.md for ## OUTLINE COMPLETE / ## PLAN COMPLETE formats'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('planner-chunked.md — chunked return formats (#2310)', () => {
|
||||
const content = fs.readFileSync(PLANNER_CHUNKED_REF, 'utf-8');
|
||||
|
||||
test('planner-chunked.md defines OUTLINE COMPLETE structured return', () => {
|
||||
assert.ok(
|
||||
content.includes('## OUTLINE COMPLETE'),
|
||||
'planner-chunked.md must define ## OUTLINE COMPLETE return format for outline-only mode'
|
||||
);
|
||||
});
|
||||
|
||||
test('planner-chunked.md defines PLAN COMPLETE structured return for single-plan mode', () => {
|
||||
assert.ok(
|
||||
content.includes('## PLAN COMPLETE'),
|
||||
'planner-chunked.md must define ## PLAN COMPLETE return format for single-plan mode'
|
||||
);
|
||||
});
|
||||
|
||||
test('planner-chunked.md describes resume behaviour', () => {
|
||||
assert.ok(
|
||||
content.includes('Resume') || content.includes('resume'),
|
||||
'planner-chunked.md must describe resume behaviour for interrupted chunked runs'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('config-schema.cjs — workflow.plan_chunked key (#2310)', () => {
|
||||
test('VALID_CONFIG_KEYS includes workflow.plan_chunked', () => {
|
||||
const { VALID_CONFIG_KEYS } = require(CONFIG_SCHEMA);
|
||||
assert.ok(
|
||||
VALID_CONFIG_KEYS.has('workflow.plan_chunked'),
|
||||
'config-schema.cjs VALID_CONFIG_KEYS must include workflow.plan_chunked'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('docs/CONFIGURATION.md — workflow.plan_chunked documented (#2310)', () => {
|
||||
const content = fs.readFileSync(CONFIGURATION_MD, 'utf-8');
|
||||
|
||||
test('CONFIGURATION.md documents workflow.plan_chunked', () => {
|
||||
assert.ok(
|
||||
content.includes('`workflow.plan_chunked`'),
|
||||
'docs/CONFIGURATION.md must document workflow.plan_chunked'
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user