Merge pull request #1152 from gsd-build/Solvely/execute-phase-active-flags

This commit is contained in:
Tom Boucher
2026-03-20 13:56:33 -04:00
committed by GitHub
6 changed files with 246 additions and 8 deletions

View File

@@ -1,7 +1,7 @@
---
name: gsd:execute-phase
description: Execute all plans in a phase with wave-based parallelization
argument-hint: "<phase-number> [--gaps-only] [--interactive]"
argument-hint: "<phase-number> [--wave N] [--gaps-only] [--interactive]"
allowed-tools:
- Read
- Write
@@ -18,6 +18,15 @@ Execute all plans in a phase using wave-based parallel execution.
Orchestrator stays lean: discover plans, analyze dependencies, group into waves, spawn subagents, collect results. Each subagent loads the full execute-plan context and handles its own plan.
Optional wave filter:
- `--wave N` executes only Wave `N` for pacing, quota management, or staged rollout
- phase verification/completion still only happens when no incomplete plans remain after the selected wave finishes
Flag handling rule:
- The optional flags documented below are available behaviors, not implied active behaviors
- A flag is active only when its literal token appears in `$ARGUMENTS`
- If a documented flag is absent from `$ARGUMENTS`, treat it as inactive
Context budget: ~15% orchestrator, 100% fresh per subagent.
</objective>
@@ -29,10 +38,18 @@ Context budget: ~15% orchestrator, 100% fresh per subagent.
<context>
Phase: $ARGUMENTS
**Flags:**
**Available optional flags (documentation only — not automatically active):**
- `--wave N` — Execute only Wave `N` in the phase. Use when you want to pace execution or stay inside usage limits.
- `--gaps-only` — Execute only gap closure plans (plans with `gap_closure: true` in frontmatter). Use after verify-work creates fix plans.
- `--interactive` — Execute plans sequentially inline (no subagents) with user checkpoints between tasks. Lower token usage, pair-programming style. Best for small phases, bug fixes, and verification gaps.
**Active flags must be derived from `$ARGUMENTS`:**
- `--wave N` is active only if the literal `--wave` token is present in `$ARGUMENTS`
- `--gaps-only` is active only if the literal `--gaps-only` token is present in `$ARGUMENTS`
- `--interactive` is active only if the literal `--interactive` token is present in `$ARGUMENTS`
- If none of these tokens appear, run the standard full-phase execution flow with no flag-specific filtering
- Do not infer that a flag is active just because it is documented in this prompt
Context files are resolved inside the workflow via `gsd-tools init execute-phase` and per-subagent `<files_to_read>` blocks.
</context>

View File

@@ -102,17 +102,19 @@ Research, plan, and verify a phase.
### `/gsd:execute-phase`
Execute all plans in a phase with wave-based parallelization.
Execute all plans in a phase with wave-based parallelization, or run a specific wave.
| Argument | Required | Description |
|----------|----------|-------------|
| `N` | **Yes** | Phase number to execute |
| `--wave N` | No | Execute only Wave `N` in the phase |
**Prerequisites:** Phase has PLAN.md files
**Produces:** `{phase}-{N}-SUMMARY.md`, `{phase}-VERIFICATION.md`, git commits
**Produces:** per-plan `{phase}-{N}-SUMMARY.md`, git commits, and `{phase}-VERIFICATION.md` when the phase is fully complete
```bash
/gsd:execute-phase 1 # Execute phase 1
/gsd:execute-phase 1 --wave 2 # Execute only Wave 2
```
---

View File

