You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
GSD ships two entry points for "what should I run next": /gsd-progress --next, a global smart router that reads project state and dispatches to the appropriate next command, and /gsd-autonomous, which runs all remaining phases end to end. Both are useful. Neither is per-phase gate routing.
The consequence is that every phase pays the same pipeline weight regardless of which domains it actually touches. A copy-tweak phase in the UI layer and a destructive-migration phase run the same gate set: the same research depth, the same review-convergence cycles, the same threat-model and validation passes. In a long-lived project that ratio is badly wrong in both directions at once:
Small phases are over-gated. A one-line change in a UI component pays for security review, schema preflight, and multi-cycle plan convergence that have nothing to touch. The predictable human response is to skip steps by hand.
Small phases in critical domains are under-gated by exactly that habit. A one-line edit to a money-path helper is tiny — so it looks skippable by the same reflex — but it is precisely where the safety gates earn their cost. Manual skipping is error-prone in the unsafe direction: the phases people skip gates on and the phases where skipping is catastrophic overlap heavily.
The missing concept is that phase size and phase domain are independent variables. A large UI phase touching every screen still needs no money/schema/security gates, but it is not "light". A one-line change to a payment-splitting helper is minimal in size but must pass every money-path safety gate. Any single linear tier — "light / standard / heavy" — collapses two axes into one and is therefore wrong in one direction or the other for most real phases.
(b) Design sketch — two orthogonal axes: surfaces × depth
Split the single tier into two independent axes:
Axis 1 — surfaces:which gate clusters run, selected à la carte from the paths the phase touches.
Axis 2 — depth:how many cycles the cycle-heavy gates run for.
Surfaces as configuration, not as hardcode
Surfaces should be a configurable map in config.json, not a fixed vocabulary baked into the workflow. A project declares its own domains, the path globs that activate them, the gate steps each pulls in, and whether the surface is safety-pinned:
Generic starter names — payments / schema / security / ui / ai / data — are a sensible default set, but the point of the design is that the map is the project's, not the framework's. A data-pipeline project and a mobile app want different domains.
Cross-cutting gates always run
A fixed set stays unconditional at any surface/depth combination: research, SPEC, planning, plan check, execute, code review, learnings extraction, ship. Surfaces only route the optional clusters.
Pinned safety gates — the load-bearing rule
The key rule that makes this safe: for surfaces marked pinned: true, the safety gates (threat model, validation) run regardless of diff size. Only the cycle gates (research depth, review-convergence cycles, wave count) scale down with size.
This is what prevents the optimization from eating its own safety margin. "Small but catastrophic" is a real and common phase shape; the whole design fails if size alone can turn off a money-path or migration gate.
Raise-only and the ambiguity tiebreaker
Two constraints keep the classification honest:
Raise-only: a surface pin may lift the depth level, never lower it. Depth is a max over derived-depth and every active pin's floor.
Ambiguity tiebreaker: when it is unclear whether a surface applies, include the surface and raise the depth. Silent downgrade is forbidden, and downgrade after the phase has started is forbidden outright. Every inactive surface is recorded as an explicit skip decision with a reason, so a skipped gate is auditable — silent omission is a bug, a documented skip is not.
The classification runs twice: a rough pre-detect from the roadmap entry (enough to pick a branch), then a confirming lock immediately after SPEC, when the touched paths are exact. The locked result is written into the phase's context artifact as machine-readable lines — active surfaces, derived depth, one skip decision per inactive surface, plus the rationale.
(c) Auto-derived depth
Depth should not be a manual dial. Every signal it needs is already produced by the pipeline itself, so it can be computed:
inputs:
waves = estimated wave count from plan-phase
ambiguity = SPEC ambiguity score (a gate that already exists)
diff = git diff --stat (files + lines)
pins = any pinned surface active? any destructive operation in scope?
derive:
if destructive_op or waves >= many or ambiguity_high: depth = deep
elif waves > 1 or any_pin_active: depth = standard
else: depth = shallow
# raise-only: a pin can lift the level, never lower it
depth = max(depth, floor_of_each_active_pin)
The cycle gates then read the derived level:
Cycle gate
shallow
standard
deep
Research
inline bullets
light pass
full research artifact
Discussion round
skip
if ≥3 gray areas
full
Discussion --analyze
skip
optional
mandatory
Plan review convergence
single pass, 1 reviewer, --max-cycles 1
--max-cycles 2
--max-cycles 3, multiple reviewers
Code review
single pass, critical+high only
--fix --auto
--fix --all --auto
Plan waves
1
multi-wave
multi-wave
"Deep" is deliberately identical to today's uniform behaviour. That matters for adoption: the proposal never makes the current worst case worse, it only lets phases that provably do not need that weight opt down — and only along the cycle axis, never along the safety axis.
(d) Artifact-based resume auto-detect
A related problem the same model solves: resuming a phase that was interrupted, without asking the human "where were we?".
Because each pipeline step produces a named artifact, the observed artifact state on disk determines the resume step deterministically. Walk a table top-down, first match wins:
Observed state
Resume at
Phase summary exists AND project state marks the phase complete
Nothing — refuse to resume, report and exit
Learnings artifact exists, no summary
Ship step
Verification artifact exists and passed, no learnings
PLAN files exist and reviews addressed, no execute commits
Pre-execute checkpoint
PLAN files exist, no reviews artifact
Plan review
SPEC exists, no context artifact
Discussion round
Nothing exists for this phase
Research (step 1)
Two refinements make it work in practice:
The resume table must be surface/depth-aware. Read the locked surfaces and depth from the phase context artifact before walking the table, and skip rows for gates this phase intentionally omits. Otherwise the deliberate absence of a skipped artifact misfires as "unfinished step" — a shallow phase with no UI surface would be endlessly routed back to a UI review it correctly never ran. This is the single most important interaction between (b) and (d).
Announce the decision in one line — resumed step, step name, and what triggered the match — then execute only that step. Never silently re-run earlier steps.
An explicitly supplied step number always takes precedence over auto-detect; the global smart router is a good fallback when the table matches ambiguously (mixed-state artifacts), but its answer should be treated as an advisory hint that gets mapped back to an explicit step number, not chained directly — chaining it bypasses the checkpoint gates in (f).
(e) Git-truth interruption detection and a recovery matrix
The resume table above has a sharp failure mode, and it is worth designing against explicitly:
A file on disk does not prove the step completed. The truth is in git.
A half-written artifact killed by a network drop, a token limit, or a crashed process looks identical to a finished one from the filesystem's point of view. So before any routing decision — auto-detected or explicit — run an interruption diagnostic:
git status --porcelain over the planning artifact directory — untracked or modified files for the current phase mean a step started writing and never committed.
git status --porcelain over the source paths — uncommitted source changes mean execution was interrupted mid-task.
git log --oneline <base>..HEAD -- <phase artifact dir> — the committed artifact trail. Whatever is not there is not really done.
Artifact step crashed before its own commit; the file may be partial
Read it first. If structurally complete, commit it and continue. If truncated, delete it and re-run the originating step
Modified project-state or roadmap file alone
Ship step was writing finalization
Run the state-rebuild check in dry-run mode first. No drift → commit as is. Drift → repair or discard and re-run the ship step
Source changes without a matching execute commit
Execute interrupted mid-task
git diff --stat for scope. Matches one plan task cleanly → finish and commit it. Spans several tasks or looks inconsistent → stash, resume execute from the last completed task, review the stash after
Interrupted by a provider quota/rate limit
Execute was killed mid-wave; plan summary missing, wave commits partial
Do not re-dispatch immediately — the retry fails the same way. Spot-check for commits without a summary, take the safe-resume path, and resume only after the quota resets or the model changes
Both artifact WIP and source WIP
Execute crashed mid-task and an artifact was left unwritten
Resolve the source case first, then the artifact case. Do not commit source and planning artifacts together — the pipeline wants atomic per-artifact commits
No WIP, last commit is an execute task commit, plan has remaining tasks
Clean end between waves
Auto-detect routes correctly; no recovery needed
No WIP, session ended at a checkpoint
Clean stop
Proceed with normal routing
Two operating rules around the matrix:
When WIP is detected, stop auto-advancing. Print the diagnostic, name the matching row, announce the intended recovery, and ask for confirmation before mutating anything (commit, delete, stash). Automatic recovery of an ambiguous WIP state is how work gets destroyed.
Rows in the resume table should mean "exists AND committed", not "exists on disk". A WIP file must never satisfy an "exists" row — it must route to the interruption path instead. That one-line reinterpretation is what makes (d) and (e) compose.
Full-phase rollback is a different operation and should stay a different command: the recovery matrix repairs uncommitted working-tree state, while unwinding a phase whose approach turned out to be wrong reverts committed work with dependency checks. Mixing the two is a foot-gun.
(f) Context-reset checkpoints
The last piece is mechanical rather than semantic. Long phases overflow the context window mid-plan or mid-execute, and an overflow crash costs the whole step.
Insert checkpoints keyed to context utilization percentage at the natural seams — before planning, before execute, after execute. Each checkpoint measures current utilization and compares it against a threshold: above it, the checkpoint is a non-negotiable hard stop; below it, it may be skipped with an inline note. This is independent of both axes — overflow is a mechanical property, not a domain property, so deep multi-wave phases trip it nearly always and shallow phases usually never.
The detail that makes checkpoints actually work is the banner. When the pipeline stops, it prints a ready-to-paste resume command with the step number already filled in:
Resume then depends on nothing the agent remembers — the number is on screen, and the fresh session reloads the artifacts from disk. Note the interaction with (d): the artifact auto-detect is the second safety net for the same mechanism. If the human forgets the step number and sends the bare command, auto-detect lands on the same checkpoint rather than somewhere arbitrary. Belt and suspenders for the single most common way a long pipeline loses work.
(g) Closing
This model has been battle-tested across 44 phases of a production solo-dev project, where it replaced a uniform pipeline that ran every gate on every phase. The observations that generalize:
Savings land exactly where predicted — UI and AI phases stop paying for money/schema/security gates, and small phases on any surface stop paying for convergence cycles.
Critical phases lose nothing. Money, schema, and destructive phases classify to deep with every pin active, which is byte-for-byte the previous behaviour.
The classification artifact turned out to be as valuable as the routing. Having "which surfaces, what depth, why, and which gates were skipped for what reason" written down per phase makes skipped gates auditable after the fact, which is precisely what ad-hoc manual skipping never gave.
Two adjacent open threads suggest there is demand for finer-grained, per-phase workflow management generally — #2542 (concurrency model for the planning store) and #276 (shared plans versus isolated workspaces). This proposal is orthogonal to both, but it lives in the same problem space: the pipeline currently has one global shape, and real projects want it shaped per phase.
Happy to write this up as a proper RFC or as a PR against the core workflows if maintainers are interested — including the config schema for the surface map, the depth-derivation function, and the resume/recovery tables as reusable reference documents. Feedback on the shape of the config, and on whether the surface vocabulary should ship with defaults or stay entirely project-defined, would be the most useful thing to hear first.
🤖 Drafted with Claude Code on the author's behalf, distilled from a production pipeline in daily use.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
(a) Problem statement
GSD ships two entry points for "what should I run next":
/gsd-progress --next, a global smart router that reads project state and dispatches to the appropriate next command, and/gsd-autonomous, which runs all remaining phases end to end. Both are useful. Neither is per-phase gate routing.The consequence is that every phase pays the same pipeline weight regardless of which domains it actually touches. A copy-tweak phase in the UI layer and a destructive-migration phase run the same gate set: the same research depth, the same review-convergence cycles, the same threat-model and validation passes. In a long-lived project that ratio is badly wrong in both directions at once:
The missing concept is that phase size and phase domain are independent variables. A large UI phase touching every screen still needs no money/schema/security gates, but it is not "light". A one-line change to a payment-splitting helper is minimal in size but must pass every money-path safety gate. Any single linear tier — "light / standard / heavy" — collapses two axes into one and is therefore wrong in one direction or the other for most real phases.
(b) Design sketch — two orthogonal axes: surfaces × depth
Split the single tier into two independent axes:
Surfaces as configuration, not as hardcode
Surfaces should be a configurable map in
config.json, not a fixed vocabulary baked into the workflow. A project declares its own domains, the path globs that activate them, the gate steps each pulls in, and whether the surface is safety-pinned:{ "surfaces": { "payments": { "globs": ["src/payments/**", "src/billing/**", "src/lib/money/**"], "gates": ["secure", "validate", "full-uat"], "pinned": true }, "schema": { "globs": ["migrations/**", "db/schema/**", "docker-compose.yml"], "gates": ["secure", "validate", "migration-idempotency", "rollback-runbook"], "pinned": true, "destructive_pins_depth": "deep" }, "security": { "globs": ["src/auth/**", "src/middleware/**", "config/secrets/**"], "gates": ["secure", "validate"], "pinned": true }, "ui": { "globs": ["src/app/**", "src/components/**", "src/hooks/**", "DESIGN.md"], "gates": ["ui-spec", "ui-review", "browser-uat"], "pinned": false }, "ai": { "globs": ["prompts/**", "src/llm/**", "evals/**"], "gates": ["ai-spec", "eval-review"], "pinned": false }, "data": { "globs": ["src/models/**", "src/lib/identity/**"], "gates": ["validate", "parity-check"], "pinned": false } } }Generic starter names —
payments / schema / security / ui / ai / data— are a sensible default set, but the point of the design is that the map is the project's, not the framework's. A data-pipeline project and a mobile app want different domains.Cross-cutting gates always run
A fixed set stays unconditional at any surface/depth combination: research, SPEC, planning, plan check, execute, code review, learnings extraction, ship. Surfaces only route the optional clusters.
Pinned safety gates — the load-bearing rule
The key rule that makes this safe: for surfaces marked
pinned: true, the safety gates (threat model, validation) run regardless of diff size. Only the cycle gates (research depth, review-convergence cycles, wave count) scale down with size.This is what prevents the optimization from eating its own safety margin. "Small but catastrophic" is a real and common phase shape; the whole design fails if size alone can turn off a money-path or migration gate.
Raise-only and the ambiguity tiebreaker
Two constraints keep the classification honest:
The classification runs twice: a rough pre-detect from the roadmap entry (enough to pick a branch), then a confirming lock immediately after SPEC, when the touched paths are exact. The locked result is written into the phase's context artifact as machine-readable lines — active surfaces, derived depth, one skip decision per inactive surface, plus the rationale.
(c) Auto-derived depth
Depth should not be a manual dial. Every signal it needs is already produced by the pipeline itself, so it can be computed:
The cycle gates then read the derived level:
--analyze--max-cycles 1--max-cycles 2--max-cycles 3, multiple reviewers--fix --auto--fix --all --auto"Deep" is deliberately identical to today's uniform behaviour. That matters for adoption: the proposal never makes the current worst case worse, it only lets phases that provably do not need that weight opt down — and only along the cycle axis, never along the safety axis.
(d) Artifact-based resume auto-detect
A related problem the same model solves: resuming a phase that was interrupted, without asking the human "where were we?".
Because each pipeline step produces a named artifact, the observed artifact state on disk determines the resume step deterministically. Walk a table top-down, first match wins:
Two refinements make it work in practice:
An explicitly supplied step number always takes precedence over auto-detect; the global smart router is a good fallback when the table matches ambiguously (mixed-state artifacts), but its answer should be treated as an advisory hint that gets mapped back to an explicit step number, not chained directly — chaining it bypasses the checkpoint gates in (f).
(e) Git-truth interruption detection and a recovery matrix
The resume table above has a sharp failure mode, and it is worth designing against explicitly:
A half-written artifact killed by a network drop, a token limit, or a crashed process looks identical to a finished one from the filesystem's point of view. So before any routing decision — auto-detected or explicit — run an interruption diagnostic:
git status --porcelainover the planning artifact directory — untracked or modified files for the current phase mean a step started writing and never committed.git status --porcelainover the source paths — uncommitted source changes mean execution was interrupted mid-task.git log --oneline <base>..HEAD -- <phase artifact dir>— the committed artifact trail. Whatever is not there is not really done.Then classify against a recovery matrix:
git diff --statfor scope. Matches one plan task cleanly → finish and commit it. Spans several tasks or looks inconsistent → stash, resume execute from the last completed task, review the stash afterTwo operating rules around the matrix:
Full-phase rollback is a different operation and should stay a different command: the recovery matrix repairs uncommitted working-tree state, while unwinding a phase whose approach turned out to be wrong reverts committed work with dependency checks. Mixing the two is a foot-gun.
(f) Context-reset checkpoints
The last piece is mechanical rather than semantic. Long phases overflow the context window mid-plan or mid-execute, and an overflow crash costs the whole step.
Insert checkpoints keyed to context utilization percentage at the natural seams — before planning, before execute, after execute. Each checkpoint measures current utilization and compares it against a threshold: above it, the checkpoint is a non-negotiable hard stop; below it, it may be skipped with an inline note. This is independent of both axes — overflow is a mechanical property, not a domain property, so deep multi-wave phases trip it nearly always and shallow phases usually never.
The detail that makes checkpoints actually work is the banner. When the pipeline stops, it prints a ready-to-paste resume command with the step number already filled in:
Resume then depends on nothing the agent remembers — the number is on screen, and the fresh session reloads the artifacts from disk. Note the interaction with (d): the artifact auto-detect is the second safety net for the same mechanism. If the human forgets the step number and sends the bare command, auto-detect lands on the same checkpoint rather than somewhere arbitrary. Belt and suspenders for the single most common way a long pipeline loses work.
(g) Closing
This model has been battle-tested across 44 phases of a production solo-dev project, where it replaced a uniform pipeline that ran every gate on every phase. The observations that generalize:
Two adjacent open threads suggest there is demand for finer-grained, per-phase workflow management generally — #2542 (concurrency model for the planning store) and #276 (shared plans versus isolated workspaces). This proposal is orthogonal to both, but it lives in the same problem space: the pipeline currently has one global shape, and real projects want it shaped per phase.
Happy to write this up as a proper RFC or as a PR against the core workflows if maintainers are interested — including the config schema for the surface map, the depth-derivation function, and the resume/recovery tables as reusable reference documents. Feedback on the shape of the config, and on whether the surface vocabulary should ship with defaults or stay entirely project-defined, would be the most useful thing to hear first.
🤖 Drafted with Claude Code on the author's behalf, distilled from a production pipeline in daily use.
All reactions