@@ -47,6 +47,16 @@ Always use the exact name from this list — do not fall back to 'general-purpos
<process>
<step name="parse_args" priority="first">
Parse `$ARGUMENTS` before loading any context:
- First positional token → `PHASE_ARG`
- Optional `--wave N` → `WAVE_FILTER`
- Optional `--gaps-only` keeps its current meaning
If `--wave` is absent, preserve the current behavior of executing all incomplete waves in the phase.
</step>
<step name="initialize" priority="first">
Load all context in one call:
@@ -162,13 +172,19 @@ PLAN_INDEX=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" phase-plan-ind
Parse JSON for: `phase`, `plans[]` (each with `id`, `wave`, `autonomous`, `objective`, `files_modified`, `task_count`, `has_summary`), `waves` (map of wave number → plan IDs), `incomplete`, `has_checkpoints`.
**Filtering:** Skip plans where `has_summary: true`. If `--gaps-only`: also skip non-gap_closure plans. If all filtered: "No matching incomplete plans" → exit.
**Filtering:** Skip plans where `has_summary: true`. If `--gaps-only`: also skip non-gap_closure plans. If `WAVE_FILTER` is set: also skip plans whose `wave` does not equal `WAVE_FILTER`.
**Wave safety check:** If `WAVE_FILTER` is set and there are still incomplete plans in any lower wave that match the current execution mode, STOP and tell the user to finish earlier waves first. Do not let Wave 2+ execute while prerequisite earlier-wave plans remain incomplete.
If all filtered: "No matching incomplete plans" → exit.
Report:
```
## Execution Plan
**Phase {X}: {Name}** — {total_plans} plans across {wave_count} waves
**Phase {X}: {Name}** — {total_plans} matching plans across {wave_count} wave(s)
{If WAVE_FILTER is set: `Wave filter active: executing only Wave {WAVE_FILTER}`.}
| Wave | Plans | What it builds |
|------|-------|----------------|
@@ -178,7 +194,7 @@ Report:
</step>
<step name="execute_waves">
Execute each wave in sequence. Within a wave: parallel if `PARALLELIZATION=true`, sequential if `false`.
Execute each selected wave in sequence. Within a wave: parallel if `PARALLELIZATION=true`, sequential if `false`.
**For each wave:**
@@ -418,6 +434,37 @@ After all waves:
```
</step>
<step name="handle_partial_wave_execution">
If `WAVE_FILTER` was used, re-run plan discovery after execution:
```bash
POST_PLAN_INDEX=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" phase-plan-index "${PHASE_NUMBER}")
```
Apply the same "incomplete" filtering rules as earlier:
- ignore plans with `has_summary: true`
- if `--gaps-only`, only consider `gap_closure: true` plans
**If incomplete plans still remain anywhere in the phase:**
- STOP here
- Do NOT run phase verification
- Do NOT mark the phase complete in ROADMAP/STATE
- Present:
```markdown
## Wave {WAVE_FILTER} Complete
Selected wave finished successfully. This phase still has incomplete plans, so phase-level verification and completion were intentionally skipped.
/gsd:execute-phase {phase} # Continue remaining waves
/gsd:execute-phase {phase} --wave {next} # Run the next wave explicitly
```
**If no incomplete plans remain after the selected wave finishes:**
- continue with the normal phase-level verification and completion flow below
- this means the selected wave happened to be the last remaining work in the phase
</step>
<step name="close_parent_artifacts">
**For decimal/polish phases only (X.Y pattern):** Close the feedback loop by resolving parent UAT and debug artifacts.

View File

@@ -107,14 +107,16 @@ Result: Creates `.planning/phases/01-foundation/01-01-PLAN.md`
### Execution
**`/gsd:execute-phase <phase-number>`**
Execute all plans in a phase.
Execute all plans in a phase, or run a specific wave.
- Groups plans by wave (from frontmatter), executes waves sequentially
- Plans within each wave run in parallel via Task tool
- Optional `--wave N` flag executes only Wave `N` and stops unless the phase is now fully complete
- Verifies phase goal after all plans complete
- Updates REQUIREMENTS.md, ROADMAP.md, STATE.md
Usage: `/gsd:execute-phase 5`
Usage: `/gsd:execute-phase 5 --wave 2`
### Smart Router

View File

@@ -0,0 +1,62 @@
/**
* Execute-phase active flag prompt tests
*
* Guards against prompt wording that makes optional flags look active by default.
* This is especially important for weaker runtimes that may infer `--gaps-only`
* from the command docs instead of the literal user arguments.
*/
const { test, describe } = require('node:test');
const assert = require('node:assert');
const fs = require('fs');
const path = require('path');
const COMMAND_PATH = path.join(__dirname, '..', 'commands', 'gsd', 'execute-phase.md');
describe('execute-phase command: active flags are explicit', () => {
test('command file exists', () => {
assert.ok(fs.existsSync(COMMAND_PATH), 'commands/gsd/execute-phase.md should exist');
});
test('objective says documented flags are not implied active', () => {
const content = fs.readFileSync(COMMAND_PATH, 'utf-8');
const objectiveMatch = content.match(/<objective>([\s\S]*?)<\/objective>/);
assert.ok(objectiveMatch, 'should have <objective> section');
assert.ok(
objectiveMatch[1].includes('available behaviors, not implied active behaviors'),
'objective should state that documented flags are not automatically active'
);
assert.ok(
objectiveMatch[1].includes('appears in `$ARGUMENTS`'),
'objective should tie flag activation to literal $ARGUMENTS presence'
);
});
test('context separates available flags from active flags', () => {
const content = fs.readFileSync(COMMAND_PATH, 'utf-8');
assert.ok(
content.includes('Available optional flags (documentation only'),
'context should clearly label flags as documentation only'
);
assert.ok(
content.includes('Active flags must be derived from `$ARGUMENTS`'),
'context should have a separate active-flags section'
);
});
test('context explicitly warns against inferring inactive flags', () => {
const content = fs.readFileSync(COMMAND_PATH, 'utf-8');
assert.ok(
content.includes('Do not infer that a flag is active just because it is documented in this prompt'),
'context should forbid inferring flags from documentation alone'
);
assert.ok(
content.includes('`--interactive` is active only if the literal `--interactive` token is present in `$ARGUMENTS`'),
'context should apply the same active-flag rule to --interactive'
);
assert.ok(
content.includes('If none of these tokens appear, run the standard full-phase execution flow'),
'context should define the no-flags fallback behavior'
);
});
});

View File

@@ -0,0 +1,108 @@
/**
* Execute-phase wave filter tests
*
* Validates the /gsd:execute-phase --wave feature contract:
* - Command frontmatter advertises --wave
* - Workflow parses WAVE_FILTER
* - Workflow enforces lower-wave safety
* - Partial wave runs do not mark the phase complete
*/
const { test, describe } = require('node:test');
const assert = require('node:assert');
const fs = require('fs');
const path = require('path');
const COMMAND_PATH = path.join(__dirname, '..', 'commands', 'gsd', 'execute-phase.md');
const WORKFLOW_PATH = path.join(__dirname, '..', 'get-shit-done', 'workflows', 'execute-phase.md');
const COMMANDS_DOC_PATH = path.join(__dirname, '..', 'docs', 'COMMANDS.md');
const HELP_PATH = path.join(__dirname, '..', 'get-shit-done', 'workflows', 'help.md');
describe('execute-phase command: --wave flag', () => {
test('command file exists', () => {
assert.ok(fs.existsSync(COMMAND_PATH), 'commands/gsd/execute-phase.md should exist');
});
test('argument-hint includes --wave, --gaps-only, and --interactive', () => {
const content = fs.readFileSync(COMMAND_PATH, 'utf-8');
const hintLine = content.split('\n').find(l => l.includes('argument-hint'));
assert.ok(hintLine, 'should have argument-hint line');
assert.ok(hintLine.includes('--wave N'), 'argument-hint should include --wave N');
assert.ok(hintLine.includes('--gaps-only'), 'argument-hint should keep --gaps-only');
assert.ok(hintLine.includes('--interactive'), 'argument-hint should preserve --interactive');
});
test('objective describes wave-filter execution', () => {
const content = fs.readFileSync(COMMAND_PATH, 'utf-8');
const objectiveMatch = content.match(/<objective>([\s\S]*?)<\/objective>/);
assert.ok(objectiveMatch, 'should have <objective> section');
assert.ok(objectiveMatch[1].includes('--wave N'), 'objective should mention --wave N');
assert.ok(
objectiveMatch[1].includes('no incomplete plans remain'),
'objective should mention phase completion guardrail'
);
});
});
describe('execute-phase workflow: wave filtering', () => {
test('workflow file exists', () => {
assert.ok(fs.existsSync(WORKFLOW_PATH), 'workflows/execute-phase.md should exist');
});
test('workflow parses WAVE_FILTER from arguments', () => {
const content = fs.readFileSync(WORKFLOW_PATH, 'utf-8');
assert.ok(content.includes('WAVE_FILTER'), 'workflow should reference WAVE_FILTER');
assert.ok(content.includes('Optional `--wave N`'), 'workflow should parse --wave N');
});
test('workflow enforces lower-wave safety', () => {
const content = fs.readFileSync(WORKFLOW_PATH, 'utf-8');
assert.ok(
content.includes('Wave safety check'),
'workflow should contain a wave safety check section'
);
assert.ok(
content.includes('finish earlier waves first'),
'workflow should block later-wave execution when lower waves are incomplete'
);
});
test('workflow has partial-wave completion guardrail', () => {
const content = fs.readFileSync(WORKFLOW_PATH, 'utf-8');
assert.ok(
content.includes('<step name="handle_partial_wave_execution">'),
'workflow should have a partial wave handling step'
);
assert.ok(
content.includes('Do NOT run phase verification'),
'partial wave step should skip phase verification'
);
assert.ok(
content.includes('Do NOT mark the phase complete'),
'partial wave step should skip phase completion'
);
});
});
describe('execute-phase docs: user-facing wave flag', () => {
test('COMMANDS.md documents --wave usage', () => {
const content = fs.readFileSync(COMMANDS_DOC_PATH, 'utf-8');
assert.ok(content.includes('`--wave N`'), 'COMMANDS.md should mention --wave N');
assert.ok(
content.includes('/gsd:execute-phase 1 --wave 2'),
'COMMANDS.md should include a wave-filter example'
);
});
test('help workflow documents --wave behavior', () => {
const content = fs.readFileSync(HELP_PATH, 'utf-8');
assert.ok(
content.includes('Optional `--wave N` flag executes only Wave `N`'),
'help.md should describe wave-specific execution'
);
assert.ok(
content.includes('Usage: `/gsd:execute-phase 5 --wave 2`'),
'help.md should include wave-filter usage'
);
});
});