chore(#1671): migrate the remaining 13 LARGE/XL workflows to the fragment model — Phase 6.3 (#3030)

* chore(#2994): fragmentize progress.md forensic audit onto the fragment model

Extract the --forensic-gated forensic_audit step to
workflows/progress/steps/forensic-audit.md behind a section marker, and
repair progress.md's init line to forward --forensic so the atom is
actually true in production rather than only under direct CLI tests.

progress.md shrinks 32630 -> 27207 bytes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* chore(#2994): fragmentize the four manifest-wired workflows

new-project, quick, new-milestone and progress each already had a
dedicated cmdInit* entry point but zero marked sections. Extract nine
gated bodies to workflows/<wf>/steps/ behind section markers and repair
each init line to forward its flags.

Fold --full into the discuss/research/validate facts inside cmdInitQuick
so the when= grammar never sees an OR, per the chunked-mode precedent.

Fixes found while working, per the no-defer rule:
- cmdInitProgress passed no phase info to buildSectionManifestField, so
  state:phase-mvp-mode was permanently false — an atom in the vocabulary
  whose fact could never be computed.
- the quick init router folded flag tokens into the free-text
  description, which the new forwarding would have corrupted.
- a #2508 dispatch note was nested inside quick.md's Agent(prompt=)
  fence, leaking orchestrator guidance into the subagent prompt.
- progress.md had a 3-vs-4 backtick outer-fence imbalance.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* chore(#2994): fragmentize verify-work.md and admit state:ui-phase-active

Wire cmdInitVerifyWork to buildSectionManifestField — it was a dedicated
entry point that never emitted a manifest — and mark two sections.

state:ui-phase-active folds (plan:pre hooks include an active ui step) OR
(the phase dir holds a *-UI-SPEC.md) into one boolean in init.cts, so the
grammar still sees a single operator-free atom. The inner Playwright-MCP
check stays as prose inside the fragment: it is live session state and no
init seam can precompute it.

The MVP false-branch note is a real fallback, not redundant prose, so it
sits outside the marker — gating it away would delete the text needed
precisely when MVP mode is off.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* test(#2994): follow moved workflow content in drift guards

Retarget every guard that asserted on content this branch moved into
workflows/<wf>/steps/, mirroring 815b3d897. Each retargeted assertion was
verified to still fail when its step file is blanked, so none was
weakened into vacuity.

Three assertions in verify-mvp-uat were genuinely red. Three more were
worse than red — passing for the wrong reason:
- quick-commit-boundary and worktree-cleanup anchored on indexOf('Step
  5.6'), which matched a later cross-reference and sliced 16069 chars
  that coincidentally held the asserted substrings. Replaced with an
  expandWorkflowSections helper that splices step content back in place.
- phase6-review-capabilities lost its end boundary and widened to EOF.
- playwright-ui-verify matched 'UI' in an unrelated bullet and 'fall
  back' in a subagent-dispatch line after the real content moved.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* chore(#2994): fragmentize code-review and complete-milestone, admit three atoms

Add dedicated cmdInitCodeReview and cmdInitCompleteMilestone entry points
alongside the shared generic ones rather than modifying them — init.phase-op
and init.manager carry a CRITICAL blast radius (179 dependents, 24
processes) and stay byte-identical for their other callers.

Admit flag:--fix, state:fallow-enabled and state:git-create-tag, each with
a consuming section and a fact its own entry point computes.

Both sections had the resolver-in-body hazard: the fallow config-gate and
the git.create_tag check each sat inside the very block being gated, so
gating would have disabled the resolver that decides the gate. Both are
hoisted into init and the bodies now consume the resolved fact.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* test(#2994): retarget code-review and milestone drift guards, fix two red tests

Retarget guards that asserted on content moved into steps/, proving
non-vacuity by blanking each step file and confirming failure.

Also fixes two genuinely red tests found while working, per the no-defer
rule:
- workflow-fragments' frozen-vocabulary lock was missing
  state:ui-phase-active, so commit 7ef7f8336 shipped red. Lint and build
  both passed over it, which is why neither is sufficient verification.
- code-review's quick.md capability-hook assertion carried a stale
  delimiter after the 18ff35d20 extraction.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* chore(#2994): fragmentize autonomous.md and admit state:plan-strategy-converge

Five sections share one atom, the pattern plan-phase already uses for
flag:--research-phase. The atom folds --converge OR --cross-ai into a
single boolean in cmdInitAutonomous so the grammar stays operator-free.

cmdInitAutonomous is additive; init.milestone-op, init.manager and
init.phase-op are untouched and still consumed. The $PLAN_STRATEGY bash
resolver is deliberately retained — ungated local-planning bullets still
read it, so the init-side fact supplements it rather than replacing it.

converge-fail-fast required splitting one bash fence so the always-run
CONVERGENCE_ARGS construction stays outside the marker. All three
flag-absent fallbacks were left outside their markers.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* chore(#2994): fragmentize review and discuss-phase-assumptions

Admit state:reviewer-instances-configured (two peripheral notes share it;
the core reviewer-lane dispatch stays unmarked — it is the workflow's
primary always-evaluated logic, not an optional branch) and
state:auto-advance-active, which folds --auto OR two config keys into one
boolean so the grammar stays operator-free.

discuss-phase-assumptions was the highest-risk edit in this PR. Its
auto_advance step is a full if/elif/else; gating it whole would have
deleted the flag-absent fallback needed exactly when --auto is off. Split
verified exact: resolvers 636-651 and the 'End here' fallback 668-669 both
stay outside the marker; only 653-667 is gated.

Adds emitted-drift acks for the two files that grew — review.md (+55 B)
and autonomous.md (+737 B from 80799211c, which had none and would have
red-gated the push.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* chore(#2994): fragmentize docs-update, update, transition and new-milestone Part A

Completes the 13-workflow rollout. Three of these had no init call at all
and gained a dedicated entry point plus their first gsd_run query line.

Admits state:is-monorepo and adds state:next-channel, state:workstream-active
and state:flat-mode. Vocabulary 26 -> 30 atoms.

Part A of new-milestone applies when NO workstream is active — the negation
of state:workstream-active. Rather than teach the grammar negation, which is
the Greenspun drift the frozen list exists to prevent, it gets a separate
positively-phrased atom whose fact is the inverse. Part B, which always runs,
stays outside the marker.

flag:--verify-only is deliberately NOT admitted: docs-update has no
contiguous purely-additive region for it, and an atom without a consuming
section is dead vocabulary. Evidence recorded in the slice report.

update.md reuses its existing resolved $GSD_TOOLS rather than prepending the
canonical preamble, which would have clobbered it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(#2994): stop automated-ui-verification re-resolving its own gate, retire dead vocabulary

Two defects the new tests caught.

The automated-ui-verification step re-ran gsd_run loop render-hooks and
recomputed UI_PHASE_ACTIVE inside a body that is only read when that fact
is already true — the circular self-disabling pattern this design forbids,
introduced by 3c654b168. cmdInitVerifyWork now exposes ui_phase_active and
the step consumes it. Its launcher preamble goes too: no gsd_run remains.
The Playwright-MCP check stays as prose — that is live session state.

Dead vocabulary predating this PR: flag:--full and state:needs-codebase-map
were admitted with a gate-1 claim that never materialized. flag:--full is
removed, redundant once quick folds it into discuss/research/validate.
state:needs-codebase-map gets the real consumer it always lacked, gating
new-project's codebase-map offer. Vocabulary 30 -> 29, and no atom is now
without a consuming section.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* test(#2994): add the atom-admission, inversion and resolver-hoist gates

The two existing parity guards prove vocabulary/predicate symmetry but
never that a fact is computed — an atom no cmdInit* assembles evaluates
false forever. These close that hole:

- per-atom satisfiability for all 29 atoms, plus an anti-vacuity assertion
  so the loop cannot silently cover zero atoms
- dead-vocabulary check against the shipped manifest
- inversion guard: the flag-absent fallbacks in discuss-phase-assumptions
  and verify-work must stay outside their markers
- data-driven resolver-hoist guard over the shipped manifest, so a future
  extraction cannot reintroduce the circular class
- compound-fold coverage (--full, --cross-ai, --rc, config-only --auto)
- null-vs-[] degraded/computed distinction, and flag value shapes

Also repairs the frozen-vocabulary lock, which was stale and red for the
seven atoms earlier commits on this branch shipped.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs(#2994): add changeset for the fragment-model rollout

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* test(#2994): cite the issue on the two new allow-test-rule exemptions

ADR-456 requires an issue ref on the same line as the annotation.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs(#2994): correct the atom-count claims after retiring flag:--full

The vocabulary doc comments still said 30 entries; it is 29 since
flag:--full was removed as dead vocabulary.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(#2994): dedupe the phase-fallback block and harden --ws parsing

Review findings.

MAJOR: the three new init entry points each pasted a verbatim copy of the
guardedFindPhase/guardedGetRoadmapPhase fallback, taking the repo from four
copies to seven — DEFECT.GENERATIVE-FIX. Extracted applyRoadmapFallback and
folded six of the seven; each call site keeps its own field-set via a
closure. Duplication removed rather than papered over with a parity test.
cmdInitPhaseOp stays out: its fallback omits has_reviews, so it is not a
byte-identical copy, and it is CRITICAL-radius.

LOW, pre-existing: GSD_WS captured [^[:space:]]+ and expands unquoted, so a
workstream name holding glob metacharacters would expand against the
filesystem. Narrowed to [A-Za-z0-9._-]+. The unquoted expansion is kept —
it must word-split into two args and vanish when empty.

Also restores the vocabulary ordering convention, and fixes a masked test
bug the mandated run surfaced: the flag-forwarding guard checked only the
first init line per workflow, but new-milestone has two, so a real failure
was reporting exit 0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(#2994): drop the stale new-milestone emitted-drift ack

new-milestone.md was acked for a +406 B growth measured against an
intermediate commit. Net against origin/next it SHRANK by 8 bytes, so
nothing needed the ack and it explained nothing — which the differential
attribution check reports as a stale acknowledgment, not a pass.

update.md's entry stays: it genuinely grew +703 B.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(#2994): resolve the 15 failures from the full matrix run

All 15 were real and identical on both lanes.

REAL REGRESSION: autonomous.md hit 41479 chars against the #2196 guard's
40960 cap — a CHARS cap distinct from the LARGE tier byte cap, which the
five section stubs pushed it over. Extracted the 3a.5 UI Design Contract
body to references/; now 39968 chars, and the file nets -795 B vs base, so
its growth ack is deleted rather than left stale.

REAL DEFECT: docs referenced /gsd-transition, which is not a live
registered command. Reworded.

STALE FIXTURE: the emission byte-identity test hardcoded two marked
workflows; this branch legitimately marks fifteen. Fixture corrected — the
source was right.

The rest were drift guards over the eight workflows the earlier sweep did
not cover, retargeted at where the content now lives with non-vacuity
proven by blanking each step file and confirming failure. The GSD_WS
forwarding guard was checked as a possible real break and is not one: the
charclass narrowing is intact and forwarding works end to end.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(#2994): drop the ack for a newly-added reference file

A new file's emitted ripple is attributable to the diff that adds it, so
the acknowledgment explained nothing and the differential check reports it
as stale. Removing the last entry removes the fragment — an empty one
signals nothing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(#2994): retarget the UI-contract guards and clear two transitive advisories

The §3a.5 extraction that brought autonomous.md under the #2196 char cap
moved its body to references/autonomous-ui-design-contract.md, so ten
guards in autonomous-ui-steps and check-ui-safety-gate were asserting it
against the host. Retargeted via a combined read, each proven non-vacuous
by blanking the reference file and confirming failure.

This class had already bitten twice on this branch because each sweep was
scoped to the workflows touched at that moment, so this one was
exhaustive: ~70 test files across all 13 workflows, zero further broken or
vacuous assertions found.

Also clears two high transitive advisories the matrix flagged on one lane
— fast-uri GHSA-7p8r-x3mc-p8w7 and three ip-address SSRF/trust-boundary
issues. Both pre-date this branch: package-lock.json was untouched until
now, so the production tree was byte-identical to the base. Lockfile-only,
package.json unchanged, verified against a real npm ci install.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* chore(#2994): backfill changeset pr number to 3030

---------

Co-authored-by: sim <sim@local>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-08-03 19:59:58 -04:00
committed by GitHub
parent c6ce4d1d9a
commit ff4a57b78c
106 changed files with 4971 additions and 1474 deletions

View File

@@ -0,0 +1,5 @@
---
type: Changed
pr: 3030
---
**Workflow guidance now loads only the branch your invocation actually took** — thirteen more large workflows moved onto the fragment model, so running `/gsd-code-review` without `--fix` no longer loads the fix-dispatch branch, `/gsd-progress` without `--forensic` no longer loads the forensic audit, and so on across every migrated workflow. (#2994)

View File

@@ -501,10 +501,19 @@ node gsd-tools.cjs init quick <description>
node gsd-tools.cjs init resume
node gsd-tools.cjs init verify-work <phase>
node gsd-tools.cjs init phase-op <phase>
node gsd-tools.cjs init code-review <phase> [--fix]
node gsd-tools.cjs init review <phase>
node gsd-tools.cjs init discuss-phase-assumptions <phase> [--auto]
node gsd-tools.cjs init todos [area]
node gsd-tools.cjs init milestone-op
node gsd-tools.cjs init map-codebase
node gsd-tools.cjs init progress
node gsd-tools.cjs init manager
node gsd-tools.cjs init complete-milestone
node gsd-tools.cjs init autonomous [--converge] [--cross-ai]
node gsd-tools.cjs init docs-update
node gsd-tools.cjs init update [--next] [--rc]
node gsd-tools.cjs init transition
# Workstream-scoped init (`--ws` flag)
node gsd-tools.cjs init execute-phase <phase> --ws <name>

View File

@@ -210,6 +210,7 @@
"api-coverage.md",
"artifact-types.md",
"autonomous-smart-discuss.md",
"autonomous-ui-design-contract.md",
"checkpoints.md",
"common-bug-patterns.md",
"context-budget.md",

View File

@@ -46,9 +46,22 @@ gap fragment and composes back byte-identical to its source.
## The frozen `when=` vocabulary
`when=` takes exactly one of 19 atoms (widened from 4 to 14 via the ADR-1671
`when=` takes exactly one of 29 atoms (widened from 4 to 14 via the ADR-1671
amendment for #2992, epic #1671 Phase 6.1, then from 14 to 19 via the
ADR-1671 amendment for #2993, epic #1671 Phase 6.2):
ADR-1671 amendment for #2993, epic #1671 Phase 6.2, then from 19 to 20 via
the ADR-1671 amendment for #2994, epic #1671 Phase 6.3, then from 20 to 23
via a further #2994 amendment fragmentizing `code-review.md` and
`complete-milestone.md`, then from 23 to 24 via a still further #2994
amendment fragmentizing `autonomous.md`, then from 24 to 26 via a still
further #2994 amendment fragmentizing `review.md` and
`discuss-phase-assumptions.md`, then from 26 to 30 via the FINAL #2994
amendment fragmentizing `docs-update.md`, `update.md`, `transition.md`, and
`new-milestone.md` — the last four of the 13 workflows targeted by
ADR-1671 — then from 30 to 29 via a dead-vocabulary cleanup: `flag:--full`
was removed (no `when=` marker ever consumed it — `quick.md` folds `--full`
into the `--discuss`/`--research`/`--validate` facts before evaluation) and
`state:needs-codebase-map` gained its first real consumer,
`new-project.md`'s `codebase-map-offer` section):
| Value | Meaning |
|---|---|
@@ -58,8 +71,8 @@ ADR-1671 amendment for #2993, epic #1671 Phase 6.2):
| `state:has-prior-phases` | Applicable when prior phases (and their `VERIFICATION.md` files) exist. |
| `flag:--auto` | Applicable when the workflow runs with `--auto`. |
| `flag:--discuss` | Applicable when the workflow runs with `--discuss`. |
| `flag:--fix` | Applicable when the workflow runs with `--fix` (`code-review.md`'s resolved fix decision — `--fix` itself, or `--all`/`--auto` implying it via `code-review-flags.cjs`). |
| `flag:--forensic` | Applicable when the workflow runs with `--forensic`. |
| `flag:--full` | Applicable when the workflow runs with `--full`. |
| `flag:--ingest` | Applicable when the workflow runs with `--ingest <path-or-glob>`. |
| `flag:--prd` | Applicable when the workflow runs with `--prd <file>`. |
| `flag:--research` | Applicable when the workflow runs with `--research`. |
@@ -67,9 +80,19 @@ ADR-1671 amendment for #2993, epic #1671 Phase 6.2):
| `flag:--reset-phase-numbers` | Applicable when the workflow runs with `--reset-phase-numbers`. |
| `flag:--reviews` | Applicable when the workflow runs with `--reviews`. |
| `flag:--validate` | Applicable when the workflow runs with `--validate`. |
| `state:auto-advance-active` | Applicable when `discuss-phase-assumptions.md`'s `auto_advance` step should dispatch — `--auto` flag OR a consolidated auto-mode config fact (see [Compound conditions are resolved in the fact, never the grammar](#compound-conditions-are-resolved-in-the-fact-never-the-grammar) below). |
| `state:chunked-mode` | Applicable when chunked planning mode is active — see [Compound conditions are resolved in the fact, never the grammar](#compound-conditions-are-resolved-in-the-fact-never-the-grammar) below. |
| `state:fallow-enabled` | Applicable when `.planning/config.json`'s `code_quality.fallow.enabled` is `true` (fail-closed default `false`). |
| `state:flat-mode` | Applicable when NO workstream is active — the positively-phrased inverse of `state:workstream-active` (the grammar has no negation operator). |
| `state:git-create-tag` | Applicable when `.planning/config.json`'s `git.create_tag` is not `false` (fail-OPEN default `true`). |
| `state:is-monorepo` | Applicable when the project's `monorepo_workspaces` list is non-empty. |
| `state:needs-codebase-map` | Applicable when a codebase map is needed (init-computed). |
| `state:next-channel` | Applicable when `update.md`'s release channel is `next` — the workflow runs with `--next`, or its documented alias `--rc`. |
| `state:phase-mvp-mode` | Applicable when the current phase's `ROADMAP.md` entry declares `**Mode:** mvp`. |
| `state:plan-strategy-converge` | Applicable when `autonomous.md`'s planning step should route through plan-review convergence instead of `gsd-plan-phase` — the workflow runs with `--converge`, or its documented alias `--cross-ai` (`autonomous.md`'s own `PLAN_STRATEGY` resolver folds both). |
| `state:reviewer-instances-configured` | Applicable when `.planning/config.json`'s `review.reviewer_instances` is present AND non-empty. |
| `state:ui-phase-active` | Applicable when the phase's active `plan:pre` loop hooks include the `ui-phase` step, OR the phase directory already contains a `*-UI-SPEC.md` file — see [Compound conditions are resolved in the fact, never the grammar](#compound-conditions-are-resolved-in-the-fact-never-the-grammar) below. |
| `state:workstream-active` | Applicable when a workstream is active — `GSD_WORKSTREAM` env, falling back to the stored active-workstream pointer. |
| `state:worktrees-enabled` | Applicable when `.planning/config.json`'s `workflow.use_worktrees` is enabled. |
This list is **closed by design** (Greenspun's Tenth Rule): left open-ended,
@@ -97,13 +120,44 @@ required:
section marked with it would silently never include: the exact
silent-wrong-answer class this gate exists to prevent.
Six further atoms (`flag:--converge`, `flag:--fix`, `flag:--verify-only`,
`state:fallow-enabled`, `state:git-create-tag`, `state:is-monorepo`) satisfy
gate 1 but not yet gate 2 — their workflows (`autonomous`, `code-review`,
`complete-milestone`, `docs-update`) route through shared generic init entry
points invoked by 20+ other workflows, so a dedicated `cmdInit*` seam does
not yet exist to compute their facts. They are withheld pending that seam,
not rejected.
One further atom, `flag:--verify-only`, is surveyed but NOT admitted even now
that `docs-update` has its own dedicated `cmdInit*` entry point
(`cmdInitDocsUpdate`) — it fails gate 1, not gate 2: the flag's control flow
is INTERLEAVED across three non-contiguous touch-points in `docs-update.md`
(an inline early-exit check in `init_context`, a "Skip condition" note
embedded in another step's body, and the `verify_only_report` step itself)
rather than a single contiguous, whole-line, purely-additive region.
Admitting the atom to gate only the `verify_only_report` step would leave
the other two touch-points as un-migrated raw `$ARGUMENTS` checks — an atom
whose consuming section covers only PART of the flag's real behavior is not
a clean win. `state:is-monorepo` IS admitted (`dispatch-monorepo-packages`
section, `cmdInitDocsUpdate`) — see [Piloted on execute-phase.md, then rolled
out across the wired
workflows](#piloted-on-execute-phasemd-then-rolled-out-across-the-wired-workflows)
below. `flag:--fix`, `state:fallow-enabled`, and
`state:git-create-tag` were withheld for the same reason until a further
#2994 amendment gave `code-review` and `complete-milestone` their own
dedicated `cmdInit*` entry points (`cmdInitCodeReview`,
`cmdInitCompleteMilestone`) — see [Piloted on execute-phase.md, then rolled
out across the wired
workflows](#piloted-on-execute-phasemd-then-rolled-out-across-the-wired-workflows)
below. A third atom, originally surveyed as `flag:--converge`, was withheld
for the same reason and never shipped under that name: a still further
#2994 amendment gave `autonomous` its own dedicated `cmdInit*` entry point
(`cmdInitAutonomous`), and the atom that shipped is
`state:plan-strategy-converge` instead — `--cross-ai` is a documented alias
for `--converge` (`autonomous.md`'s own `PLAN_STRATEGY` resolver folds
both), so a `flag:--converge`-only atom would have left a `--cross-ai`-only
invocation silently excluded from the same sections. `state:reviewer-instances-configured`
and `state:auto-advance-active` were withheld the same way until a still
further #2994 amendment gave `review` and `discuss-phase-assumptions` their
own dedicated `cmdInit*` entry points (`cmdInitReview`,
`cmdInitDiscussPhaseAssumptions`). The FINAL #2994 amendment gives
`docs-update`, `update`, `transition`, and `new-milestone` their own atoms —
`state:is-monorepo`, `state:next-channel`, `state:workstream-active`, and
`state:flat-mode` — backed by three brand-new dedicated entry points
(`cmdInitDocsUpdate`, `cmdInitUpdate`, `cmdInitTransition`) plus an extension
of the pre-existing `cmdInitNewMilestone`.
### Compound conditions are resolved in the fact, never the grammar
@@ -130,6 +184,35 @@ Greenspun's Tenth Rule drift [The frozen `when=`
vocabulary](#the-frozen-when-vocabulary) above exists to prevent, regardless
of how reasonable a single compound condition looks in isolation.
`state:ui-phase-active` (#2994) is the same shape: `verify-work.md`'s
`automated_ui_verification` step originally computed its own OR at RUNTIME
(`UI_PHASE_ACTIVE` from `gsd_run loop render-hooks plan:pre` OR a `*-UI-SPEC.md`
file check). `cmdInitVerifyWork` now resolves the identical disjunction ahead
of time — `resolveLoopHooks({point: 'plan:pre', ...}).activeHooks` filtered to
`kind === 'step' && ref.skill === 'ui-phase'`, OR'd with a `*-UI-SPEC.md`
existence check under the phase directory — into `InvocationFacts.uiPhaseActive`,
so `WHEN_PREDICATES['state:ui-phase-active']` again reads only that one field.
`state:plan-strategy-converge` (#2994) is the same shape again:
`autonomous.md`'s own bash `PLAN_STRATEGY` resolver already folds `--converge`
OR its documented alias `--cross-ai` into a single `"converge"`/`"local"`
value at the top of the `initialize` step. `cmdInitAutonomous` mirrors that
identical disjunction — `flags.has('--converge') || flags.has('--cross-ai')`
— into `InvocationFacts.planStrategyConverge`, so
`WHEN_PREDICATES['state:plan-strategy-converge']` reads only that one field,
never `--converge`/`--cross-ai` separately.
`state:auto-advance-active` (#2994) is the same shape once more:
`discuss-phase-assumptions.md`'s own `auto_advance` step already resolves
`--auto` OR a consolidated `check auto-mode --pick active` fact (itself
`workflow._auto_chain_active` OR `workflow.auto_advance`) via a runtime
`gsd_run` call before deciding whether to dispatch. `cmdInitDiscussPhaseAssumptions`
mirrors that identical disjunction — `options['auto'] === true ||
readConfigJsonBoolean(cwd, ['workflow', '_auto_chain_active']) ||
readConfigJsonBoolean(cwd, ['workflow', 'auto_advance'])` — into
`InvocationFacts.autoAdvanceActive`, so `WHEN_PREDICATES['state:auto-advance-active']`
reads only that one field, never the flag and the two config keys separately.
## Fails closed
An authoring mistake throws at parse time, naming the source file and 1-based
@@ -223,14 +306,19 @@ At init time, a separate pure evaluator, `src/section-manifest.cts`
(`selectSections`), partitions a workflow's manifest sections into
`included`/`excluded` id lists against one invocation's
`InvocationFacts` — `{flags, phaseNumber, hasPriorPhases, needsCodebaseMap?,
phaseMvpMode?, worktreesEnabled?, chunkedMode?}`. Only a workflow with a **dedicated
phaseMvpMode?, worktreesEnabled?, chunkedMode?, uiPhaseActive?, fallowEnabled?,
gitCreateTag?, planStrategyConverge?, reviewerInstancesConfigured?,
autoAdvanceActive?, isMonorepo?, nextChannel?, workstreamActive?, flatMode?}`.
Only a workflow with a **dedicated
`cmdInit*` entry point** in `src/init.cts` can have this evaluation run for
it, because only that entry point can assemble `InvocationFacts` from its own
parsed CLI options and `.planning/` state reads — this is admission gate 2
from [The frozen `when=` vocabulary](#the-frozen-when-vocabulary) above,
applied per-workflow rather than per-atom. Six entry points are wired today:
`execute-phase`, `plan-phase`, `new-project`, `new-milestone`, `quick`, and
`progress`.
applied per-workflow rather than per-atom. Fifteen entry points are wired
today: `execute-phase`, `plan-phase`, `new-project`, `new-milestone`,
`quick`, `progress`, `verify-work`, `code-review`, `complete-milestone`,
`autonomous`, `review`, `discuss-phase-assumptions`, `docs-update`, `update`,
and `transition`.
`InvocationFacts.flags` is a `ReadonlySet<string>` of the literal `--<name>`
tokens seen on the invocation, and **membership is token-presence, not
@@ -245,13 +333,27 @@ own `false` into `undefined` (`namedArgs['wave'] || undefined`) before
handing options to the facts builder, so `flags` only ever contains tokens
that were actually seen.
## Piloted on execute-phase.md, then rolled out to plan-phase.md
## Piloted on execute-phase.md, then rolled out across the wired workflows
Two workflows carry markers today: `gsd-core/workflows/execute-phase.md` (the
#2930/Phase-3 pilot) and `gsd-core/workflows/plan-phase.md` (#2993, epic
#1671 Phase 6.2). The marker grammar and composer seam are general-purpose
across any workflow file; rollout to further LARGE/XL workflows remains
sequenced as later work.
Fifteen workflows carry markers today, all of them the workflows with a
dedicated `cmdInit*` entry point (see [The manifest artifact](#the-manifest-artifact-and-per-workflow-keying)
above): `gsd-core/workflows/execute-phase.md` (the #2930/Phase-3 pilot),
`gsd-core/workflows/plan-phase.md` (#2993, epic #1671 Phase 6.2),
`gsd-core/workflows/progress.md`, `gsd-core/workflows/new-project.md`,
`gsd-core/workflows/quick.md`, `gsd-core/workflows/new-milestone.md` (those
four, #2994, epic #1671 Phase 6.3), `gsd-core/workflows/verify-work.md`
(also #2994, epic #1671 Phase 6.3), `gsd-core/workflows/code-review.md` /
`gsd-core/workflows/complete-milestone.md` (a further #2994 amendment, epic
#1671 Phase 6.3), `gsd-core/workflows/autonomous.md` (a still further
#2994 amendment, epic #1671 Phase 6.3), `gsd-core/workflows/review.md` /
`gsd-core/workflows/discuss-phase-assumptions.md` (a still further #2994
amendment, epic #1671 Phase 6.3), and finally
`gsd-core/workflows/docs-update.md` / `gsd-core/workflows/update.md` /
`gsd-core/workflows/transition.md` (the LAST #2994 amendment, epic #1671
Phase 6.3 — none of these three carried a `gsd_run query init.*` call before
this slice). Every one of the 13 workflows ADR-1671 targeted is now on the
fragment model; the marker grammar and composer seam remain general-purpose
across any future workflow file, but no further rollout is scheduled.
`execute-phase.md` marks three `<step>` blocks: `partial-wave`
(`flag:--wave`), `gap-closure-artifacts` (`state:gap-closure-phase`), and
@@ -264,6 +366,233 @@ sequenced as later work.
`RESEARCH_ONLY` condition, so they include/exclude together), and
`chunked-planning-mode` (`state:chunked-mode`).
`progress.md` marks two sections: `forensic-audit` (`flag:--forensic`, #2994
forensic audit) and `mvp-display` (`state:phase-mvp-mode`). `mvp-display`'s
own body used to re-resolve its own gating fact via a `gsd_run query
phase.mvp-mode` call — circular, since a section's body re-deriving the exact
condition that gated its own inclusion is self-disabling the moment the init
seam's computation and the body's computation drift. `cmdInitProgress` now
computes `phaseMvpMode` for the CURRENT phase directly (threading a real
`phase_number` into `buildSectionManifestField`, where before it passed
`null` and the fact was permanently `false`) and exposes it as a top-level
`phase_mvp_mode` init-bundle field, so the step body consumes an
already-resolved fact instead of recomputing it.
`new-project.md` marks two sections, both `flag:--auto`: `auto-mode-detection`
(the `<auto_mode>` tag itself stays outside the marker — only its body is
extracted) and `auto-mode-config` (`## 2a. Auto Mode Config`).
`quick.md` marks five sections: `discussion-phase` (`flag:--discuss`),
`research-phase` (`flag:--research`), `plan-checker-loop` and
`quick-verification` (both `flag:--validate` — two consumers sharing one
atom, mirroring `plan-phase.md`'s `research-only-*` pair), and
`worktree-pre-dispatch-commit` (`state:worktrees-enabled`). `quick.md`'s
`--full` flag IMPLIES `--discuss`/`--research`/`--validate` — folded into the
facts inside `cmdInitQuick` (mirroring `state:chunked-mode`'s disjunction
fold) before `buildSectionManifestField` builds its flags Set, so a bare
`--full` invocation still includes the three flag-gated sections without the
grammar ever seeing an OR.
`new-milestone.md` marks two sections: `reset-phase-safety`
(`flag:--reset-phase-numbers`) and, from the final #2994 slice,
`project-md-milestone-write` (`state:flat-mode`) — Step 4's "Part A"
milestone-state write, which must run ONLY when NO workstream is active
(#2308: an active workstream's own `.planning/workstreams/<name>/STATE.md`/
`ROADMAP.md`/`REQUIREMENTS.md` already carry this milestone's state, so
writing the shared `## Current Milestone` heading here would clobber it).
Part A's true condition is the NEGATION of "a workstream is active", and the
grammar has no negation operator, so `state:flat-mode` is a SEPARATE,
positively-phrased atom (fact `= !workstreamActive`) rather than a negated
`state:workstream-active` — see `transition.md` below for the atom this one
inverts. Part B ("Evolution structural repair", always runs regardless of
workstream mode) stays OUTSIDE the marker, directly after it, for the same
reason every other flag-absent/state-false fallback in this document does.
`cmdInitNewMilestone` computes both `workstreamActive` and `flatMode` from
the SAME authoritative source `cmdInitProgress` already established
(`GSD_WORKSTREAM` env, falling back to the stored active-workstream
pointer). Because Step 4 (where the marker lives) runs well before
`new-milestone.md`'s pre-existing `init.new-milestone` call (Step 7, kept
AFTER Step 6's phase archival so its `phase_dir_count`/`roadmap_exists`
fields reflect POST-archival state), a SECOND, section-manifest-only
`init.new-milestone` call is added early in Step 1 — `init.new-milestone`
is a pure read with no mutation, so calling it twice is side-effect-free.
`verify-work.md` marks two sections: `automated-ui-verification`
(the new `state:ui-phase-active`, #2994 — see [Compound conditions are
resolved in the fact, never the grammar](#compound-conditions-are-resolved-in-the-fact-never-the-grammar)
above) and `mvp-uat-framing` (`state:phase-mvp-mode`, sharing the atom
already computed for `progress.md`'s `mvp-display`). `mvp-uat-framing`'s
extraction is narrower than `progress.md`'s `mvp-display`: only the
true-branch prose (the three ordered UAT sections plus the User Story format
guard) moves into the step file — the false-branch note ("When `MVP_MODE=false`
… fall back to the standard UAT generation path") stays OUTSIDE the marker,
directly after it, because gating it away with the rest of the section would
delete the exact text needed on every invocation where the atom is `false`
(the common, non-MVP case). Unlike `progress.md`'s `mvp-display`, `verify-work.md`
keeps its own `MVP_MODE=$(gsd_run query phase.mvp-mode ...)` runtime resolver
(in the unconditional `initialize` step, not inside the gated section) — it is
not circular/self-disabling the way `progress.md`'s inline resolver was,
because the un-marked false-branch note and the step-file prose both still
reference `$MVP_MODE` as a runtime variable, so the resolver keeps a live
consumer outside the gate.
`code-review.md` marks two sections: `structural-pre-pass` (`state:fallow-enabled`)
and `dispatch-fix` (`flag:--fix`). `structural-pre-pass`'s own body used to
re-resolve its own gating fact via four `gsd_run query config-get
code_quality.fallow.*` calls — circular, for the same reason `progress.md`'s
pre-hoist `mvp-display` was: a section's body re-deriving the exact condition
that gated its own inclusion is self-disabling the moment the init seam's
computation and the body's computation drift. `cmdInitCodeReview` now resolves
`code_quality.fallow.{enabled,scope,profile,mcp}` once (`detectFallowConfig`,
`src/init.cts`) and exposes them as top-level `fallow_enabled`/`fallow_scope`/
`fallow_profile`/`fallow_mcp`/`fallow_max_crap` init-bundle fields; the
unconditional part of the `structural_pre_pass` step now just parses those
fields, and only the fallow-binary-resolve-and-execute portion (which produces
`FALLOW.json`) is gated behind the marker — the `FALLOW_JSON_PATH=""`
disabled-path fallback stays OUTSIDE the marker, directly after it, for the
same reason `verify-work.md`'s MVP false-branch note does (deleting it would
break the common, fallow-disabled case). `dispatch-fix` moves the entire
`--fix`-gated step wholesale (mirroring `progress.md`'s `forensic-audit`
extraction) — `code-review.md`'s `initialize` step now resolves the RESOLVED
fix decision (`--fix` itself, or `--all`/`--auto` implying it, via
`code-review-flags.cjs`) before the `init.code-review` call, so the
section-manifest gate matches the flags module's own implication logic rather
than a raw `--fix` token scan.
`complete-milestone.md` marks one section: `git-tag` (`state:git-create-tag`).
The `git_tag` step's own `<config-check>` sub-tag used to re-resolve
`git.create_tag` via `gsd-tools.cjs query config-get` to decide whether to
skip the step — again a section (here, a whole step) gating its own inclusion
on a fact its own body computed. `cmdInitCompleteMilestone` now resolves it
once (`detectGitCreateTag`, `src/init.cts`, fail-OPEN default `true` — an
unset key means "create the tag", the inverse polarity of `detectFallowConfig`'s
fail-closed default, mirroring the two source resolvers' own opposite
defaults) and exposes it as the init-bundle's `git_create_tag` field; the
entire `git_tag` step moves to its step file wholesale, with no
`<config-check>` left to re-derive. `complete-milestone.md` gains an
ADDITIVE `init.complete-milestone` call (in the `handle_branches` step,
alongside its pre-existing `init.manager` and `init.execute-phase` calls,
neither of which is removed) purely to carry `git_create_tag` and
`section_manifest` — it has no phase-listing logic of its own to delegate.
`autonomous.md` marks five sections, all sharing the single
`state:plan-strategy-converge` atom (legal and precedented — `plan-phase.md`'s
`research-only-*` pair already shares `flag:--research-phase`): `converge-fail-fast`
(the `workflow.plan_review_convergence` feature-gate check, split out of the
surrounding `CONVERGENCE_ARGS` bash block — that block's reviewer-flag/`--max-cycles`
parsing stays UNGATED, directly before the marker, because it always needs to
run regardless of `PLAN_STRATEGY`, and only the `if [ "$PLAN_STRATEGY" =
"converge" ]` fail-fast check moves into the step file), `converge-banner`
(a single display line — still a legitimate section per [Marker
syntax](#marker-syntax) above; `scripts/gen-section-manifest.cjs`'s
`FAIL_MISSING_STEP_FILE` check requires a step file for every explicit marker
regardless of body size, so the stub+step-file round trip is not optional
here even though the body is trivially small), `converge-dispatch-bg` and
`converge-dispatch-inline` (the `PLAN_STRATEGY=converge` branch of step 3b's
background/inline `FLATTEN` dispatch — an ORTHOGONAL condition interleaved in
the same list; each converge branch is independently contiguous and the
sibling `- Otherwise, print: ...`/`- Otherwise (local planning):` fallback
bullets stay OUTSIDE the marker, immediately after it, because they are the
`PLAN_STRATEGY=local` default that must always render), and `converge-loop`
(the unconditional-`INTERACTIVE` bottom-of-3b convergence dispatch, with the
`PLAN_STRATEGY=local` regular-planner fallback again staying outside).
`autonomous.md` keeps its own bash `PLAN_STRATEGY` resolver (`"local"` vs.
`"converge"`, folding `--converge` OR `--cross-ai`) in the UNCONDITIONAL
`initialize` step — never moved or removed — because ungated content later
in the same step (the "local" planning bullets) still references
`$PLAN_STRATEGY` as a runtime variable, same discipline as
`verify-work.md`'s retained `$MVP_MODE` resolver. `cmdInitAutonomous` mirrors
the identical disjunction into `InvocationFacts.planStrategyConverge`, and
`autonomous.md` gains an ADDITIVE `init.autonomous` call (in the
`initialize` step, alongside its pre-existing `init.milestone-op`,
`init.manager`, and `init.phase-op` calls — CRITICAL blast radius, none
removed, none modified) purely to carry `section_manifest`; like
`complete-milestone.md`'s entry point, it has no phase-listing logic of its
own to delegate.
`review.md` marks two sections, both sharing the single
`state:reviewer-instances-configured` atom (legal and precedented —
`plan-phase.md`'s `research-only-*` pair already shares
`flag:--research-phase`): `reviewer-instances-note-1` and
`reviewer-instances-note-2`, two peripheral additive notes in the
`detect_clis` and `invoke_reviewers` steps respectively. Neither note is
part of the workflow's core reviewer-lane dispatch — that dispatch is the
workflow's primary always-evaluated logic and is never gated. `review.md`
previously routed through the shared, 20+-caller `init.phase-op`, reading
only 3 of its ~60 fields (`phase_dir`, `phase_number`, `padded_phase`);
`cmdInitReview` now resolves those 3 fields itself via the same
`guardedFindPhase`/`guardedGetRoadmapPhase` primitives, plus the
`review.reviewer_instances` config-presence fact (reusing
`readConfigJsonValue`, added for `detectFallowConfig` — no second config
reader).
`discuss-phase-assumptions.md` marks one section: `auto-advance-dispatch`
(`state:auto-advance-active`), inside the `auto_advance` step. The step's
own `--auto`-flag parse, chain-flag sync, and consolidated `AUTO_MODE`
resolver (all of which must always run) stay OUTSIDE the marker; only the
flag-present display-banner-and-launch body is gated, and the flag-absent
"End here" fallback stays OUTSIDE the marker too, directly after it — gating
the whole step would delete the fallback text needed exactly when `--auto`
is absent, the same class of hazard `verify-work.md`'s MVP false-branch note
and `code-review.md`'s fallow-disabled fallback both document. `discuss-phase-assumptions.md`
previously routed through `init.phase-op`, reading 14 of its fields;
`cmdInitDiscussPhaseAssumptions` now resolves those 14 fields itself (via
the same shared primitives, reproducing `cmdInitPhaseOp`'s archived/not-found
fallback shape), plus `state:auto-advance-active` — `--auto` flag OR a
consolidated auto-mode config fact, resolved to one boolean the same way
`state:chunked-mode` is.
`docs-update.md` marks one section: `dispatch-monorepo-packages`
(`state:is-monorepo`) — the whole `dispatch_monorepo_packages` `<step>`,
which already carried a hand-written `condition="monorepo_workspaces is
non-empty"` attribute identifying it as the gated region before this slice.
`docs-update.md` previously called `docs-init` (`cmdDocsInit`, `src/docs.cts`)
only — a SEPARATE, pre-existing entry point outside the `init.*` family,
left untouched. The new `cmdInitDocsUpdate` is purely ADDITIVE, carrying only
`section_manifest`; its `isMonorepo` fact reuses `detectMonorepoWorkspaces`
(exported from `src/docs.cts` for this purpose) rather than a second,
divergence-prone workspace-glob scan — the SAME detector that already backs
`docs-init`'s own `monorepo_workspaces` field.
`update.md` marks one section: `channel-banner` (`state:next-channel`) — the
"Only when `TAG=next`" channel-banner paragraph in `compare_versions`.
`update.md` previously called NO `init.*` command at all; it resolves
`gsd-tools.cjs` itself via a bespoke `PREFERRED_CONFIG_DIR`/
`PREFERRED_RUNTIME`-aware `$GSD_TOOLS` cascade (`get_installed_version`,
~lines 13-45) because the update workflow must run before any install can be
assumed resolvable. The new `init.update` call REUSES that already-resolved
`$GSD_TOOLS` (dual `.cjs`/PATH-shim invocation style, matching the
pre-existing `update-context` call) rather than copying the canonical
launcher preamble — both resolutions assign the identical `$GSD_TOOLS` shell
variable, and the canonical preamble would silently clobber the value later
steps (`backup_custom_files`, `restore_custom_files`) still depend on.
`cmdInitUpdate`'s `nextChannel` fact (`--next` OR its documented alias
`--rc`) is resolved in PARALLEL with, not in place of, `update.md`'s own
`TAG="next"`/`TAG="latest"` case-statement in `parse_update_channel`, which
stays byte-identical — issue #815's regression test
(`tests/issue-815-update-next-channel.test.cjs`) asserts that literal
case-statement text remains in the workflow, since the npm dist-tag
selection has to run in the workflow's own shell before any `gsd_run`
round-trip.
`transition.md` marks one section: `workstream-collision-check`
(`state:workstream-active`) — an internal workflow (it has no user-facing
slash command of its own) that previously called NO `init.*` command at
all. It already establishes `gsd_run()` via the canonical launcher preamble
in its `update_roadmap_and_state` step, before this call's insertion point
in `offer_next_phase`, so no second preamble copy is needed. The section's
body previously re-derived its own gating condition via an inline `gsd_run
query workstream.list --raw` call wrapped in `if [ -n "$GSD_WORKSTREAM" ]`
— the identical condition that now backs the section's own admission (the
same resolver-in-body hazard `code-review.md`'s `structural-pre-pass` and
`complete-milestone.md`'s `git-tag` documented). `cmdInitTransition` hoists
this via `getOtherActiveWorkstreamInventories` (`src/workstream-inventory.cts`
— the SAME primitive `workstream.list` itself calls), pre-filtered exactly
as the step's own prose described (excludes the current workstream and any
workstream whose status contains "milestone complete" or "archived",
case-insensitively), exposed as `other_active_workstreams`; the step body is
now a pure JSON consumer with no `gsd_run` call of its own.
**`plan-phase.md` was originally retargeted away from the #2930 pilot,
then fragmentized here once the blocker cleared.** Issue #2930's own
motivating mutually-exclusive branches (`--prd`, `--ingest`, `--mvp`,
@@ -304,5 +633,5 @@ question 1's resolution for the full record.
`InvocationFacts`) consumed by the init seam.
- `scripts/gen-section-manifest.cjs` — generates the committed
`gsd-core/workflows/section-manifest.json` artifact from markers.
- `src/init.cts` — `buildSectionManifestField` and the six wired
- `src/init.cts` — `buildSectionManifestField` and the fifteen wired
`cmdInit*` entry points.

View File

@@ -0,0 +1,42 @@
# UI Design Contract (Frontend Phases) — Autonomous Mode
Step 3a.5 of `/gsd:autonomous`: resolve whether the current phase needs a UI-SPEC.md generated before planning, and generate one via active `plan:pre` step hooks if so. Always non-blocking — proceeds to step 3b (Plan) regardless of outcome.
**Inputs:** `PHASE_NUM`, `PHASE_DIR` from execute_phase.
Resolve active `plan:pre` hooks:
```bash
UI_SPEC_FILE=$(ls "${PHASE_DIR}"/*-UI-SPEC.md 2>/dev/null | head -1)
HOOKS_JSON=$(gsd_run loop render-hooks plan:pre --raw)
```
Read the `activeHooks` array directly from `HOOKS_JSON` (in-context — do NOT invoke a shell pipeline). **Compute the active UI step hooks** = entries from `activeHooks` where `kind == "step"` and `ref.skill` is set. **If there are NO active step hooks → skip silently to 3b.** (This covers `workflow.ui_phase=false` — including configurations where only a gate-only entry is present, e.g. `ui_phase=false` + `ui_safety_gate=true` produces `activeHooks=[{kind:"gate"}]`. Autonomous never runs the plan:pre gate — it is always pipeline mode — so a gate-only active set is equivalent to no active step and is silently skipped here. This matches OLD §3a.5 behaviour.)
(At least one active step hook ⇒ `workflow.ui_phase` is on.) Run the UI-SPEC gate:
```bash
GATE=$(gsd_run check ui-plan-gate "${PHASE_NUM}" --raw)
```
Read `frontend` and `hasUiSpec` from `GATE` (in-context).
**If `frontend` is false:** Skip silently to 3b.
**If `hasUiSpec` is true (UI-SPEC already exists):** Skip silently to 3b.
**Otherwise (frontend phase + no UI-SPEC):** For each active step hook (the `kind == "step"` set from above, in array order):
```
Skill(skill="gsd-${ref.skill}", args="${PHASE_NUM}")
```
(Prepend `gsd-` to `ref.skill` — so `ui-phase` → `gsd-ui-phase`. Bare `${PHASE_NUM}` args — autonomous style, same pattern as the verify:post dispatch.) Entries where `kind == "gate"` are silently ignored — autonomous is always pipeline mode, there is no blocking gate here.
After all step hooks return, re-read:
```bash
UI_SPEC_FILE=$(ls "${PHASE_DIR}"/*-UI-SPEC.md 2>/dev/null | head -1)
```
**If `UI_SPEC_FILE` is still empty:** Display warning `Phase ${PHASE_NUM}: UI-SPEC generation did not produce output — continuing without design contract.` and proceed to 3b. NON-BLOCKING.

View File

@@ -44,6 +44,15 @@ PLAN_STRATEGY="local"
if echo "$ARGUMENTS" | grep -qE '(^|[[:space:]])\-\-(converge|cross-ai)([[:space:]]|$)'; then
PLAN_STRATEGY="converge"
fi
CONVERGE_PARAM=""
if echo "$ARGUMENTS" | grep -qE '(^|[[:space:]])\-\-converge([[:space:]]|$)'; then
CONVERGE_PARAM="--converge"
fi
CROSS_AI_PARAM=""
if echo "$ARGUMENTS" | grep -qE '(^|[[:space:]])\-\-cross-ai([[:space:]]|$)'; then
CROSS_AI_PARAM="--cross-ai"
fi
```
When `--only` is set, also set `FROM_PHASE` to the same value so existing filter logic applies.
@@ -58,8 +67,12 @@ Bootstrap via milestone-level init:
_GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; if [ -f "$GSD_TOOLS" ]; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif command -v gsd-tools >/dev/null 2>&1; then GSD_TOOLS="$(command -v gsd-tools)"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif [ -f "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and gsd-tools is not on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
INIT=$(gsd_run query init.milestone-op)
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
INIT_AUTONOMOUS=$(gsd_run query init.autonomous $CONVERGE_PARAM $CROSS_AI_PARAM)
if [[ "$INIT_AUTONOMOUS" == @file:* ]]; then INIT_AUTONOMOUS=$(cat "${INIT_AUTONOMOUS#@file:}"); fi
```
Extract `section_manifest` from `INIT_AUTONOMOUS` (used by the `converge-*` sections below and in step 3).
If `PLAN_STRATEGY` is `converge`, fail fast unless the existing convergence feature gate is enabled:
```bash
@@ -79,23 +92,12 @@ if echo "$ARGUMENTS" | grep -qE '\-\-max-cycles\s+[0-9]+'; then
MAX_CYCLES_ARG=$(echo "$ARGUMENTS" | grep -oE '\-\-max-cycles\s+[0-9]+' | awk '{print $2}')
CONVERGENCE_ARGS="${CONVERGENCE_ARGS} --max-cycles ${MAX_CYCLES_ARG}"
fi
if [ "$PLAN_STRATEGY" = "converge" ]; then
CONVERGENCE_ENABLED=$(gsd_run query config-get workflow.plan_review_convergence 2>/dev/null || echo "false")
if [ "$CONVERGENCE_ENABLED" != "true" ]; then
printf '%s\n' \
'gsd-autonomous --converge is disabled (workflow.plan_review_convergence=false).' \
'' \
'Enable plan convergence with:' \
'' \
' gsd config-set workflow.plan_review_convergence true' \
'' \
'Then re-run the autonomous command with --converge.'
exit 1
fi
fi
```
<!-- gsd:section id="converge-fail-fast" when="state:plan-strategy-converge" -->
If `section_manifest` is `null` or `"converge-fail-fast"` is in its `included` list: read and execute `gsd-core/workflows/autonomous/steps/converge-fail-fast.md`. Otherwise skip — do not read the file.
<!-- /gsd:section -->
Parse JSON for: `milestone_version`, `milestone_name`, `phase_count`, `completed_phases`, `roadmap_exists`, `state_exists`, `commit_docs`.
**If `roadmap_exists` is false:** Error — "No ROADMAP.md found. Run `/gsd:new-milestone` first."
@@ -116,7 +118,9 @@ If `ONLY_PHASE` is set, display: `Single phase mode: Phase ${ONLY_PHASE}`
Else if `FROM_PHASE` is set, display: `Starting from phase ${FROM_PHASE}`
If `TO_PHASE` is set, display: `Stopping after phase ${TO_PHASE}`
If `INTERACTIVE` is set, display: `Mode: Interactive (discuss inline, plan+execute inline — background on Codex only)`
If `PLAN_STRATEGY` is `converge`, display: `Planning: Plan-review convergence enabled`
<!-- gsd:section id="converge-banner" when="state:plan-strategy-converge" -->
If `section_manifest` is `null` or `"converge-banner"` is in its `included` list: read and execute `gsd-core/workflows/autonomous/steps/converge-banner.md`. Otherwise skip — do not read the file.
<!-- /gsd:section -->
**Agent skills (delegated agents self-load):** This workflow delegates plan/execute/review via flat `Skill()` invocations rather than resolving `agent_skills` itself. Each consumer agent (`gsd-planner`, `gsd-executor`, `gsd-plan-checker`, `gsd-verifier`, …) self-loads its configured `.planning/config.json` `agent_skills` in its own mandatory init step per `@~/.claude/gsd-core/references/agent-skills-bootstrap.md`. This is the durable path that works on every runtime — including Cursor, where `Skill()`-delegated workflow bash init does not reliably execute. No per-delegation injection is needed here. See open-gsd/gsd-core#1866.
@@ -332,42 +336,11 @@ Check `has_context`. If false → go to handle_blocker: "Discuss for phase ${PHA
**3a.5. UI Design Contract (Frontend Phases)**
Resolve active `plan:pre` hooks:
> Full instructions are in `gsd-core/references/autonomous-ui-design-contract.md`. Read that file now and follow it exactly.
```bash
UI_SPEC_FILE=$(ls "${PHASE_DIR}"/*-UI-SPEC.md 2>/dev/null | head -1)
HOOKS_JSON=$(gsd_run loop render-hooks plan:pre --raw)
```
**Inputs:** `PHASE_NUM`, `PHASE_DIR` from execute_phase. Resolves whether the phase needs a UI-SPEC.md generated before planning via active `plan:pre` step hooks. Always non-blocking — proceeds to 3b regardless of outcome.
Read the `activeHooks` array directly from `HOOKS_JSON` (in-context — do NOT invoke a shell pipeline). **Compute the active UI step hooks** = entries from `activeHooks` where `kind == "step"` and `ref.skill` is set. **If there are NO active step hooks → skip silently to 3b.** (This covers `workflow.ui_phase=false` — including configurations where only a gate-only entry is present, e.g. `ui_phase=false` + `ui_safety_gate=true` produces `activeHooks=[{kind:"gate"}]`. Autonomous never runs the plan:pre gate — it is always pipeline mode — so a gate-only active set is equivalent to no active step and is silently skipped here. This matches OLD §3a.5 behaviour.)
(At least one active step hook ⇒ `workflow.ui_phase` is on.) Run the UI-SPEC gate:
```bash
GATE=$(gsd_run check ui-plan-gate "${PHASE_NUM}" --raw)
```
Read `frontend` and `hasUiSpec` from `GATE` (in-context).
**If `frontend` is false:** Skip silently to 3b.
**If `hasUiSpec` is true (UI-SPEC already exists):** Skip silently to 3b.
**Otherwise (frontend phase + no UI-SPEC):** For each active step hook (the `kind == "step"` set from above, in array order):
```
Skill(skill="gsd-${ref.skill}", args="${PHASE_NUM}")
```
(Prepend `gsd-` to `ref.skill` — so `ui-phase` → `gsd-ui-phase`. Bare `${PHASE_NUM}` args — autonomous style, same pattern as the verify:post dispatch.) Entries where `kind == "gate"` are silently ignored — autonomous is always pipeline mode, there is no blocking gate here.
After all step hooks return, re-read:
```bash
UI_SPEC_FILE=$(ls "${PHASE_DIR}"/*-UI-SPEC.md 2>/dev/null | head -1)
```
**If `UI_SPEC_FILE` is still empty:** Display warning `Phase ${PHASE_NUM}: UI-SPEC generation did not produce output — continuing without design contract.` and proceed to 3b. NON-BLOCKING.
Read and execute: `$HOME/.claude/gsd-core/references/autonomous-ui-design-contract.md`
**3b. Plan**
@@ -379,15 +352,9 @@ FLATTEN=$(gsd_run query dispatch-should-flatten --raw 2>/dev/null || echo "true"
- **If `FLATTEN` is `false`:** Dispatch plan as a background agent to keep the main context lean. While plan runs, the workflow can immediately start discussing the next phase (see step 4).
- If `PLAN_STRATEGY=converge`, print: `◆ Spawning background plan-convergence loop for phase ${PHASE_NUM}... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)`
```
Agent(
description="Plan convergence phase ${PHASE_NUM}: ${PHASE_NAME}",
run_in_background=true,
prompt="Run plan convergence for phase ${PHASE_NUM}: Skill(skill=\"gsd-plan-review-convergence\", args=\"${PHASE_NUM} ${CONVERGENCE_ARGS}\")"
)
```
<!-- gsd:section id="converge-dispatch-bg" when="state:plan-strategy-converge" -->
If `section_manifest` is `null` or `"converge-dispatch-bg"` is in its `included` list: read and execute `gsd-core/workflows/autonomous/steps/converge-dispatch-bg.md`. Otherwise skip — do not read the file.
<!-- /gsd:section -->
- Otherwise, print: `◆ Spawning background planner for phase ${PHASE_NUM}... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)`
@@ -403,11 +370,9 @@ FLATTEN=$(gsd_run query dispatch-should-flatten --raw 2>/dev/null || echo "true"
- **Otherwise (`FLATTEN` is `true` — run inline):** Run plan **inline** (do NOT background) so the plan-checker runs. The next phase's discuss does not overlap planning here — correctness over overlap.
- If `PLAN_STRATEGY=converge`:
```
Skill(skill="gsd-plan-review-convergence", args="${PHASE_NUM} ${CONVERGENCE_ARGS}")
```
<!-- gsd:section id="converge-dispatch-inline" when="state:plan-strategy-converge" -->
If `section_manifest` is `null` or `"converge-dispatch-inline"` is in its `included` list: read and execute `gsd-core/workflows/autonomous/steps/converge-dispatch-inline.md`. Otherwise skip — do not read the file.
<!-- /gsd:section -->
- Otherwise (local planning):
@@ -417,11 +382,9 @@ FLATTEN=$(gsd_run query dispatch-should-flatten --raw 2>/dev/null || echo "true"
**If `INTERACTIVE` is NOT set (default):** Run plan inline.
If `PLAN_STRATEGY=converge`, run the convergence loop:
```
Skill(skill="gsd-plan-review-convergence", args="${PHASE_NUM} ${CONVERGENCE_ARGS}")
```
<!-- gsd:section id="converge-loop" when="state:plan-strategy-converge" -->
If `section_manifest` is `null` or `"converge-loop"` is in its `included` list: read and execute `gsd-core/workflows/autonomous/steps/converge-loop.md`. Otherwise skip — do not read the file.
<!-- /gsd:section -->
If `PLAN_STRATEGY=local`, run the regular planner:

View File

@@ -0,0 +1 @@
If `PLAN_STRATEGY` is `converge`, display: `Planning: Plan-review convergence enabled`

View File

@@ -0,0 +1,11 @@
## Converge Dispatch (Background)
If `PLAN_STRATEGY=converge`, print: `◆ Spawning background plan-convergence loop for phase ${PHASE_NUM}... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)`
```
Agent(
description="Plan convergence phase ${PHASE_NUM}: ${PHASE_NAME}",
run_in_background=true,
prompt="Run plan convergence for phase ${PHASE_NUM}: Skill(skill=\"gsd-plan-review-convergence\", args=\"${PHASE_NUM} ${CONVERGENCE_ARGS}\")"
)
```

View File

@@ -0,0 +1,7 @@
## Converge Dispatch (Inline)
If `PLAN_STRATEGY=converge`:
```
Skill(skill="gsd-plan-review-convergence", args="${PHASE_NUM} ${CONVERGENCE_ARGS}")
```

View File

@@ -0,0 +1,21 @@
## Converge Fail-Fast
When `PLAN_STRATEGY` is `converge`, fail fast unless the existing convergence feature gate is enabled:
```bash
_GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; if [ -f "$GSD_TOOLS" ]; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif command -v gsd-tools >/dev/null 2>&1; then GSD_TOOLS="$(command -v gsd-tools)"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif [ -f "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and gsd-tools is not on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
if [ "$PLAN_STRATEGY" = "converge" ]; then
CONVERGENCE_ENABLED=$(gsd_run query config-get workflow.plan_review_convergence 2>/dev/null || echo "false")
if [ "$CONVERGENCE_ENABLED" != "true" ]; then
printf '%s\n' \
'gsd-autonomous --converge is disabled (workflow.plan_review_convergence=false).' \
'' \
'Enable plan convergence with:' \
'' \
' gsd config-set workflow.plan_review_convergence true' \
'' \
'Then re-run the autonomous command with --converge.'
exit 1
fi
fi
```

View File

@@ -0,0 +1,7 @@
## Converge Loop
If `PLAN_STRATEGY=converge`, run the convergence loop:
```
Skill(skill="gsd-plan-review-convergence", args="${PHASE_NUM} ${CONVERGENCE_ARGS}")
```

View File

@@ -19,7 +19,33 @@ Parse arguments and load project state:
```bash
_GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; if [ -f "$GSD_TOOLS" ]; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif command -v gsd-tools >/dev/null 2>&1; then GSD_TOOLS="$(command -v gsd-tools)"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif [ -f "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and gsd-tools is not on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
PHASE_ARG="${1}"
INIT=$(gsd_run query init.phase-op "${PHASE_ARG}")
# Parse all code-review flags into a structured IR via code-review-flags.cjs.
# This is the canonical flag-parsing surface — do not replicate inline bash parsing
# for --fix/--all/--auto here; the module handles all flag extraction and implication
# logic (e.g., --all and --auto imply --fix). Resolved BEFORE the init call below so
# the section-manifest gate forwards the RESOLVED (post-implication) fix decision, not
# just a literal --fix token check.
FLAGS_JSON=$(node -e "
const { parseCodeReviewFlags } = require('./gsd-core/bin/lib/code-review-flags.cjs');
const flags = parseCodeReviewFlags(process.argv.slice(1));
process.stdout.write(JSON.stringify(flags));
" -- "$@" 2>/dev/null)
# Extract individual flag values from the IR
FIX_FLAG=$(echo "$FLAGS_JSON" | node -e "process.stdout.write(String(JSON.parse(require('fs').readFileSync('/dev/stdin','utf-8')).fix))")
FIX_ALL=$(echo "$FLAGS_JSON" | node -e "process.stdout.write(String(JSON.parse(require('fs').readFileSync('/dev/stdin','utf-8')).all))")
FIX_AUTO=$(echo "$FLAGS_JSON" | node -e "process.stdout.write(String(JSON.parse(require('fs').readFileSync('/dev/stdin','utf-8')).auto))")
DEPTH_OVERRIDE=$(echo "$FLAGS_JSON" | node -e "process.stdout.write(JSON.parse(require('fs').readFileSync('/dev/stdin','utf-8')).depth)")
FILES_OVERRIDE=$(echo "$FLAGS_JSON" | node -e "process.stdout.write(JSON.parse(require('fs').readFileSync('/dev/stdin','utf-8')).files)")
# Forward the resolved fix decision (--fix itself, or --all/--auto implying it) to
# init.code-review so section_manifest's flag:--fix gating (dispatch-fix section)
# matches code-review-flags.cjs's own implication logic rather than a raw token scan.
FIX_PARAM=""
if [ "$FIX_FLAG" = "true" ]; then FIX_PARAM="--fix"; fi
INIT=$(gsd_run query init.code-review "${PHASE_ARG}" $FIX_PARAM)
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
AGENT_SKILLS_REVIEWER=$(gsd_run query agent-skills gsd-code-reviewer)
# #2072: resolve the routed model so model_overrides / models.verification are honored
@@ -27,7 +53,7 @@ AGENT_SKILLS_REVIEWER=$(gsd_run query agent-skills gsd-code-reviewer)
REVIEWER_MODEL=$(gsd_run query resolve-model gsd-code-reviewer --raw)
```
Parse from init JSON: `phase_found`, `phase_dir`, `phase_number`, `phase_name`, `padded_phase`, `commit_docs`.
Parse from init JSON: `phase_found`, `phase_dir`, `phase_number`, `phase_name`, `padded_phase`, `commit_docs`, `fallow_enabled`, `fallow_scope`, `fallow_profile`, `fallow_mcp`, `fallow_max_crap`.
**Input sanitization (defense-in-depth):**
```bash
@@ -46,27 +72,6 @@ Error: Phase ${PHASE_ARG} not found. Run /gsd:progress to see available phases.
This runs BEFORE config gate check so user errors are surfaced immediately regardless of config state.
Parse optional flags from $ARGUMENTS using the typed flag parser:
```bash
# Parse all code-review flags into a structured IR via code-review-flags.cjs.
# This is the canonical flag-parsing surface — do not replicate inline bash parsing
# for --fix/--all/--auto here; the module handles all flag extraction and implication
# logic (e.g., --all and --auto imply --fix).
FLAGS_JSON=$(node -e "
const { parseCodeReviewFlags } = require('./gsd-core/bin/lib/code-review-flags.cjs');
const flags = parseCodeReviewFlags(process.argv.slice(1));
process.stdout.write(JSON.stringify(flags));
" -- "$@" 2>/dev/null)
# Extract individual flag values from the IR
FIX_FLAG=$(echo "$FLAGS_JSON" | node -e "process.stdout.write(String(JSON.parse(require('fs').readFileSync('/dev/stdin','utf-8')).fix))")
FIX_ALL=$(echo "$FLAGS_JSON" | node -e "process.stdout.write(String(JSON.parse(require('fs').readFileSync('/dev/stdin','utf-8')).all))")
FIX_AUTO=$(echo "$FLAGS_JSON" | node -e "process.stdout.write(String(JSON.parse(require('fs').readFileSync('/dev/stdin','utf-8')).auto))")
DEPTH_OVERRIDE=$(echo "$FLAGS_JSON" | node -e "process.stdout.write(JSON.parse(require('fs').readFileSync('/dev/stdin','utf-8')).depth)")
FILES_OVERRIDE=$(echo "$FLAGS_JSON" | node -e "process.stdout.write(JSON.parse(require('fs').readFileSync('/dev/stdin','utf-8')).files)")
```
If FILES_OVERRIDE is set, split by comma into array:
```bash
if [ -n "$FILES_OVERRIDE" ]; then
@@ -389,20 +394,7 @@ Exit workflow. Do NOT spawn agent or create REVIEW.md.
<step name="structural_pre_pass">
Optional structural cross-module pass powered by fallow.
Read fallow config gates:
```bash
FALLOW_ENABLED=$(gsd_run query config-get code_quality.fallow.enabled 2>/dev/null || echo "false")
FALLOW_SCOPE=$(gsd_run query config-get code_quality.fallow.scope 2>/dev/null || echo "phase")
FALLOW_PROFILE=$(gsd_run query config-get code_quality.fallow.profile 2>/dev/null || echo "standard")
FALLOW_MCP=$(gsd_run query config-get code_quality.fallow.mcp 2>/dev/null || echo "false")
# profile maps to a --max-crap threshold since fallow has no native profile concept.
# minimal=50 (more lenient), standard=30 (default), strict=15 (tighter).
case "$FALLOW_PROFILE" in
minimal) FALLOW_MAX_CRAP=50 ;;
strict) FALLOW_MAX_CRAP=15 ;;
*) FALLOW_MAX_CRAP=30 ;; # standard (default)
esac
```
Parse `fallow_enabled`, `fallow_scope`, `fallow_profile`, `fallow_mcp`, `fallow_max_crap` from the init JSON as `FALLOW_ENABLED`, `FALLOW_SCOPE`, `FALLOW_PROFILE`, `FALLOW_MCP`, `FALLOW_MAX_CRAP`. These are resolved once by `init.code-review` at init time — consuming the pre-resolved values here (instead of a `config-get` call inside this step) avoids gating this section's own inclusion on a fact its own body would otherwise compute (see `state:fallow-enabled` in docs/reference/workflow-fragments.md).
Defaults are fail-closed and opt-in:
- `enabled=false` (skip entirely)
@@ -410,88 +402,9 @@ Defaults are fail-closed and opt-in:
- `profile=standard` (maps to `--max-crap 30`; minimal=50, standard=30, strict=15 — fallow has no native profile concept)
- `mcp=false`
When `FALLOW_ENABLED=true`:
1) Resolve binary via PATH first, then `node_modules/.bin/fallow`.
```bash
FALLOW_BIN=$(FALLOW_CWD="$(pwd)" node -e "
const { resolveFallowBinary } = require('./gsd-core/bin/lib/fallow-runner.cjs');
const resolved = resolveFallowBinary({ cwd: process.env.FALLOW_CWD });
if (resolved) process.stdout.write(resolved);
")
```
2) If binary is missing, fail with actionable message:
```bash
if [ -z \"$FALLOW_BIN\" ]; then
echo \"Error: fallow is enabled but no binary was found.\"
echo \"Install fallow via \`npm install -D fallow\` or \`cargo install fallow\`.\"
# Exit workflow
fi
```
3) Execute structural pass and persist JSON (bounded at 120s). Note: `fallow audit` exits 0 when clean and 1 when issues are found — BOTH are successful runs. Only a timeout (124), usage error (2), or crash yields no usable JSON; success is decided by whether the output parses as a valid fallow report, not by exit code:
```bash
FALLOW_JSON_PATH="${PHASE_DIR}/FALLOW.json"
FALLOW_STDERR_TMP=$(mktemp)
# Phase scope uses fallow's native changed-files scoping (--changed-since <base>).
# Derive the phase base commit; if none is found, fall back to repo scope (fallow
# auto-detects the base branch).
FALLOW_SCOPE_ARGS=()
if [ \"$FALLOW_SCOPE\" = \"phase\" ]; then
FALLOW_PHASE_COMMITS=$(git log --oneline --all --grep=\"${PADDED_PHASE}\" --format=\"%H\" 2>/dev/null)
if [ -n \"$FALLOW_PHASE_COMMITS\" ]; then
FALLOW_BASE=$(echo \"$FALLOW_PHASE_COMMITS\" | tail -1)^
FALLOW_SCOPE_ARGS=(--changed-since \"$FALLOW_BASE\")
fi
fi
gsd_run run-with-timeout 120 -- \"$FALLOW_BIN\" audit --format json --quiet --max-crap \"$FALLOW_MAX_CRAP\" \"${FALLOW_SCOPE_ARGS[@]+\"${FALLOW_SCOPE_ARGS[@]}\"}\" > \"${FALLOW_JSON_PATH}.tmp\" 2>\"$FALLOW_STDERR_TMP\"
FALLOW_EXIT=$?
# fallow exits 0 (clean) or 1 (issues found) — BOTH are successful runs that produce a
# valid JSON report. Only a timeout (124), usage error (2), or crash yields no usable JSON.
# Decide success by whether the output parses as a fallow report, not by exit code.
FALLOW_OK=$(FALLOW_TMP=\"${FALLOW_JSON_PATH}.tmp\" node -e \"
try {
const fs = require('fs');
const txt = fs.readFileSync(process.env.FALLOW_TMP, 'utf8');
const o = JSON.parse(txt);
process.stdout.write(o && typeof o === 'object' && 'verdict' in o ? '1' : '0');
} catch { process.stdout.write('0'); }
\")
if [ \"$FALLOW_OK\" != \"1\" ]; then
FALLOW_STDERR_SUMMARY=$(head -5 \"$FALLOW_STDERR_TMP\")
rm -f \"${FALLOW_JSON_PATH}.tmp\" \"$FALLOW_STDERR_TMP\"
# #2667: distinguish a hard EXECUTION failure (the binary was found at step 1
# but would not run) from the binary-missing path (step 2). Exit 124 = timeout,
# 2 = usage error, 125 = spawn failure (e.g. Windows EINVAL on a .cmd shim —
# CVE-2024-27980, now mediated by run-with-timeout), 126/127 = not executable /
# not found. A non-zero exit here with a resolved binary means fallow is
# installed but did not produce a report — surface that loudly so a Windows
# user does not mistake it for "fallow absent".
case \"$FALLOW_EXIT\" in
124) FALLOW_FAIL_KIND=\"timed out\" ;;
2) FALLOW_FAIL_KIND=\"usage error\" ;;
125) FALLOW_FAIL_KIND=\"spawn failure (the binary was found but did not start — e.g. a Windows .cmd shim; run-with-timeout mediates this)\" ;;
126) FALLOW_FAIL_KIND=\"not executable\" ;;
127) FALLOW_FAIL_KIND=\"not found\" ;;
*) FALLOW_FAIL_KIND=\"crashed\" ;;
esac
echo \"WARNING: fallow structural pre-pass failed (${FALLOW_FAIL_KIND}, exit ${FALLOW_EXIT}): ${FALLOW_STDERR_SUMMARY}\"
FALLOW_JSON_PATH=\"\"
else
mv \"${FALLOW_JSON_PATH}.tmp\" \"$FALLOW_JSON_PATH\"
rm -f \"$FALLOW_STDERR_TMP\"
fi
```
On any failure of the structural pre-pass (binary missing at step 2, or an execution failure here — timeout, spawn failure, crash, empty output, or unparseable JSON), the workflow continues with no `<structural_findings>` injection; the reviewer agent receives a normal review request. The WARNING above names the failure KIND so a hard execution failure (e.g. a Windows `.cmd` spawn failure) is not mistaken for an absent optional dependency.
4) Optional MCP bridge path (runtime-dependent):
- If `FALLOW_MCP=true`, set reviewer input mode to MCP-backed structural findings.
- Otherwise pass static JSON findings from `FALLOW.json`.
<!-- gsd:section id="structural-pre-pass" when="state:fallow-enabled" -->
If `section_manifest` is `null` or `"structural-pre-pass"` is in its `included` list: read and execute `gsd-core/workflows/code-review/steps/structural-pre-pass.md`. Otherwise skip — do not read the file.
<!-- /gsd:section -->
When disabled, set:
```bash
@@ -636,45 +549,9 @@ fi
```
</step>
<step name="dispatch_fix">
If the `--fix` flag was passed (`FIX_FLAG=true`), delegate to the `code-review-fix.md` workflow
to auto-apply findings from the REVIEW.md that was just written (or that already existed).
This step runs AFTER `commit_review` so REVIEW.md is guaranteed to be on disk before the fixer
is invoked. If REVIEW.md was not created (agent failed, scope was empty, etc.), the `code-review-fix.md`
workflow handles the missing-review error and exits cleanly.
```bash
if [ "$FIX_FLAG" = "true" ]; then
echo ""
echo "─────────────────────────────────────────────────────────────────"
echo " --fix: delegating to code-review-fix.md"
echo "─────────────────────────────────────────────────────────────────"
echo ""
# Build the fix sub-arguments: pass phase arg plus any --all/--auto flags
FIX_ARGS="${PHASE_ARG}"
if [ "$FIX_ALL" = "true" ]; then
FIX_ARGS="${FIX_ARGS} --all"
fi
if [ "$FIX_AUTO" = "true" ]; then
FIX_ARGS="${FIX_ARGS} --auto"
fi
# Load and execute the code-review-fix workflow.
# The fix workflow is the canonical implementation for all fix logic:
# gsd-code-fixer agent dispatch, --auto iteration loop, REVIEW-FIX.md commit,
# and result presentation. Do not duplicate that logic here.
Workflow(workflow="gsd-core/workflows/code-review-fix.md", args="${FIX_ARGS}")
# Exit after fix workflow completes — present_results is for review-only output.
# The fix workflow has its own present_results step.
# Exit workflow.
fi
```
If `FIX_FLAG` is false, skip this step entirely and proceed to `present_results`.
</step>
<!-- gsd:section id="dispatch-fix" when="flag:--fix" -->
If `section_manifest` is `null` or `"dispatch-fix"` is in its `included` list: read and execute `gsd-core/workflows/code-review/steps/dispatch-fix.md`. Otherwise skip — do not read the file; proceed to `present_results`.
<!-- /gsd:section -->
<step name="present_results">
Read the REVIEW.md YAML frontmatter to extract finding counts.

View File

@@ -0,0 +1,39 @@
<step name="dispatch_fix">
If the `--fix` flag was passed (`FIX_FLAG=true`), delegate to the `code-review-fix.md` workflow
to auto-apply findings from the REVIEW.md that was just written (or that already existed).
This step runs AFTER `commit_review` so REVIEW.md is guaranteed to be on disk before the fixer
is invoked. If REVIEW.md was not created (agent failed, scope was empty, etc.), the `code-review-fix.md`
workflow handles the missing-review error and exits cleanly.
```bash
if [ "$FIX_FLAG" = "true" ]; then
echo ""
echo "─────────────────────────────────────────────────────────────────"
echo " --fix: delegating to code-review-fix.md"
echo "─────────────────────────────────────────────────────────────────"
echo ""
# Build the fix sub-arguments: pass phase arg plus any --all/--auto flags
FIX_ARGS="${PHASE_ARG}"
if [ "$FIX_ALL" = "true" ]; then
FIX_ARGS="${FIX_ARGS} --all"
fi
if [ "$FIX_AUTO" = "true" ]; then
FIX_ARGS="${FIX_ARGS} --auto"
fi
# Load and execute the code-review-fix workflow.
# The fix workflow is the canonical implementation for all fix logic:
# gsd-code-fixer agent dispatch, --auto iteration loop, REVIEW-FIX.md commit,
# and result presentation. Do not duplicate that logic here.
Workflow(workflow="gsd-core/workflows/code-review-fix.md", args="${FIX_ARGS}")
# Exit after fix workflow completes — present_results is for review-only output.
# The fix workflow has its own present_results step.
# Exit workflow.
fi
```
If `FIX_FLAG` is false, skip this step entirely and proceed to `present_results`.
</step>

View File

@@ -0,0 +1,83 @@
When `FALLOW_ENABLED=true`:
1) Resolve binary via PATH first, then `node_modules/.bin/fallow`.
```bash
FALLOW_BIN=$(FALLOW_CWD="$(pwd)" node -e "
const { resolveFallowBinary } = require('./gsd-core/bin/lib/fallow-runner.cjs');
const resolved = resolveFallowBinary({ cwd: process.env.FALLOW_CWD });
if (resolved) process.stdout.write(resolved);
")
```
2) If binary is missing, fail with actionable message:
```bash
if [ -z \"$FALLOW_BIN\" ]; then
echo \"Error: fallow is enabled but no binary was found.\"
echo \"Install fallow via \`npm install -D fallow\` or \`cargo install fallow\`.\"
# Exit workflow
fi
```
3) Execute structural pass and persist JSON (bounded at 120s). Note: `fallow audit` exits 0 when clean and 1 when issues are found — BOTH are successful runs. Only a timeout (124), usage error (2), or crash yields no usable JSON; success is decided by whether the output parses as a valid fallow report, not by exit code:
```bash
_GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; if [ -f "$GSD_TOOLS" ]; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif command -v gsd-tools >/dev/null 2>&1; then GSD_TOOLS="$(command -v gsd-tools)"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif [ -f "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and gsd-tools is not on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
FALLOW_JSON_PATH="${PHASE_DIR}/FALLOW.json"
FALLOW_STDERR_TMP=$(mktemp)
# Phase scope uses fallow's native changed-files scoping (--changed-since <base>).
# Derive the phase base commit; if none is found, fall back to repo scope (fallow
# auto-detects the base branch).
FALLOW_SCOPE_ARGS=()
if [ \"$FALLOW_SCOPE\" = \"phase\" ]; then
FALLOW_PHASE_COMMITS=$(git log --oneline --all --grep=\"${PADDED_PHASE}\" --format=\"%H\" 2>/dev/null)
if [ -n \"$FALLOW_PHASE_COMMITS\" ]; then
FALLOW_BASE=$(echo \"$FALLOW_PHASE_COMMITS\" | tail -1)^
FALLOW_SCOPE_ARGS=(--changed-since \"$FALLOW_BASE\")
fi
fi
gsd_run run-with-timeout 120 -- \"$FALLOW_BIN\" audit --format json --quiet --max-crap \"$FALLOW_MAX_CRAP\" \"${FALLOW_SCOPE_ARGS[@]+\"${FALLOW_SCOPE_ARGS[@]}\"}\" > \"${FALLOW_JSON_PATH}.tmp\" 2>\"$FALLOW_STDERR_TMP\"
FALLOW_EXIT=$?
# fallow exits 0 (clean) or 1 (issues found) — BOTH are successful runs that produce a
# valid JSON report. Only a timeout (124), usage error (2), or crash yields no usable JSON.
# Decide success by whether the output parses as a fallow report, not by exit code.
FALLOW_OK=$(FALLOW_TMP=\"${FALLOW_JSON_PATH}.tmp\" node -e \"
try {
const fs = require('fs');
const txt = fs.readFileSync(process.env.FALLOW_TMP, 'utf8');
const o = JSON.parse(txt);
process.stdout.write(o && typeof o === 'object' && 'verdict' in o ? '1' : '0');
} catch { process.stdout.write('0'); }
\")
if [ \"$FALLOW_OK\" != \"1\" ]; then
FALLOW_STDERR_SUMMARY=$(head -5 \"$FALLOW_STDERR_TMP\")
rm -f \"${FALLOW_JSON_PATH}.tmp\" \"$FALLOW_STDERR_TMP\"
# #2667: distinguish a hard EXECUTION failure (the binary was found at step 1
# but would not run) from the binary-missing path (step 2). Exit 124 = timeout,
# 2 = usage error, 125 = spawn failure (e.g. Windows EINVAL on a .cmd shim —
# CVE-2024-27980, now mediated by run-with-timeout), 126/127 = not executable /
# not found. A non-zero exit here with a resolved binary means fallow is
# installed but did not produce a report — surface that loudly so a Windows
# user does not mistake it for "fallow absent".
case \"$FALLOW_EXIT\" in
124) FALLOW_FAIL_KIND=\"timed out\" ;;
2) FALLOW_FAIL_KIND=\"usage error\" ;;
125) FALLOW_FAIL_KIND=\"spawn failure (the binary was found but did not start — e.g. a Windows .cmd shim; run-with-timeout mediates this)\" ;;
126) FALLOW_FAIL_KIND=\"not executable\" ;;
127) FALLOW_FAIL_KIND=\"not found\" ;;
*) FALLOW_FAIL_KIND=\"crashed\" ;;
esac
echo \"WARNING: fallow structural pre-pass failed (${FALLOW_FAIL_KIND}, exit ${FALLOW_EXIT}): ${FALLOW_STDERR_SUMMARY}\"
FALLOW_JSON_PATH=\"\"
else
mv \"${FALLOW_JSON_PATH}.tmp\" \"$FALLOW_JSON_PATH\"
rm -f \"$FALLOW_STDERR_TMP\"
fi
```
On any failure of the structural pre-pass (binary missing at step 2, or an execution failure here — timeout, spawn failure, crash, empty output, or unparseable JSON), the workflow continues with no `<structural_findings>` injection; the reviewer agent receives a normal review request. The WARNING above names the failure KIND so a hard execution failure (e.g. a Windows `.cmd` spawn failure) is not mistaken for an absent optional dependency.
4) Optional MCP bridge path (runtime-dependent):
- If `FALLOW_MCP=true`, set reviewer input mode to MCP-backed structural findings.
- Otherwise pass static JSON findings from `FALLOW.json`.

View File

@@ -592,9 +592,11 @@ Use `init milestone-op` for context, or load config directly:
```bash
INIT=$(gsd_run query init.execute-phase "1")
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
INIT_CM=$(gsd_run query init.complete-milestone)
if [[ "$INIT_CM" == @file:* ]]; then INIT_CM=$(cat "${INIT_CM#@file:}"); fi
```
Extract `branching_strategy`, `phase_branch_template`, `milestone_branch_template`, and `commit_docs` from init JSON.
Extract `branching_strategy`, `phase_branch_template`, `milestone_branch_template`, and `commit_docs` from init JSON. Extract `git_create_tag` and `section_manifest` from `INIT_CM` (used by the `git_tag` step below).
Detect base branch:
```bash
@@ -711,40 +713,9 @@ fi
</step>
<step name="git_tag">
<config-check>
Read `git.create_tag` via `gsd-tools.cjs query config-get git.create_tag 2>/dev/null || echo "true"`.
If the result is `false` → skip this step entirely and proceed to `git_commit_milestone`.
</config-check>
Create git tag:
```bash
# Pre-check: skip if tag already exists (prevents silent failure on retry)
if git rev-parse "v[X.Y]" >/dev/null 2>&1; then echo "Tag v[X.Y] already exists, skipping"; exit 0; fi
git tag -a v[X.Y] -m "v[X.Y] [Name]
Delivered: [One sentence]
Key accomplishments:
- [Item 1]
- [Item 2]
- [Item 3]
See .planning/MILESTONES.md for full details."
```
Confirm: "Tagged: v[X.Y]"
Ask: "Push tag to remote? (y/n)"
If yes:
```bash
git push origin v[X.Y]
```
</step>
<!-- gsd:section id="git-tag" when="state:git-create-tag" -->
If `section_manifest` is `null` or `"git-tag"` is in its `included` list: read and execute `gsd-core/workflows/complete-milestone/steps/git-tag.md`. Otherwise skip — do not read the file; proceed to `git_commit_milestone`.
<!-- /gsd:section -->
<step name="git_commit_milestone">

View File

@@ -0,0 +1,29 @@
<step name="git_tag">
Create git tag:
```bash
# Pre-check: skip if tag already exists (prevents silent failure on retry)
if git rev-parse "v[X.Y]" >/dev/null 2>&1; then echo "Tag v[X.Y] already exists, skipping"; exit 0; fi
git tag -a v[X.Y] -m "v[X.Y] [Name]
Delivered: [One sentence]
Key accomplishments:
- [Item 1]
- [Item 2]
- [Item 3]
See .planning/MILESTONES.md for full details."
```
Confirm: "Tagged: v[X.Y]"
Ask: "Push tag to remote? (y/n)"
If yes:
```bash
git push origin v[X.Y]
```
</step>

View File

@@ -64,7 +64,8 @@ Phase number from argument (required).
```bash
_GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; if [ -f "$GSD_TOOLS" ]; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif command -v gsd-tools >/dev/null 2>&1; then GSD_TOOLS="$(command -v gsd-tools)"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif [ -f "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and gsd-tools is not on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
RESPONSE_LANGUAGE=$(gsd_run query config-get response_language --default "" 2>/dev/null || echo "")
INIT=$(gsd_run query init.phase-op "${PHASE}")
AUTO_PARAM=""; if [[ "$ARGUMENTS" =~ (^|[[:space:]])--auto([[:space:]]|$) ]]; then AUTO_PARAM="--auto"; fi
INIT=$(gsd_run query init.discuss-phase-assumptions "${PHASE}" $AUTO_PARAM)
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
AGENT_SKILLS_ANALYZER=$(gsd_run query agent-skills gsd-assumptions-analyzer)
# #2072: resolve the routed model so model_overrides / models.discuss are honored
@@ -649,21 +650,9 @@ Check for auto-advance trigger:
gsd_run query config-set workflow._auto_chain_active true
```
**If `--auto` flag present OR `AUTO_MODE` is true:**
Display banner:
```text
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
GSD ► AUTO-ADVANCING TO PLAN
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Context captured (assumptions mode). Launching plan-phase...
```
Launch: `Skill(skill="gsd-plan-phase", args="${PHASE} --auto")`
Handle return: PHASE COMPLETE / PLANNING COMPLETE / INCONCLUSIVE / GAPS FOUND
(identical handling to discuss-phase.md auto_advance step)
<!-- gsd:section id="auto-advance-dispatch" when="state:auto-advance-active" -->
If `section_manifest` is `null` or `"auto-advance-dispatch"` is in its `included` list: read and execute `gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md`. Otherwise skip — do not read the file.
<!-- /gsd:section -->
**If neither `--auto` nor config enabled:**
End here — `confirm_creation` already ran; do not route back to it.

View File

@@ -0,0 +1,15 @@
**If `--auto` flag present OR `AUTO_MODE` is true:**
Display banner:
```text
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
GSD ► AUTO-ADVANCING TO PLAN
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Context captured (assumptions mode). Launching plan-phase...
```
Launch: `Skill(skill="gsd-plan-phase", args="${PHASE} --auto")`
Handle return: PHASE COMPLETE / PLANNING COMPLETE / INCONCLUSIVE / GAPS FOUND
(identical handling to discuss-phase.md auto_advance step)

View File

@@ -18,6 +18,10 @@ _GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-pars
INIT=$(gsd_run query docs-init)
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
AGENT_SKILLS=$(gsd_run query agent-skills gsd-doc-writer)
# #2994: dedicated init.docs-update call — additive to docs-init above, carries
# only the section_manifest field (gates dispatch_monorepo_packages).
INIT_DOCS_UPDATE=$(gsd_run query init.docs-update)
if [[ "$INIT_DOCS_UPDATE" == @file:* ]]; then INIT_DOCS_UPDATE=$(cat "${INIT_DOCS_UPDATE#@file:}"); fi
```
Extract from init JSON:
@@ -27,6 +31,7 @@ Extract from init JSON:
- `project_type` — object with boolean signals: `has_package_json`, `has_api_routes`, `has_cli_bin`, `is_open_source`, `has_deploy_config`, `is_monorepo`, `has_tests`
- `doc_tooling` — object with booleans: `docusaurus`, `vitepress`, `mkdocs`, `storybook`
- `monorepo_workspaces` — array of workspace glob patterns (empty if not a monorepo)
- `section_manifest` — parsed from `INIT_DOCS_UPDATE` (not `INIT`); gates the `dispatch-monorepo-packages` section below
- `project_root` — absolute path to the project root
- `response_language` — if set, present all user-facing questions, prompts, and explanations in this workflow in that language; technical terms, code, file paths, and subagent prompts stay in English
</step>
@@ -710,57 +715,9 @@ If any agent failed or its file is missing, note the failure and continue. Missi
Continue to dispatch_monorepo_packages (if monorepo_workspaces is non-empty) or commit_docs.
</step>
<step name="dispatch_monorepo_packages" condition="monorepo_workspaces is non-empty">
After Wave 2 collection, generate per-package READMEs for each monorepo workspace.
**Condition:** Only run this step if `monorepo_workspaces` from the init JSON is non-empty.
**Resolve workspace packages from glob patterns:**
```bash
# Expand workspace globs to actual package directories
for pattern in {monorepo_workspaces}; do
ls -d $pattern 2>/dev/null
done
```
**For each resolved directory that contains a `package.json`:**
Determine mode:
- If `{package_dir}/README.md` exists: mode = `update`, read existing content
- Else: mode = `create`
Spawn a `gsd-doc-writer` agent with `run_in_background=true`:
```
Agent(
subagent_type="gsd-doc-writer",
model="{doc_writer_model}",
run_in_background=true,
description="Generate per-package README for {package_dir}",
prompt="<doc_assignment>
type: readme
mode: {create|update}
scope: per_package
package_dir: {absolute path to package directory}
project_context: {INIT JSON with project_root set to package directory}
{existing_content: | (include full README.md content here if mode is update, else omit)}
</doc_assignment>
{AGENT_SKILLS}
Write {package_dir}/README.md directly. Return confirmation only — do not return doc content."
)
```
> **ORCHESTRATOR RULE — CODEX RUNTIME**: After calling all per-package Agent() calls above with `run_in_background=true`, do NOT generate any package READMEs independently while the subagents are active. Wait for all agents to complete before proceeding. This prevents duplicate work and wasted context.
Collect confirmations by reading each package agent's `outputFile` once it reports completion — each `run_in_background=true` Agent call returns an `async_launched` result carrying an `outputFile` path (with `canReadOutputFile: true`). Note failures in the final report.
**Fallback when Task tool is unavailable:** Generate per-package READMEs sequentially inline after the `sequential_generation` step. For each package directory with a `package.json`, construct the equivalent `doc_assignment` block and generate the README following gsd-doc-writer instructions.
Continue to commit_docs.
</step>
<!-- gsd:section id="dispatch-monorepo-packages" when="state:is-monorepo" -->
If `section_manifest` (from `INIT_DOCS_UPDATE`) is `null` or `"dispatch-monorepo-packages"` is in its `included` list: read and execute `gsd-core/workflows/docs-update/steps/dispatch-monorepo-packages.md`. Otherwise skip — do not read the file; continue to commit_docs.
<!-- /gsd:section -->
<step name="sequential_generation" condition="Task tool is NOT available (e.g. Antigravity, Gemini CLI, Codex, Copilot)">
**Read the work manifest first:** `Read .planning/tmp/docs-work-manifest.json` — use `canonical_queue` items for generation order. Update `status` after each doc is generated. Write the updated manifest back to disk after all docs are complete.

View File

@@ -0,0 +1,51 @@
<step name="dispatch_monorepo_packages" condition="monorepo_workspaces is non-empty">
After Wave 2 collection, generate per-package READMEs for each monorepo workspace.
**Condition:** Only run this step if `monorepo_workspaces` from the init JSON is non-empty.
**Resolve workspace packages from glob patterns:**
```bash
# Expand workspace globs to actual package directories
for pattern in {monorepo_workspaces}; do
ls -d $pattern 2>/dev/null
done
```
**For each resolved directory that contains a `package.json`:**
Determine mode:
- If `{package_dir}/README.md` exists: mode = `update`, read existing content
- Else: mode = `create`
Spawn a `gsd-doc-writer` agent with `run_in_background=true`:
```
Agent(
subagent_type="gsd-doc-writer",
model="{doc_writer_model}",
run_in_background=true,
description="Generate per-package README for {package_dir}",
prompt="<doc_assignment>
type: readme
mode: {create|update}
scope: per_package
package_dir: {absolute path to package directory}
project_context: {INIT JSON with project_root set to package directory}
{existing_content: | (include full README.md content here if mode is update, else omit)}
</doc_assignment>
{AGENT_SKILLS}
Write {package_dir}/README.md directly. Return confirmation only — do not return doc content."
)
```
> **ORCHESTRATOR RULE — CODEX RUNTIME**: After calling all per-package Agent() calls above with `run_in_background=true`, do NOT generate any package READMEs independently while the subagents are active. Wait for all agents to complete before proceeding. This prevents duplicate work and wasted context.
Collect confirmations by reading each package agent's `outputFile` once it reports completion — each `run_in_background=true` Agent call returns an `async_launched` result carrying an `outputFile` path (with `canReadOutputFile: true`). Note failures in the final report.
**Fallback when Task tool is unavailable:** Generate per-package READMEs sequentially inline after the `sequential_generation` step. For each package directory with a `package.json`, construct the equivalent `doc_assignment` block and generate the README following gsd-doc-writer instructions.
Continue to commit_docs.
</step>

View File

@@ -32,9 +32,20 @@ Parse `GSD_WS` and `MILESTONE_ARG` using the established idiom (see `verify-work
```bash
_GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; if [ -f "$GSD_TOOLS" ]; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif command -v gsd-tools >/dev/null 2>&1; then GSD_TOOLS="$(command -v gsd-tools)"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif [ -f "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and gsd-tools is not on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
GSD_WS=""
echo "$ARGUMENTS" | grep -qE -- '--ws[[:space:]]+[^[:space:]]+' && GSD_WS=$(echo "$ARGUMENTS" | grep -oE -- '--ws[[:space:]]+[^[:space:]]+')
MILESTONE_ARG=$(echo "$ARGUMENTS" | sed -E 's/--ws[[:space:]]+[^[:space:]]+//g' | xargs)
echo "$ARGUMENTS" | grep -qE -- '--ws[[:space:]]+[A-Za-z0-9._-]+' && GSD_WS=$(echo "$ARGUMENTS" | grep -oE -- '--ws[[:space:]]+[A-Za-z0-9._-]+')
MILESTONE_ARG=$(echo "$ARGUMENTS" | sed -E 's/--ws[[:space:]]+[A-Za-z0-9._-]+//g' | xargs)
RESPONSE_LANGUAGE=$(gsd_run query config-get response_language --default "" 2>/dev/null || echo "")
# #2994: EARLY, section-manifest-only init.new-milestone call — needed here
# (before Step 4) to gate the project-md-milestone-write section. This is
# DELIBERATELY separate from Step 7's full init.new-milestone call below,
# which must stay AFTER Step 6's phase archival/phases.clear so its
# phase_dir_count / roadmap_exists / latest_completed_milestone fields
# reflect POST-archival state — moving that call here would compute those
# fields too early and corrupt the roadmapper's phase-numbering context.
# init.new-milestone is a pure read (no mutation), so calling it twice is
# safe; only `section_manifest` is consumed from this early call.
INIT_EARLY=$(gsd_run query init.new-milestone)
if [[ "$INIT_EARLY" == @file:* ]]; then INIT_EARLY=$(cat "${INIT_EARLY#@file:}"); fi
```
`GSD_WS` must chain to every downstream routing suggestion in this workflow (Step 4's shared-file guard, and the `/gsd:discuss-phase`/`/gsd:plan-phase` routing hints below) per the routing-propagation contract in `references/workstream-flag.md` — never let it silently drop.
@@ -151,22 +162,9 @@ AskUserQuestion:
PROJECT.md is shared across workstreams (`references/workstream-flag.md` marks it `# Shared` in the directory diagram). This step has two independently-scoped parts — only Part A is workstream-guarded.
**Part A — milestone-state write (skip when a workstream is active).** Skip Part A if `GSD_WS` is non-empty (parsed in Step 1). The active workstream's own `.planning/workstreams/<name>/STATE.md`/`ROADMAP.md`/`REQUIREMENTS.md` already carry this milestone's state. Writing a `## Current Milestone` heading here would clobber the shared file, and with parallel milestones across workstreams, whichever workstream runs `new-milestone` last would silently win the shared heading (#2308). In flat mode (`GSD_WS` empty), run Part A exactly as before:
Add/update:
```markdown
## Current Milestone: v[X.Y] [Name]
**Goal:** [One sentence describing milestone focus]
**Target features:**
- [Feature 1]
- [Feature 2]
- [Feature 3]
```
Update Active requirements section and "Last updated" footer.
<!-- gsd:section id="project-md-milestone-write" when="state:flat-mode" -->
If `section_manifest` (from `INIT_EARLY`) is `null` or `"project-md-milestone-write"` is in its `included` list: read and execute `gsd-core/workflows/new-milestone/steps/project-md-milestone-write.md`. Otherwise (a workstream is active) skip — do not read the file; Part B below still runs regardless of `GSD_WS`.
<!-- /gsd:section -->
**Part B — Evolution structural repair (always runs, regardless of `GSD_WS`).** `## Evolution` is a shared, idempotent structural section, not workstream state — a pre-Evolution project must be backfilled whether or not a workstream is active, so this part is NOT covered by Part A's skip. Ensure the `## Evolution` section exists in PROJECT.md. If missing (projects created before this feature), add it before the footer:
@@ -273,7 +271,8 @@ gsd_run query commit "docs: start milestone v[X.Y] [Name]" --files .planning/PRO
## 7. Load Context and Resolve Models
```bash
INIT=$(gsd_run query init.new-milestone)
RESET_PHASE_NUMBERS_PARAM=""; if [[ "$ARGUMENTS" =~ (^|[[:space:]])--reset-phase-numbers([[:space:]]|$) ]]; then RESET_PHASE_NUMBERS_PARAM="--reset-phase-numbers"; fi
INIT=$(gsd_run query init.new-milestone $RESET_PHASE_NUMBERS_PARAM)
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
AGENT_SKILLS_RESEARCHER=$(gsd_run query agent-skills gsd-project-researcher)
AGENT_SKILLS_SYNTHESIZER=$(gsd_run query agent-skills gsd-research-synthesizer)
@@ -296,25 +295,9 @@ Proceeding without research subagents — roadmap will be generated inline.
```
Skip the parallel research spawn step and generate the roadmap inline.
## 7.5 Reset-phase safety (only when `--reset-phase-numbers`)
If `--reset-phase-numbers` is active:
1. Set starting phase number to `1` for the upcoming roadmap.
2. If `phase_dir_count > 0`, archive the old phase directories before roadmapping so new `01-*` / `02-*` directories cannot collide with stale milestone directories.
If `phase_dir_count > 0` and `phase_archive_path` is available:
```bash
mkdir -p "${phase_archive_path}"
find .planning/phases -mindepth 1 -maxdepth 1 -type d -exec mv {} "${phase_archive_path}/" \;
```
Then verify `.planning/phases/` no longer contains old milestone directories before continuing.
If `phase_dir_count > 0` but `phase_archive_path` is missing:
- Stop and explain that reset numbering is unsafe without a completed milestone archive target.
- Tell the user to complete/archive the previous milestone first, then rerun `/gsd:new-milestone --reset-phase-numbers ${GSD_WS}`.
<!-- gsd:section id="reset-phase-safety" when="flag:--reset-phase-numbers" -->
If `section_manifest` is `null` or `"reset-phase-safety"` is in its `included` list: read and execute `gsd-core/workflows/new-milestone/steps/reset-phase-safety.md`. Otherwise skip — do not read the file.
<!-- /gsd:section -->
## 8. Research Decision

View File

@@ -0,0 +1,16 @@
**Part A — milestone-state write (skip when a workstream is active).** Skip Part A if `GSD_WS` is non-empty (parsed in Step 1). The active workstream's own `.planning/workstreams/<name>/STATE.md`/`ROADMAP.md`/`REQUIREMENTS.md` already carry this milestone's state. Writing a `## Current Milestone` heading here would clobber the shared file, and with parallel milestones across workstreams, whichever workstream runs `new-milestone` last would silently win the shared heading (#2308). In flat mode (`GSD_WS` empty), run Part A exactly as before:
Add/update:
```markdown
## Current Milestone: v[X.Y] [Name]
**Goal:** [One sentence describing milestone focus]
**Target features:**
- [Feature 1]
- [Feature 2]
- [Feature 3]
```
Update Active requirements section and "Last updated" footer.

View File

@@ -0,0 +1,19 @@
## 7.5 Reset-phase safety (only when `--reset-phase-numbers`)
If `--reset-phase-numbers` is active:
1. Set starting phase number to `1` for the upcoming roadmap.
2. If `phase_dir_count > 0`, archive the old phase directories before roadmapping so new `01-*` / `02-*` directories cannot collide with stale milestone directories.
If `phase_dir_count > 0` and `phase_archive_path` is available:
```bash
mkdir -p "${phase_archive_path}"
find .planning/phases -mindepth 1 -maxdepth 1 -type d -exec mv {} "${phase_archive_path}/" \;
```
Then verify `.planning/phases/` no longer contains old milestone directories before continuing.
If `phase_dir_count > 0` but `phase_archive_path` is missing:
- Stop and explain that reset numbering is unsafe without a completed milestone archive target.
- Tell the user to complete/archive the previous milestone first, then rerun `/gsd:new-milestone --reset-phase-numbers ${GSD_WS}`.

View File

@@ -15,38 +15,9 @@ Valid GSD subagent types (use exact names — do not fall back to 'general-purpo
<auto_mode>
## Auto Mode Detection
Check if `--auto` flag is present in $ARGUMENTS.
**If auto mode:**
- Skip brownfield mapping offer (assume greenfield)
- Skip deep questioning (extract context from provided document)
- Config: YOLO mode is implicit (skip that question), but ask granularity/git/agents FIRST (Step 2a)
- After config: run Steps 6-9 automatically with smart defaults:
- Research: Always yes
- Requirements: Include all table stakes + features from provided document
- Requirements approval: Auto-approve
- Roadmap approval: Auto-approve
**Document requirement:**
Auto mode requires an idea document — either:
- File reference: `/gsd:new-project --auto @prd.md`
- Pasted/written text in the prompt
If no document content provided, error:
```
Error: --auto requires an idea document.
Usage:
/gsd:new-project --auto @your-idea.md
/gsd:new-project --auto [paste or write your idea here]
The document should describe what you want to build.
```
<!-- gsd:section id="auto-mode-detection" when="flag:--auto" -->
If `section_manifest` is `null` or `"auto-mode-detection"` is in its `included` list: read and execute `gsd-core/workflows/new-project/steps/auto-mode-detection.md`. Otherwise skip — do not read the file.
<!-- /gsd:section -->
</auto_mode>
@@ -58,7 +29,8 @@ The document should describe what you want to build.
```bash
_GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; if [ -f "$GSD_TOOLS" ]; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif command -v gsd-tools >/dev/null 2>&1; then GSD_TOOLS="$(command -v gsd-tools)"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif [ -f "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and gsd-tools is not on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
INIT=$(gsd_run query init.new-project)
AUTO_PARAM=""; if [[ "$ARGUMENTS" =~ (^|[[:space:]])--auto([[:space:]]|$) ]]; then AUTO_PARAM="--auto"; fi
INIT=$(gsd_run query init.new-project $AUTO_PARAM)
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
AGENT_SKILLS_RESEARCHER=$(gsd_run query agent-skills gsd-project-researcher)
AGENT_SKILLS_SYNTHESIZER=$(gsd_run query agent-skills gsd-research-synthesizer)
@@ -132,202 +104,15 @@ All subsequent references to the project instruction file use `$INSTRUCTION_FILE
**If auto mode:** Skip to Step 4 (assume greenfield, synthesize PROJECT.md from provided document).
**If `needs_codebase_map` is true** (from init — existing code detected but no codebase map):
**Text mode (`workflow.text_mode: true` in config or `--text` flag):** 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 non-Claude runtimes (OpenAI Codex, Gemini CLI, etc.) where `AskUserQuestion` is not available.
Use AskUserQuestion:
- header: "Codebase"
- question: "I detected existing code in this directory. Would you like to map the codebase first?"
- options:
- "Map codebase first" — Run /gsd:map-codebase to understand existing architecture (Recommended)
- "Skip mapping" — Proceed with project initialization
**If "Map codebase first":**
```
Run `/gsd:map-codebase` first, then return to `/gsd:new-project`
```
Exit command.
<!-- gsd:section id="codebase-map-offer" when="state:needs-codebase-map" -->
If `section_manifest` is `null` or `"codebase-map-offer"` is in its `included` list: read and execute `gsd-core/workflows/new-project/steps/codebase-map-offer.md`. Otherwise skip — do not read the file.
<!-- /gsd:section -->
**If "Skip mapping" OR `needs_codebase_map` is false:** Continue to Step 3.
## 2a. Auto Mode Config (auto mode only)
**If auto mode:** Collect config settings upfront before processing the idea document.
YOLO mode is implicit (auto = YOLO). Ask remaining config questions:
**Round 1 — Core settings (3 questions, no Mode question):**
```
AskUserQuestion([
{
header: "Granularity",
question: "How finely should scope be sliced into phases?",
multiSelect: false,
options: [
{ label: "Coarse (Recommended)", description: "Fewer, broader phases (3-5 phases, 1-3 plans each)" },
{ label: "Standard", description: "Balanced phase size (5-8 phases, 3-5 plans each)" },
{ label: "Fine", description: "Many focused phases (8-12 phases, 5-10 plans each)" }
]
},
{
header: "Execution",
question: "Run plans in parallel?",
multiSelect: false,
options: [
{ label: "Parallel (Recommended)", description: "Independent plans run simultaneously" },
{ label: "Sequential", description: "One plan at a time" }
]
},
{
header: "Git Tracking",
question: "Commit planning docs to git?",
multiSelect: false,
options: [
{ label: "Yes (Recommended)", description: "Planning docs tracked in version control" },
{ label: "No", description: "Keep .planning/ local-only (add to .gitignore)" }
]
}
])
```
**Round 2 — Workflow agents (same as Step 5):**
```
AskUserQuestion([
{
header: "Research",
question: "Research before planning each phase? (adds tokens/time)",
multiSelect: false,
options: [
{ label: "Yes (Recommended)", description: "Investigate domain, find patterns, surface gotchas" },
{ label: "No", description: "Plan directly from requirements" }
]
},
{
header: "Plan Check",
question: "Verify plans will achieve their goals? (adds tokens/time)",
multiSelect: false,
options: [
{ label: "Yes (Recommended)", description: "Catch gaps before execution starts" },
{ label: "No", description: "Execute plans without verification" }
]
},
{
header: "Verifier",
question: "Verify work satisfies requirements after each phase? (adds tokens/time)",
multiSelect: false,
options: [
{ label: "Yes (Recommended)", description: "Confirm deliverables match phase goals" },
{ label: "No", description: "Trust execution, skip verification" }
]
},
{
header: "Drift Guard",
question: "Enable the plan drift-guard? It verifies that symbols your plans cite (decorators, classes, functions, CLI flags) actually exist in your source at review time, catching hallucinated names before execution. [Y/n]",
multiSelect: false,
options: [
{ label: "Yes (Recommended)", description: "Resolve symbol references against live source during plan review — catches hallucinated names before execution" },
{ label: "No", description: "Skip symbol grounding — plan review proceeds without source verification" }
]
}
])
// Model profile uses a two-question split because AskUserQuestion enforces a hard
// 4-option cap and there are 5 valid profiles (quality, balanced, budget, adaptive,
// inherit). Q1 routes between adaptive/standard-tier/inherit; Q2 (shown only when
// Q1 = "Standard tier…") picks among the three standard profiles. Mirrors the
// /gsd:settings split (#3784, #1516).
AskUserQuestion([
{
header: "AI Models",
question: "Which AI models for planning agents?",
multiSelect: false,
options: [
{ label: "Adaptive (Recommended)", description: "Role-based cost optimization: heavy roles use the highest-tier model available on the active runtime, light roles use the cheapest. Best balance of quality and cost across all supported runtimes (Claude, Codex, Gemini, OpenRouter, local)." },
{ label: "Standard tier…", description: "Choose Quality, Balanced, or Budget — flat tier applied to all agents" },
{ label: "Inherit", description: "Use the current session model for all agents (required for non-Claude runtimes: Codex, Gemini CLI, OpenCode /model, OpenRouter, local models)" }
]
}
])
**Conditional visibility — model_profile (Q2):**
Only ask this question when Q1's answer is "Standard tier…".
If Q1 = "Adaptive (Recommended)" → write model_profile=adaptive and SKIP Q2.
If Q1 = "Inherit" → write model_profile=inherit and SKIP Q2.
If user cancels Q2 after picking "Standard tier…" → leave existing model_profile value unchanged.
AskUserQuestion([
{
question: "Which standard profile? (Quality / Balanced / Budget)",
header: "Model Tier",
multiSelect: false,
options: [
{ label: "Quality", description: "Opus everywhere except verification (highest cost) — Claude only" },
{ label: "Balanced", description: "Opus for planning, Sonnet for research/execution/verification — Claude only" },
{ label: "Budget", description: "Sonnet for writing, Haiku for research/verification (lowest cost) — Claude only" }
]
}
])
// Map UI choices → config values:
// Q1 "Adaptive (Recommended)" → model_profile = "adaptive"
// Q1 "Inherit" → model_profile = "inherit"
// Q1 "Standard tier…" + Q2 "Quality" → model_profile = "quality"
// Q1 "Standard tier…" + Q2 "Balanced" → model_profile = "balanced"
// Q1 "Standard tier…" + Q2 "Budget" → model_profile = "budget"
```
**Round 3 — PR body onboarding:**
Ask which optional PRD-style sections `/gsd:ship` should append to generated PR bodies. These map to `ship.pr_body_sections`; selected sections are written with `"enabled": true`, unselected seeded sections are written with `"enabled": false` so the project can enable them later without editing `ship.md`.
Prefer lean/agile PRD sections that make the delivered increment clear: user stories, acceptance criteria, Definition of Done or release criteria, risks, dependencies, and stakeholder review.
```
AskUserQuestion([
{
header: "PR Body",
question: "Which optional PRD-style sections should /gsd:ship include in PR bodies?",
multiSelect: true,
options: [
{ label: "User Stories & Acceptance Criteria", description: "Append user-facing stories and acceptance checks from REQUIREMENTS.md" },
{ label: "Risks & Dependencies", description: "Append rollout risks, dependencies, and rollback notes from PLAN.md" },
{ label: "Success Metrics & Release Criteria", description: "Append measurable Definition of Done and release checks for stakeholder review" },
{ label: "Stakeholder Review & Approval", description: "Append approval checklist for projects that need sign-off traceability" }
]
}
])
```
Build `ship.pr_body_sections` from those choices. For selected options, set `enabled: true`; for seeded but unselected options, set `enabled: false`. If the user selects none, use `"ship":{"pr_body_sections":[]}`.
Create `.planning/config.json` with all settings (CLI fills in remaining defaults automatically):
```bash
mkdir -p .planning
gsd_run query config-new-project '{"mode":"yolo","granularity":"[selected]","parallelization":true|false,"commit_docs":true|false,"model_profile":"quality|balanced|budget|adaptive|inherit","workflow":{"research":true|false,"plan_check":true|false,"verifier":true|false,"nyquist_validation":true|false,"auto_advance":true},"plan_review":{"source_grounding":true|false},"ship":{"pr_body_sections":[{"heading":"User Stories & Acceptance Criteria","enabled":true|false,"source":"REQUIREMENTS.md ## User Stories || REQUIREMENTS.md ## Acceptance Criteria","fallback":"- Acceptance criteria are covered by the linked requirements and verification evidence."},{"heading":"Risks & Dependencies","enabled":true|false,"source":"PLAN.md ## Risks || PLAN.md ## Dependencies","fallback":"- No known high-risk rollout dependencies."},{"heading":"Success Metrics & Release Criteria","enabled":true|false,"source":"REQUIREMENTS.md ## Definition of Done || VERIFICATION.md ## Release Criteria","fallback":"- Release when automated verification and required manual checks pass."},{"heading":"Stakeholder Review & Approval","enabled":true|false,"template":"- Product owner approval pending for {phase_name}."}]}}'
```
**If commit_docs = No:** Add `.planning/` to `.gitignore`.
**Commit config.json:**
```bash
mkdir -p .planning
gsd_run query commit "chore: add project config" --files .planning/config.json
```
**Persist auto-advance chain flag to config (survives context compaction):**
```bash
gsd_run query config-set workflow._auto_chain_active true
```
Proceed to Step 4 (skip Steps 3 and 5).
<!-- gsd:section id="auto-mode-config" when="flag:--auto" -->
If `section_manifest` is `null` or `"auto-mode-config"` is in its `included` list: read and execute `gsd-core/workflows/new-project/steps/auto-mode-config.md`. Otherwise skip — do not read the file.
<!-- /gsd:section -->
## 2b. Prior Spike/Sketch Detection

View File

@@ -0,0 +1,176 @@
## 2a. Auto Mode Config (auto mode only)
**If auto mode:** Collect config settings upfront before processing the idea document.
YOLO mode is implicit (auto = YOLO). Ask remaining config questions:
**Round 1 — Core settings (3 questions, no Mode question):**
```
AskUserQuestion([
{
header: "Granularity",
question: "How finely should scope be sliced into phases?",
multiSelect: false,
options: [
{ label: "Coarse (Recommended)", description: "Fewer, broader phases (3-5 phases, 1-3 plans each)" },
{ label: "Standard", description: "Balanced phase size (5-8 phases, 3-5 plans each)" },
{ label: "Fine", description: "Many focused phases (8-12 phases, 5-10 plans each)" }
]
},
{
header: "Execution",
question: "Run plans in parallel?",
multiSelect: false,
options: [
{ label: "Parallel (Recommended)", description: "Independent plans run simultaneously" },
{ label: "Sequential", description: "One plan at a time" }
]
},
{
header: "Git Tracking",
question: "Commit planning docs to git?",
multiSelect: false,
options: [
{ label: "Yes (Recommended)", description: "Planning docs tracked in version control" },
{ label: "No", description: "Keep .planning/ local-only (add to .gitignore)" }
]
}
])
```
**Round 2 — Workflow agents (same as Step 5):**
```
AskUserQuestion([
{
header: "Research",
question: "Research before planning each phase? (adds tokens/time)",
multiSelect: false,
options: [
{ label: "Yes (Recommended)", description: "Investigate domain, find patterns, surface gotchas" },
{ label: "No", description: "Plan directly from requirements" }
]
},
{
header: "Plan Check",
question: "Verify plans will achieve their goals? (adds tokens/time)",
multiSelect: false,
options: [
{ label: "Yes (Recommended)", description: "Catch gaps before execution starts" },
{ label: "No", description: "Execute plans without verification" }
]
},
{
header: "Verifier",
question: "Verify work satisfies requirements after each phase? (adds tokens/time)",
multiSelect: false,
options: [
{ label: "Yes (Recommended)", description: "Confirm deliverables match phase goals" },
{ label: "No", description: "Trust execution, skip verification" }
]
},
{
header: "Drift Guard",
question: "Enable the plan drift-guard? It verifies that symbols your plans cite (decorators, classes, functions, CLI flags) actually exist in your source at review time, catching hallucinated names before execution. [Y/n]",
multiSelect: false,
options: [
{ label: "Yes (Recommended)", description: "Resolve symbol references against live source during plan review — catches hallucinated names before execution" },
{ label: "No", description: "Skip symbol grounding — plan review proceeds without source verification" }
]
}
])
// Model profile uses a two-question split because AskUserQuestion enforces a hard
// 4-option cap and there are 5 valid profiles (quality, balanced, budget, adaptive,
// inherit). Q1 routes between adaptive/standard-tier/inherit; Q2 (shown only when
// Q1 = "Standard tier…") picks among the three standard profiles. Mirrors the
// /gsd:settings split (#3784, #1516).
AskUserQuestion([
{
header: "AI Models",
question: "Which AI models for planning agents?",
multiSelect: false,
options: [
{ label: "Adaptive (Recommended)", description: "Role-based cost optimization: heavy roles use the highest-tier model available on the active runtime, light roles use the cheapest. Best balance of quality and cost across all supported runtimes (Claude, Codex, Gemini, OpenRouter, local)." },
{ label: "Standard tier…", description: "Choose Quality, Balanced, or Budget — flat tier applied to all agents" },
{ label: "Inherit", description: "Use the current session model for all agents (required for non-Claude runtimes: Codex, Gemini CLI, OpenCode /model, OpenRouter, local models)" }
]
}
])
**Conditional visibility — model_profile (Q2):**
Only ask this question when Q1's answer is "Standard tier…".
If Q1 = "Adaptive (Recommended)" → write model_profile=adaptive and SKIP Q2.
If Q1 = "Inherit" → write model_profile=inherit and SKIP Q2.
If user cancels Q2 after picking "Standard tier…" → leave existing model_profile value unchanged.
AskUserQuestion([
{
question: "Which standard profile? (Quality / Balanced / Budget)",
header: "Model Tier",
multiSelect: false,
options: [
{ label: "Quality", description: "Opus everywhere except verification (highest cost) — Claude only" },
{ label: "Balanced", description: "Opus for planning, Sonnet for research/execution/verification — Claude only" },
{ label: "Budget", description: "Sonnet for writing, Haiku for research/verification (lowest cost) — Claude only" }
]
}
])
// Map UI choices → config values:
// Q1 "Adaptive (Recommended)" → model_profile = "adaptive"
// Q1 "Inherit" → model_profile = "inherit"
// Q1 "Standard tier…" + Q2 "Quality" → model_profile = "quality"
// Q1 "Standard tier…" + Q2 "Balanced" → model_profile = "balanced"
// Q1 "Standard tier…" + Q2 "Budget" → model_profile = "budget"
```
**Round 3 — PR body onboarding:**
Ask which optional PRD-style sections `/gsd:ship` should append to generated PR bodies. These map to `ship.pr_body_sections`; selected sections are written with `"enabled": true`, unselected seeded sections are written with `"enabled": false` so the project can enable them later without editing `ship.md`.
Prefer lean/agile PRD sections that make the delivered increment clear: user stories, acceptance criteria, Definition of Done or release criteria, risks, dependencies, and stakeholder review.
```
AskUserQuestion([
{
header: "PR Body",
question: "Which optional PRD-style sections should /gsd:ship include in PR bodies?",
multiSelect: true,
options: [
{ label: "User Stories & Acceptance Criteria", description: "Append user-facing stories and acceptance checks from REQUIREMENTS.md" },
{ label: "Risks & Dependencies", description: "Append rollout risks, dependencies, and rollback notes from PLAN.md" },
{ label: "Success Metrics & Release Criteria", description: "Append measurable Definition of Done and release checks for stakeholder review" },
{ label: "Stakeholder Review & Approval", description: "Append approval checklist for projects that need sign-off traceability" }
]
}
])
```
Build `ship.pr_body_sections` from those choices. For selected options, set `enabled: true`; for seeded but unselected options, set `enabled: false`. If the user selects none, use `"ship":{"pr_body_sections":[]}`.
Create `.planning/config.json` with all settings (CLI fills in remaining defaults automatically):
```bash
_GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; if [ -f "$GSD_TOOLS" ]; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif command -v gsd-tools >/dev/null 2>&1; then GSD_TOOLS="$(command -v gsd-tools)"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif [ -f "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and gsd-tools is not on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
mkdir -p .planning
gsd_run query config-new-project '{"mode":"yolo","granularity":"[selected]","parallelization":true|false,"commit_docs":true|false,"model_profile":"quality|balanced|budget|adaptive|inherit","workflow":{"research":true|false,"plan_check":true|false,"verifier":true|false,"nyquist_validation":true|false,"auto_advance":true},"plan_review":{"source_grounding":true|false},"ship":{"pr_body_sections":[{"heading":"User Stories & Acceptance Criteria","enabled":true|false,"source":"REQUIREMENTS.md ## User Stories || REQUIREMENTS.md ## Acceptance Criteria","fallback":"- Acceptance criteria are covered by the linked requirements and verification evidence."},{"heading":"Risks & Dependencies","enabled":true|false,"source":"PLAN.md ## Risks || PLAN.md ## Dependencies","fallback":"- No known high-risk rollout dependencies."},{"heading":"Success Metrics & Release Criteria","enabled":true|false,"source":"REQUIREMENTS.md ## Definition of Done || VERIFICATION.md ## Release Criteria","fallback":"- Release when automated verification and required manual checks pass."},{"heading":"Stakeholder Review & Approval","enabled":true|false,"template":"- Product owner approval pending for {phase_name}."}]}}'
```
**If commit_docs = No:** Add `.planning/` to `.gitignore`.
**Commit config.json:**
```bash
mkdir -p .planning
gsd_run query commit "chore: add project config" --files .planning/config.json
```
**Persist auto-advance chain flag to config (survives context compaction):**
```bash
gsd_run query config-set workflow._auto_chain_active true
```
Proceed to Step 4 (skip Steps 3 and 5).

View File

@@ -0,0 +1,32 @@
## Auto Mode Detection
Check if `--auto` flag is present in $ARGUMENTS.
**If auto mode:**
- Skip brownfield mapping offer (assume greenfield)
- Skip deep questioning (extract context from provided document)
- Config: YOLO mode is implicit (skip that question), but ask granularity/git/agents FIRST (Step 2a)
- After config: run Steps 6-9 automatically with smart defaults:
- Research: Always yes
- Requirements: Include all table stakes + features from provided document
- Requirements approval: Auto-approve
- Roadmap approval: Auto-approve
**Document requirement:**
Auto mode requires an idea document — either:
- File reference: `/gsd:new-project --auto @prd.md`
- Pasted/written text in the prompt
If no document content provided, error:
```
Error: --auto requires an idea document.
Usage:
/gsd:new-project --auto @your-idea.md
/gsd:new-project --auto [paste or write your idea here]
The document should describe what you want to build.
```

View File

@@ -0,0 +1,18 @@
**If `needs_codebase_map` is true** (from init — existing code detected but no codebase map):
**Text mode (`workflow.text_mode: true` in config or `--text` flag):** 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 non-Claude runtimes (OpenAI Codex, Gemini CLI, etc.) where `AskUserQuestion` is not available.
Use AskUserQuestion:
- header: "Codebase"
- question: "I detected existing code in this directory. Would you like to map the codebase first?"
- options:
- "Map codebase first" — Run /gsd:map-codebase to understand existing architecture (Recommended)
- "Skip mapping" — Proceed with project initialization
**If "Map codebase first":**
```
Run `/gsd:map-codebase` first, then return to `/gsd:new-project`
```
Exit command.

View File

@@ -13,11 +13,12 @@ Read all files referenced by the invoking prompt's execution_context before star
```bash
_GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; if [ -f "$GSD_TOOLS" ]; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif command -v gsd-tools >/dev/null 2>&1; then GSD_TOOLS="$(command -v gsd-tools)"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif [ -f "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and gsd-tools is not on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
INIT=$(gsd_run query init.progress)
FORENSIC_PARAM=""; if [[ "$ARGUMENTS" =~ (^|[[:space:]])--forensic([[:space:]]|$) ]]; then FORENSIC_PARAM="--forensic"; fi
INIT=$(gsd_run query init.progress $FORENSIC_PARAM)
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
```
Extract from init JSON: `project_exists`, `roadmap_exists`, `state_exists`, `phases`, `current_phase`, `next_phase`, `milestone_version`, `completed_count`, `phase_count`, `paused_at`, `state_path`, `roadmap_path`, `project_path`, `config_path`.
Extract from init JSON: `project_exists`, `roadmap_exists`, `state_exists`, `phases`, `current_phase`, `next_phase`, `milestone_version`, `completed_count`, `phase_count`, `paused_at`, `state_path`, `roadmap_path`, `project_path`, `config_path`, `phase_mvp_mode`.
```bash
DISCUSS_MODE=$(gsd_run query config-get workflow.discuss_mode 2>/dev/null || echo "discuss")
@@ -104,7 +105,7 @@ PROGRESS_BAR=$(gsd_run query progress.bar --raw)
Present:
```
````
# [Project Name]
**Progress:** {PROGRESS_BAR}
@@ -150,34 +151,13 @@ Render `Open Windows` only when `$WINDOWS_OPEN` is greater than `0` (or `$WINDOW
## What's Next
[Next phase/plan objective from roadmap analyze]
```
````
</step>
<step name="mvp_display">
**MVP-mode display (when phase has `**Mode:** mvp` in ROADMAP.md).**
Resolve `MVP_MODE` per phase via the centralized resolver. progress has no `--mvp` CLI flag (mode is inherited from the planned phase), so we omit `--cli-flag`:
```bash
MVP_MODE=$(gsd_run query phase.mvp-mode "${PHASE_NUMBER}" --pick active)
```
When `MVP_MODE=true`, the per-phase progress block adds a **user-flow status** sub-block sourced from the phase's PLAN.md task names. Each task whose name reads like a user-visible capability (e.g., "Register flow", "Login flow", "Password reset") is rendered as a status line:
```
Phase 1 — User Auth MVP
✅ Walking Skeleton complete ← from SKELETON.md existence
✅ Register flow working ← from PLAN.md task with summary
✅ Login flow working ← from PLAN.md task with summary
🔄 Password reset (in progress) ← from PLAN.md task without summary
⬜ Email verification ← from PLAN.md task not yet started
```
**User-flow filter:** Tasks whose names are technical-sounding ("Wire DB schema", "Create migration", "Bump deps") are NOT rendered as user-flow status lines. Heuristic: a task name is user-flow-shaped if it ends in "flow", "page", "screen", or starts with a verb the user would recognize ("Register", "Login", "Upload", "View"). Tasks that fail the heuristic still count toward the standard task progress total but don't appear in the user-flow sub-block.
When `MVP_MODE=false` (mode is null, absent, or the phase has no `**Mode:**` line), fall back to the standard display path — no behavioral change.
</step>
<!-- gsd:section id="mvp-display" when="state:phase-mvp-mode" -->
If `section_manifest` is `null` or `"mvp-display"` is in its `included` list: read and execute `gsd-core/workflows/progress/steps/mvp-display.md`. Otherwise skip — do not read the file.
<!-- /gsd:section -->
<step name="route">
**Determine next action based on verified counts.**
@@ -678,131 +658,9 @@ Ready to plan the next milestone.
- Handoff file exists → mention it, offer `/gsd:resume-work ${GSD_WS}`
</step>
<step name="forensic_audit">
**Forensic Integrity Audit** — only runs when `--forensic` is present in ARGUMENTS.
If `--forensic` is NOT present in ARGUMENTS: skip this step entirely. Default progress behavior (standard report + routing) is unchanged.
If `--forensic` IS present: after the standard report and routing suggestion have been displayed, append the following audit section.
---
## Forensic Integrity Audit
Running 7 deep checks against project state...
Run each check in order. For each check, emit ✓ (pass) or ⚠ (warning) with concrete evidence when a problem is found.
**Check 1 — STATE vs artifact consistency**
Read STATE.md `status` / `stopped_at` fields (from the STATE snapshot already loaded). Compare against the artifact count from the roadmap analysis. If STATE.md claims the current phase is pending/mid-flight but the artifact count shows it as complete (all PLAN.md files have matching SUMMARY.md files), flag inconsistency. Emit:
- ✓ `STATE.md consistent with artifact count` — if both agree
- ⚠ `STATE.md claims [status] but artifact count shows phase complete` — with the specific values
**Check 2 — Orphaned handoff files**
Check for existence of:
```bash
ls .planning/HANDOFF.json .planning/phases/*/.continue-here.md .planning/phases/*/*HANDOFF*.md 2>/dev/null || true
```
Also check `.planning/continue-here.md`.
Emit:
- ✓ `No orphaned handoff files` — if none found
- ⚠ `Orphaned handoff files found` — list each file path, add: `→ Work was paused mid-flight. Read the handoff before continuing.`
**Check 3 — Deferred scope drift**
Search phase artifacts (CONTEXT.md, DISCUSSION-LOG.md, BUG-BRIEF.md, VERIFICATION.md, SUMMARY.md, HANDOFF.md files under `.planning/phases/`) for patterns:
```bash
grep -rl "defer to Phase\|future phase\|out of scope Phase\|deferred to Phase" .planning/phases/ 2>/dev/null || true
```
For each match, extract the referenced phase number. Cross-reference against ROADMAP.md phase list. If the referenced phase number is NOT in ROADMAP.md, flag as deferred scope not captured.
Emit:
- ✓ `All deferred scope captured in ROADMAP` — if no mismatches
- ⚠ `Deferred scope references phase(s) not in ROADMAP` — list: file, reference text, missing phase number
**Check 4 — Memory-flagged pending work**
Check if `.planning/MEMORY.md` or `.planning/memory/` exists:
```bash
ls .planning/MEMORY.md .planning/memory/*.md 2>/dev/null || true
```
If found, grep for entries containing: `pending`, `status`, `deferred`, `not yet run`, `backfill`, `blocking`.
Emit:
- ✓ `No memory entries flagging pending work` — if none found or no MEMORY.md
- ⚠ `Memory entries flag pending/deferred work` — list the matching lines (max 5, truncated at 80 chars)
**Check 5 — Blocking operational todos**
Check for pending todos:
```bash
ls .planning/todos/pending/*.md 2>/dev/null || true
```
For files found, scan for keywords indicating operational blockers: `script`, `credential`, `API key`, `manual`, `verification`, `setup`, `configure`, `run `.
Emit:
- ✓ `No blocking operational todos` — if no pending todos or none match operational keywords
- ⚠ `Blocking operational todos found` — list the file names and matching keywords (max 5)
**Check 6 — Uncommitted code**
```bash
git status --porcelain 2>/dev/null | grep -v "^??" | grep -v "^.planning\/" | grep -v "^\.\." | head -10
```
If output is non-empty (modified/staged files outside `.planning/`), flag as uncommitted code.
Emit:
- ✓ `Working tree clean` — if no modified files outside `.planning/`
- ⚠ `Uncommitted changes in source files` — list up to 10 file paths
**Check 7 — Unresolved deferred items**
Glob every phase directory's SCOPE BOUNDARY log (executor writes out-of-scope discoveries here per `agents/gsd-executor.md`):
```bash
ls .planning/phases/*/deferred-items.md 2>/dev/null || true
```
For each `deferred-items.md` found, read its entries (bullet list, one entry per top-level `- ` line, continuation lines indented beneath it). An entry is RESOLVED only if it carries an explicit `status: resolved` field (case-insensitive) on one of its lines; every other entry — including one with no `status:` field at all — is UNRESOLVED and must be surfaced (fail-safe: never silently drop a possibly-open item).
Emit:
- ✓ `No unresolved deferred items` — if no `deferred-items.md` files exist, or every entry in every file is `status: resolved`
- ⚠ `Unresolved deferred items found` — list each file's phase directory and its unresolved entry text (max 5 per file, truncated at 80 chars)
---
After all 7 checks, display the verdict:
**If all 7 checks passed:**
```
### Verdict: CLEAN
The standard progress report is trustworthy — proceed with the routing suggestion above.
```
**If 1 or more checks failed:**
```
### Verdict: N INTEGRITY ISSUE(S) FOUND
The standard progress report may not reflect true project state.
Review the flagged items above before acting on the routing suggestion.
```
Then for each failed check, add a concrete next action:
- Check 2 (orphaned handoff): `Read the handoff file(s) and resume from where work was paused: /gsd:resume-work ${GSD_WS}`
- Check 3 (deferred scope): `Add the missing phases to ROADMAP.md or update the deferred references`
- Check 4 (memory pending): `Review the flagged memory entries and resolve or clear them`
- Check 5 (blocking todos): `Complete the operational steps in .planning/todos/pending/ before continuing`
- Check 6 (uncommitted code): `Commit or stash the uncommitted changes before advancing`
- Check 7 (unresolved deferred items): `Address each deferred item and mark it status: resolved in its deferred-items.md, or fold it into the roadmap`
- Check 1 (STATE inconsistency): `Run /gsd:verify-work ${PHASE} ${GSD_WS} to reconcile state`
</step>
<!-- gsd:section id="forensic-audit" when="flag:--forensic" -->
If `section_manifest` is `null` or `"forensic-audit"` is in its `included` list: read and execute `gsd-core/workflows/progress/steps/forensic-audit.md`. Otherwise skip — do not read the file.
<!-- /gsd:section -->
</process>

View File

@@ -0,0 +1,125 @@
<step name="forensic_audit">
**Forensic Integrity Audit** — only runs when `--forensic` is present in ARGUMENTS.
If `--forensic` is NOT present in ARGUMENTS: skip this step entirely. Default progress behavior (standard report + routing) is unchanged.
If `--forensic` IS present: after the standard report and routing suggestion have been displayed, append the following audit section.
---
## Forensic Integrity Audit
Running 7 deep checks against project state...
Run each check in order. For each check, emit ✓ (pass) or ⚠ (warning) with concrete evidence when a problem is found.
**Check 1 — STATE vs artifact consistency**
Read STATE.md `status` / `stopped_at` fields (from the STATE snapshot already loaded). Compare against the artifact count from the roadmap analysis. If STATE.md claims the current phase is pending/mid-flight but the artifact count shows it as complete (all PLAN.md files have matching SUMMARY.md files), flag inconsistency. Emit:
- ✓ `STATE.md consistent with artifact count` — if both agree
- ⚠ `STATE.md claims [status] but artifact count shows phase complete` — with the specific values
**Check 2 — Orphaned handoff files**
Check for existence of:
```bash
ls .planning/HANDOFF.json .planning/phases/*/.continue-here.md .planning/phases/*/*HANDOFF*.md 2>/dev/null || true
```
Also check `.planning/continue-here.md`.
Emit:
- ✓ `No orphaned handoff files` — if none found
- ⚠ `Orphaned handoff files found` — list each file path, add: `→ Work was paused mid-flight. Read the handoff before continuing.`
**Check 3 — Deferred scope drift**
Search phase artifacts (CONTEXT.md, DISCUSSION-LOG.md, BUG-BRIEF.md, VERIFICATION.md, SUMMARY.md, HANDOFF.md files under `.planning/phases/`) for patterns:
```bash
grep -rl "defer to Phase\|future phase\|out of scope Phase\|deferred to Phase" .planning/phases/ 2>/dev/null || true
```
For each match, extract the referenced phase number. Cross-reference against ROADMAP.md phase list. If the referenced phase number is NOT in ROADMAP.md, flag as deferred scope not captured.
Emit:
- ✓ `All deferred scope captured in ROADMAP` — if no mismatches
- ⚠ `Deferred scope references phase(s) not in ROADMAP` — list: file, reference text, missing phase number
**Check 4 — Memory-flagged pending work**
Check if `.planning/MEMORY.md` or `.planning/memory/` exists:
```bash
ls .planning/MEMORY.md .planning/memory/*.md 2>/dev/null || true
```
If found, grep for entries containing: `pending`, `status`, `deferred`, `not yet run`, `backfill`, `blocking`.
Emit:
- ✓ `No memory entries flagging pending work` — if none found or no MEMORY.md
- ⚠ `Memory entries flag pending/deferred work` — list the matching lines (max 5, truncated at 80 chars)
**Check 5 — Blocking operational todos**
Check for pending todos:
```bash
ls .planning/todos/pending/*.md 2>/dev/null || true
```
For files found, scan for keywords indicating operational blockers: `script`, `credential`, `API key`, `manual`, `verification`, `setup`, `configure`, `run `.
Emit:
- ✓ `No blocking operational todos` — if no pending todos or none match operational keywords
- ⚠ `Blocking operational todos found` — list the file names and matching keywords (max 5)
**Check 6 — Uncommitted code**
```bash
git status --porcelain 2>/dev/null | grep -v "^??" | grep -v "^.planning\/" | grep -v "^\.\." | head -10
```
If output is non-empty (modified/staged files outside `.planning/`), flag as uncommitted code.
Emit:
- ✓ `Working tree clean` — if no modified files outside `.planning/`
- ⚠ `Uncommitted changes in source files` — list up to 10 file paths
**Check 7 — Unresolved deferred items**
Glob every phase directory's SCOPE BOUNDARY log (executor writes out-of-scope discoveries here per `agents/gsd-executor.md`):
```bash
ls .planning/phases/*/deferred-items.md 2>/dev/null || true
```
For each `deferred-items.md` found, read its entries (bullet list, one entry per top-level `- ` line, continuation lines indented beneath it). An entry is RESOLVED only if it carries an explicit `status: resolved` field (case-insensitive) on one of its lines; every other entry — including one with no `status:` field at all — is UNRESOLVED and must be surfaced (fail-safe: never silently drop a possibly-open item).
Emit:
- ✓ `No unresolved deferred items` — if no `deferred-items.md` files exist, or every entry in every file is `status: resolved`
- ⚠ `Unresolved deferred items found` — list each file's phase directory and its unresolved entry text (max 5 per file, truncated at 80 chars)
---
After all 7 checks, display the verdict:
**If all 7 checks passed:**
```
### Verdict: CLEAN
The standard progress report is trustworthy — proceed with the routing suggestion above.
```
**If 1 or more checks failed:**
```
### Verdict: N INTEGRITY ISSUE(S) FOUND
The standard progress report may not reflect true project state.
Review the flagged items above before acting on the routing suggestion.
```
Then for each failed check, add a concrete next action:
- Check 2 (orphaned handoff): `Read the handoff file(s) and resume from where work was paused: /gsd:resume-work ${GSD_WS}`
- Check 3 (deferred scope): `Add the missing phases to ROADMAP.md or update the deferred references`
- Check 4 (memory pending): `Review the flagged memory entries and resolve or clear them`
- Check 5 (blocking todos): `Complete the operational steps in .planning/todos/pending/ before continuing`
- Check 6 (uncommitted code): `Commit or stash the uncommitted changes before advancing`
- Check 7 (unresolved deferred items): `Address each deferred item and mark it status: resolved in its deferred-items.md, or fold it into the roadmap`
- Check 1 (STATE inconsistency): `Run /gsd:verify-work ${PHASE} ${GSD_WS} to reconcile state`
</step>

View File

@@ -0,0 +1,18 @@
<step name="mvp_display">
**MVP-mode display (when phase has `**Mode:** mvp` in ROADMAP.md).**
`init.progress` already resolved `phase_mvp_mode` for the current phase (progress has no `--mvp` CLI flag — mode is inherited from the planned phase), and this step is only read at all when that same fact is `true` (`section_manifest`'s `mvp-display` inclusion gates on it) — so there is no need to re-resolve `MVP_MODE` here via a fresh `gsd_run query phase.mvp-mode` call; that would gate this section on a fact its own body recomputes, which is circular and self-disabling. Simply consume `$phase_mvp_mode` (already extracted from init JSON in `init_context`) as `true` for the remainder of this step.
The per-phase progress block adds a **user-flow status** sub-block sourced from the phase's PLAN.md task names. Each task whose name reads like a user-visible capability (e.g., "Register flow", "Login flow", "Password reset") is rendered as a status line:
```
Phase 1 — User Auth MVP
✅ Walking Skeleton complete ← from SKELETON.md existence
✅ Register flow working ← from PLAN.md task with summary
✅ Login flow working ← from PLAN.md task with summary
🔄 Password reset (in progress) ← from PLAN.md task without summary
⬜ Email verification ← from PLAN.md task not yet started
```
**User-flow filter:** Tasks whose names are technical-sounding ("Wire DB schema", "Create migration", "Bump deps") are NOT rendered as user-flow status lines. Heuristic: a task name is user-flow-shaped if it ends in "flow", "page", "screen", or starts with a verb the user would recognize ("Register", "Login", "Upload", "View"). Tasks that fail the heuristic still count toward the standard task progress total but don't appear in the user-flow sub-block.
</step>

View File

@@ -131,7 +131,11 @@ If `$VALIDATE_MODE` only:
**Step 2: Initialize**
```bash
INIT=$(gsd_run query init.quick "$DESCRIPTION")
DISCUSS_PARAM=""; if [[ "$ARGUMENTS" =~ (^|[[:space:]])--discuss([[:space:]]|$) ]]; then DISCUSS_PARAM="--discuss"; fi
RESEARCH_PARAM=""; if [[ "$ARGUMENTS" =~ (^|[[:space:]])--research([[:space:]]|$) ]]; then RESEARCH_PARAM="--research"; fi
VALIDATE_PARAM=""; if [[ "$ARGUMENTS" =~ (^|[[:space:]])--validate([[:space:]]|$) ]]; then VALIDATE_PARAM="--validate"; fi
FULL_PARAM=""; if [[ "$ARGUMENTS" =~ (^|[[:space:]])--full([[:space:]]|$) ]]; then FULL_PARAM="--full"; fi
INIT=$(gsd_run query init.quick "$DESCRIPTION" $DISCUSS_PARAM $RESEARCH_PARAM $VALIDATE_PARAM $FULL_PARAM)
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
AGENT_SKILLS_PLANNER=$(gsd_run query agent-skills gsd-planner)
AGENT_SKILLS_EXECUTOR=$(gsd_run query agent-skills gsd-executor)
@@ -276,205 +280,15 @@ Store `$QUICK_DIR` for use in orchestration.
---
**Step 4.5: Discussion phase (only when `$DISCUSS_MODE`)**
Skip this step entirely if NOT `$DISCUSS_MODE`.
Display banner:
```
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
GSD ► DISCUSSING QUICK TASK
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
◆ Surfacing gray areas for: ${DESCRIPTION}
```
**4.5a. Identify gray areas**
Analyze `$DESCRIPTION` to identify 2-4 gray areas — implementation decisions that would change the outcome and that the user should weigh in on.
Use the domain-aware heuristic to generate phase-specific (not generic) gray areas:
- Something users **SEE** → layout, density, interactions, states
- Something users **CALL** → responses, errors, auth, versioning
- Something users **RUN** → output format, flags, modes, error handling
- Something users **READ** → structure, tone, depth, flow
- Something being **ORGANIZED** → criteria, grouping, naming, exceptions
Each gray area should be a concrete decision point, not a vague category. Example: "Loading behavior" not "UX".
**4.5b. Present gray areas**
```
AskUserQuestion(
header: "Gray Areas",
question: "Which areas need clarification before planning?",
options: [
{ label: "${area_1}", description: "${why_it_matters_1}" },
{ label: "${area_2}", description: "${why_it_matters_2}" },
{ label: "${area_3}", description: "${why_it_matters_3}" },
{ label: "All clear", description: "Skip discussion — I know what I want" }
],
multiSelect: true
)
```
If user selects "All clear" → skip to Step 5 (no CONTEXT.md written).
**4.5c. Discuss selected areas**
For each selected area, ask 1-2 focused questions via AskUserQuestion:
```
AskUserQuestion(
header: "${area_name}",
question: "${specific_question_about_this_area}",
options: [
{ label: "${concrete_choice_1}", description: "${what_this_means}" },
{ label: "${concrete_choice_2}", description: "${what_this_means}" },
{ label: "${concrete_choice_3}", description: "${what_this_means}" },
{ label: "You decide", description: "Claude's discretion" }
],
multiSelect: false
)
```
Rules:
- Options must be concrete choices, not abstract categories
- Highlight recommended choice where you have a clear opinion
- If user selects "Other" with freeform text, switch to plain text follow-up (per questioning.md freeform rule)
- If user selects "You decide", capture as Claude's Discretion in CONTEXT.md
- Max 2 questions per area — this is lightweight, not a deep dive
Collect all decisions into `$DECISIONS`.
**4.5d. Write CONTEXT.md**
Write `${QUICK_DIR}/${quick_id}-CONTEXT.md` using the standard context template structure:
```markdown
# Quick Task ${quick_id}: ${DESCRIPTION} - Context
**Gathered:** ${date}
**Status:** Ready for planning
<domain>
## Task Boundary
${DESCRIPTION}
</domain>
<decisions>
## Implementation Decisions
### ${area_1_name}
- ${decision_from_discussion}
### ${area_2_name}
- ${decision_from_discussion}
### Claude's Discretion
${areas_where_user_said_you_decide_or_areas_not_discussed}
</decisions>
<specifics>
## Specific Ideas
${any_specific_references_or_examples_from_discussion}
[If none: "No specific requirements — open to standard approaches"]
</specifics>
<canonical_refs>
## Canonical References
${any_specs_adrs_or_docs_referenced_during_discussion}
[If none: "No external specs — requirements fully captured in decisions above"]
</canonical_refs>
```
Note: Quick task CONTEXT.md omits `<code_context>` and `<deferred>` sections (no codebase scouting, no phase scope to defer to). Keep it lean. The `<canonical_refs>` section is included when external docs were referenced — omit it only if no external docs apply.
Report: `Context captured: ${QUICK_DIR}/${quick_id}-CONTEXT.md`
<!-- gsd:section id="discussion-phase" when="flag:--discuss" -->
If `section_manifest` is `null` or `"discussion-phase"` is in its `included` list: read and execute `gsd-core/workflows/quick/steps/discussion-phase.md`. Otherwise skip — do not read the file.
<!-- /gsd:section -->
---
**Step 4.75: Research phase (only when `$RESEARCH_MODE`)**
Skip this step entirely if NOT `$RESEARCH_MODE`.
Display banner:
```
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
GSD ► RESEARCHING QUICK TASK
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
◆ Investigating approaches for: ${DESCRIPTION} (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
```
Spawn a single focused researcher (not 4 parallel researchers like full phases — quick tasks need targeted research, not broad domain surveys):
<!-- #2517 model-omit-on-inherit -->
> **Model omission (#2517).** Omit the `model` parameter entirely when the value it would carry (`planner_model`, `checker_model`, `executor_model`, `reviewer_model`, `verifier_model`) is `"inherit"` or empty. An empty value 404s on runtimes without native tier aliases — the default on non-Claude runtimes. Omitting it inherits the orchestrator's model. See @gsd-core/references/model-profile-resolution.md.
```
Agent(
prompt="
<research_context>
**Mode:** quick-task
**Task:** ${DESCRIPTION}
**Output:** ${QUICK_DIR}/${quick_id}-RESEARCH.md
<files_to_read>
- ${STATE_PATH} (Project state — what's already built)
- ${PROJECT_PATH} (Project context)
- ./CLAUDE.md or ./.claude/CLAUDE.md (if exists — project-specific guidelines)
${DISCUSS_MODE ? '- ' + QUICK_DIR + '/' + quick_id + '-CONTEXT.md (User decisions — research should align with these)' : ''}
</files_to_read>
${AGENT_SKILLS_PLANNER}
</research_context>
<focus>
This is a quick task, not a full phase. Research should be concise and targeted:
1. Best libraries/patterns for this specific task
2. Common pitfalls and how to avoid them
3. Integration points with existing codebase
4. Any constraints or gotchas worth knowing before planning
Do NOT produce a full domain survey. Target 1-2 pages of actionable findings.
</focus>
<!-- #2508 runtime-aware-dispatch -->
> **Runtime-aware dispatch (#2508 Phase 4).** GSD workflows dispatch specialized subagents by role. Before dispatching on a built-in-only runtime (kimi-code — three built-ins only), resolve the role to a built-in via `gsd_run query resolve-dispatch-type --requested <role> --raw`. On named-dispatch runtimes (Claude/OpenCode/…) the role is returned unchanged; on kimi-code it maps to `coder`/`explore`/`plan` by role-suffix. The persona rides `${AGENT_SKILLS_<ROLE>}` (Phase 3) regardless. See @gsd-core/references/runtime-aware-dispatch.md.
<output>
Write research to: ${QUICK_DIR}/${quick_id}-RESEARCH.md
Use standard research format but keep it lean — skip sections that don't apply.
Return: ## RESEARCH COMPLETE with file path
</output>
",
subagent_type="gsd-phase-researcher",
model="{planner_model}",
description="Research: ${DESCRIPTION}"
)
```
> **ORCHESTRATOR RULE — CODEX RUNTIME**: After calling Agent() above, stop working on this task immediately. Do not read more files, edit code, or run tests related to this task while the subagent is active. Wait for the subagent to return its result. This prevents duplicate work, conflicting edits, and wasted context. Only resume when the subagent result is available.
After researcher returns:
1. Verify research exists at `${QUICK_DIR}/${quick_id}-RESEARCH.md`
2. Report: "Research complete: ${QUICK_DIR}/${quick_id}-RESEARCH.md"
If research file not found, warn but continue: "Research agent did not produce output — proceeding to planning without research."
<!-- gsd:section id="research-phase" when="flag:--research" -->
If `section_manifest` is `null` or `"research-phase"` is in its `included` list: read and execute `gsd-core/workflows/quick/steps/research-phase.md`. Otherwise skip — do not read the file.
<!-- /gsd:section -->
---
@@ -539,156 +353,15 @@ If plan not found, error: "Planner failed to create ${quick_id}-PLAN.md"
---
**Step 5.5: Plan-checker loop (only when `$VALIDATE_MODE`)**
Skip this step entirely if NOT `$VALIDATE_MODE`.
Display banner:
```
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
GSD ► CHECKING PLAN
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
◆ Spawning plan checker... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
```
Checker prompt:
```markdown
<verification_context>
**Mode:** quick-full
**Task Description:** ${DESCRIPTION}
<files_to_read>
- ${QUICK_DIR}/${quick_id}-PLAN.md (Plan to verify)
</files_to_read>
${AGENT_SKILLS_CHECKER}
**Scope:** This is a quick task, not a full phase. Skip checks that require a ROADMAP phase goal.
</verification_context>
<check_dimensions>
- Requirement coverage: Does the plan address the task description?
- Task completeness: Do tasks have files, action, verify, done fields?
- Key links: Are referenced files real?
- Scope sanity: Is this appropriately sized for a quick task (1-3 tasks)?
- must_haves derivation: Are must_haves traceable to the task description?
Skip: cross-plan deps (single plan), ROADMAP alignment
${DISCUSS_MODE ? '- Context compliance: Does the plan honor locked decisions from CONTEXT.md?' : '- Skip: context compliance (no CONTEXT.md)'}
</check_dimensions>
<expected_output>
- ## VERIFICATION PASSED — all checks pass
- ## ISSUES FOUND — structured issue list
</expected_output>
```
```
Agent(
prompt=checker_prompt,
subagent_type="gsd-plan-checker",
model="{checker_model}",
description="Check quick plan: ${DESCRIPTION}"
)
```
> **ORCHESTRATOR RULE — CODEX RUNTIME**: After calling Agent() above, stop working on this task immediately. Do not read more files, edit code, or run tests related to this task while the subagent is active. Wait for the subagent to return its result. This prevents duplicate work, conflicting edits, and wasted context. Only resume when the subagent result is available.
**Handle checker return:**
- **`## VERIFICATION PASSED`:** Display confirmation, proceed to step 6.
- **`## ISSUES FOUND`:** Display issues, check iteration count, enter revision loop.
**Revision loop (max 2 iterations):**
Track `iteration_count` (starts at 1 after initial plan + check).
**If iteration_count < 2:**
Display: `Sending back to planner for revision... (iteration ${N}/2)`
Revision prompt:
```markdown
<revision_context>
**Mode:** quick-full (revision)
<files_to_read>
- ${QUICK_DIR}/${quick_id}-PLAN.md (Existing plan)
</files_to_read>
${AGENT_SKILLS_PLANNER}
**Checker issues:** ${structured_issues_from_checker}
</revision_context>
<instructions>
Make targeted updates to address checker issues.
Do NOT replan from scratch unless issues are fundamental.
Return what changed.
</instructions>
```
```
Agent(
prompt=revision_prompt,
subagent_type="gsd-planner",
model="{planner_model}",
description="Revise quick plan: ${DESCRIPTION}"
)
```
> **ORCHESTRATOR RULE — CODEX RUNTIME**: After calling Agent() above, stop working on this task immediately. Do not read more files, edit code, or run tests related to this task while the subagent is active. Wait for the subagent to return its result. This prevents duplicate work, conflicting edits, and wasted context. Only resume when the subagent result is available.
After planner returns → spawn checker again, increment iteration_count.
**If iteration_count >= 2:**
Display: `Max iterations reached. ${N} issues remain:` + issue list
Offer: 1) Force proceed, 2) Abort
<!-- gsd:section id="plan-checker-loop" when="flag:--validate" -->
If `section_manifest` is `null` or `"plan-checker-loop"` is in its `included` list: read and execute `gsd-core/workflows/quick/steps/plan-checker-loop.md`. Otherwise skip — do not read the file.
<!-- /gsd:section -->
---
**Step 5.6: Pre-dispatch plan commit (worktree mode only)**
When `USE_WORKTREES !== "false"`, commit PLAN.md to the current branch **before** spawning the executor. This ensures the worktree inherits PLAN.md at its branch HEAD so the executor can read it via a worktree-rooted path — avoiding the main-repo path priming that triggers CC #36182 path-resolution drift.
Skip this step entirely if `USE_WORKTREES === "false"` (non-worktree mode: PLAN.md is committed in Step 8 as usual).
```bash
QUICK_PLAN_PARENT=""
QUICK_PLAN_COMMIT=""
if [ "${USE_WORKTREES}" != "false" ]; then
QUICK_PLAN_PARENT=$(git rev-parse HEAD)
COMMIT_DOCS=$(gsd_run query config-get commit_docs 2>/dev/null || echo "true")
if [ "$COMMIT_DOCS" != "false" ]; then
git add "${QUICK_DIR}/${quick_id}-PLAN.md"
# No-op skip if nothing actually staged (idempotent re-runs).
if git diff --cached --quiet -- "${QUICK_DIR}/${quick_id}-PLAN.md"; then
echo "ℹ Pre-dispatch PLAN.md commit skipped (no staged changes)"
else
# Run hooks normally (#2924). If a project opts out via
# workflow.worktree_skip_hooks=true, honor that opt-in only.
SKIP_HOOKS=$(gsd_run query config-get workflow.worktree_skip_hooks 2>/dev/null || echo "false")
if [ "$SKIP_HOOKS" = "true" ]; then
git commit --no-verify -m "docs(${quick_id}): pre-dispatch plan for ${DESCRIPTION}" -- "${QUICK_DIR}/${quick_id}-PLAN.md" \
|| { echo "ERROR: pre-dispatch PLAN.md commit failed (--no-verify path). Aborting before executor dispatch." >&2; exit 1; }
else
git commit -m "docs(${quick_id}): pre-dispatch plan for ${DESCRIPTION}" -- "${QUICK_DIR}/${quick_id}-PLAN.md" \
|| { echo "ERROR: pre-dispatch PLAN.md commit failed — likely a pre-commit hook failure. Fix the hook output above (or set workflow.worktree_skip_hooks=true to bypass) and re-run." >&2; exit 1; }
fi
QUICK_PLAN_COMMIT=$(git rev-parse HEAD)
fi
fi
if [ -z "$QUICK_PLAN_COMMIT" ]; then
QUICK_PLAN_COMMIT=$(git rev-parse HEAD)
fi
fi
```
<!-- gsd:section id="worktree-pre-dispatch-commit" when="state:worktrees-enabled" -->
If `section_manifest` is `null` or `"worktree-pre-dispatch-commit"` is in its `included` list: read and execute `gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md`. Otherwise skip — do not read the file.
<!-- /gsd:section -->
---
@@ -901,52 +574,9 @@ If review produces findings, display advisory message. **Error handling:** Failu
---
**Step 6.5: Verification (only when `$VALIDATE_MODE`)**
Skip this step entirely if NOT `$VALIDATE_MODE`.
Display banner:
```
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
GSD ► VERIFYING RESULTS
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
◆ Spawning verifier... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
```
```
Agent(
prompt="Verify quick task goal achievement.
Task directory: ${QUICK_DIR}
Task goal: ${DESCRIPTION}
<files_to_read>
- ${QUICK_DIR}/${quick_id}-PLAN.md (Plan)
</files_to_read>
${AGENT_SKILLS_VERIFIER}
Check must_haves against actual codebase. Create VERIFICATION.md at ${QUICK_DIR}/${quick_id}-VERIFICATION.md.",
subagent_type="gsd-verifier",
model="{verifier_model}",
description="Verify: ${DESCRIPTION}"
)
```
> **ORCHESTRATOR RULE — CODEX RUNTIME**: After calling Agent() above, stop working on this task immediately. Do not read more files, edit code, or run tests related to this task while the subagent is active. Wait for the subagent to return its result. This prevents duplicate work, conflicting edits, and wasted context. Only resume when the subagent result is available.
Read verification status:
```bash
grep "^status:" "${QUICK_DIR}/${quick_id}-VERIFICATION.md" | cut -d: -f2 | tr -d ' '
```
Store as `$VERIFICATION_STATUS`.
| Status | Action |
|--------|--------|
| `passed` | Store `$VERIFICATION_STATUS = "Verified"`, continue to step 7 |
| `human_needed` | Display items needing manual check, store `$VERIFICATION_STATUS = "Needs Review"`, continue |
| `gaps_found` | Display gap summary, offer: 1) Re-run executor to fix gaps, 2) Accept as-is. Store `$VERIFICATION_STATUS = "Gaps"` |
<!-- gsd:section id="quick-verification" when="flag:--validate" -->
If `section_manifest` is `null` or `"quick-verification"` is in its `included` list: read and execute `gsd-core/workflows/quick/steps/quick-verification.md`. Otherwise skip — do not read the file.
<!-- /gsd:section -->
---

View File

@@ -0,0 +1,124 @@
**Step 4.5: Discussion phase (only when `$DISCUSS_MODE`)**
Skip this step entirely if NOT `$DISCUSS_MODE`.
Display banner:
```
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
GSD ► DISCUSSING QUICK TASK
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
◆ Surfacing gray areas for: ${DESCRIPTION}
```
**4.5a. Identify gray areas**
Analyze `$DESCRIPTION` to identify 2-4 gray areas — implementation decisions that would change the outcome and that the user should weigh in on.
Use the domain-aware heuristic to generate phase-specific (not generic) gray areas:
- Something users **SEE** → layout, density, interactions, states
- Something users **CALL** → responses, errors, auth, versioning
- Something users **RUN** → output format, flags, modes, error handling
- Something users **READ** → structure, tone, depth, flow
- Something being **ORGANIZED** → criteria, grouping, naming, exceptions
Each gray area should be a concrete decision point, not a vague category. Example: "Loading behavior" not "UX".
**4.5b. Present gray areas**
```
AskUserQuestion(
header: "Gray Areas",
question: "Which areas need clarification before planning?",
options: [
{ label: "${area_1}", description: "${why_it_matters_1}" },
{ label: "${area_2}", description: "${why_it_matters_2}" },
{ label: "${area_3}", description: "${why_it_matters_3}" },
{ label: "All clear", description: "Skip discussion — I know what I want" }
],
multiSelect: true
)
```
If user selects "All clear" → skip to Step 5 (no CONTEXT.md written).
**4.5c. Discuss selected areas**
For each selected area, ask 1-2 focused questions via AskUserQuestion:
```
AskUserQuestion(
header: "${area_name}",
question: "${specific_question_about_this_area}",
options: [
{ label: "${concrete_choice_1}", description: "${what_this_means}" },
{ label: "${concrete_choice_2}", description: "${what_this_means}" },
{ label: "${concrete_choice_3}", description: "${what_this_means}" },
{ label: "You decide", description: "Claude's discretion" }
],
multiSelect: false
)
```
Rules:
- Options must be concrete choices, not abstract categories
- Highlight recommended choice where you have a clear opinion
- If user selects "Other" with freeform text, switch to plain text follow-up (per questioning.md freeform rule)
- If user selects "You decide", capture as Claude's Discretion in CONTEXT.md
- Max 2 questions per area — this is lightweight, not a deep dive
Collect all decisions into `$DECISIONS`.
**4.5d. Write CONTEXT.md**
Write `${QUICK_DIR}/${quick_id}-CONTEXT.md` using the standard context template structure:
```markdown
# Quick Task ${quick_id}: ${DESCRIPTION} - Context
**Gathered:** ${date}
**Status:** Ready for planning
<domain>
## Task Boundary
${DESCRIPTION}
</domain>
<decisions>
## Implementation Decisions
### ${area_1_name}
- ${decision_from_discussion}
### ${area_2_name}
- ${decision_from_discussion}
### Claude's Discretion
${areas_where_user_said_you_decide_or_areas_not_discussed}
</decisions>
<specifics>
## Specific Ideas
${any_specific_references_or_examples_from_discussion}
[If none: "No specific requirements — open to standard approaches"]
</specifics>
<canonical_refs>
## Canonical References
${any_specs_adrs_or_docs_referenced_during_discussion}
[If none: "No external specs — requirements fully captured in decisions above"]
</canonical_refs>
```
Note: Quick task CONTEXT.md omits `<code_context>` and `<deferred>` sections (no codebase scouting, no phase scope to defer to). Keep it lean. The `<canonical_refs>` section is included when external docs were referenced — omit it only if no external docs apply.
Report: `Context captured: ${QUICK_DIR}/${quick_id}-CONTEXT.md`

View File

@@ -0,0 +1,111 @@
**Step 5.5: Plan-checker loop (only when `$VALIDATE_MODE`)**
Skip this step entirely if NOT `$VALIDATE_MODE`.
Display banner:
```
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
GSD ► CHECKING PLAN
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
◆ Spawning plan checker... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
```
Checker prompt:
```markdown
<verification_context>
**Mode:** quick-full
**Task Description:** ${DESCRIPTION}
<files_to_read>
- ${QUICK_DIR}/${quick_id}-PLAN.md (Plan to verify)
</files_to_read>
${AGENT_SKILLS_CHECKER}
**Scope:** This is a quick task, not a full phase. Skip checks that require a ROADMAP phase goal.
</verification_context>
<check_dimensions>
- Requirement coverage: Does the plan address the task description?
- Task completeness: Do tasks have files, action, verify, done fields?
- Key links: Are referenced files real?
- Scope sanity: Is this appropriately sized for a quick task (1-3 tasks)?
- must_haves derivation: Are must_haves traceable to the task description?
Skip: cross-plan deps (single plan), ROADMAP alignment
${DISCUSS_MODE ? '- Context compliance: Does the plan honor locked decisions from CONTEXT.md?' : '- Skip: context compliance (no CONTEXT.md)'}
</check_dimensions>
<expected_output>
- ## VERIFICATION PASSED — all checks pass
- ## ISSUES FOUND — structured issue list
</expected_output>
```
```
Agent(
prompt=checker_prompt,
subagent_type="gsd-plan-checker",
model="{checker_model}",
description="Check quick plan: ${DESCRIPTION}"
)
```
> **ORCHESTRATOR RULE — CODEX RUNTIME**: After calling Agent() above, stop working on this task immediately. Do not read more files, edit code, or run tests related to this task while the subagent is active. Wait for the subagent to return its result. This prevents duplicate work, conflicting edits, and wasted context. Only resume when the subagent result is available.
**Handle checker return:**
- **`## VERIFICATION PASSED`:** Display confirmation, proceed to step 6.
- **`## ISSUES FOUND`:** Display issues, check iteration count, enter revision loop.
**Revision loop (max 2 iterations):**
Track `iteration_count` (starts at 1 after initial plan + check).
**If iteration_count < 2:**
Display: `Sending back to planner for revision... (iteration ${N}/2)`
Revision prompt:
```markdown
<revision_context>
**Mode:** quick-full (revision)
<files_to_read>
- ${QUICK_DIR}/${quick_id}-PLAN.md (Existing plan)
</files_to_read>
${AGENT_SKILLS_PLANNER}
**Checker issues:** ${structured_issues_from_checker}
</revision_context>
<instructions>
Make targeted updates to address checker issues.
Do NOT replan from scratch unless issues are fundamental.
Return what changed.
</instructions>
```
```
Agent(
prompt=revision_prompt,
subagent_type="gsd-planner",
model="{planner_model}",
description="Revise quick plan: ${DESCRIPTION}"
)
```
> **ORCHESTRATOR RULE — CODEX RUNTIME**: After calling Agent() above, stop working on this task immediately. Do not read more files, edit code, or run tests related to this task while the subagent is active. Wait for the subagent to return its result. This prevents duplicate work, conflicting edits, and wasted context. Only resume when the subagent result is available.
After planner returns → spawn checker again, increment iteration_count.
**If iteration_count >= 2:**
Display: `Max iterations reached. ${N} issues remain:` + issue list
Offer: 1) Force proceed, 2) Abort

View File

@@ -0,0 +1,46 @@
**Step 6.5: Verification (only when `$VALIDATE_MODE`)**
Skip this step entirely if NOT `$VALIDATE_MODE`.
Display banner:
```
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
GSD ► VERIFYING RESULTS
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
◆ Spawning verifier... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
```
```
Agent(
prompt="Verify quick task goal achievement.
Task directory: ${QUICK_DIR}
Task goal: ${DESCRIPTION}
<files_to_read>
- ${QUICK_DIR}/${quick_id}-PLAN.md (Plan)
</files_to_read>
${AGENT_SKILLS_VERIFIER}
Check must_haves against actual codebase. Create VERIFICATION.md at ${QUICK_DIR}/${quick_id}-VERIFICATION.md.",
subagent_type="gsd-verifier",
model="{verifier_model}",
description="Verify: ${DESCRIPTION}"
)
```
> **ORCHESTRATOR RULE — CODEX RUNTIME**: After calling Agent() above, stop working on this task immediately. Do not read more files, edit code, or run tests related to this task while the subagent is active. Wait for the subagent to return its result. This prevents duplicate work, conflicting edits, and wasted context. Only resume when the subagent result is available.
Read verification status:
```bash
grep "^status:" "${QUICK_DIR}/${quick_id}-VERIFICATION.md" | cut -d: -f2 | tr -d ' '
```
Store as `$VERIFICATION_STATUS`.
| Status | Action |
|--------|--------|
| `passed` | Store `$VERIFICATION_STATUS = "Verified"`, continue to step 7 |
| `human_needed` | Display items needing manual check, store `$VERIFICATION_STATUS = "Needs Review"`, continue |
| `gaps_found` | Display gap summary, offer: 1) Re-run executor to fix gaps, 2) Accept as-is. Store `$VERIFICATION_STATUS = "Gaps"` |

View File

@@ -0,0 +1,72 @@
**Step 4.75: Research phase (only when `$RESEARCH_MODE`)**
Skip this step entirely if NOT `$RESEARCH_MODE`.
Display banner:
```
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
GSD ► RESEARCHING QUICK TASK
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
◆ Investigating approaches for: ${DESCRIPTION} (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
```
Spawn a single focused researcher (not 4 parallel researchers like full phases — quick tasks need targeted research, not broad domain surveys):
<!-- #2517 model-omit-on-inherit -->
> **Model omission (#2517).** Omit the `model` parameter entirely when the value it would carry (`planner_model`, `checker_model`, `executor_model`, `reviewer_model`, `verifier_model`) is `"inherit"` or empty. An empty value 404s on runtimes without native tier aliases — the default on non-Claude runtimes. Omitting it inherits the orchestrator's model. See @gsd-core/references/model-profile-resolution.md.
<!-- #2508 runtime-aware-dispatch -->
> **Runtime-aware dispatch (#2508 Phase 4).** GSD workflows dispatch specialized subagents by role. Before dispatching on a built-in-only runtime (kimi-code — three built-ins only), resolve the role to a built-in via `gsd_run query resolve-dispatch-type --requested <role> --raw`. On named-dispatch runtimes (Claude/OpenCode/…) the role is returned unchanged; on kimi-code it maps to `coder`/`explore`/`plan` by role-suffix. The persona rides `${AGENT_SKILLS_<ROLE>}` (Phase 3) regardless. See @gsd-core/references/runtime-aware-dispatch.md.
```
Agent(
prompt="
<research_context>
**Mode:** quick-task
**Task:** ${DESCRIPTION}
**Output:** ${QUICK_DIR}/${quick_id}-RESEARCH.md
<files_to_read>
- ${STATE_PATH} (Project state — what's already built)
- ${PROJECT_PATH} (Project context)
- ./CLAUDE.md or ./.claude/CLAUDE.md (if exists — project-specific guidelines)
${DISCUSS_MODE ? '- ' + QUICK_DIR + '/' + quick_id + '-CONTEXT.md (User decisions — research should align with these)' : ''}
</files_to_read>
${AGENT_SKILLS_PLANNER}
</research_context>
<focus>
This is a quick task, not a full phase. Research should be concise and targeted:
1. Best libraries/patterns for this specific task
2. Common pitfalls and how to avoid them
3. Integration points with existing codebase
4. Any constraints or gotchas worth knowing before planning
Do NOT produce a full domain survey. Target 1-2 pages of actionable findings.
</focus>
<output>
Write research to: ${QUICK_DIR}/${quick_id}-RESEARCH.md
Use standard research format but keep it lean — skip sections that don't apply.
Return: ## RESEARCH COMPLETE with file path
</output>
",
subagent_type="gsd-phase-researcher",
model="{planner_model}",
description="Research: ${DESCRIPTION}"
)
```
> **ORCHESTRATOR RULE — CODEX RUNTIME**: After calling Agent() above, stop working on this task immediately. Do not read more files, edit code, or run tests related to this task while the subagent is active. Wait for the subagent to return its result. This prevents duplicate work, conflicting edits, and wasted context. Only resume when the subagent result is available.
After researcher returns:
1. Verify research exists at `${QUICK_DIR}/${quick_id}-RESEARCH.md`
2. Report: "Research complete: ${QUICK_DIR}/${quick_id}-RESEARCH.md"
If research file not found, warn but continue: "Research agent did not produce output — proceeding to planning without research."

View File

@@ -0,0 +1,37 @@
**Step 5.6: Pre-dispatch plan commit (worktree mode only)**
When `USE_WORKTREES !== "false"`, commit PLAN.md to the current branch **before** spawning the executor. This ensures the worktree inherits PLAN.md at its branch HEAD so the executor can read it via a worktree-rooted path — avoiding the main-repo path priming that triggers CC #36182 path-resolution drift.
Skip this step entirely if `USE_WORKTREES === "false"` (non-worktree mode: PLAN.md is committed in Step 8 as usual).
```bash
_GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; if [ -f "$GSD_TOOLS" ]; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif command -v gsd-tools >/dev/null 2>&1; then GSD_TOOLS="$(command -v gsd-tools)"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif [ -f "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and gsd-tools is not on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
QUICK_PLAN_PARENT=""
QUICK_PLAN_COMMIT=""
if [ "${USE_WORKTREES}" != "false" ]; then
QUICK_PLAN_PARENT=$(git rev-parse HEAD)
COMMIT_DOCS=$(gsd_run query config-get commit_docs 2>/dev/null || echo "true")
if [ "$COMMIT_DOCS" != "false" ]; then
git add "${QUICK_DIR}/${quick_id}-PLAN.md"
# No-op skip if nothing actually staged (idempotent re-runs).
if git diff --cached --quiet -- "${QUICK_DIR}/${quick_id}-PLAN.md"; then
echo "ℹ Pre-dispatch PLAN.md commit skipped (no staged changes)"
else
# Run hooks normally (#2924). If a project opts out via
# workflow.worktree_skip_hooks=true, honor that opt-in only.
SKIP_HOOKS=$(gsd_run query config-get workflow.worktree_skip_hooks 2>/dev/null || echo "false")
if [ "$SKIP_HOOKS" = "true" ]; then
git commit --no-verify -m "docs(${quick_id}): pre-dispatch plan for ${DESCRIPTION}" -- "${QUICK_DIR}/${quick_id}-PLAN.md" \
|| { echo "ERROR: pre-dispatch PLAN.md commit failed (--no-verify path). Aborting before executor dispatch." >&2; exit 1; }
else
git commit -m "docs(${quick_id}): pre-dispatch plan for ${DESCRIPTION}" -- "${QUICK_DIR}/${quick_id}-PLAN.md" \
|| { echo "ERROR: pre-dispatch PLAN.md commit failed — likely a pre-commit hook failure. Fix the hook output above (or set workflow.worktree_skip_hooks=true to bypass) and re-run." >&2; exit 1; }
fi
QUICK_PLAN_COMMIT=$(git rev-parse HEAD)
fi
fi
if [ -z "$QUICK_PLAN_COMMIT" ]; then
QUICK_PLAN_COMMIT=$(git rev-parse HEAD)
fi
fi
```

View File

@@ -105,10 +105,9 @@ user who wants a preferred set has `review.default_reviewers`. Both stay lenient
preference evaluated across many hosts, so a subset being present is expected, not an error
- If all configured reviewers are unavailable, fail with an actionable message
**Reviewer instances (#1517, optional):** if `review.reviewer_instances` is configured,
instance names in `review.default_reviewers` run as independent identities. Resolution rules
are in `gsd-core/references/reviewer-instances.md` — load it lazily only when instances are
configured. Unconfigured → default path unchanged.
<!-- gsd:section id="reviewer-instances-note-1" when="state:reviewer-instances-configured" -->
If `section_manifest` is `null` or `"reviewer-instances-note-1"` is in its `included` list: read and execute `gsd-core/workflows/review/steps/reviewer-instances-note-1.md`. Otherwise skip — do not read the file.
<!-- /gsd:section -->
If no CLIs are available:
```
@@ -156,7 +155,7 @@ Rules:
Collect phase artifacts for the review prompt:
```bash
INIT=$(gsd_run query init.phase-op "${PHASE_ARG}")
INIT=$(gsd_run query init.review "${PHASE_ARG}")
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
# #2358: ONE run-scoped temp dir (portable via ${TMPDIR:-/tmp}) so overlapping
@@ -306,9 +305,9 @@ An environment that genuinely hits an untrusted-hook prompt surfaces through the
the empty-output stub as a dropped lane with diagnosable stderr, not silent attrition. Do not
reintroduce the flag (even spelled out in prose — a regression test bans the literal file-wide).
**Reviewer instances (#1517, optional):** instances resolve *through* a lane and are not lanes
themselves (ADR-2782 D8). Each selected instance invokes its base `cli` with its own `model`/`agent`
as opaque argv. Exact invocation in `gsd-core/references/reviewer-instances.md`.
<!-- gsd:section id="reviewer-instances-note-2" when="state:reviewer-instances-configured" -->
If `section_manifest` is `null` or `"reviewer-instances-note-2"` is in its `included` list: read and execute `gsd-core/workflows/review/steps/reviewer-instances-note-2.md`. Otherwise skip — do not read the file.
<!-- /gsd:section -->
Lanes run **sequentially, not in parallel** — concurrent invocation trips provider rate limits.

View File

@@ -0,0 +1,4 @@
**Reviewer instances (#1517, optional):** if `review.reviewer_instances` is configured,
instance names in `review.default_reviewers` run as independent identities. Resolution rules
are in `gsd-core/references/reviewer-instances.md` — load it lazily only when instances are
configured. Unconfigured → default path unchanged.

View File

@@ -0,0 +1,3 @@
**Reviewer instances (#1517, optional):** instances resolve *through* a lane and are not lanes
themselves (ADR-2782 D8). Each selected instance invokes its base `cli` with its own `model`/`agent`
as opaque argv. Exact invocation in `gsd-core/references/reviewer-instances.md`.

View File

@@ -1,5 +1,65 @@
{
"workflows": {
"autonomous": [
{
"id": "converge-fail-fast",
"when": "state:plan-strategy-converge",
"read": "gsd-core/workflows/autonomous/steps/converge-fail-fast.md"
},
{
"id": "converge-banner",
"when": "state:plan-strategy-converge",
"read": "gsd-core/workflows/autonomous/steps/converge-banner.md"
},
{
"id": "converge-dispatch-bg",
"when": "state:plan-strategy-converge",
"read": "gsd-core/workflows/autonomous/steps/converge-dispatch-bg.md"
},
{
"id": "converge-dispatch-inline",
"when": "state:plan-strategy-converge",
"read": "gsd-core/workflows/autonomous/steps/converge-dispatch-inline.md"
},
{
"id": "converge-loop",
"when": "state:plan-strategy-converge",
"read": "gsd-core/workflows/autonomous/steps/converge-loop.md"
}
],
"code-review": [
{
"id": "structural-pre-pass",
"when": "state:fallow-enabled",
"read": "gsd-core/workflows/code-review/steps/structural-pre-pass.md"
},
{
"id": "dispatch-fix",
"when": "flag:--fix",
"read": "gsd-core/workflows/code-review/steps/dispatch-fix.md"
}
],
"complete-milestone": [
{
"id": "git-tag",
"when": "state:git-create-tag",
"read": "gsd-core/workflows/complete-milestone/steps/git-tag.md"
}
],
"discuss-phase-assumptions": [
{
"id": "auto-advance-dispatch",
"when": "state:auto-advance-active",
"read": "gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md"
}
],
"docs-update": [
{
"id": "dispatch-monorepo-packages",
"when": "state:is-monorepo",
"read": "gsd-core/workflows/docs-update/steps/dispatch-monorepo-packages.md"
}
],
"execute-phase": [
{
"id": "partial-wave",
@@ -17,6 +77,35 @@
"read": "gsd-core/workflows/execute-phase/steps/regression-gate.md"
}
],
"new-milestone": [
{
"id": "project-md-milestone-write",
"when": "state:flat-mode",
"read": "gsd-core/workflows/new-milestone/steps/project-md-milestone-write.md"
},
{
"id": "reset-phase-safety",
"when": "flag:--reset-phase-numbers",
"read": "gsd-core/workflows/new-milestone/steps/reset-phase-safety.md"
}
],
"new-project": [
{
"id": "auto-mode-detection",
"when": "flag:--auto",
"read": "gsd-core/workflows/new-project/steps/auto-mode-detection.md"
},
{
"id": "codebase-map-offer",
"when": "state:needs-codebase-map",
"read": "gsd-core/workflows/new-project/steps/codebase-map-offer.md"
},
{
"id": "auto-mode-config",
"when": "flag:--auto",
"read": "gsd-core/workflows/new-project/steps/auto-mode-config.md"
}
],
"plan-phase": [
{
"id": "reviews-prerequisite",
@@ -48,6 +137,83 @@
"when": "state:chunked-mode",
"read": "gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md"
}
],
"progress": [
{
"id": "mvp-display",
"when": "state:phase-mvp-mode",
"read": "gsd-core/workflows/progress/steps/mvp-display.md"
},
{
"id": "forensic-audit",
"when": "flag:--forensic",
"read": "gsd-core/workflows/progress/steps/forensic-audit.md"
}
],
"quick": [
{
"id": "discussion-phase",
"when": "flag:--discuss",
"read": "gsd-core/workflows/quick/steps/discussion-phase.md"
},
{
"id": "research-phase",
"when": "flag:--research",
"read": "gsd-core/workflows/quick/steps/research-phase.md"
},
{
"id": "plan-checker-loop",
"when": "flag:--validate",
"read": "gsd-core/workflows/quick/steps/plan-checker-loop.md"
},
{
"id": "worktree-pre-dispatch-commit",
"when": "state:worktrees-enabled",
"read": "gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md"
},
{
"id": "quick-verification",
"when": "flag:--validate",
"read": "gsd-core/workflows/quick/steps/quick-verification.md"
}
],
"review": [
{
"id": "reviewer-instances-note-1",
"when": "state:reviewer-instances-configured",
"read": "gsd-core/workflows/review/steps/reviewer-instances-note-1.md"
},
{
"id": "reviewer-instances-note-2",
"when": "state:reviewer-instances-configured",
"read": "gsd-core/workflows/review/steps/reviewer-instances-note-2.md"
}
],
"transition": [
{
"id": "workstream-collision-check",
"when": "state:workstream-active",
"read": "gsd-core/workflows/transition/steps/workstream-collision-check.md"
}
],
"update": [
{
"id": "channel-banner",
"when": "state:next-channel",
"read": "gsd-core/workflows/update/steps/channel-banner.md"
}
],
"verify-work": [
{
"id": "automated-ui-verification",
"when": "state:ui-phase-active",
"read": "gsd-core/workflows/verify-work/steps/automated-ui-verification.md"
},
{
"id": "mvp-uat-framing",
"when": "state:phase-mvp-mode",
"read": "gsd-core/workflows/verify-work/steps/mvp-uat-framing.md"
}
]
}
}

View File

@@ -417,33 +417,20 @@ ROADMAP=$(gsd_run query roadmap.analyze)
This returns all phases with goals, disk status, and completion info.
---
**Workstream collision check (when `is_last_phase: true`):**
Before routing to Route B, check whether other workstreams are still active.
This prevents one workstream from advancing or completing the milestone while
other workstreams are still working on their phases.
**Skip this check if NOT in workstream mode** (i.e., `GSD_WORKSTREAM` is not set / flat mode).
In flat mode, go directly to **Route B**.
**Section-manifest gate (#2994):** `gsd_run` is already established above (`update_roadmap_and_state` step) — fetch the dedicated `init.transition` bundle for the workstream-collision-check gate below:
```bash
# Only check if we're in workstream mode
if [ -n "$GSD_WORKSTREAM" ]; then
WS_LIST=$(gsd_run query workstream.list --raw)
fi
INIT_TRANSITION=$(gsd_run query init.transition)
if [[ "$INIT_TRANSITION" == @file:* ]]; then INIT_TRANSITION=$(cat "${INIT_TRANSITION#@file:}"); fi
```
Parse the JSON result. The output has `{ mode, workstreams: [...] }`.
Each workstream entry has: `name`, `status`, `current_phase`, `phase_count`, `completed_phases`.
Extract from `INIT_TRANSITION`: `other_active_workstreams`, `section_manifest`.
Filter out the current workstream (`$GSD_WORKSTREAM`) and any workstreams with
status containing "milestone complete" or "archived" (case-insensitive).
The remaining entries are **other active workstreams**.
---
- **If other active workstreams exist** → Go to **Route B1**
- **If NO other active workstreams** (or flat mode) → Go to **Route B**
<!-- gsd:section id="workstream-collision-check" when="state:workstream-active" -->
If `section_manifest` (from `INIT_TRANSITION`) is `null` or `"workstream-collision-check"` is in its `included` list: read and execute `gsd-core/workflows/transition/steps/workstream-collision-check.md`. Otherwise (flat mode) skip — do not read the file; go directly to **Route B**.
<!-- /gsd:section -->
---

View File

@@ -0,0 +1,17 @@
**Workstream collision check (when `is_last_phase: true`):**
Before routing to Route B, check whether other workstreams are still active.
This prevents one workstream from advancing or completing the milestone while
other workstreams are still working on their phases.
**Skip this check if NOT in workstream mode** (i.e., `GSD_WORKSTREAM` is not set / flat mode).
In flat mode, go directly to **Route B**.
Parse `other_active_workstreams` from `INIT_TRANSITION` (already fetched above — no
`gsd_run` call needed here). `init.transition` pre-filters this list exactly as this
check requires: it excludes the current workstream (`$GSD_WORKSTREAM`) and any
workstream whose status contains "milestone complete" or "archived"
(case-insensitive). Each remaining entry has `name` and `status`.
- **If `other_active_workstreams` is non-empty** → Go to **Route B1**
- **If `other_active_workstreams` is empty** (or flat mode) → Go to **Route B**

View File

@@ -102,6 +102,21 @@ esac
```
`TAG` is restricted to `latest`/`next` by `check-latest-version.cjs` (it rejects any other value with exit 2), so no arbitrary dist-tag can leak through. Omitting `--next`/`--rc` reproduces the prior behavior exactly: `TAG=latest`.
**Section-manifest gate (#2994):** reuse the `$GSD_TOOLS` already resolved by `get_installed_version` above — do NOT copy the canonical launcher preamble here, it assigns the SAME `$GSD_TOOLS` variable via a different (fixed-candidate) resolution and would silently override the value `backup_custom_files`/`restore_custom_files` (later steps) still depend on. Forward `--next`/`--rc` from `$ARGUMENTS` so `init.update`'s `state:next-channel` fact matches this step's own case-statement:
```bash
INIT_UPDATE=""
if [ -n "$GSD_TOOLS" ]; then
case "$GSD_TOOLS" in
*.cjs) INIT_UPDATE="$(node "$GSD_TOOLS" query init.update $([ "$TAG" = "next" ] && echo --next) 2>/dev/null)" ;;
*) INIT_UPDATE="$("$GSD_TOOLS" query init.update $([ "$TAG" = "next" ] && echo --next) 2>/dev/null)" ;;
esac
fi
if [[ "$INIT_UPDATE" == @file:* ]]; then INIT_UPDATE=$(cat "${INIT_UPDATE#@file:}"); fi
```
Extract `section_manifest` from `INIT_UPDATE` — gates the `channel-banner` section in `compare_versions` below.
</step>
<step name="check_latest_version">
@@ -152,13 +167,9 @@ Exit.
<step name="compare_versions">
Compare installed vs latest:
**Only when `TAG=next`** (the user passed `--next`/`--rc`), prepend a channel banner so they know they are leaving the stable line — add this line immediately after the `**Latest:**` line in whichever output block renders:
**Channel:** {CHANNEL_LABEL}
On the default stable channel (`TAG=latest`), do NOT add a channel line — the output must match the prior stable behavior exactly.
When `TAG=next`, the "latest" value is the release candidate published under `@next` (e.g. `1.4.0-rc.1`). Apply standard semver precedence for prereleases (`1.4.0-rc.1` is newer than `1.3.1` but older than the final `1.4.0`). Do NOT treat an `-rc.N` suffix as a dev install or as "behind" — offer it as an available update.
<!-- gsd:section id="channel-banner" when="state:next-channel" -->
If `section_manifest` (from `INIT_UPDATE`) is `null` or `"channel-banner"` is in its `included` list: read and execute `gsd-core/workflows/update/steps/channel-banner.md`. Otherwise (default stable channel) skip — do not read the file; the output must match the prior stable behavior exactly, with no channel line.
<!-- /gsd:section -->
**If installed == latest:**
```

View File

@@ -0,0 +1,7 @@
**Only when `TAG=next`** (the user passed `--next`/`--rc`), prepend a channel banner so they know they are leaving the stable line — add this line immediately after the `**Latest:**` line in whichever output block renders:
**Channel:** {CHANNEL_LABEL}
On the default stable channel (`TAG=latest`), do NOT add a channel line — the output must match the prior stable behavior exactly.
When `TAG=next`, the "latest" value is the release candidate published under `@next` (e.g. `1.4.0-rc.1`). Apply standard semver precedence for prereleases (`1.4.0-rc.1` is newer than `1.3.1` but older than the final `1.4.0`). Do NOT treat an `-rc.N` suffix as a dev install or as "behind" — offer it as an available update.

View File

@@ -39,8 +39,8 @@ If $ARGUMENTS contains a phase number, load context:
```bash
_GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; if [ -f "$GSD_TOOLS" ]; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif command -v gsd-tools >/dev/null 2>&1; then GSD_TOOLS="$(command -v gsd-tools)"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif [ -f "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and gsd-tools is not on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
GSD_WS=""
echo "$ARGUMENTS" | grep -qE -- '--ws[[:space:]]+[^[:space:]]+' && GSD_WS=$(echo "$ARGUMENTS" | grep -oE -- '--ws[[:space:]]+[^[:space:]]+')
PHASE_ARG=$(echo "$ARGUMENTS" | sed -E 's/--ws[[:space:]]+[^[:space:]]+//g' | xargs)
echo "$ARGUMENTS" | grep -qE -- '--ws[[:space:]]+[A-Za-z0-9._-]+' && GSD_WS=$(echo "$ARGUMENTS" | grep -oE -- '--ws[[:space:]]+[A-Za-z0-9._-]+')
PHASE_ARG=$(echo "$ARGUMENTS" | sed -E 's/--ws[[:space:]]+[A-Za-z0-9._-]+//g' | xargs)
INIT=$(gsd_run query init.verify-work "${PHASE_ARG}" ${GSD_WS})
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
@@ -145,42 +145,9 @@ Provide a phase number to start testing (e.g., /gsd:verify-work 4)
Continue to `create_uat_file`.
</step>
<step name="automated_ui_verification">
**Automated UI Verification (when Playwright-MCP is available)**
Before UAT, check UI capability activation and whether Playwright/Puppeteer MCP tools are available.
```bash
PLAN_HOOKS_JSON=$(gsd_run loop render-hooks plan:pre --raw)
UI_SPEC_FILE=$(ls "${PHASE_DIR}"/*-UI-SPEC.md 2>/dev/null | head -1)
```
Set `UI_PHASE_ACTIVE=true` when `PLAN_HOOKS_JSON.activeHooks` contains an active `ui` step hook.
**If Playwright-MCP tools are available in this session (`mcp__playwright__*` tools
respond to tool calls) AND (`UI_PHASE_ACTIVE` is `true` OR `UI_SPEC_FILE` is non-empty):**
For each UI checkpoint listed in the phase's UI-SPEC.md (or inferred from SUMMARY.md):
1. Use `mcp__playwright__navigate` (or equivalent) to open the component's URL.
2. Use `mcp__playwright__screenshot` to capture a screenshot.
3. Compare the screenshot visually against the spec's stated requirements
(dimensions, color, layout, spacing).
4. Automatically mark checkpoints as **passed** or **needs review** based on the
visual comparison — no manual question required for items that clearly match.
5. Flag items that require human judgment (subjective aesthetics, content accuracy)
and present only those as manual UAT questions.
If automated verification is not available, fall back to the standard manual
checkpoint questions defined in this workflow unchanged. This step is entirely
conditional: if Playwright-MCP is not configured, behavior is unchanged from today.
**Display summary line before proceeding:**
```
UI checkpoints: {N} auto-verified, {M} queued for manual review
```
</step>
<!-- gsd:section id="automated-ui-verification" when="state:ui-phase-active" -->
If `section_manifest` is `null` or `"automated-ui-verification"` is in its `included` list: read and execute `gsd-core/workflows/verify-work/steps/automated-ui-verification.md`. Otherwise skip — do not read the file.
<!-- /gsd:section -->
<step name="find_summaries">
**Find what to test:**
@@ -195,29 +162,12 @@ Read each SUMMARY.md to extract testable deliverables.
</step>
<step name="extract_tests">
**MVP-mode UAT framing.** When `MVP_MODE=true`, follow the rules in `@~/.claude/gsd-core/references/verify-mvp-mode.md`. Briefly:
1. Generate the UAT script in three ordered sections: (a) user-flow walk-through derived from the phase's user-story goal, (b) technical checks (deferred — only run after user flow passes), (c) coverage check (goal-backward, narrowed to the user story's outcome clause).
2. **User-flow steps run first.** Each step is one user action: open, fill, click, type, observe. No HTTP verbs, no JSON shapes, no error codes in user-flow steps.
3. **Technical checks are deferred.** They run AFTER the user flow passes — same checks as non-MVP mode (endpoint schemas, error states, edge cases), just reordered.
4. **If user-flow step N fails, do not advance.** The verdict is FAIL; technical checks do not run. The user can re-run after fixing the underlying flow.
<!-- gsd:section id="mvp-uat-framing" when="state:phase-mvp-mode" -->
If `section_manifest` is `null` or `"mvp-uat-framing"` is in its `included` list: read and execute `gsd-core/workflows/verify-work/steps/mvp-uat-framing.md`. Otherwise skip — do not read the file.
<!-- /gsd:section -->
When `MVP_MODE=false` (mode is null, absent, or the phase has no `**Mode:**` line in ROADMAP.md), fall back to the standard UAT generation path — no behavioral change.
**User-story format guard.** When `MVP_MODE=true`, also verify the phase's goal is in User Story format via the centralized validator:
```bash
PHASE_GOAL=$(gsd_run query roadmap.get-phase "${phase_number}" ${GSD_WS} --pick goal)
USER_STORY_VALID=$(gsd_run query user-story.validate --story "$PHASE_GOAL" --pick valid)
if [ "$USER_STORY_VALID" != "true" ]; then
echo "Phase ${phase_number} has '**Mode:** mvp' in ROADMAP.md but the **Goal:** is not in user-story format."
echo "Run /gsd mvp-phase ${phase_number} to set a user-story goal before verifying."
exit 1
fi
```
The verb owns the canonical regex `/^As a .+, I want to .+, so that .+\.$/` and returns slot extractions plus per-error guidance when invalid. Halt UAT generation on failure — never attempt to derive user-flow steps from a non-User-Story goal (low-quality UAT).
**Coverage-aware deterministic classification (#1602).** Before deriving checkpoints from prose, classify each SUMMARY's structured `coverage:` block. For each `*-SUMMARY.md`:
```bash

View File

@@ -0,0 +1,36 @@
<step name="automated_ui_verification">
**Automated UI Verification (when Playwright-MCP is available)**
Before UAT, check whether Playwright/Puppeteer MCP tools are available. UI-phase
activation itself (`ui_phase_active`) is already resolved at init time — this
section is only reached when that fact is `true` (see the `state:ui-phase-active`
gate above), so re-deriving it here would be redundant.
```bash
UI_SPEC_FILE=$(ls "${PHASE_DIR}"/*-UI-SPEC.md 2>/dev/null | head -1)
```
**If Playwright-MCP tools are available in this session (`mcp__playwright__*` tools
respond to tool calls):**
For each UI checkpoint listed in the phase's UI-SPEC.md (or inferred from SUMMARY.md):
1. Use `mcp__playwright__navigate` (or equivalent) to open the component's URL.
2. Use `mcp__playwright__screenshot` to capture a screenshot.
3. Compare the screenshot visually against the spec's stated requirements
(dimensions, color, layout, spacing).
4. Automatically mark checkpoints as **passed** or **needs review** based on the
visual comparison — no manual question required for items that clearly match.
5. Flag items that require human judgment (subjective aesthetics, content accuracy)
and present only those as manual UAT questions.
If automated verification is not available, fall back to the standard manual
checkpoint questions defined in this workflow unchanged. This step is entirely
conditional: if Playwright-MCP is not configured, behavior is unchanged from today.
**Display summary line before proceeding:**
```
UI checkpoints: {N} auto-verified, {M} queued for manual review
```
</step>

View File

@@ -0,0 +1,21 @@
**MVP-mode UAT framing.** When `MVP_MODE=true`, follow the rules in `@~/.claude/gsd-core/references/verify-mvp-mode.md`. Briefly:
1. Generate the UAT script in three ordered sections: (a) user-flow walk-through derived from the phase's user-story goal, (b) technical checks (deferred — only run after user flow passes), (c) coverage check (goal-backward, narrowed to the user story's outcome clause).
2. **User-flow steps run first.** Each step is one user action: open, fill, click, type, observe. No HTTP verbs, no JSON shapes, no error codes in user-flow steps.
3. **Technical checks are deferred.** They run AFTER the user flow passes — same checks as non-MVP mode (endpoint schemas, error states, edge cases), just reordered.
4. **If user-flow step N fails, do not advance.** The verdict is FAIL; technical checks do not run. The user can re-run after fixing the underlying flow.
**User-story format guard.** When `MVP_MODE=true`, also verify the phase's goal is in User Story format via the centralized validator:
```bash
_GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; if [ -f "$GSD_TOOLS" ]; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif command -v gsd-tools >/dev/null 2>&1; then GSD_TOOLS="$(command -v gsd-tools)"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif [ -f "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and gsd-tools is not on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
PHASE_GOAL=$(gsd_run query roadmap.get-phase "${phase_number}" ${GSD_WS} --pick goal)
USER_STORY_VALID=$(gsd_run query user-story.validate --story "$PHASE_GOAL" --pick valid)
if [ "$USER_STORY_VALID" != "true" ]; then
echo "Phase ${phase_number} has '**Mode:** mvp' in ROADMAP.md but the **Goal:** is not in user-story format."
echo "Run /gsd mvp-phase ${phase_number} to set a user-story goal before verifying."
exit 1
fi
```
The verb owns the canonical regex `/^As a .+, I want to .+, so that .+\.$/` and returns slot extractions plus per-error guidance when invalid. Halt UAT generation on failure — never attempt to derive user-flow steps from a non-User-Story goal (low-quality UAT).

View File

@@ -344,6 +344,30 @@ export const INIT_COMMAND_ALIASES: CommandAlias[] = [
"subcommand": "phase-op",
"mutation": false
},
{
"canonical": "init.code-review",
"aliases": [
"init code-review"
],
"subcommand": "code-review",
"mutation": false
},
{
"canonical": "init.review",
"aliases": [
"init review"
],
"subcommand": "review",
"mutation": false
},
{
"canonical": "init.discuss-phase-assumptions",
"aliases": [
"init discuss-phase-assumptions"
],
"subcommand": "discuss-phase-assumptions",
"mutation": false
},
{
"canonical": "init.todos",
"aliases": [
@@ -384,6 +408,46 @@ export const INIT_COMMAND_ALIASES: CommandAlias[] = [
"subcommand": "manager",
"mutation": false
},
{
"canonical": "init.complete-milestone",
"aliases": [
"init complete-milestone"
],
"subcommand": "complete-milestone",
"mutation": false
},
{
"canonical": "init.autonomous",
"aliases": [
"init autonomous"
],
"subcommand": "autonomous",
"mutation": false
},
{
"canonical": "init.docs-update",
"aliases": [
"init docs-update"
],
"subcommand": "docs-update",
"mutation": false
},
{
"canonical": "init.update",
"aliases": [
"init update"
],
"subcommand": "update",
"mutation": false
},
{
"canonical": "init.transition",
"aliases": [
"init transition"
],
"subcommand": "transition",
"mutation": false
},
{
"canonical": "init.new-workspace",
"aliases": [

View File

@@ -295,4 +295,8 @@ function cmdDocsInit(cwd: string, raw: boolean): void {
output(result, raw, undefined);
}
export = { cmdDocsInit };
// #2994: `detectMonorepoWorkspaces` is additionally exported so
// `cmdInitDocsUpdate` (src/init.cts) can reuse the SAME detector that backs
// this command's own `monorepo_workspaces` field, rather than a second,
// divergence-prone monorepo-glob scan (DEFECT.GENERATIVE-FIX dual surface).
export = { cmdDocsInit, detectMonorepoWorkspaces };

View File

@@ -34,11 +34,19 @@ interface InitModule {
cmdInitResume(cwd: string, raw: boolean): void;
cmdInitVerifyWork(cwd: string, phase: string | undefined, raw: boolean): void;
cmdInitPhaseOp(cwd: string, phase: string | undefined, raw: boolean): void;
cmdInitCodeReview(cwd: string, phase: string | undefined, raw: boolean, options?: Record<string, string | boolean | null | undefined>): void;
cmdInitReview(cwd: string, phase: string | undefined, raw: boolean, options?: Record<string, string | boolean | null | undefined>): void;
cmdInitDiscussPhaseAssumptions(cwd: string, phase: string | undefined, raw: boolean, options?: Record<string, string | boolean | null | undefined>): void;
cmdInitTodos(cwd: string, phase: string | undefined, raw: boolean): void;
cmdInitMilestoneOp(cwd: string, raw: boolean): void;
cmdInitMapCodebase(cwd: string, raw: boolean): void;
cmdInitProgress(cwd: string, raw: boolean, options?: Record<string, string | boolean | null | undefined>): void;
cmdInitManager(cwd: string, raw: boolean): void;
cmdInitCompleteMilestone(cwd: string, raw: boolean, options?: Record<string, string | boolean | null | undefined>): void;
cmdInitAutonomous(cwd: string, raw: boolean, options?: Record<string, string | boolean | null | undefined>): void;
cmdInitDocsUpdate(cwd: string, raw: boolean, options?: Record<string, string | boolean | null | undefined>): void;
cmdInitUpdate(cwd: string, raw: boolean, options?: Record<string, string | boolean | null | undefined>): void;
cmdInitTransition(cwd: string, raw: boolean, options?: Record<string, string | boolean | null | undefined>): void;
cmdInitNewWorkspace(cwd: string, raw: boolean): void;
cmdInitListWorkspaces(cwd: string, raw: boolean): void;
cmdInitRemoveWorkspace(cwd: string, name: string | undefined, raw: boolean): void;
@@ -108,7 +116,18 @@ function routeInitCommand({ init, args, cwd, raw, error }: RouteInitCommandOptio
},
quick: () => {
const namedArgs = parseNamedArgs(args, [], ['discuss', 'research', 'validate', 'full']);
init.cmdInitQuick(cwd, args.slice(2).join(' '), raw, {
// #2994: `args.slice(2)` is the free-text description, but section-manifest
// gating (buildSectionManifestField, src/init.cts) now requires forwarding
// --discuss/--research/--validate/--full alongside it — a plain `.join(' ')`
// would otherwise fold those recognized flag tokens straight into the
// description text. Strip them before joining so the description stays
// exactly what it was before this workflow started forwarding flags.
const quickFlagTokens = new Set(['--discuss', '--research', '--validate', '--full']);
const description = args
.slice(2)
.filter((token) => !quickFlagTokens.has(token))
.join(' ');
init.cmdInitQuick(cwd, description, raw, {
discuss: namedArgs['discuss'],
research: namedArgs['research'],
validate: namedArgs['validate'],
@@ -119,6 +138,15 @@ function routeInitCommand({ init, args, cwd, raw, error }: RouteInitCommandOptio
resume: () => init.cmdInitResume(cwd, raw),
'verify-work': () => init.cmdInitVerifyWork(cwd, args[2], raw),
'phase-op': () => init.cmdInitPhaseOp(cwd, args[2], raw),
'code-review': () => {
const namedArgs = parseNamedArgs(args, [], ['fix']);
init.cmdInitCodeReview(cwd, args[2], raw, { fix: namedArgs['fix'] });
},
review: () => init.cmdInitReview(cwd, args[2], raw, {}),
'discuss-phase-assumptions': () => {
const namedArgs = parseNamedArgs(args, [], ['auto']);
init.cmdInitDiscussPhaseAssumptions(cwd, args[2], raw, { auto: namedArgs['auto'] });
},
todos: () => init.cmdInitTodos(cwd, args[2], raw),
'milestone-op': () => init.cmdInitMilestoneOp(cwd, raw),
'map-codebase': () => init.cmdInitMapCodebase(cwd, raw),
@@ -129,6 +157,20 @@ function routeInitCommand({ init, args, cwd, raw, error }: RouteInitCommandOptio
// Keep manager on CJS for now so runtime-specific command rendering
// (e.g. $gsd-* for codex) stays consistent with runtime-slash helpers.
manager: () => init.cmdInitManager(cwd, raw),
'complete-milestone': () => init.cmdInitCompleteMilestone(cwd, raw),
autonomous: () => {
const namedArgs = parseNamedArgs(args, [], ['converge', 'cross-ai']);
init.cmdInitAutonomous(cwd, raw, {
converge: namedArgs['converge'],
'cross-ai': namedArgs['cross-ai'],
});
},
'docs-update': () => init.cmdInitDocsUpdate(cwd, raw, {}),
update: () => {
const namedArgs = parseNamedArgs(args, [], ['next', 'rc']);
init.cmdInitUpdate(cwd, raw, { next: namedArgs['next'], rc: namedArgs['rc'] });
},
transition: () => init.cmdInitTransition(cwd, raw, {}),
'new-workspace': () => init.cmdInitNewWorkspace(cwd, raw),
'list-workspaces': () => init.cmdInitListWorkspaces(cwd, raw),
'remove-workspace': () => init.cmdInitRemoveWorkspace(cwd, args[2], raw),

View File

@@ -49,6 +49,18 @@ import uatPredicateMod = require('./uat-predicate.cjs');
import agentInstallCheck = require('./agent-install-check.cjs');
// eslint-disable-next-line @typescript-eslint/no-require-imports -- section-manifest.cjs is compiled from section-manifest.cts's named exports; imported as a namespace to read selectSections/SelectableSection/InvocationFacts off module.exports directly (#2932).
import sectionManifest = require('./section-manifest.cjs');
// eslint-disable-next-line @typescript-eslint/no-require-imports -- loop-resolver.cjs is an export= CommonJS module
import loopResolverMod = require('./loop-resolver.cjs');
// eslint-disable-next-line @typescript-eslint/no-require-imports -- capability-loader.cjs is compiled from capability-loader.cts's named exports; imported as a namespace to read loadRegistry off module.exports directly.
import capabilityLoaderMod = require('./capability-loader.cjs');
// eslint-disable-next-line @typescript-eslint/no-require-imports -- capability-state.cjs is an export= CommonJS module
import capabilityStateMod = require('./capability-state.cjs');
// eslint-disable-next-line @typescript-eslint/no-require-imports -- docs.cjs is an export= CommonJS module
import docsMod = require('./docs.cjs');
const { detectMonorepoWorkspaces } = docsMod;
// eslint-disable-next-line @typescript-eslint/no-require-imports -- workstream-inventory.cjs is an export= CommonJS module
import workstreamInventoryMod = require('./workstream-inventory.cjs');
const { getOtherActiveWorkstreamInventories } = workstreamInventoryMod;
const { checkAgentsInstalled } = agentInstallCheck;
// eslint-disable-next-line @typescript-eslint/no-require-imports -- git-base-branch.cjs is an export= CommonJS module
import gitBaseBranch = require('./git-base-branch.cjs');
@@ -92,6 +104,9 @@ const { determinePhaseStatus } = commandsMod;
const { extractFrontmatter } = frontmatterMod;
const { readVerificationStatus } = verificationMod;
const { evaluateUatPassed } = uatPredicateMod;
const { resolveLoopHooks } = loopResolverMod;
const { loadRegistry } = capabilityLoaderMod;
const { resolveCapabilityRuntimeState } = capabilityStateMod;
// Unused but imported for structural parity
void stripShippedMilestones;
@@ -149,6 +164,53 @@ function guardedGetRoadmapPhase(
return roadmapPhase;
}
// #2994: `phase_slug` is re-derived from a roadmap-only `phase_name` (no disk
// directory exists yet) identically at every synthetic-fallback call site
// below — factored out once so the slugification formula itself cannot drift.
function slugifyPhaseName(phaseName: string | null): string | null {
return phaseName
? phaseName.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '')
: null;
}
/**
* #2994 (review finding, DEFECT.GENERATIVE-FIX): shared archived/not-found
* fallback applied identically by `cmdInitExecutePhase`, `cmdInitPlanPhase`,
* `cmdInitVerifyWork`, `cmdInitCodeReview`, `cmdInitReview`, and
* `cmdInitDiscussPhaseAssumptions` — 6 call sites previously reproducing the
* exact same two-branch control flow verbatim (only the synthetic
* replacement object's field set differs per caller, supplied here via
* `buildFallback`). `cmdInitPhaseOp` is deliberately left untouched (CRITICAL
* blast radius, 179 dependents) even though it follows the same shape, since
* its own fallback object differs by one field (`has_reviews` absent) and is
* not a byte-identical copy.
*
* Behavior-preserving by construction: every original call site either (a)
* unconditionally computed `roadmapPhase` once up front and then applied
* `phaseInfo?.archived && roadmapPhase?.found -> null` followed by
* `!phaseInfo && roadmapPhase?.found -> fallback`, or (b) computed
* `roadmapPhase` lazily inside each of those same two conditions. Because
* `guardedGetRoadmapPhase` is a pure, side-effect-free read for a given
* `(cwd, phase, projectCode)` within one command invocation, both shapes
* return identical results for identical inputs — so passing one
* unconditionally-resolved `roadmapPhase` in here (mirroring shape (a))
* reproduces shape (b)'s output exactly, just without the redundant second
* disk read shape (b) performed when the first branch already resolved it.
*/
function applyRoadmapFallback(
phaseInfo: Record<string, unknown> | null,
roadmapPhase: Record<string, unknown> | null,
buildFallback: (roadmapPhase: Record<string, unknown>) => Record<string, unknown>,
): Record<string, unknown> | null {
if (phaseInfo?.['archived'] && roadmapPhase?.['found']) {
phaseInfo = null;
}
if (!phaseInfo && roadmapPhase?.['found']) {
phaseInfo = buildFallback(roadmapPhase);
}
return phaseInfo;
}
function listPhaseSummaryFiles(phaseDir: string): string[] {
return (scanPhasePlans(phaseDir) as unknown as Record<string, string[]>)['summaryFiles'];
}
@@ -462,6 +524,76 @@ function readConfigJsonBoolean(cwd: string, keyPath: readonly string[]): boolean
}
}
/**
* Bounded, non-throwing read of a dotted key path from `.planning/config.json`,
* returning the raw resolved value (any JSON type) or `undefined` on any
* degraded condition (absent file, unreadable file, malformed JSON, or a
* non-object intermediate segment) — the generic sibling of
* {@link readConfigJsonBoolean} for callers that need the actual value
* (a string like `code_quality.fallow.profile`) rather than a strict
* boolean coercion. `keyPath` is always a fixed literal supplied by this
* module, never attacker/user input, so a plain bracket traversal carries
* no prototype hazard here (same discipline as `readConfigJsonBoolean`).
*/
function readConfigJsonValue(cwd: string, keyPath: readonly string[]): unknown {
try {
const raw = fs.readFileSync(path.join(planningDir(cwd), 'config.json'), 'utf8');
let cursor: unknown = JSON.parse(raw);
for (const segment of keyPath) {
if (cursor === null || typeof cursor !== 'object' || Array.isArray(cursor)) return undefined;
cursor = (cursor as Record<string, unknown>)[segment];
}
return cursor;
} catch {
return undefined;
}
}
/**
* `state:fallow-enabled` ground truth (#2994): resolves `code-review.md`'s
* `structural_pre_pass` fallow config gate — previously re-derived INSIDE the
* gated section body itself (`gsd_run query config-get code_quality.fallow.*`),
* which is circular/self-disabling the moment a section is gated on a fact
* its own body computes (the same hazard `state:chunked-mode` /
* `state:ui-phase-active` document for a compound condition). Fail-closed
* default `false` for `enabled`/`mcp`, matching the pre-hoist bash resolver's
* `2>/dev/null || echo "false"` fallback; `scope`/`profile` default to
* `"phase"`/`"standard"` matching that same resolver's `|| echo` fallbacks.
* `maxCrap` mirrors the step body's profile->threshold mapping (minimal=50,
* strict=15, else standard=30) so the step file never has to re-derive it.
*/
function detectFallowConfig(cwd: string): {
enabled: boolean;
scope: string;
profile: string;
mcp: boolean;
maxCrap: number;
} {
const enabled = readConfigJsonValue(cwd, ['code_quality', 'fallow', 'enabled']) === true;
const rawScope = readConfigJsonValue(cwd, ['code_quality', 'fallow', 'scope']);
const scope = typeof rawScope === 'string' && rawScope ? rawScope : 'phase';
const rawProfile = readConfigJsonValue(cwd, ['code_quality', 'fallow', 'profile']);
const profile = typeof rawProfile === 'string' && rawProfile ? rawProfile : 'standard';
const mcp = readConfigJsonValue(cwd, ['code_quality', 'fallow', 'mcp']) === true;
const maxCrap = profile === 'minimal' ? 50 : profile === 'strict' ? 15 : 30;
return { enabled, scope, profile, mcp, maxCrap };
}
/**
* `state:git-create-tag` ground truth (#2994): resolves `complete-milestone.md`'s
* `git_tag` step config gate — previously re-derived INSIDE a `<config-check>`
* sub-tag at the top of the step itself (`gsd-tools.cjs query config-get
* git.create_tag 2>/dev/null || echo "true"`), gating the step's OWN inclusion
* on a fact only that same step computed. Fail-OPEN default `true` (an unset
* or missing `git.create_tag` key means "create the tag"), matching the
* pre-hoist resolver's `|| echo "true"` fallback exactly — this is
* deliberately the inverse polarity of `detectFallowConfig`'s fail-closed
* default, mirroring the two source resolvers' own opposite defaults.
*/
function detectGitCreateTag(cwd: string): boolean {
return readConfigJsonValue(cwd, ['git', 'create_tag']) !== false;
}
/**
* `state:phase-mvp-mode` ground truth (design doc §Behavior table: ROADMAP.md
* `**Mode:** mvp` for the CURRENT phase). Bounded, non-throwing — an absent
@@ -502,6 +634,59 @@ function detectPhaseMvpMode(cwd: string, phaseNumber: string | null): boolean {
}
}
/**
* `state:ui-phase-active` ground truth (#2994): whether the phase's active
* `plan:pre` loop hooks include the `ui-phase` step (`capabilities/ui/
* capability.json`'s `plan:pre` step, `ref.skill: "ui-phase"`, gated on
* config `workflow.ui_phase`), OR the phase directory already contains a
* `*-UI-SPEC.md` file. The disjunction is resolved to ONE boolean here —
* same discipline as `chunkedMode` above — so the `when=` grammar never
* sees an OR. Mirrors `cmdLoopRenderHooks`'s own registry/capability-state
* setup (`src/loop-resolver.cts`) rather than reinventing a second loop-hook
* resolution path. Bounded, non-throwing: any failure in loop-hook /
* registry / capability-state resolution degrades that half of the OR to
* `false`, never throws; the UI-SPEC file check is independently bounded.
*/
function detectUiPhaseActive(cwd: string, phaseInfo: Record<string, unknown> | null): boolean {
let hasActiveUiStep = false;
try {
const config = loadConfig(cwd);
const state = resolveCapabilityRuntimeState(cwd, undefined, config) as {
capabilities: Array<{ id: string; enabled?: boolean; active: boolean }>;
};
const registry = loadRegistry({ includeInstalled: true, cwd, gsdHome: process.env['GSD_HOME'] });
const capabilityStatesById = new Map<string, { enabled?: boolean; active: boolean }>();
for (const cap of state.capabilities || []) {
capabilityStatesById.set(cap.id, cap);
}
const resolved = resolveLoopHooks({ point: 'plan:pre', registry, config, cwd, capabilityStatesById }) as {
activeHooks: Array<{ kind?: string; ref?: { skill?: string } }>;
};
hasActiveUiStep = resolved.activeHooks.some(
(h) => h.kind === 'step' && h.ref?.skill === 'ui-phase',
);
} catch {
hasActiveUiStep = false;
}
let hasUiSpecFile = false;
const rawDir = phaseInfo?.['directory'];
if (typeof rawDir === 'string' && rawDir) {
try {
// Re-derive under planningDir(cwd)/phases/<basename> rather than trusting
// rawDir's own absolute/relative-ness (callers mix both — see the #2376
// comments elsewhere in this file), same technique as detectHasPriorPhases above.
const dirName = path.basename(rawDir);
const files = fs.readdirSync(path.join(planningDir(cwd), 'phases', dirName));
hasUiSpecFile = files.some((f) => f.endsWith('-UI-SPEC.md') || f === 'UI-SPEC.md');
} catch {
hasUiSpecFile = false;
}
}
return hasActiveUiStep || hasUiSpecFile;
}
/**
* Builds the `section_manifest` init-bundle field (#2932 Deliverable 2): resolves
* {@link sectionManifest.InvocationFacts} from this invocation, loads the generated
@@ -538,7 +723,19 @@ function buildSectionManifestField(
phaseInfo: Record<string, unknown> | null,
options: Record<string, unknown>,
workflow: string,
overrides: { needsCodebaseMap?: boolean } = {},
overrides: {
needsCodebaseMap?: boolean;
fallowEnabled?: boolean;
gitCreateTag?: boolean;
planStrategyConverge?: boolean;
reviewerInstancesConfigured?: boolean;
autoAdvanceActive?: boolean;
isMonorepo?: boolean;
nextChannel?: boolean;
workstreamActive?: boolean;
flatMode?: boolean;
uiPhaseActive?: boolean;
} = {},
): Record<string, unknown> | null {
const sections = loadSectionManifestSections(workflow);
if (!sections) return null;
@@ -573,6 +770,16 @@ function buildSectionManifestField(
phaseMvpMode: detectPhaseMvpMode(cwd, phaseNumber),
needsCodebaseMap: overrides.needsCodebaseMap,
chunkedMode,
uiPhaseActive: overrides.uiPhaseActive,
fallowEnabled: overrides.fallowEnabled,
gitCreateTag: overrides.gitCreateTag,
planStrategyConverge: overrides.planStrategyConverge,
reviewerInstancesConfigured: overrides.reviewerInstancesConfigured,
autoAdvanceActive: overrides.autoAdvanceActive,
isMonorepo: overrides.isMonorepo,
nextChannel: overrides.nextChannel,
workstreamActive: overrides.workstreamActive,
flatMode: overrides.flatMode,
};
try {
@@ -606,21 +813,14 @@ function cmdInitExecutePhase(
const milestone = getMilestoneInfo(cwd) as unknown as Record<string, unknown>;
const roadmapPhase = guardedGetRoadmapPhase(cwd, phase, config.project_code);
if (phaseInfo?.['archived'] && roadmapPhase?.['found']) {
phaseInfo = null;
}
if (!phaseInfo && roadmapPhase?.['found']) {
const phaseName = roadmapPhase['phase_name'] as string | null;
phaseInfo = {
phaseInfo = applyRoadmapFallback(phaseInfo, roadmapPhase, (rp) => {
const phaseName = rp['phase_name'] as string | null;
return {
found: true,
directory: null,
phase_number: roadmapPhase['phase_number'],
phase_number: rp['phase_number'],
phase_name: phaseName,
phase_slug: phaseName
? phaseName.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '')
: null,
phase_slug: slugifyPhaseName(phaseName),
plans: [],
summaries: [],
incomplete_plans: [],
@@ -629,7 +829,7 @@ function cmdInitExecutePhase(
has_verification: false,
has_reviews: false,
};
}
});
const reqMatch = (roadmapPhase?.['section'] as string | undefined)?.match(REQUIREMENTS_HEADER_RE);
const reqExtracted = reqMatch
? reqMatch[1].replace(/[\[\]]/g, '').split(',').map((s) => s.trim()).filter(Boolean).join(', ')
@@ -744,21 +944,14 @@ function cmdInitPlanPhase(
// #2056/#2104: foreign-prefixed queries must not collapse to numeric phases.
let phaseInfo = guardedFindPhase(cwd, phase, config.project_code);
const roadmapPhase = guardedGetRoadmapPhase(cwd, phase, config.project_code);
if (phaseInfo?.['archived'] && roadmapPhase?.['found']) {
phaseInfo = null;
}
if (!phaseInfo && roadmapPhase?.['found']) {
const phaseName = roadmapPhase['phase_name'] as string | null;
phaseInfo = {
phaseInfo = applyRoadmapFallback(phaseInfo, roadmapPhase, (rp) => {
const phaseName = rp['phase_name'] as string | null;
return {
found: true,
directory: null,
phase_number: roadmapPhase['phase_number'],
phase_number: rp['phase_number'],
phase_name: phaseName,
phase_slug: phaseName
? phaseName.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '')
: null,
phase_slug: slugifyPhaseName(phaseName),
plans: [],
summaries: [],
incomplete_plans: [],
@@ -767,7 +960,7 @@ function cmdInitPlanPhase(
has_verification: false,
has_reviews: false,
};
}
});
const reqMatch = (roadmapPhase?.['section'] as string | undefined)?.match(REQUIREMENTS_HEADER_RE);
const reqExtracted = reqMatch
? reqMatch[1].replace(/[\[\]]/g, '').split(',').map((s) => s.trim()).filter(Boolean).join(', ')
@@ -1041,8 +1234,30 @@ function cmdInitNewMilestone(cwd: string, raw: boolean, options: Record<string,
milestones_path: toPosixPath(path.join(planningDir(cwd), 'MILESTONES.md')),
};
// `state:flat-mode` (#2994): whether NO workstream is active — the inverse
// of `state:workstream-active` (introduced for `cmdInitTransition` below).
// `new-milestone.md`'s Step 4 Part A (milestone-state write) runs ONLY in
// flat mode; a workstream's own `.planning/workstreams/<name>/STATE.md`/
// `ROADMAP.md`/`REQUIREMENTS.md` already carry the milestone state, so
// writing the shared `## Current Milestone` heading here would clobber it
// (#2308). The `when=` grammar has no negation operator (ADR-1671:69), so
// Part A's condition — "skip when a workstream IS active" — cannot be
// expressed by negating `state:workstream-active` in the marker; a
// SEPARATE, positively-phrased atom whose fact is the inverse is the
// sanctioned resolution (same discipline as `state:chunked-mode` folding
// an OR — never an operator in the grammar itself). Same authoritative
// source as `cmdInitTransition`: `GSD_WORKSTREAM` env, falling back to the
// stored active-workstream pointer (mirrors `cmdInitProgress`'s own
// resolution above).
const resolvedWorkstream = process.env['GSD_WORKSTREAM'] || getActiveWorkstream(cwd);
const workstreamActive = !!resolvedWorkstream;
const flatMode = !workstreamActive;
// #2992 (Phase 6.1): additive, optional field — degrades to null, never throws.
result['section_manifest'] = buildSectionManifestField(cwd, null, options, 'new-milestone');
result['section_manifest'] = buildSectionManifestField(cwd, null, options, 'new-milestone', {
workstreamActive,
flatMode,
});
output(withProjectRoot(cwd, result), raw);
}
@@ -1103,8 +1318,22 @@ function cmdInitQuick(
planning_exists: fs.existsSync(planningRoot(cwd)),
};
// #2994: `--full` IMPLIES `--discuss`/`--research`/`--validate` — resolved to
// ONE set of facts HERE, in fact computation, never in the `when=` grammar
// itself (mirrors `state:chunked-mode`'s disjunction fold at
// `buildSectionManifestField`'s `chunkedMode` computation above). The three
// implied tokens are folded into the flags BEFORE `buildSectionManifestField`
// builds its `InvocationFacts.flags` Set, so `discussion-phase`/`research-phase`/
// `plan-checker-loop`/`quick-verification` (all gated on their own single
// `flag:--discuss`/`flag:--research`/`flag:--validate` atom) include correctly
// for a bare `/gsd:quick --full` invocation that never passed the individual
// tokens — the grammar still sees exactly one atom per marker, no OR.
const sectionManifestOptions: Record<string, unknown> = options['full']
? { ...options, discuss: true, research: true, validate: true }
: options;
// #2992 (Phase 6.1): additive, optional field — degrades to null, never throws.
result['section_manifest'] = buildSectionManifestField(cwd, null, options, 'quick');
result['section_manifest'] = buildSectionManifestField(cwd, null, sectionManifestOptions, 'quick');
output(withProjectRoot(cwd, result), raw);
}
@@ -1188,35 +1417,23 @@ function cmdInitVerifyWork(cwd: string, phase: string, raw: boolean): void {
const config = loadConfig(cwd);
const _slashRuntime = resolveRuntime(cwd);
let phaseInfo = guardedFindPhase(cwd, phase, config.project_code);
if (phaseInfo?.['archived']) {
const roadmapPhase = guardedGetRoadmapPhase(cwd, phase, config.project_code);
if (roadmapPhase?.['found']) {
phaseInfo = null;
}
}
if (!phaseInfo) {
const roadmapPhase = guardedGetRoadmapPhase(cwd, phase, config.project_code);
if (roadmapPhase?.['found']) {
const phaseName = roadmapPhase['phase_name'] as string | null;
phaseInfo = {
found: true,
directory: null,
phase_number: roadmapPhase['phase_number'],
phase_name: phaseName,
phase_slug: phaseName
? phaseName.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '')
: null,
plans: [],
summaries: [],
incomplete_plans: [],
has_research: false,
has_context: false,
has_verification: false,
};
}
}
const roadmapPhase = guardedGetRoadmapPhase(cwd, phase, config.project_code);
phaseInfo = applyRoadmapFallback(phaseInfo, roadmapPhase, (rp) => {
const phaseName = rp['phase_name'] as string | null;
return {
found: true,
directory: null,
phase_number: rp['phase_number'],
phase_name: phaseName,
phase_slug: slugifyPhaseName(phaseName),
plans: [],
summaries: [],
incomplete_plans: [],
has_research: false,
has_context: false,
has_verification: false,
};
});
const phaseDir = (phaseInfo?.['directory'] as string | null | undefined) || null;
const planCount = (phaseInfo?.['plans'] as unknown[] | undefined)?.length || 0;
@@ -1234,6 +1451,7 @@ function cmdInitVerifyWork(cwd: string, phase: string, raw: boolean): void {
policy: { requireVerification: true },
})
: null;
const uiPhaseActive = detectUiPhaseActive(cwd, phaseInfo);
const result: Record<string, unknown> = {
planner_model: resolveModelInternal(cwd, 'gsd-planner'),
@@ -1260,8 +1478,270 @@ function cmdInitVerifyWork(cwd: string, phase: string, raw: boolean): void {
uat_blockers: uatReport?.blockers ?? [],
ready_to_transition: completion.phase_complete && (uatReport?.passed ?? false),
},
// #2994 (resolver-hoist-guard G5): hoisted `state:ui-phase-active` ground
// truth (previously re-derived inline inside the automated_ui_verification
// step body via its own `gsd_run loop render-hooks plan:pre --raw` call —
// a circular, self-disabling resolver, since the section is only read
// when this same fact is already true). Resolved once here, exposed so
// the step body can consume it directly instead of recomputing it.
ui_phase_active: uiPhaseActive,
};
// #2994 (Phase 6.3): additive, optional field — degrades to null, never throws.
// phaseInfo is passed through directly (mirrors cmdInitExecutePhase / cmdInitPlanPhase)
// so buildSectionManifestField's internal detectPhaseMvpMode call gets a real
// phase_number/directory rather than permanently-false facts. uiPhaseActive is
// computed once above (not re-derived here) and threaded through via overrides,
// mirroring the fallow/git-create-tag hoist pattern.
result['section_manifest'] = buildSectionManifestField(cwd, phaseInfo, {}, 'verify-work', {
uiPhaseActive,
});
output(withProjectRoot(cwd, result), raw);
}
/**
* `code-review.md`'s dedicated init entry point (#2994, epic #1671 Phase
* 6.3). `code-review.md` previously routed through the shared, 20+-caller
* `init.phase-op` (`cmdInitPhaseOp` below), reading only 6 of its fields
* (`phase_found`, `phase_dir`, `phase_number`, `phase_name`, `padded_phase`,
* `commit_docs` — verified against the workflow's own "Parse from init
* JSON" line). `cmdInitPhaseOp` is CRITICAL blast radius (179 dependents
* across 24 processes per the #2994 dispatch) and is never modified for
* this — this function resolves phase info itself via the SAME shared
* primitives `cmdInitPhaseOp` calls (`guardedFindPhase`/
* `guardedGetRoadmapPhase`, plus the shared `applyRoadmapFallback` archived/
* not-found fallback also used by execute-phase, plan-phase, verify-work
* and review — see `applyRoadmapFallback`'s own doc comment; `cmdInitPhaseOp`
* is deliberately excluded from that shared helper), producing the identical
* 6-field shape rather than a second, hand-maintained copy of
* `cmdInitPhaseOp`'s full ~60-field bundle. See
* `tests/init-code-review-parity.test.cjs` for the DEFECT.GENERATIVE-FIX
* parity guard between the two.
*
* Two further facts are resolved and exposed here that `init.phase-op`
* never carried: the fallow structural-pre-pass config gate
* (`detectFallowConfig`, `state:fallow-enabled`) and the `--fix` flag
* (folded into `options` so `buildSectionManifestField` picks it up as
* `flag:--fix`).
*/
function cmdInitCodeReview(
cwd: string,
phase: string,
raw: boolean,
options: Record<string, unknown> = {},
): void {
const config = loadConfig(cwd);
let phaseInfo = guardedFindPhase(cwd, phase, config.project_code);
const roadmapPhase = guardedGetRoadmapPhase(cwd, phase, config.project_code);
phaseInfo = applyRoadmapFallback(phaseInfo, roadmapPhase, (rp) => {
const rpName = rp['phase_name'] as string | null;
return {
found: true,
directory: null,
phase_number: rp['phase_number'],
phase_name: rpName,
phase_slug: slugifyPhaseName(rpName),
};
});
const phaseDir = (phaseInfo?.['directory'] as string | undefined) || null;
const phaseNumber = (phaseInfo?.['phase_number'] as string | undefined) || null;
const phaseName = (phaseInfo?.['phase_name'] as string | undefined) || null;
const fallow = detectFallowConfig(cwd);
const result: Record<string, unknown> = {
commit_docs: config.commit_docs,
phase_found: !!phaseInfo,
// #2376: absolute — see comment on phase_dir in cmdInitExecutePhase.
phase_dir: phaseDir ? toPosixPath(path.join(cwd, phaseDir)) : null,
phase_number: phaseNumber,
phase_name: phaseName,
padded_phase: phaseNumber ? normalizePhaseName(phaseNumber) : null,
// #2994: hoisted fallow config-gate resolution (previously re-derived
// inline inside code-review.md's structural_pre_pass step body — a
// circular self-disabling gate now resolved once here at init time).
fallow_enabled: fallow.enabled,
fallow_scope: fallow.scope,
fallow_profile: fallow.profile,
fallow_mcp: fallow.mcp,
fallow_max_crap: fallow.maxCrap,
};
// #2994 (Phase 6.3): additive, optional field — degrades to null, never throws.
const sectionManifestOptions: Record<string, unknown> = {
...options,
fix: options['fix'] || undefined,
};
result['section_manifest'] = buildSectionManifestField(cwd, phaseInfo, sectionManifestOptions, 'code-review', {
fallowEnabled: fallow.enabled,
});
output(withProjectRoot(cwd, result), raw);
}
/**
* `review.md`'s dedicated init entry point (#2994, epic #1671 Phase 6.3
* amendment). `review.md` previously routed through the shared, 20+-caller
* `init.phase-op` (`cmdInitPhaseOp` below), reading only 3 of its fields
* (`phase_dir`, `phase_number`, `padded_phase` — verified against the
* workflow's own "Read from init" line in `gather_context`). `cmdInitPhaseOp`
* is CRITICAL blast radius (179 dependents across 24 processes) and is never
* modified for this — this function resolves phase info itself via the SAME
* shared primitives `cmdInitPhaseOp` calls (`guardedFindPhase`/
* `guardedGetRoadmapPhase`), plus the shared `applyRoadmapFallback`
* archived/not-found fallback (see its own doc comment), producing the
* identical 3-field shape rather than a second, hand-maintained copy of
* `cmdInitPhaseOp`'s full ~60-field bundle.
*
* One further fact is resolved and exposed here that `init.phase-op` never
* carried: whether reviewer instances are configured
* (`.planning/config.json`'s `review.reviewer_instances`, present AND
* non-empty — `state:reviewer-instances-configured`), reusing
* `readConfigJsonValue` (added for `detectFallowConfig`) rather than a
* second, divergence-prone config reader (DEFECT.GENERATIVE-FIX).
*/
function cmdInitReview(
cwd: string,
phase: string,
raw: boolean,
options: Record<string, unknown> = {},
): void {
const config = loadConfig(cwd);
let phaseInfo = guardedFindPhase(cwd, phase, config.project_code);
const roadmapPhase = guardedGetRoadmapPhase(cwd, phase, config.project_code);
phaseInfo = applyRoadmapFallback(phaseInfo, roadmapPhase, (rp) => ({
found: true,
directory: null,
phase_number: rp['phase_number'],
phase_name: rp['phase_name'],
}));
const phaseDir = (phaseInfo?.['directory'] as string | undefined) || null;
const phaseNumber = (phaseInfo?.['phase_number'] as string | undefined) || null;
// #2994: `state:reviewer-instances-configured` ground truth — present AND
// non-empty `review.reviewer_instances` object. A missing key, a non-object
// value, or an empty object all resolve to `false` (fail-closed, matching
// the workflow's own pre-hoist prose gate — "Unconfigured -> default path
// unchanged").
const rawReviewerInstances = readConfigJsonValue(cwd, ['review', 'reviewer_instances']);
const reviewerInstancesConfigured =
rawReviewerInstances !== null &&
typeof rawReviewerInstances === 'object' &&
!Array.isArray(rawReviewerInstances) &&
Object.keys(rawReviewerInstances).length > 0;
const result: Record<string, unknown> = {
// #2376: absolute — see comment on phase_dir in cmdInitExecutePhase.
phase_dir: phaseDir ? toPosixPath(path.join(cwd, phaseDir)) : null,
phase_number: phaseNumber,
padded_phase: phaseNumber ? normalizePhaseName(phaseNumber) : null,
};
result['section_manifest'] = buildSectionManifestField(cwd, phaseInfo, options, 'review', {
reviewerInstancesConfigured,
});
output(withProjectRoot(cwd, result), raw);
}
/**
* `discuss-phase-assumptions.md`'s dedicated init entry point (#2994, epic
* #1671 Phase 6.3 amendment). Previously routed through the shared,
* 20+-caller `init.phase-op` (`cmdInitPhaseOp` below), reading 14 of its
* fields (`commit_docs`, `phase_found`, `phase_dir`, `phase_number`,
* `phase_name`, `phase_slug`, `padded_phase`, `has_research`, `has_context`,
* `has_plans`, `has_verification`, `plan_count`, `roadmap_exists`,
* `planning_exists` — verified against the workflow's own "Parse JSON for"
* line). `cmdInitPhaseOp` is CRITICAL blast radius (179 dependents across 24
* processes) and is never modified for this — this function resolves phase
* info itself via the SAME shared primitives `cmdInitPhaseOp` calls
* (`guardedFindPhase`/`guardedGetRoadmapPhase`), plus the shared
* `applyRoadmapFallback` archived/not-found fallback (see its own doc
* comment) producing the identical fallback shape (`plans: []`,
* `has_research: false`, `has_context: false`, `has_verification: false`)
* rather than a second, hand-maintained copy of `cmdInitPhaseOp`'s full
* ~60-field bundle.
*
* One further fact is resolved and exposed here that `init.phase-op` never
* carried: `state:auto-advance-active` — the workflow's own `auto_advance`
* step resolves `--auto` OR a consolidated `check auto-mode --pick active`
* fact (itself `workflow._auto_chain_active` OR `workflow.auto_advance`) via
* a runtime `gsd_run` call; that identical disjunction is folded into ONE
* boolean FACT here (same discipline as `state:chunked-mode` /
* `state:plan-strategy-converge`), exposed as `auto_advance_active`.
*/
function cmdInitDiscussPhaseAssumptions(
cwd: string,
phase: string,
raw: boolean,
options: Record<string, unknown> = {},
): void {
const config = loadConfig(cwd);
let phaseInfo = guardedFindPhase(cwd, phase, config.project_code);
const roadmapPhase = guardedGetRoadmapPhase(cwd, phase, config.project_code);
phaseInfo = applyRoadmapFallback(phaseInfo, roadmapPhase, (rp) => {
const rpName = rp['phase_name'] as string | null;
return {
found: true,
directory: null,
phase_number: rp['phase_number'],
phase_name: rpName,
phase_slug: slugifyPhaseName(rpName),
plans: [],
has_research: false,
has_context: false,
has_verification: false,
};
});
const phaseDir = (phaseInfo?.['directory'] as string | undefined) || null;
const phaseNumber = (phaseInfo?.['phase_number'] as string | undefined) || null;
const phaseName = (phaseInfo?.['phase_name'] as string | undefined) || null;
// #2994: mirrors discuss-phase-assumptions.md's own auto_advance step
// resolver — `--auto` flag OR the consolidated `check auto-mode --pick
// active` fact (workflow._auto_chain_active OR workflow.auto_advance).
const autoAdvanceActive =
options['auto'] === true ||
readConfigJsonBoolean(cwd, ['workflow', '_auto_chain_active']) ||
readConfigJsonBoolean(cwd, ['workflow', 'auto_advance']);
const result: Record<string, unknown> = {
commit_docs: config.commit_docs,
phase_found: !!phaseInfo,
// #2376: absolute — see comment on phase_dir in cmdInitExecutePhase.
phase_dir: phaseDir ? toPosixPath(path.join(cwd, phaseDir)) : null,
phase_number: phaseNumber,
phase_name: phaseName,
phase_slug: phaseInfo?.['phase_slug'] || null,
padded_phase: phaseNumber ? normalizePhaseName(phaseNumber) : null,
has_research: phaseInfo?.['has_research'] || false,
has_context: phaseInfo?.['has_context'] || false,
has_plans: ((phaseInfo?.['plans'] as unknown[] | undefined)?.length || 0) > 0,
has_verification: phaseInfo?.['has_verification'] || false,
plan_count: (phaseInfo?.['plans'] as unknown[] | undefined)?.length || 0,
roadmap_exists: fs.existsSync(path.join(planningDir(cwd), 'ROADMAP.md')),
planning_exists: fs.existsSync(planningDir(cwd)),
};
// #2994 (Phase 6.3): additive, optional field — degrades to null, never throws.
const sectionManifestOptions: Record<string, unknown> = {
...options,
auto: options['auto'] || undefined,
};
result['section_manifest'] = buildSectionManifestField(cwd, phaseInfo, sectionManifestOptions, 'discuss-phase-assumptions', {
autoAdvanceActive,
});
output(withProjectRoot(cwd, result), raw);
}
@@ -1988,6 +2468,191 @@ function cmdInitManager(cwd: string, raw: boolean): void {
output(withProjectRoot(cwd, result), raw);
}
/**
* `complete-milestone.md`'s dedicated init entry point (#2994, epic #1671
* Phase 6.3). Additive alongside the workflow's existing `init.manager`
* (readiness/phase-projection, `cmdInitManager` above — CRITICAL blast
* radius, never modified) and `init.execute-phase` (branching-strategy
* fields) calls; `cmdInitCompleteMilestone` carries NO phase-listing logic
* of its own to delegate — its only job is the `git.create_tag` config-gate
* fact the `git_tag` step's `<config-check>` sub-tag used to re-derive
* inline (gating the step's own inclusion on a fact only that step
* computed), now hoisted here and exposed as `git_create_tag`, plus the
* `section_manifest` field neither `init.manager` nor `init.execute-phase`
* carries.
*/
function cmdInitCompleteMilestone(
cwd: string,
raw: boolean,
options: Record<string, unknown> = {},
): void {
const gitCreateTag = detectGitCreateTag(cwd);
const result: Record<string, unknown> = {
// #2994: hoisted from complete-milestone.md's git_tag step
// <config-check> resolver (git.create_tag, fail-open default true).
git_create_tag: gitCreateTag,
};
result['section_manifest'] = buildSectionManifestField(cwd, null, options, 'complete-milestone', {
gitCreateTag,
});
output(withProjectRoot(cwd, result), raw);
}
/**
* `autonomous.md`'s dedicated init entry point (#2994, epic #1671 Phase
* 6.3). Additive alongside the workflow's existing `init.milestone-op`
* (`cmdInitMilestoneOp`), `init.manager` (`cmdInitManager`), and
* `init.phase-op` (`cmdInitPhaseOp`) calls — all three are CRITICAL blast
* radius (179 dependents across 24 processes) and are never modified for
* this; `autonomous.md` keeps every one of those calls exactly as it had
* them. `cmdInitAutonomous` carries NO phase-listing logic of its own to
* delegate — its only job is the `PLAN_STRATEGY` disjunction the workflow's
* own bash resolver (`PLAN_STRATEGY="converge"` on `--converge` OR
* `--cross-ai`) already computes at the top of the `initialize` step, now
* mirrored here as a single boolean FACT (same discipline as
* `state:chunked-mode`/`state:ui-phase-active`: the disjunction is resolved
* ONCE, in fact computation, never in the `when=` grammar), exposed as
* `plan_strategy_converge`, plus the `section_manifest` field none of the
* three existing calls carries.
*/
function cmdInitAutonomous(
cwd: string,
raw: boolean,
options: Record<string, unknown> = {},
): void {
const planStrategyConverge = options['converge'] === true || options['cross-ai'] === true;
const result: Record<string, unknown> = {
// #2994: mirrors autonomous.md's own PLAN_STRATEGY resolver
// (--converge OR its documented alias --cross-ai).
plan_strategy_converge: planStrategyConverge,
};
result['section_manifest'] = buildSectionManifestField(cwd, null, options, 'autonomous', {
planStrategyConverge,
});
output(withProjectRoot(cwd, result), raw);
}
/**
* `docs-update.md`'s dedicated init entry point (#2994, epic #1671 Phase
* 6.3 — final slice). `docs-update.md` previously carried NO `gsd_run query
* init.*` call at all: its own `docs-init` command (`cmdDocsInit`,
* src/docs.cts) is a SEPARATE, pre-existing entry point outside this
* `init.*` family and is left untouched here. This function's only job is
* the `section_manifest` field neither `docs-init` nor any other call
* carries, gating `docs-update.md`'s `dispatch-monorepo-packages` section.
*
* `state:is-monorepo` ground truth: the project's monorepo workspaces list
* is non-empty — reuses `detectMonorepoWorkspaces` (src/docs.cts, exported
* for this purpose) rather than a second, divergence-prone workspace-glob
* scan (DEFECT.GENERATIVE-FIX dual surface); this is the SAME detector that
* already backs `docs-init`'s own `monorepo_workspaces` field.
*/
function cmdInitDocsUpdate(cwd: string, raw: boolean, options: Record<string, unknown> = {}): void {
const isMonorepo = detectMonorepoWorkspaces(cwd).length > 0;
const result: Record<string, unknown> = {};
result['section_manifest'] = buildSectionManifestField(cwd, null, options, 'docs-update', {
isMonorepo,
});
output(withProjectRoot(cwd, result), raw);
}
/**
* `update.md`'s dedicated init entry point (#2994, epic #1671 Phase 6.3 —
* final slice). `update.md` previously carried NO `gsd_run query init.*`
* call at all: it resolves `gsd-tools.cjs` itself (its own bespoke
* `PREFERRED_CONFIG_DIR`/`PREFERRED_RUNTIME`-aware `$GSD_TOOLS` cascade,
* `update.md` ~lines 13-45) because the update workflow must run BEFORE any
* install can be assumed resolvable — the canonical launcher preamble's
* fixed candidate list is not a substitute for that cascade, and both
* resolutions assign the identical `$GSD_TOOLS` shell variable, so copying
* the canonical preamble in ADDITION to the existing cascade would silently
* clobber the value `backup_custom_files`/`restore_custom_files` (later
* steps) still depend on. This function is invoked via that ALREADY
* resolved `$GSD_TOOLS`, not a redundant `gsd_run()` shell function.
*
* `state:next-channel` ground truth: `--next` OR its documented alias
* `--rc` (same disjunction-to-one-boolean discipline as
* `state:chunked-mode`/`state:plan-strategy-converge`). This DELIBERATELY
* does not replace `update.md`'s own `parse_update_channel` case-statement
* (`TAG="next"`/`TAG="latest"`) — issue #815's regression test
* (`tests/issue-815-update-next-channel.test.cjs`) asserts that literal
* case-statement text stays in `update.md` verbatim (the npm dist-tag
* selection has to run in the workflow's own shell before any `gsd_run`
* round-trip), so `next_channel` exists purely to gate the `channel-banner`
* section's admission — a parallel, consistent-but-not-replacing
* resolution of the same flags.
*/
function cmdInitUpdate(cwd: string, raw: boolean, options: Record<string, unknown> = {}): void {
const nextChannel = options['next'] === true || options['rc'] === true;
const result: Record<string, unknown> = {
next_channel: nextChannel,
};
result['section_manifest'] = buildSectionManifestField(cwd, null, options, 'update', {
nextChannel,
});
output(withProjectRoot(cwd, result), raw);
}
/**
* `transition.md`'s dedicated init entry point (#2994, epic #1671 Phase
* 6.3 — final slice). `transition.md` is an internal workflow (no
* user-facing `/gsd-transition` command) that previously carried NO
* `gsd_run query init.*` call at all; it already establishes `gsd_run()`
* via the canonical launcher preamble in its `update_roadmap_and_state`
* step (before this call's insertion point in `offer_next_phase`), so no
* second preamble copy is needed in the host file.
*
* `state:workstream-active` ground truth: a workstream is active — resolved
* via `GSD_WORKSTREAM` env, falling back to the stored active-workstream
* pointer (mirrors `cmdInitProgress`'s own `_resolvedWorkstream` resolution
* above, the established authoritative source for "is a workstream active"
* in this file).
*
* `other_active_workstreams` hoists the resolver-in-body hazard out of
* `transition.md`'s `workstream-collision-check` step: that step's body
* previously re-derived this via an inline `gsd_run query workstream.list
* --raw` call gated on the identical `if [ -n "$GSD_WORKSTREAM" ]`
* condition that now backs this section's OWN admission — resolving it here
* instead reuses `getOtherActiveWorkstreamInventories` (src/workstream-
* inventory.cts), the SAME primitive `workstream.list` itself calls
* (`cmdWorkstreamList`, src/workstream.cts), pre-filtered exactly as the
* step's own prose described (excludes the current workstream and any
* workstream whose status contains "milestone complete" or "archived",
* case-insensitively — `isCompletedInventory`), so the step body becomes a
* pure JSON consumer with no `gsd_run` call of its own.
*/
function cmdInitTransition(cwd: string, raw: boolean, options: Record<string, unknown> = {}): void {
const resolvedWorkstream = process.env['GSD_WORKSTREAM'] || getActiveWorkstream(cwd);
const workstreamActive = !!resolvedWorkstream;
const result: Record<string, unknown> = {
other_active_workstreams: workstreamActive
? getOtherActiveWorkstreamInventories(cwd, resolvedWorkstream).map((inv) => ({
name: inv.name,
status: inv.status,
}))
: [],
};
result['section_manifest'] = buildSectionManifestField(cwd, null, options, 'transition', {
workstreamActive,
});
output(withProjectRoot(cwd, result), raw);
}
function cmdInitProgress(cwd: string, raw: boolean, options: Record<string, unknown> = {}): void {
try {
(pruneOrphanedWorktrees as (cwd: string) => void)(cwd);
@@ -2166,6 +2831,20 @@ function cmdInitProgress(cwd: string, raw: boolean, options: Record<string, unkn
if (pauseMatch) pausedAt = pauseMatch[1].trim();
}
// #2994: the CURRENT phase's number, used both to expose `phase_mvp_mode`
// at the top level (so the `mvp-display` step body can consume an
// already-resolved fact instead of re-invoking `gsd_run query
// phase.mvp-mode` itself — that inline resolver would otherwise gate a
// section on a fact the section's own body recomputes, which is circular
// and self-disabling) and to thread a real `phase_number` into
// `buildSectionManifestField` below so `state:phase-mvp-mode` is genuinely
// computed for this workflow rather than permanently false (the previous
// `buildSectionManifestField(cwd, null, ...)` call passed no phase info at
// all, so `detectPhaseMvpMode` always short-circuited on the `!phaseNumber`
// guard).
const currentPhaseNumber = (currentPhase?.['number'] as string | undefined) ?? null;
const phaseMvpMode = detectPhaseMvpMode(cwd, currentPhaseNumber);
const result: Record<string, unknown> = {
executor_model: resolveModelInternal(cwd, 'gsd-executor'),
planner_model: resolveModelInternal(cwd, 'gsd-planner'),
@@ -2186,6 +2865,7 @@ function cmdInitProgress(cwd: string, raw: boolean, options: Record<string, unkn
next_phase: nextPhase,
paused_at: pausedAt,
has_work_in_progress: !!currentPhase,
phase_mvp_mode: phaseMvpMode,
project_exists: pathExistsInternal(cwd, '.planning/PROJECT.md'),
roadmap_exists: fs.existsSync(path.join(planningDir(cwd), 'ROADMAP.md')),
@@ -2198,7 +2878,12 @@ function cmdInitProgress(cwd: string, raw: boolean, options: Record<string, unkn
};
// #2992 (Phase 6.1): additive, optional field — degrades to null, never throws.
result['section_manifest'] = buildSectionManifestField(cwd, null, options, 'progress');
result['section_manifest'] = buildSectionManifestField(
cwd,
currentPhaseNumber ? { phase_number: currentPhaseNumber } : null,
options,
'progress',
);
output(withProjectRoot(cwd, result), raw);
}
@@ -2973,11 +3658,19 @@ export = {
cmdInitResume,
cmdInitVerifyWork,
cmdInitPhaseOp,
cmdInitCodeReview,
cmdInitReview,
cmdInitDiscussPhaseAssumptions,
cmdInitTodos,
cmdInitMilestoneOp,
cmdInitMapCodebase,
cmdInitProgress,
cmdInitManager,
cmdInitCompleteMilestone,
cmdInitAutonomous,
cmdInitDocsUpdate,
cmdInitUpdate,
cmdInitTransition,
cmdInitNewWorkspace,
cmdInitListWorkspaces,
cmdInitRemoveWorkspace,

View File

@@ -20,7 +20,16 @@
* Phase 6.1; see `.gsd/phase/chore-2992-widen-when-vocabulary/
* 40-design.md`), then from 14 to 19 via the ADR-1671 amendment for #2993
* (epic #1671 Phase 6.2; see `.gsd/phase/chore-2993-fragmentize-plan-phase/
* 40-design.md`). {@link WHEN_PREDICATES} is a total map from each frozen
* 40-design.md`), then from 19 to 20 via the ADR-1671 amendment for #2994
* (epic #1671 Phase 6.3), then from 20 to 23 via a further #2994 amendment
* fragmentizing `code-review.md` and `complete-milestone.md`, then from 23
* to 24 via a still further #2994 amendment fragmentizing `autonomous.md`,
* then from 24 to 26 via a still further #2994 amendment fragmentizing
* `review.md` and `discuss-phase-assumptions.md`, then from 26 to 30 — and
* finally to 29, `flag:--full` having been retired as dead vocabulary — via the
* final #2994 slice fragmentizing `docs-update.md`, `update.md`,
* `transition.md`, and `new-milestone.md`.
* {@link WHEN_PREDICATES} is a total map from each frozen
* vocabulary entry to exactly one predicate over {@link InvocationFacts}.
* It MUST NOT tokenize, split on operators, or interpret structure in the
* `when=` string — the moment it parses, the ad-hoc language has begun.
@@ -92,6 +101,110 @@ export interface InvocationFacts {
* Absent/undefined is falsy, never throws.
*/
readonly chunkedMode?: boolean;
/**
* Whether the current phase's UI-phase surface is active (#2994):
* the phase's active `plan:pre` loop hooks include the `ui-phase` step OR
* the phase directory already contains a `*-UI-SPEC.md` file. Same
* discipline as {@link chunkedMode} — the disjunction is resolved by the
* caller (the init seam) into this single boolean BEFORE it reaches this
* module. Absent/undefined is falsy, never throws.
*/
readonly uiPhaseActive?: boolean;
/**
* Whether the fallow structural cross-module pre-pass is enabled
* (`.planning/config.json` `code_quality.fallow.enabled`, #2994). Fail-closed
* default `false` — resolved by the caller (`cmdInitCodeReview`, the init
* seam) from the same config key `code-review.md`'s `structural_pre_pass`
* step used to re-derive inline (a circular self-disabling gate); the
* resolved value is exposed as the init-bundle's `fallow_enabled` field for
* the step body to consume directly. Absent/undefined is falsy, never throws.
*/
readonly fallowEnabled?: boolean;
/**
* Whether git tag creation is enabled on milestone close
* (`.planning/config.json` `git.create_tag`, #2994). Fail-OPEN default
* `true` (unset/missing config means "create the tag"), matching
* `complete-milestone.md`'s pre-hoist `gsd-tools.cjs query config-get
* git.create_tag 2>/dev/null || echo "true"` resolver — now hoisted into
* `cmdInitCompleteMilestone` and exposed as the init-bundle's
* `git_create_tag` field. Absent/undefined is falsy at the PREDICATE level
* (per the totality convention every other atom here follows), so a caller
* MUST always populate this fact with a real boolean (never omit it) or the
* `git-tag` section will incorrectly evaluate to excluded.
*/
readonly gitCreateTag?: boolean;
/**
* Whether `autonomous.md`'s planning step should route through plan-review
* convergence instead of `gsd-plan-phase` (#2994): `--converge` flag OR its
* documented alias `--cross-ai`. Same discipline as {@link chunkedMode} /
* {@link uiPhaseActive} — the disjunction is resolved by the caller
* (`cmdInitAutonomous`, the init seam) into this single boolean BEFORE it
* reaches this module; `state:plan-strategy-converge`'s predicate below
* reads only this field, never `--converge`/`--cross-ai` separately.
* Absent/undefined is falsy, never throws.
*/
readonly planStrategyConverge?: boolean;
/**
* Whether `review.md`'s `review.reviewer_instances` config surface is
* configured — present AND non-empty (#2994). Resolved by the caller
* (`cmdInitReview`, the init seam) via `readConfigJsonValue`; the same
* fact backs both `reviewer-instances-note-1` and `reviewer-instances-note-2`
* (two sections sharing one atom — legal, mirrors `plan-phase.md`'s
* `research-only-*` pair). Absent/undefined is falsy, never throws.
*/
readonly reviewerInstancesConfigured?: boolean;
/**
* Whether `discuss-phase-assumptions.md`'s `auto_advance` step should
* dispatch (#2994): `--auto` flag OR a consolidated auto-mode config fact
* (`workflow._auto_chain_active` OR `workflow.auto_advance`). Same
* discipline as {@link chunkedMode} — the disjunction is resolved by the
* caller (`cmdInitDiscussPhaseAssumptions`, the init seam) into this
* single boolean BEFORE it reaches this module. Absent/undefined is
* falsy, never throws.
*/
readonly autoAdvanceActive?: boolean;
/**
* Whether the project is a monorepo with a non-empty workspaces list
* (#2994, final slice): the SAME `monorepo_workspaces` glob list
* `docs-init` (`src/docs.cts`) already produces, resolved by the caller
* (`cmdInitDocsUpdate`, the init seam) via the exported
* `detectMonorepoWorkspaces` detector rather than a second scan. Absent/
* undefined is falsy, never throws.
*/
readonly isMonorepo?: boolean;
/**
* Whether `update.md`'s release channel is `next` (#2994, final slice):
* `--next` flag OR its documented alias `--rc`. Same disjunction-to-one-
* boolean discipline as {@link chunkedMode} — resolved by the caller
* (`cmdInitUpdate`, the init seam) into this single boolean BEFORE it
* reaches this module. This fact exists purely to gate the
* `channel-banner` section's admission; it deliberately does NOT replace
* `update.md`'s own `TAG="next"`/`TAG="latest"` case-statement (issue
* #815's regression test asserts that literal text stays in the
* workflow). Absent/undefined is falsy, never throws.
*/
readonly nextChannel?: boolean;
/**
* Whether a workstream is active (#2994, final slice): `GSD_WORKSTREAM`
* env, falling back to the stored active-workstream pointer — resolved by
* the caller (`cmdInitTransition`/`cmdInitNewMilestone`, the init seam),
* mirroring `cmdInitProgress`'s own established resolution of the same
* question. Shared across two workflows' `cmdInit*` entry points (not
* redefined per-caller). Absent/undefined is falsy, never throws.
*/
readonly workstreamActive?: boolean;
/**
* Whether NO workstream is active — the positively-phrased INVERSE of
* {@link workstreamActive} (#2994, final slice). The `when=` grammar has
* no negation operator (ADR-1671:69), so a section whose true condition is
* "skip when a workstream IS active" (`new-milestone.md`'s Step 4 Part A)
* cannot negate `state:workstream-active` in its marker; a separate atom
* whose fact is independently resolved to the inverse is the sanctioned
* pattern instead. Resolved by the caller (`cmdInitNewMilestone`, the init
* seam) as `!workstreamActive` from the SAME authoritative source.
* Absent/undefined is falsy, never throws.
*/
readonly flatMode?: boolean;
}
/** A single input to {@link selectSections}: structurally compatible with {@link workflowFragments.WorkflowSection}. */
@@ -177,8 +290,8 @@ export const WHEN_PREDICATES: Readonly<Record<string, (facts: InvocationFacts) =
'state:has-prior-phases': (facts: InvocationFacts) => facts.hasPriorPhases === true,
'flag:--auto': (facts: InvocationFacts) => hasFlag(facts, '--auto'),
'flag:--discuss': (facts: InvocationFacts) => hasFlag(facts, '--discuss'),
'flag:--fix': (facts: InvocationFacts) => hasFlag(facts, '--fix'),
'flag:--forensic': (facts: InvocationFacts) => hasFlag(facts, '--forensic'),
'flag:--full': (facts: InvocationFacts) => hasFlag(facts, '--full'),
'flag:--ingest': (facts: InvocationFacts) => hasFlag(facts, '--ingest'),
'flag:--prd': (facts: InvocationFacts) => hasFlag(facts, '--prd'),
'flag:--research': (facts: InvocationFacts) => hasFlag(facts, '--research'),
@@ -186,9 +299,19 @@ export const WHEN_PREDICATES: Readonly<Record<string, (facts: InvocationFacts) =
'flag:--reset-phase-numbers': (facts: InvocationFacts) => hasFlag(facts, '--reset-phase-numbers'),
'flag:--reviews': (facts: InvocationFacts) => hasFlag(facts, '--reviews'),
'flag:--validate': (facts: InvocationFacts) => hasFlag(facts, '--validate'),
'state:auto-advance-active': (facts: InvocationFacts) => facts.autoAdvanceActive === true,
'state:chunked-mode': (facts: InvocationFacts) => facts.chunkedMode === true,
'state:fallow-enabled': (facts: InvocationFacts) => facts.fallowEnabled === true,
'state:flat-mode': (facts: InvocationFacts) => facts.flatMode === true,
'state:git-create-tag': (facts: InvocationFacts) => facts.gitCreateTag === true,
'state:is-monorepo': (facts: InvocationFacts) => facts.isMonorepo === true,
'state:needs-codebase-map': (facts: InvocationFacts) => facts.needsCodebaseMap === true,
'state:next-channel': (facts: InvocationFacts) => facts.nextChannel === true,
'state:phase-mvp-mode': (facts: InvocationFacts) => facts.phaseMvpMode === true,
'state:plan-strategy-converge': (facts: InvocationFacts) => facts.planStrategyConverge === true,
'state:reviewer-instances-configured': (facts: InvocationFacts) => facts.reviewerInstancesConfigured === true,
'state:ui-phase-active': (facts: InvocationFacts) => facts.uiPhaseActive === true,
'state:workstream-active': (facts: InvocationFacts) => facts.workstreamActive === true,
'state:worktrees-enabled': (facts: InvocationFacts) => facts.worktreesEnabled === true,
}),
);

View File

@@ -95,19 +95,50 @@ import contextComposer = require('./context-composer.cjs');
* #1671 Phase 6.1; see `.gsd/phase/chore-2992-widen-when-vocabulary/
* 40-design.md`), then from 14 to 19 via the ADR-1671 amendment for #2993
* (epic #1671 Phase 6.2; see `.gsd/phase/chore-2993-fragmentize-plan-phase/
* 40-design.md`). The vocabulary remains CLOSED: no operators, no negation,
* no nesting. Cardinality is not expressiveness — a 19-entry flat list with
* no composition is still not a language.
* 40-design.md`), then from 19 to 20 via the ADR-1671 amendment for #2994
* (epic #1671 Phase 6.3, `verify-work.md`), then from 20 to 23 via a further
* #2994 amendment fragmentizing `code-review.md` and `complete-milestone.md`,
* then from 23 to 24 via a further #2994 amendment fragmentizing
* `autonomous.md`, then from 24 to 26 via a still further #2994 amendment
* fragmentizing `review.md` and `discuss-phase-assumptions.md`, then from 26
* to 30 (then 29; `flag:--full` retired as dead vocabulary) via the FINAL
* #2994 slice (epic #1671 Phase 6.3) fragmentizing
* `docs-update.md`, `update.md`, `transition.md`, and `new-milestone.md` —
* every one of the 13 workflows targeted by ADR-1671 is now on the fragment
* model. The vocabulary remains CLOSED: no operators, no negation, no nesting.
* Cardinality is not expressiveness — a 29-entry flat list with no
* composition is still not a language.
*
* Held at 14, not wider: an atom whose fact is never computed always
* evaluates FALSE, so a section marked with it would silently never
* include — a silent-exclusion bug, not a feature. Six further atoms
* (`flag:--converge`, `flag:--fix`, `flag:--verify-only`,
* `state:fallow-enabled`, `state:git-create-tag`, `state:is-monorepo`) were
* surveyed and are justified in principle, but their workflows
* (docs-update, autonomous, code-review, complete-milestone) have no
* dedicated `cmdInit*` entry point yet to compute the backing fact, so they
* are withheld until that entry point exists (#2992 / ADR-1671 Phase 6.1).
* include — a silent-exclusion bug, not a feature. One further atom
* (`flag:--verify-only`) was surveyed but is NOT admitted even now that
* `docs-update` has its own `cmdInit*` entry point (`cmdInitDocsUpdate`):
* the flag's control flow is INTERLEAVED across three non-contiguous
* touch-points in `docs-update.md` (an inline early-exit check in
* `init_context` — "If `--verify-only` is present…skip to
* verify_only_report" — a "Skip condition" note embedded in another step's
* body, and the `verify_only_report` step itself) rather than a single
* contiguous, whole-line, purely-additive region — admitting the atom to
* gate only the `verify_only_report` step would leave the other two
* touch-points as un-migrated raw `$ARGUMENTS` checks. `state:is-monorepo`
* (`dispatch-monorepo-packages` section) IS admitted in this slice — see the
* paragraph below.
* `flag:--fix`, `state:fallow-enabled`, and `state:git-create-tag` were
* withheld for the same reason until a further #2994 amendment gave
* `code-review` and `complete-milestone` their own dedicated `cmdInit*`
* entry points (`cmdInitCodeReview`, `cmdInitCompleteMilestone`). The
* originally-surveyed `flag:--converge` never shipped under that name: once
* `autonomous` gained its own dedicated `cmdInitAutonomous` entry point, the
* admitted atom is `state:plan-strategy-converge` instead — `--cross-ai` is
* a documented alias for `--converge` (`autonomous.md`'s own `PLAN_STRATEGY`
* resolver folds both into one value), so a `flag:--converge`-only atom
* would have left `--cross-ai`-only invocations silently excluded from the
* same sections; see the `state:plan-strategy-converge` paragraph below.
* `state:reviewer-instances-configured` and `state:auto-advance-active` were
* withheld the same way until `review` and `discuss-phase-assumptions`
* gained their own dedicated `cmdInit*` entry points (`cmdInitReview`,
* `cmdInitDiscussPhaseAssumptions`).
*
* The #2993 widening adds 5 entries fragmentizing `plan-phase.md`:
* `flag:--ingest`, `flag:--prd`, `flag:--research-phase`, `flag:--reviews`,
@@ -115,6 +146,64 @@ import contextComposer = require('./context-composer.cjs');
* flag OR `.planning/config.json` `workflow.plan_chunked`) resolved to a
* single boolean FACT by the init seam (`src/init.cts`) — the grammar still
* sees exactly one atom with no operator, preserving the same guard.
*
* The #2994 widening adds 1 entry fragmentizing `verify-work.md`:
* `state:ui-phase-active`. Like `state:chunked-mode`, it is a disjunction —
* the phase's active `plan:pre` loop hooks include the `ui-phase` step OR
* the phase directory already contains a `*-UI-SPEC.md` — resolved to a
* single boolean FACT by the init seam before it ever reaches this grammar.
*
* A further #2994 widening (epic #1671 Phase 6.3) adds 3 entries
* fragmentizing `code-review.md` and `complete-milestone.md`: `flag:--fix`
* (`dispatch-fix` section), `state:fallow-enabled` (`structural-pre-pass`
* section — the fallow config-gate resolver, previously re-derived inside
* the section body itself, is hoisted into `cmdInitCodeReview` and exposed
* as top-level `fallow_*` init-bundle fields), and `state:git-create-tag`
* (`git-tag` section — the `git.create_tag` config-gate resolver is hoisted
* into `cmdInitCompleteMilestone`).
*
* A still further #2994 widening (epic #1671 Phase 6.3) adds 1 entry
* fragmentizing `autonomous.md`: `state:plan-strategy-converge`, gating five
* sections (`converge-fail-fast`, `converge-banner`, `converge-dispatch-bg`,
* `converge-dispatch-inline`, `converge-loop`) that all share the same atom
* — legal and precedented (`plan-phase.md`'s `research-only-*` pair already
* shares `flag:--research-phase`). It is a disjunction — `--converge` OR its
* documented alias `--cross-ai` — resolved to a single boolean FACT by the
* new `cmdInitAutonomous` entry point (`flags.has('--converge') ||
* flags.has('--cross-ai')`) before it ever reaches this grammar, same
* discipline as `state:chunked-mode`/`state:ui-phase-active` above.
*
* A still further #2994 widening (epic #1671 Phase 6.3) adds 2 entries
* fragmentizing `review.md` and `discuss-phase-assumptions.md`:
* `state:reviewer-instances-configured` (`reviewer-instances-note-1` and
* `reviewer-instances-note-2` sections — two peripheral notes sharing one
* atom, the same sharing pattern `plan-phase.md`'s `research-only-*` pair
* already established) and `state:auto-advance-active` (`auto-advance-dispatch`
* section — a disjunction, `--auto` flag OR a consolidated auto-mode config
* fact, resolved to a single boolean FACT by the new
* `cmdInitDiscussPhaseAssumptions` entry point before it ever reaches this
* grammar, same discipline as `state:chunked-mode` above).
*
* The FINAL #2994 widening (epic #1671 Phase 6.3) adds 4 entries, closing
* out the last four workflows on ADR-1671's fragmentization list —
* `docs-update.md`, `update.md`, `transition.md`, `new-milestone.md` — none
* of which carried a `gsd_run query init.*` call before this slice:
* `state:is-monorepo` (`dispatch-monorepo-packages` section, new
* `cmdInitDocsUpdate` entry point — reuses `docs.cts`'s own
* `detectMonorepoWorkspaces` detector rather than a second scan);
* `state:next-channel` (`channel-banner` section, new `cmdInitUpdate` entry
* point — `--next` OR its documented alias `--rc`, resolved in PARALLEL
* with, not in place of, `update.md`'s own `TAG="next"` case-statement,
* which issue #815's regression test requires to stay literal in the
* workflow); `state:workstream-active` (`workstream-collision-check`
* section, new `cmdInitTransition` entry point — a workstream is active,
* `GSD_WORKSTREAM` env falling back to the stored active-workstream
* pointer, the same authoritative source `cmdInitProgress` already uses);
* and `state:flat-mode` (`project-md-milestone-write` section,
* `cmdInitNewMilestone` — the positively-phrased INVERSE of
* `state:workstream-active`, introduced because the grammar has no negation
* operator and `new-milestone.md`'s Step 4 Part A is gated on the OPPOSITE
* condition from `transition.md`'s section).
*/
export const WHEN_VOCABULARY: readonly string[] = Object.freeze([
'always',
@@ -123,8 +212,8 @@ export const WHEN_VOCABULARY: readonly string[] = Object.freeze([
'state:has-prior-phases',
'flag:--auto',
'flag:--discuss',
'flag:--fix',
'flag:--forensic',
'flag:--full',
'flag:--ingest',
'flag:--prd',
'flag:--research',
@@ -132,9 +221,19 @@ export const WHEN_VOCABULARY: readonly string[] = Object.freeze([
'flag:--reset-phase-numbers',
'flag:--reviews',
'flag:--validate',
'state:auto-advance-active',
'state:chunked-mode',
'state:fallow-enabled',
'state:flat-mode',
'state:git-create-tag',
'state:is-monorepo',
'state:needs-codebase-map',
'state:next-channel',
'state:phase-mvp-mode',
'state:plan-strategy-converge',
'state:reviewer-instances-configured',
'state:ui-phase-active',
'state:workstream-active',
'state:worktrees-enabled',
]);

View File

@@ -16,6 +16,14 @@ const WORKFLOW_PATH = path.join(REPO_ROOT, 'gsd-core', 'workflows', 'autonomous.
const COMMANDS_DOC_PATH = path.join(REPO_ROOT, 'docs', 'COMMANDS.md');
const HOW_TO_PATH = path.join(REPO_ROOT, 'docs', 'how-to', 'run-phases-autonomously.md');
const TOOLS = path.join(REPO_ROOT, 'gsd-core', 'bin', 'gsd-tools.cjs');
// #2994: fragmentization moved the five converge-gated regions out of the host
// autonomous.md into dedicated step files (state:plan-strategy-converge) —
// see docs/reference/workflow-fragments.md. Tests that assert on this moved
// content read the step file directly rather than the host.
const STEP_FAIL_FAST_PATH = path.join(REPO_ROOT, 'gsd-core', 'workflows', 'autonomous', 'steps', 'converge-fail-fast.md');
const STEP_DISPATCH_BG_PATH = path.join(REPO_ROOT, 'gsd-core', 'workflows', 'autonomous', 'steps', 'converge-dispatch-bg.md');
const STEP_DISPATCH_INLINE_PATH = path.join(REPO_ROOT, 'gsd-core', 'workflows', 'autonomous', 'steps', 'converge-dispatch-inline.md');
const STEP_LOOP_PATH = path.join(REPO_ROOT, 'gsd-core', 'workflows', 'autonomous', 'steps', 'converge-loop.md');
function read(filePath) {
return fs.readFileSync(filePath, 'utf8');
@@ -47,30 +55,50 @@ describe('autonomous --converge flag (#711)', () => {
});
test('workflow fails fast when convergence is requested but disabled', () => {
// #2994: this check lives in the converge-fail-fast step file now
// (state:plan-strategy-converge) — the host only carries the gated
// conditional-read stub.
const workflow = read(WORKFLOW_PATH);
const step = read(STEP_FAIL_FAST_PATH);
assert.match(
workflow,
/config-get workflow\.plan_review_convergence/,
'workflow should check workflow.plan_review_convergence before planning',
/gsd:section id="converge-fail-fast" when="state:plan-strategy-converge"/,
'workflow should gate the fail-fast check behind state:plan-strategy-converge',
);
assert.match(
workflow,
step,
/config-get workflow\.plan_review_convergence/,
'converge-fail-fast step should check workflow.plan_review_convergence before planning',
);
assert.match(
step,
/gsd config-set workflow\.plan_review_convergence true/,
'workflow should print the enable command instead of silently downgrading',
'converge-fail-fast step should print the enable command instead of silently downgrading',
);
});
test('workflow routes planning through plan-review-convergence when enabled', () => {
// #2994: the converge dispatch/loop bodies live in dedicated step files
// now (state:plan-strategy-converge) — only the local-planning fallback
// remains inline in the host.
const workflow = read(WORKFLOW_PATH);
const dispatchInline = read(STEP_DISPATCH_INLINE_PATH);
const loop = read(STEP_LOOP_PATH);
const dispatchBg = read(STEP_DISPATCH_BG_PATH);
assert.match(
workflow,
dispatchInline,
/Skill\(skill="gsd-plan-review-convergence", args="\$\{PHASE_NUM\} \$\{CONVERGENCE_ARGS\}"\)/,
'non-interactive converge mode should call gsd-plan-review-convergence',
'inline converge dispatch step should call gsd-plan-review-convergence',
);
assert.match(
workflow,
loop,
/Skill\(skill="gsd-plan-review-convergence", args="\$\{PHASE_NUM\} \$\{CONVERGENCE_ARGS\}"\)/,
'default converge loop step should call gsd-plan-review-convergence',
);
assert.match(
dispatchBg,
/Run plan convergence for phase \$\{PHASE_NUM\}: Skill\(skill=\\"gsd-plan-review-convergence\\"/,
'interactive converge mode should dispatch plan convergence in the background agent',
);

View File

@@ -12,13 +12,32 @@ const fs = require('fs');
const path = require('path');
const WORKFLOW_PATH = path.join(__dirname, '..', 'gsd-core', 'workflows', 'autonomous.md');
const UI_DESIGN_CONTRACT_REF_PATH = path.join(
__dirname, '..', 'gsd-core', 'references', 'autonomous-ui-design-contract.md'
);
/**
* #2994 fragmentization moved §3a.5's body out of autonomous.md into
* gsd-core/references/autonomous-ui-design-contract.md, leaving an
* unconditional `Read and execute:` pointer in the host. Splice the
* reference file's content back in at the pointer's position so §3a.5
* slicing/ordering assertions still see the real step body.
*/
function readAutonomousCombined() {
const raw = fs.readFileSync(WORKFLOW_PATH, 'utf-8');
const pointerRe = /Read and execute: `\$HOME\/\.claude\/gsd-core\/references\/autonomous-ui-design-contract\.md`/;
assert.ok(pointerRe.test(raw), 'autonomous.md must still contain the §3a.5 reference pointer');
const refContent = fs.readFileSync(UI_DESIGN_CONTRACT_REF_PATH, 'utf-8');
return raw.replace(pointerRe, refContent);
}
describe('autonomous workflow ui-phase and ui-review integration (#1375)', () => {
let content;
beforeEach(() => {
assert.ok(fs.existsSync(WORKFLOW_PATH), 'workflows/autonomous.md should exist');
content = fs.readFileSync(WORKFLOW_PATH, 'utf-8');
assert.ok(fs.existsSync(UI_DESIGN_CONTRACT_REF_PATH), 'references/autonomous-ui-design-contract.md should exist');
content = readAutonomousCombined();
});
describe('step 3a.5 — UI design contract before planning', () => {

View File

@@ -301,9 +301,25 @@ const { cleanup } = require('./helpers.cjs');
const HELPER_PATH = path.join(__dirname, '..', 'bin', 'lib', 'ui-safety-gate.cjs');
const PLAN_PHASE_PATH = path.join(__dirname, '..', 'gsd-core', 'workflows', 'plan-phase.md');
const AUTONOMOUS_PATH = path.join(__dirname, '..', 'gsd-core', 'workflows', 'autonomous.md');
const AUTONOMOUS_UI_DESIGN_CONTRACT_REF_PATH = path.join(
__dirname, '..', 'gsd-core', 'references', 'autonomous-ui-design-contract.md'
);
const { checkUiPresence } = require(HELPER_PATH);
/**
* #2994 fragmentization moved §3a.5's body out of autonomous.md into
* gsd-core/references/autonomous-ui-design-contract.md, leaving an
* unconditional `Read and execute:` pointer in the host. Read host +
* reference file combined so the structural guards below still see the
* real §3a.5 body (and its absence of the retired ui-safety-gate.cjs /
* RUNTIME_DIR / LC_ALL=C grep patterns).
*/
function readAutonomousCombined() {
return fs.readFileSync(AUTONOMOUS_PATH, 'utf-8') +
'\n' + fs.readFileSync(AUTONOMOUS_UI_DESIGN_CONTRACT_REF_PATH, 'utf-8');
}
// ── Helper ────────────────────────────────────────────────────────────────────
/**
@@ -362,7 +378,7 @@ describe('Workflow .md structural guard (#3718)', () => {
// was intentionally removed — cross-shell portability is provided by the CLI layer.
test('autonomous.md must invoke check ui-plan-gate (capability-driven UI gate — #1031)', () => {
const label = 'autonomous.md';
const content = fs.readFileSync(AUTONOMOUS_PATH, 'utf-8');
const content = readAutonomousCombined();
// §3a.5 must delegate to the capability-driven gate, not inline shell code
assert.ok(

View File

@@ -235,6 +235,18 @@ describe('#3727 — parseCodeReviewFlags: typed IR for code-review argv', () =>
describe('#3727 — code-review.md structural dispatch contract', () => {
const src = fs.readFileSync(WORKFLOW_PATH, 'utf8');
// #2994: dispatch_fix moved wholly into a marker-gated step file
// (gsd-core/workflows/code-review/steps/dispatch-fix.md, when="flag:--fix")
// — the host now carries only the gsd:section marker + read-or-skip stub.
const DISPATCH_FIX_STEP_PATH = path.join(
ROOT,
'gsd-core',
'workflows',
'code-review',
'steps',
'dispatch-fix.md',
);
const dispatchFixSrc = fs.readFileSync(DISPATCH_FIX_STEP_PATH, 'utf8');
test('initialize step references code-review-flags.cjs for flag parsing', () => {
const initStart = src.indexOf('<step name="initialize">');
@@ -247,20 +259,21 @@ describe('#3727 — code-review.md structural dispatch contract', () => {
);
});
test('workflow has a <step name="dispatch_fix"> step', () => {
test('workflow gates a dispatch-fix section (flag:--fix) whose step file has the dispatch_fix step', () => {
assert.ok(
src.includes('<step name="dispatch_fix">'),
'code-review.md must have a dispatch_fix step (missing = --fix is silently no-op)'
src.includes('<!-- gsd:section id="dispatch-fix" when="flag:--fix" -->') &&
src.includes('gsd-core/workflows/code-review/steps/dispatch-fix.md'),
'code-review.md must gate a dispatch-fix section pointing at dispatch-fix.md (missing = --fix is silently no-op)'
);
assert.ok(
dispatchFixSrc.includes('<step name="dispatch_fix">'),
'dispatch-fix.md step file must contain the dispatch_fix step'
);
});
test('dispatch_fix step delegates to code-review-fix.md when fix flag is true', () => {
const stepStart = src.indexOf('<step name="dispatch_fix">');
const stepEnd = src.indexOf('</step>', stepStart);
assert.ok(stepStart !== -1, 'dispatch_fix step must exist');
const stepSection = src.slice(stepStart, stepEnd);
assert.ok(
stepSection.includes('code-review-fix.md'),
dispatchFixSrc.includes('code-review-fix.md'),
'dispatch_fix step must reference code-review-fix.md workflow'
);
});
@@ -268,11 +281,8 @@ describe('#3727 — code-review.md structural dispatch contract', () => {
test('dispatch_fix step references gsd-code-fixer agent (via code-review-fix.md chain)', () => {
// The dispatch step must show that fixing will occur, either directly or
// by loading code-review-fix.md (which in turn spawns gsd-code-fixer).
const stepStart = src.indexOf('<step name="dispatch_fix">');
const stepEnd = src.indexOf('</step>', stepStart);
const stepSection = src.slice(stepStart, stepEnd);
assert.ok(
stepSection.includes('gsd-code-fixer') || stepSection.includes('code-review-fix.md'),
dispatchFixSrc.includes('gsd-code-fixer') || dispatchFixSrc.includes('code-review-fix.md'),
'dispatch_fix step must reference gsd-code-fixer agent or code-review-fix.md workflow'
);
});

View File

@@ -531,7 +531,14 @@ describe('CR-INTEGRATION: workflow integration points', () => {
test('quick.md resolves code-review capability hook', () => {
const content = fs.readFileSync(path.join(WORKFLOWS_DIR, 'quick.md'), 'utf-8');
const start = content.indexOf('**Step 6.25: Code review (auto)**');
const end = content.indexOf('**Step 6.5: Verification', start);
// #2994 (pre-existing since #2994's earlier quick-verification.md extraction,
// 18ff35d20): Step 6.5's content moved into
// gsd-core/workflows/quick/steps/quick-verification.md behind a
// `<!-- gsd:section id="quick-verification" -->` marker, so the literal
// "**Step 6.5: Verification" heading text no longer follows Step 6.25 in
// this file — the marker is the correct end-of-step delimiter now (mirrors
// phase6-review-capabilities.test.cjs's identical retarget for the same move).
const end = content.indexOf('<!-- gsd:section id="quick-verification"', start);
assert.ok(start !== -1 && end !== -1, 'quick.md missing Step 6.25 code review section');
const reviewContent = content.slice(start, end);

View File

@@ -1672,10 +1672,16 @@ describe('bug-853 — manager/autonomous gate background dispatch by runtime', (
assert.match(AUTONOMOUS, /If `FLATTEN` is `false`[\s\S]{0,1200}?run_in_background=true[\s\S]{0,600}?gsd-plan-phase/);
// Background block: run_in_background=true appears within the FLATTEN=false branch and gsd-execute-phase is nearby
assert.match(AUTONOMOUS, /If `FLATTEN` is `false`[\s\S]{0,3000}?run_in_background=true[\s\S]{0,200}?gsd-execute-phase/);
// Inline is the otherwise/else branch for plan — anchored on FLATTEN=true language (not runtime name)
// Inline is the otherwise/else branch for plan — anchored on FLATTEN=true language (not runtime name).
// #2994: the window between `FLATTEN` and the inline Skill() call widened
// past manager.md's 400 because this branch now carries a
// state:plan-strategy-converge conditional-read stub (converge-dispatch-inline,
// gsd-core/workflows/autonomous/steps/converge-dispatch-inline.md) ahead of
// the local-planning fallback — the stub's full step-file path is more
// verbose than the terse `Skill()` call it replaced.
assert.match(
AUTONOMOUS,
/Otherwise[\s\S]{0,100}?`FLATTEN`[\s\S]{0,400}?Skill\(skill="gsd-plan-phase"/,
/Otherwise[\s\S]{0,100}?`FLATTEN`[\s\S]{0,700}?Skill\(skill="gsd-plan-phase"/,
);
// Inline is the otherwise/else branch for execute — anchored on FLATTEN=true language (not runtime name)
assert.match(

View File

@@ -943,48 +943,60 @@ describe('feat-3210 / #1012: code-review workflow invokes fallow with the real C
path.join(ROOT, 'gsd-core', 'workflows', 'code-review.md'),
'utf8',
);
// #2994: the actual fallow invocation (audit --format json, --changed-since,
// --max-crap, verdict-based exit handling) moved to a marker-gated step file
// (gsd-core/workflows/code-review/steps/structural-pre-pass.md, when=
// "state:fallow-enabled") — read it for the invocation-specific assertions
// below; the negative "removed flags" assertion checks BOTH files.
const fallowStepSrc = fs.readFileSync(
path.join(ROOT, 'gsd-core', 'workflows', 'code-review', 'steps', 'structural-pre-pass.md'),
'utf8',
);
const combinedSrc = workflowSrc + '\n' + fallowStepSrc;
test('uses audit --format json and --quiet (real fallow 2.x flags)', () => {
assert.ok(
workflowSrc.includes('audit --format json'),
fallowStepSrc.includes('audit --format json'),
'workflow must invoke: audit --format json',
);
assert.ok(
workflowSrc.includes('--quiet'),
fallowStepSrc.includes('--quiet'),
'workflow must pass --quiet to suppress progress output',
);
});
test('does NOT use removed flags: --json , --profile, --stdin-files', () => {
assert.ok(
!workflowSrc.includes('--json '),
!combinedSrc.includes('--json '),
'workflow must not use old --json flag (note trailing space to avoid matching --format json)',
);
assert.ok(
!workflowSrc.includes('--profile'),
!combinedSrc.includes('--profile'),
'workflow must not use --profile (fallow has no native profile concept)',
);
assert.ok(
!workflowSrc.includes('--stdin-files'),
!combinedSrc.includes('--stdin-files'),
'workflow must not use --stdin-files (removed in fallow 2.x)',
);
});
test('uses --max-crap for threshold control (profile maps to max-crap)', () => {
assert.ok(
workflowSrc.includes('--max-crap'),
fallowStepSrc.includes('--max-crap'),
'workflow must use --max-crap to control threshold (profile mapped to this flag)',
);
});
test('scopes phase via --changed-since (native fallow git-ref scoping)', () => {
assert.ok(
workflowSrc.includes('--changed-since'),
fallowStepSrc.includes('--changed-since'),
'workflow must use --changed-since for phase scoping',
);
});
test('normalizes fallow output via normalizeFallowReportFile before embedding', () => {
// Still in the host (spawn_reviewer step, unconditional) — not gated by
// state:fallow-enabled, since the reviewer prompt assembly always runs.
assert.ok(
workflowSrc.includes('normalizeFallowReportFile'),
'workflow must call normalizeFallowReportFile to normalize before embedding into reviewer prompt',
@@ -993,7 +1005,7 @@ describe('feat-3210 / #1012: code-review workflow invokes fallow with the real C
test('exit-handling gates on valid JSON (verdict in o), not on exit code', () => {
assert.ok(
workflowSrc.includes("'verdict' in o"),
fallowStepSrc.includes("'verdict' in o"),
"workflow exit-handling must use 'verdict' in o to decide success (not exit code)",
);
});
@@ -1047,6 +1059,13 @@ describe('feat-3210: workflow and config contracts', () => {
// The workflow .md uses XML-like <step> tags as its runtime DSL; we parse the step block
// structurally and assert on structural properties, not on prose strings.
test('code-review workflow structural_pre_pass step is parseable and references FALLOW.json output', () => {
// #2994: structural_pre_pass's gated execution (fallow binary resolve + run
// + FALLOW.json persistence) moved to a marker-gated step file
// (gsd-core/workflows/code-review/steps/structural-pre-pass.md, when=
// "state:fallow-enabled"). The config-gate resolver itself — previously an
// inline `gsd_run query config-get code_quality.fallow.enabled` call inside
// this step's own body (a circular self-disabling gate) — was hoisted into
// `detectFallowConfig` (src/init.cts), consumed by `cmdInitCodeReview`.
const workflow = fs.readFileSync(
path.join(ROOT, 'gsd-core', 'workflows', 'code-review.md'),
'utf8',
@@ -1061,16 +1080,30 @@ describe('feat-3210: workflow and config contracts', () => {
const stepBody = stepMatch[1];
// Structural property: the step body must reference the FALLOW.json output artifact
// Structural property: the host stub gates the FALLOW.json-producing
// execution behind state:fallow-enabled and points at the step file.
assert.ok(
stepBody.includes('FALLOW.json'),
'structural_pre_pass step body must reference the FALLOW.json output artifact',
stepBody.includes('when="state:fallow-enabled"') &&
stepBody.includes('gsd-core/workflows/code-review/steps/structural-pre-pass.md'),
'structural_pre_pass step must gate its FALLOW.json-producing execution behind state:fallow-enabled',
);
// Structural property: the step body must gate on the fallow enabled config key
const stepFile = fs.readFileSync(
path.join(ROOT, 'gsd-core', 'workflows', 'code-review', 'steps', 'structural-pre-pass.md'),
'utf8',
);
assert.ok(
stepBody.includes('code_quality.fallow.enabled'),
'structural_pre_pass step body must gate on code_quality.fallow.enabled',
stepFile.includes('FALLOW.json'),
'structural-pre-pass.md step file must reference the FALLOW.json output artifact',
);
// Structural property: the config-gate fact is resolved from the real
// code_quality.fallow.enabled config key — checked against the resolver
// it was hoisted into (src/init.cts's detectFallowConfig).
const initSource = fs.readFileSync(path.join(ROOT, 'src', 'init.cts'), 'utf8');
assert.ok(
/detectFallowConfig[\s\S]{0,600}'code_quality'[\s\S]{0,40}'fallow'[\s\S]{0,40}'enabled'/.test(initSource),
'detectFallowConfig (src/init.cts) must gate on code_quality.fallow.enabled',
);
});

View File

@@ -1884,15 +1884,38 @@ describe('#3086: git.create_tag config key', () => {
);
});
test('D. complete-milestone.md contains <config-check> gate for git.create_tag', () => {
test('D. complete-milestone.md gates git_tag behind state:git-create-tag (#2994: <config-check> hoisted into cmdInitCompleteMilestone)', () => {
// #2994: the inline <config-check> (`gsd-tools.cjs query config-get
// git.create_tag ... || echo "true"`) that used to gate the git_tag step's
// own inclusion was hoisted into `detectGitCreateTag` (src/init.cts),
// consumed by the new `cmdInitCompleteMilestone` entry point and exposed
// as the init-bundle's `git_create_tag` field / the `state:git-create-tag`
// when= atom. The workflow markdown no longer contains a literal
// <config-check> block or `git.create_tag` string — it gates the whole
// git_tag step behind a gsd:section marker instead.
const content = fs.readFileSync(WORKFLOW_PATH, 'utf8');
assert.ok(
content.includes('git.create_tag'),
'complete-milestone.md must reference git.create_tag in a <config-check> block',
content.includes('<!-- gsd:section id="git-tag" when="state:git-create-tag" -->'),
'complete-milestone.md must gate the git_tag step behind the state:git-create-tag section marker',
);
assert.ok(
content.includes('<config-check>'),
'complete-milestone.md must have a <config-check> block in the git_tag step',
content.includes('gsd-core/workflows/complete-milestone/steps/git-tag.md'),
'complete-milestone.md must point the git-tag section at its step file',
);
const stepFile = fs.readFileSync(
path.join(__dirname, '..', 'gsd-core', 'workflows', 'complete-milestone', 'steps', 'git-tag.md'),
'utf8',
);
assert.ok(
stepFile.includes('<step name="git_tag">') && stepFile.includes('git tag -a'),
'git-tag.md step file must contain the git_tag step body (git tag creation)',
);
const initSource = fs.readFileSync(path.join(__dirname, '..', 'src', 'init.cts'), 'utf8');
assert.ok(
/detectGitCreateTag[\s\S]{0,300}'git'[\s\S]{0,40}'create_tag'/.test(initSource),
'src/init.cts detectGitCreateTag must resolve the git.create_tag config key',
);
});
});

View File

@@ -0,0 +1,6 @@
{
"version": 1,
"paths": {
"update.md": "#2994 (epic #1671 Phase 6.3, FINAL slice): fragmentizes update.md onto the marker grammar, admitting `state:next-channel` (channel-banner section) and a new dedicated `init.update` entry point (cmdInitUpdate). update.md previously carried NO `gsd_run query init.*` call at all; the new INIT_UPDATE fetch (reusing the already-resolved $GSD_TOOLS from get_installed_version, dual .cjs/PATH-shim invocation style, matching the pre-existing update-context call) plus its surrounding comment outweighs the single `<!-- gsd:section -->` marker-pair stub that replaces the extracted channel-banner prose (now living in gsd-core/workflows/update/steps/channel-banner.md). Net SOURCE growth is +703 bytes (26,024 -> 26,727). The pre-existing TAG=\"next\"/TAG=\"latest\" case-statement in parse_update_channel is deliberately left byte-identical — issue #815's regression test (tests/issue-815-update-next-channel.test.cjs) asserts that literal text stays in the workflow, since the npm dist-tag selection must run in the workflow's own shell before any gsd_run round-trip. The EMITTED artifact composeWorkflow produces at install time still includes the extracted prose verbatim (markers strip, gap+section bodies re-join byte-for-byte) when the atom is unresolved (section_manifest null -> read-everything fallback), so installed behavior is unchanged; only the SOURCE file's on-disk byte count moves."
}
}

View File

@@ -0,0 +1,6 @@
{
"version": 1,
"paths": {
"review.md": "#2994 (epic #1671 Phase 6.3, further amendment): fragmentizes review.md onto the marker grammar, admitting `state:reviewer-instances-configured` (shared by two peripheral notes, `reviewer-instances-note-1` in detect_clis and `reviewer-instances-note-2` in invoke_reviewers — the core reviewer-lane dispatch itself stays unmarked). Also retargets the init line from the shared `init.phase-op` to a new dedicated `init.review` entry point (cmdInitReview), which computes the reviewer-instances-configured fact via `review.reviewer_instances` config presence. Net SOURCE growth is +55 bytes (29,063 -> 29,118): two `<!-- gsd:section -->` marker-pair stubs (~331 B and ~195 B) replace their extracted prose bodies (328 B and 277 B respectively, now living in gsd-core/workflows/review/steps/*.md), and `init.phase-op` shrinks by 2 bytes to `init.review` on the gather_context init line. The EMITTED artifact composeWorkflow produces at install time still includes the extracted prose verbatim (markers strip, gap+section bodies re-join byte-for-byte) when the atom is unresolved (section_manifest null -> read-everything fallback), so installed behavior is unchanged; only the SOURCE file's on-disk byte count moves."
}
}

View File

@@ -228,7 +228,11 @@ describe('#2287 cmdAuditUat: deferred-items.md awareness', () => {
// ─── forensic_audit workflow-prose source-contract guard ──────────────────
const PROGRESS_MD = path.join(__dirname, '..', 'gsd-core', 'workflows', 'progress.md');
// #2994 fragmentization moved the --forensic-gated forensic_audit step out of
// progress.md into gsd-core/workflows/progress/steps/forensic-audit.md behind
// a section marker. Read that step file directly — it is the sole remaining
// source of the forensic_audit step body these guards assert on.
const PROGRESS_MD = path.join(__dirname, '..', 'gsd-core', 'workflows', 'progress', 'steps', 'forensic-audit.md');
describe('#2287 progress.md forensic_audit: deferred-items.md contract', () => {
const content = fs.readFileSync(PROGRESS_MD, 'utf-8');
@@ -237,7 +241,7 @@ describe('#2287 progress.md forensic_audit: deferred-items.md contract', () => {
const section = stepStart !== -1 && stepEnd !== -1 ? content.slice(stepStart, stepEnd) : '';
test('forensic_audit step exists', () => {
assert.notEqual(stepStart, -1, 'progress.md must contain the forensic_audit step');
assert.notEqual(stepStart, -1, 'progress.md (or its extracted progress/steps/forensic-audit.md) must contain the forensic_audit step');
});
test('forensic_audit now runs 7 checks (was 6) and globs deferred-items.md', () => {

View File

@@ -56,6 +56,7 @@
"gsd-core/references/api-coverage.md",
"gsd-core/references/artifact-types.md",
"gsd-core/references/autonomous-smart-discuss.md",
"gsd-core/references/autonomous-ui-design-contract.md",
"gsd-core/references/checkpoints.md",
"gsd-core/references/common-bug-patterns.md",
"gsd-core/references/context-budget.md",
@@ -221,15 +222,24 @@
"gsd-core/workflows/audit-milestone.md",
"gsd-core/workflows/audit-uat.md",
"gsd-core/workflows/autonomous.md",
"gsd-core/workflows/autonomous/steps/converge-banner.md",
"gsd-core/workflows/autonomous/steps/converge-dispatch-bg.md",
"gsd-core/workflows/autonomous/steps/converge-dispatch-inline.md",
"gsd-core/workflows/autonomous/steps/converge-fail-fast.md",
"gsd-core/workflows/autonomous/steps/converge-loop.md",
"gsd-core/workflows/check-todos.md",
"gsd-core/workflows/cleanup.md",
"gsd-core/workflows/code-review-fix.md",
"gsd-core/workflows/code-review.md",
"gsd-core/workflows/code-review/steps/dispatch-fix.md",
"gsd-core/workflows/code-review/steps/structural-pre-pass.md",
"gsd-core/workflows/complete-milestone.md",
"gsd-core/workflows/complete-milestone/steps/git-tag.md",
"gsd-core/workflows/debug.md",
"gsd-core/workflows/diagnose-issues.md",
"gsd-core/workflows/discovery-phase.md",
"gsd-core/workflows/discuss-phase-assumptions.md",
"gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md",
"gsd-core/workflows/discuss-phase-power.md",
"gsd-core/workflows/discuss-phase.md",
"gsd-core/workflows/discuss-phase/modes/advisor.md",
@@ -246,6 +256,7 @@
"gsd-core/workflows/discuss-phase/templates/discussion-log.md",
"gsd-core/workflows/do.md",
"gsd-core/workflows/docs-update.md",
"gsd-core/workflows/docs-update/steps/dispatch-monorepo-packages.md",
"gsd-core/workflows/edit-phase.md",
"gsd-core/workflows/eval-review.md",
"gsd-core/workflows/execute-phase.md",
@@ -282,7 +293,12 @@
"gsd-core/workflows/milestone-summary.md",
"gsd-core/workflows/mvp-phase.md",
"gsd-core/workflows/new-milestone.md",
"gsd-core/workflows/new-milestone/steps/project-md-milestone-write.md",
"gsd-core/workflows/new-milestone/steps/reset-phase-safety.md",
"gsd-core/workflows/new-project.md",
"gsd-core/workflows/new-project/steps/auto-mode-config.md",
"gsd-core/workflows/new-project/steps/auto-mode-detection.md",
"gsd-core/workflows/new-project/steps/codebase-map-offer.md",
"gsd-core/workflows/new-workspace.md",
"gsd-core/workflows/next.md",
"gsd-core/workflows/node-repair.md",
@@ -306,12 +322,21 @@
"gsd-core/workflows/pr-branch.md",
"gsd-core/workflows/profile-user.md",
"gsd-core/workflows/progress.md",
"gsd-core/workflows/progress/steps/forensic-audit.md",
"gsd-core/workflows/progress/steps/mvp-display.md",
"gsd-core/workflows/quick.md",
"gsd-core/workflows/quick/steps/discussion-phase.md",
"gsd-core/workflows/quick/steps/plan-checker-loop.md",
"gsd-core/workflows/quick/steps/quick-verification.md",
"gsd-core/workflows/quick/steps/research-phase.md",
"gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md",
"gsd-core/workflows/reapply-patches.md",
"gsd-core/workflows/remove-phase.md",
"gsd-core/workflows/remove-workspace.md",
"gsd-core/workflows/resume-project.md",
"gsd-core/workflows/review.md",
"gsd-core/workflows/review/steps/reviewer-instances-note-1.md",
"gsd-core/workflows/review/steps/reviewer-instances-note-2.md",
"gsd-core/workflows/scan.md",
"gsd-core/workflows/section-manifest.json",
"gsd-core/workflows/secure-phase.md",
@@ -330,14 +355,18 @@
"gsd-core/workflows/sync-skills.md",
"gsd-core/workflows/thread.md",
"gsd-core/workflows/transition.md",
"gsd-core/workflows/transition/steps/workstream-collision-check.md",
"gsd-core/workflows/ui-phase.md",
"gsd-core/workflows/ui-review.md",
"gsd-core/workflows/ultraplan-phase.md",
"gsd-core/workflows/undo.md",
"gsd-core/workflows/update.md",
"gsd-core/workflows/update/steps/channel-banner.md",
"gsd-core/workflows/validate-phase.md",
"gsd-core/workflows/verify-phase.md",
"gsd-core/workflows/verify-work.md",
"gsd-core/workflows/verify-work/steps/automated-ui-verification.md",
"gsd-core/workflows/verify-work/steps/mvp-uat-framing.md",
"hooks/gsd-check-update-worker.js",
"hooks/gsd-check-update.js",
"hooks/gsd-config-reload.js",

View File

@@ -127,6 +127,7 @@
"gsd-core/references/api-coverage.md",
"gsd-core/references/artifact-types.md",
"gsd-core/references/autonomous-smart-discuss.md",
"gsd-core/references/autonomous-ui-design-contract.md",
"gsd-core/references/checkpoints.md",
"gsd-core/references/common-bug-patterns.md",
"gsd-core/references/context-budget.md",
@@ -292,15 +293,24 @@
"gsd-core/workflows/audit-milestone.md",
"gsd-core/workflows/audit-uat.md",
"gsd-core/workflows/autonomous.md",
"gsd-core/workflows/autonomous/steps/converge-banner.md",
"gsd-core/workflows/autonomous/steps/converge-dispatch-bg.md",
"gsd-core/workflows/autonomous/steps/converge-dispatch-inline.md",
"gsd-core/workflows/autonomous/steps/converge-fail-fast.md",
"gsd-core/workflows/autonomous/steps/converge-loop.md",
"gsd-core/workflows/check-todos.md",
"gsd-core/workflows/cleanup.md",
"gsd-core/workflows/code-review-fix.md",
"gsd-core/workflows/code-review.md",
"gsd-core/workflows/code-review/steps/dispatch-fix.md",
"gsd-core/workflows/code-review/steps/structural-pre-pass.md",
"gsd-core/workflows/complete-milestone.md",
"gsd-core/workflows/complete-milestone/steps/git-tag.md",
"gsd-core/workflows/debug.md",
"gsd-core/workflows/diagnose-issues.md",
"gsd-core/workflows/discovery-phase.md",
"gsd-core/workflows/discuss-phase-assumptions.md",
"gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md",
"gsd-core/workflows/discuss-phase-power.md",
"gsd-core/workflows/discuss-phase.md",
"gsd-core/workflows/discuss-phase/modes/advisor.md",
@@ -317,6 +327,7 @@
"gsd-core/workflows/discuss-phase/templates/discussion-log.md",
"gsd-core/workflows/do.md",
"gsd-core/workflows/docs-update.md",
"gsd-core/workflows/docs-update/steps/dispatch-monorepo-packages.md",
"gsd-core/workflows/edit-phase.md",
"gsd-core/workflows/eval-review.md",
"gsd-core/workflows/execute-phase.md",
@@ -353,7 +364,12 @@
"gsd-core/workflows/milestone-summary.md",
"gsd-core/workflows/mvp-phase.md",
"gsd-core/workflows/new-milestone.md",
"gsd-core/workflows/new-milestone/steps/project-md-milestone-write.md",
"gsd-core/workflows/new-milestone/steps/reset-phase-safety.md",
"gsd-core/workflows/new-project.md",
"gsd-core/workflows/new-project/steps/auto-mode-config.md",
"gsd-core/workflows/new-project/steps/auto-mode-detection.md",
"gsd-core/workflows/new-project/steps/codebase-map-offer.md",
"gsd-core/workflows/new-workspace.md",
"gsd-core/workflows/next.md",
"gsd-core/workflows/node-repair.md",
@@ -377,12 +393,21 @@
"gsd-core/workflows/pr-branch.md",
"gsd-core/workflows/profile-user.md",
"gsd-core/workflows/progress.md",
"gsd-core/workflows/progress/steps/forensic-audit.md",
"gsd-core/workflows/progress/steps/mvp-display.md",
"gsd-core/workflows/quick.md",
"gsd-core/workflows/quick/steps/discussion-phase.md",
"gsd-core/workflows/quick/steps/plan-checker-loop.md",
"gsd-core/workflows/quick/steps/quick-verification.md",
"gsd-core/workflows/quick/steps/research-phase.md",
"gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md",
"gsd-core/workflows/reapply-patches.md",
"gsd-core/workflows/remove-phase.md",
"gsd-core/workflows/remove-workspace.md",
"gsd-core/workflows/resume-project.md",
"gsd-core/workflows/review.md",
"gsd-core/workflows/review/steps/reviewer-instances-note-1.md",
"gsd-core/workflows/review/steps/reviewer-instances-note-2.md",
"gsd-core/workflows/scan.md",
"gsd-core/workflows/section-manifest.json",
"gsd-core/workflows/secure-phase.md",
@@ -401,14 +426,18 @@
"gsd-core/workflows/sync-skills.md",
"gsd-core/workflows/thread.md",
"gsd-core/workflows/transition.md",
"gsd-core/workflows/transition/steps/workstream-collision-check.md",
"gsd-core/workflows/ui-phase.md",
"gsd-core/workflows/ui-review.md",
"gsd-core/workflows/ultraplan-phase.md",
"gsd-core/workflows/undo.md",
"gsd-core/workflows/update.md",
"gsd-core/workflows/update/steps/channel-banner.md",
"gsd-core/workflows/validate-phase.md",
"gsd-core/workflows/verify-phase.md",
"gsd-core/workflows/verify-work.md",
"gsd-core/workflows/verify-work/steps/automated-ui-verification.md",
"gsd-core/workflows/verify-work/steps/mvp-uat-framing.md",
"hooks/gsd-check-update-worker.js",
"hooks/gsd-check-update.js",
"hooks/gsd-config-reload.js",

View File

@@ -126,6 +126,7 @@
"gsd-core/references/api-coverage.md",
"gsd-core/references/artifact-types.md",
"gsd-core/references/autonomous-smart-discuss.md",
"gsd-core/references/autonomous-ui-design-contract.md",
"gsd-core/references/checkpoints.md",
"gsd-core/references/common-bug-patterns.md",
"gsd-core/references/context-budget.md",
@@ -291,15 +292,24 @@
"gsd-core/workflows/audit-milestone.md",
"gsd-core/workflows/audit-uat.md",
"gsd-core/workflows/autonomous.md",
"gsd-core/workflows/autonomous/steps/converge-banner.md",
"gsd-core/workflows/autonomous/steps/converge-dispatch-bg.md",
"gsd-core/workflows/autonomous/steps/converge-dispatch-inline.md",
"gsd-core/workflows/autonomous/steps/converge-fail-fast.md",
"gsd-core/workflows/autonomous/steps/converge-loop.md",
"gsd-core/workflows/check-todos.md",
"gsd-core/workflows/cleanup.md",
"gsd-core/workflows/code-review-fix.md",
"gsd-core/workflows/code-review.md",
"gsd-core/workflows/code-review/steps/dispatch-fix.md",
"gsd-core/workflows/code-review/steps/structural-pre-pass.md",
"gsd-core/workflows/complete-milestone.md",
"gsd-core/workflows/complete-milestone/steps/git-tag.md",
"gsd-core/workflows/debug.md",
"gsd-core/workflows/diagnose-issues.md",
"gsd-core/workflows/discovery-phase.md",
"gsd-core/workflows/discuss-phase-assumptions.md",
"gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md",
"gsd-core/workflows/discuss-phase-power.md",
"gsd-core/workflows/discuss-phase.md",
"gsd-core/workflows/discuss-phase/modes/advisor.md",
@@ -316,6 +326,7 @@
"gsd-core/workflows/discuss-phase/templates/discussion-log.md",
"gsd-core/workflows/do.md",
"gsd-core/workflows/docs-update.md",
"gsd-core/workflows/docs-update/steps/dispatch-monorepo-packages.md",
"gsd-core/workflows/edit-phase.md",
"gsd-core/workflows/eval-review.md",
"gsd-core/workflows/execute-phase.md",
@@ -352,7 +363,12 @@
"gsd-core/workflows/milestone-summary.md",
"gsd-core/workflows/mvp-phase.md",
"gsd-core/workflows/new-milestone.md",
"gsd-core/workflows/new-milestone/steps/project-md-milestone-write.md",
"gsd-core/workflows/new-milestone/steps/reset-phase-safety.md",
"gsd-core/workflows/new-project.md",
"gsd-core/workflows/new-project/steps/auto-mode-config.md",
"gsd-core/workflows/new-project/steps/auto-mode-detection.md",
"gsd-core/workflows/new-project/steps/codebase-map-offer.md",
"gsd-core/workflows/new-workspace.md",
"gsd-core/workflows/next.md",
"gsd-core/workflows/node-repair.md",
@@ -376,12 +392,21 @@
"gsd-core/workflows/pr-branch.md",
"gsd-core/workflows/profile-user.md",
"gsd-core/workflows/progress.md",
"gsd-core/workflows/progress/steps/forensic-audit.md",
"gsd-core/workflows/progress/steps/mvp-display.md",
"gsd-core/workflows/quick.md",
"gsd-core/workflows/quick/steps/discussion-phase.md",
"gsd-core/workflows/quick/steps/plan-checker-loop.md",
"gsd-core/workflows/quick/steps/quick-verification.md",
"gsd-core/workflows/quick/steps/research-phase.md",
"gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md",
"gsd-core/workflows/reapply-patches.md",
"gsd-core/workflows/remove-phase.md",
"gsd-core/workflows/remove-workspace.md",
"gsd-core/workflows/resume-project.md",
"gsd-core/workflows/review.md",
"gsd-core/workflows/review/steps/reviewer-instances-note-1.md",
"gsd-core/workflows/review/steps/reviewer-instances-note-2.md",
"gsd-core/workflows/scan.md",
"gsd-core/workflows/section-manifest.json",
"gsd-core/workflows/secure-phase.md",
@@ -400,14 +425,18 @@
"gsd-core/workflows/sync-skills.md",
"gsd-core/workflows/thread.md",
"gsd-core/workflows/transition.md",
"gsd-core/workflows/transition/steps/workstream-collision-check.md",
"gsd-core/workflows/ui-phase.md",
"gsd-core/workflows/ui-review.md",
"gsd-core/workflows/ultraplan-phase.md",
"gsd-core/workflows/undo.md",
"gsd-core/workflows/update.md",
"gsd-core/workflows/update/steps/channel-banner.md",
"gsd-core/workflows/validate-phase.md",
"gsd-core/workflows/verify-phase.md",
"gsd-core/workflows/verify-work.md",
"gsd-core/workflows/verify-work/steps/automated-ui-verification.md",
"gsd-core/workflows/verify-work/steps/mvp-uat-framing.md",
"hooks/gsd-check-update-worker.js",
"hooks/gsd-check-update.js",
"hooks/gsd-config-reload.js",

View File

@@ -55,6 +55,7 @@
"gsd-core/references/api-coverage.md",
"gsd-core/references/artifact-types.md",
"gsd-core/references/autonomous-smart-discuss.md",
"gsd-core/references/autonomous-ui-design-contract.md",
"gsd-core/references/checkpoints.md",
"gsd-core/references/common-bug-patterns.md",
"gsd-core/references/context-budget.md",
@@ -220,15 +221,24 @@
"gsd-core/workflows/audit-milestone.md",
"gsd-core/workflows/audit-uat.md",
"gsd-core/workflows/autonomous.md",
"gsd-core/workflows/autonomous/steps/converge-banner.md",
"gsd-core/workflows/autonomous/steps/converge-dispatch-bg.md",
"gsd-core/workflows/autonomous/steps/converge-dispatch-inline.md",
"gsd-core/workflows/autonomous/steps/converge-fail-fast.md",
"gsd-core/workflows/autonomous/steps/converge-loop.md",
"gsd-core/workflows/check-todos.md",
"gsd-core/workflows/cleanup.md",
"gsd-core/workflows/code-review-fix.md",
"gsd-core/workflows/code-review.md",
"gsd-core/workflows/code-review/steps/dispatch-fix.md",
"gsd-core/workflows/code-review/steps/structural-pre-pass.md",
"gsd-core/workflows/complete-milestone.md",
"gsd-core/workflows/complete-milestone/steps/git-tag.md",
"gsd-core/workflows/debug.md",
"gsd-core/workflows/diagnose-issues.md",
"gsd-core/workflows/discovery-phase.md",
"gsd-core/workflows/discuss-phase-assumptions.md",
"gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md",
"gsd-core/workflows/discuss-phase-power.md",
"gsd-core/workflows/discuss-phase.md",
"gsd-core/workflows/discuss-phase/modes/advisor.md",
@@ -245,6 +255,7 @@
"gsd-core/workflows/discuss-phase/templates/discussion-log.md",
"gsd-core/workflows/do.md",
"gsd-core/workflows/docs-update.md",
"gsd-core/workflows/docs-update/steps/dispatch-monorepo-packages.md",
"gsd-core/workflows/edit-phase.md",
"gsd-core/workflows/eval-review.md",
"gsd-core/workflows/execute-phase.md",
@@ -281,7 +292,12 @@
"gsd-core/workflows/milestone-summary.md",
"gsd-core/workflows/mvp-phase.md",
"gsd-core/workflows/new-milestone.md",
"gsd-core/workflows/new-milestone/steps/project-md-milestone-write.md",
"gsd-core/workflows/new-milestone/steps/reset-phase-safety.md",
"gsd-core/workflows/new-project.md",
"gsd-core/workflows/new-project/steps/auto-mode-config.md",
"gsd-core/workflows/new-project/steps/auto-mode-detection.md",
"gsd-core/workflows/new-project/steps/codebase-map-offer.md",
"gsd-core/workflows/new-workspace.md",
"gsd-core/workflows/next.md",
"gsd-core/workflows/node-repair.md",
@@ -305,12 +321,21 @@
"gsd-core/workflows/pr-branch.md",
"gsd-core/workflows/profile-user.md",
"gsd-core/workflows/progress.md",
"gsd-core/workflows/progress/steps/forensic-audit.md",
"gsd-core/workflows/progress/steps/mvp-display.md",
"gsd-core/workflows/quick.md",
"gsd-core/workflows/quick/steps/discussion-phase.md",
"gsd-core/workflows/quick/steps/plan-checker-loop.md",
"gsd-core/workflows/quick/steps/quick-verification.md",
"gsd-core/workflows/quick/steps/research-phase.md",
"gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md",
"gsd-core/workflows/reapply-patches.md",
"gsd-core/workflows/remove-phase.md",
"gsd-core/workflows/remove-workspace.md",
"gsd-core/workflows/resume-project.md",
"gsd-core/workflows/review.md",
"gsd-core/workflows/review/steps/reviewer-instances-note-1.md",
"gsd-core/workflows/review/steps/reviewer-instances-note-2.md",
"gsd-core/workflows/scan.md",
"gsd-core/workflows/section-manifest.json",
"gsd-core/workflows/secure-phase.md",
@@ -329,14 +354,18 @@
"gsd-core/workflows/sync-skills.md",
"gsd-core/workflows/thread.md",
"gsd-core/workflows/transition.md",
"gsd-core/workflows/transition/steps/workstream-collision-check.md",
"gsd-core/workflows/ui-phase.md",
"gsd-core/workflows/ui-review.md",
"gsd-core/workflows/ultraplan-phase.md",
"gsd-core/workflows/undo.md",
"gsd-core/workflows/update.md",
"gsd-core/workflows/update/steps/channel-banner.md",
"gsd-core/workflows/validate-phase.md",
"gsd-core/workflows/verify-phase.md",
"gsd-core/workflows/verify-work.md",
"gsd-core/workflows/verify-work/steps/automated-ui-verification.md",
"gsd-core/workflows/verify-work/steps/mvp-uat-framing.md",
"hooks/gsd-check-update-worker.js",
"hooks/gsd-check-update.js",
"hooks/gsd-config-reload.js",

View File

@@ -59,6 +59,7 @@
"gsd-core/references/api-coverage.md",
"gsd-core/references/artifact-types.md",
"gsd-core/references/autonomous-smart-discuss.md",
"gsd-core/references/autonomous-ui-design-contract.md",
"gsd-core/references/checkpoints.md",
"gsd-core/references/common-bug-patterns.md",
"gsd-core/references/context-budget.md",
@@ -224,15 +225,24 @@
"gsd-core/workflows/audit-milestone.md",
"gsd-core/workflows/audit-uat.md",
"gsd-core/workflows/autonomous.md",
"gsd-core/workflows/autonomous/steps/converge-banner.md",
"gsd-core/workflows/autonomous/steps/converge-dispatch-bg.md",
"gsd-core/workflows/autonomous/steps/converge-dispatch-inline.md",
"gsd-core/workflows/autonomous/steps/converge-fail-fast.md",
"gsd-core/workflows/autonomous/steps/converge-loop.md",
"gsd-core/workflows/check-todos.md",
"gsd-core/workflows/cleanup.md",
"gsd-core/workflows/code-review-fix.md",
"gsd-core/workflows/code-review.md",
"gsd-core/workflows/code-review/steps/dispatch-fix.md",
"gsd-core/workflows/code-review/steps/structural-pre-pass.md",
"gsd-core/workflows/complete-milestone.md",
"gsd-core/workflows/complete-milestone/steps/git-tag.md",
"gsd-core/workflows/debug.md",
"gsd-core/workflows/diagnose-issues.md",
"gsd-core/workflows/discovery-phase.md",
"gsd-core/workflows/discuss-phase-assumptions.md",
"gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md",
"gsd-core/workflows/discuss-phase-power.md",
"gsd-core/workflows/discuss-phase.md",
"gsd-core/workflows/discuss-phase/modes/advisor.md",
@@ -249,6 +259,7 @@
"gsd-core/workflows/discuss-phase/templates/discussion-log.md",
"gsd-core/workflows/do.md",
"gsd-core/workflows/docs-update.md",
"gsd-core/workflows/docs-update/steps/dispatch-monorepo-packages.md",
"gsd-core/workflows/edit-phase.md",
"gsd-core/workflows/eval-review.md",
"gsd-core/workflows/execute-phase.md",
@@ -285,7 +296,12 @@
"gsd-core/workflows/milestone-summary.md",
"gsd-core/workflows/mvp-phase.md",
"gsd-core/workflows/new-milestone.md",
"gsd-core/workflows/new-milestone/steps/project-md-milestone-write.md",
"gsd-core/workflows/new-milestone/steps/reset-phase-safety.md",
"gsd-core/workflows/new-project.md",
"gsd-core/workflows/new-project/steps/auto-mode-config.md",
"gsd-core/workflows/new-project/steps/auto-mode-detection.md",
"gsd-core/workflows/new-project/steps/codebase-map-offer.md",
"gsd-core/workflows/new-workspace.md",
"gsd-core/workflows/next.md",
"gsd-core/workflows/node-repair.md",
@@ -309,12 +325,21 @@
"gsd-core/workflows/pr-branch.md",
"gsd-core/workflows/profile-user.md",
"gsd-core/workflows/progress.md",
"gsd-core/workflows/progress/steps/forensic-audit.md",
"gsd-core/workflows/progress/steps/mvp-display.md",
"gsd-core/workflows/quick.md",
"gsd-core/workflows/quick/steps/discussion-phase.md",
"gsd-core/workflows/quick/steps/plan-checker-loop.md",
"gsd-core/workflows/quick/steps/quick-verification.md",
"gsd-core/workflows/quick/steps/research-phase.md",
"gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md",
"gsd-core/workflows/reapply-patches.md",
"gsd-core/workflows/remove-phase.md",
"gsd-core/workflows/remove-workspace.md",
"gsd-core/workflows/resume-project.md",
"gsd-core/workflows/review.md",
"gsd-core/workflows/review/steps/reviewer-instances-note-1.md",
"gsd-core/workflows/review/steps/reviewer-instances-note-2.md",
"gsd-core/workflows/scan.md",
"gsd-core/workflows/section-manifest.json",
"gsd-core/workflows/secure-phase.md",
@@ -333,14 +358,18 @@
"gsd-core/workflows/sync-skills.md",
"gsd-core/workflows/thread.md",
"gsd-core/workflows/transition.md",
"gsd-core/workflows/transition/steps/workstream-collision-check.md",
"gsd-core/workflows/ui-phase.md",
"gsd-core/workflows/ui-review.md",
"gsd-core/workflows/ultraplan-phase.md",
"gsd-core/workflows/undo.md",
"gsd-core/workflows/update.md",
"gsd-core/workflows/update/steps/channel-banner.md",
"gsd-core/workflows/validate-phase.md",
"gsd-core/workflows/verify-phase.md",
"gsd-core/workflows/verify-work.md",
"gsd-core/workflows/verify-work/steps/automated-ui-verification.md",
"gsd-core/workflows/verify-work/steps/mvp-uat-framing.md",
"scripts/changeset/README.md",
"scripts/changeset/cli.cjs",
"scripts/changeset/github-release-notes.cjs",

View File

@@ -127,6 +127,7 @@
"gsd-core/references/api-coverage.md",
"gsd-core/references/artifact-types.md",
"gsd-core/references/autonomous-smart-discuss.md",
"gsd-core/references/autonomous-ui-design-contract.md",
"gsd-core/references/checkpoints.md",
"gsd-core/references/common-bug-patterns.md",
"gsd-core/references/context-budget.md",
@@ -292,15 +293,24 @@
"gsd-core/workflows/audit-milestone.md",
"gsd-core/workflows/audit-uat.md",
"gsd-core/workflows/autonomous.md",
"gsd-core/workflows/autonomous/steps/converge-banner.md",
"gsd-core/workflows/autonomous/steps/converge-dispatch-bg.md",
"gsd-core/workflows/autonomous/steps/converge-dispatch-inline.md",
"gsd-core/workflows/autonomous/steps/converge-fail-fast.md",
"gsd-core/workflows/autonomous/steps/converge-loop.md",
"gsd-core/workflows/check-todos.md",
"gsd-core/workflows/cleanup.md",
"gsd-core/workflows/code-review-fix.md",
"gsd-core/workflows/code-review.md",
"gsd-core/workflows/code-review/steps/dispatch-fix.md",
"gsd-core/workflows/code-review/steps/structural-pre-pass.md",
"gsd-core/workflows/complete-milestone.md",
"gsd-core/workflows/complete-milestone/steps/git-tag.md",
"gsd-core/workflows/debug.md",
"gsd-core/workflows/diagnose-issues.md",
"gsd-core/workflows/discovery-phase.md",
"gsd-core/workflows/discuss-phase-assumptions.md",
"gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md",
"gsd-core/workflows/discuss-phase-power.md",
"gsd-core/workflows/discuss-phase.md",
"gsd-core/workflows/discuss-phase/modes/advisor.md",
@@ -317,6 +327,7 @@
"gsd-core/workflows/discuss-phase/templates/discussion-log.md",
"gsd-core/workflows/do.md",
"gsd-core/workflows/docs-update.md",
"gsd-core/workflows/docs-update/steps/dispatch-monorepo-packages.md",
"gsd-core/workflows/edit-phase.md",
"gsd-core/workflows/eval-review.md",
"gsd-core/workflows/execute-phase.md",
@@ -353,7 +364,12 @@
"gsd-core/workflows/milestone-summary.md",
"gsd-core/workflows/mvp-phase.md",
"gsd-core/workflows/new-milestone.md",
"gsd-core/workflows/new-milestone/steps/project-md-milestone-write.md",
"gsd-core/workflows/new-milestone/steps/reset-phase-safety.md",
"gsd-core/workflows/new-project.md",
"gsd-core/workflows/new-project/steps/auto-mode-config.md",
"gsd-core/workflows/new-project/steps/auto-mode-detection.md",
"gsd-core/workflows/new-project/steps/codebase-map-offer.md",
"gsd-core/workflows/new-workspace.md",
"gsd-core/workflows/next.md",
"gsd-core/workflows/node-repair.md",
@@ -377,12 +393,21 @@
"gsd-core/workflows/pr-branch.md",
"gsd-core/workflows/profile-user.md",
"gsd-core/workflows/progress.md",
"gsd-core/workflows/progress/steps/forensic-audit.md",
"gsd-core/workflows/progress/steps/mvp-display.md",
"gsd-core/workflows/quick.md",
"gsd-core/workflows/quick/steps/discussion-phase.md",
"gsd-core/workflows/quick/steps/plan-checker-loop.md",
"gsd-core/workflows/quick/steps/quick-verification.md",
"gsd-core/workflows/quick/steps/research-phase.md",
"gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md",
"gsd-core/workflows/reapply-patches.md",
"gsd-core/workflows/remove-phase.md",
"gsd-core/workflows/remove-workspace.md",
"gsd-core/workflows/resume-project.md",
"gsd-core/workflows/review.md",
"gsd-core/workflows/review/steps/reviewer-instances-note-1.md",
"gsd-core/workflows/review/steps/reviewer-instances-note-2.md",
"gsd-core/workflows/scan.md",
"gsd-core/workflows/section-manifest.json",
"gsd-core/workflows/secure-phase.md",
@@ -401,14 +426,18 @@
"gsd-core/workflows/sync-skills.md",
"gsd-core/workflows/thread.md",
"gsd-core/workflows/transition.md",
"gsd-core/workflows/transition/steps/workstream-collision-check.md",
"gsd-core/workflows/ui-phase.md",
"gsd-core/workflows/ui-review.md",
"gsd-core/workflows/ultraplan-phase.md",
"gsd-core/workflows/undo.md",
"gsd-core/workflows/update.md",
"gsd-core/workflows/update/steps/channel-banner.md",
"gsd-core/workflows/validate-phase.md",
"gsd-core/workflows/verify-phase.md",
"gsd-core/workflows/verify-work.md",
"gsd-core/workflows/verify-work/steps/automated-ui-verification.md",
"gsd-core/workflows/verify-work/steps/mvp-uat-framing.md",
"hooks/gsd-check-update-worker.js",
"hooks/gsd-check-update.js",
"hooks/gsd-config-reload.js",

View File

@@ -162,6 +162,7 @@
"gsd-core/references/api-coverage.md",
"gsd-core/references/artifact-types.md",
"gsd-core/references/autonomous-smart-discuss.md",
"gsd-core/references/autonomous-ui-design-contract.md",
"gsd-core/references/checkpoints.md",
"gsd-core/references/common-bug-patterns.md",
"gsd-core/references/context-budget.md",
@@ -327,15 +328,24 @@
"gsd-core/workflows/audit-milestone.md",
"gsd-core/workflows/audit-uat.md",
"gsd-core/workflows/autonomous.md",
"gsd-core/workflows/autonomous/steps/converge-banner.md",
"gsd-core/workflows/autonomous/steps/converge-dispatch-bg.md",
"gsd-core/workflows/autonomous/steps/converge-dispatch-inline.md",
"gsd-core/workflows/autonomous/steps/converge-fail-fast.md",
"gsd-core/workflows/autonomous/steps/converge-loop.md",
"gsd-core/workflows/check-todos.md",
"gsd-core/workflows/cleanup.md",
"gsd-core/workflows/code-review-fix.md",
"gsd-core/workflows/code-review.md",
"gsd-core/workflows/code-review/steps/dispatch-fix.md",
"gsd-core/workflows/code-review/steps/structural-pre-pass.md",
"gsd-core/workflows/complete-milestone.md",
"gsd-core/workflows/complete-milestone/steps/git-tag.md",
"gsd-core/workflows/debug.md",
"gsd-core/workflows/diagnose-issues.md",
"gsd-core/workflows/discovery-phase.md",
"gsd-core/workflows/discuss-phase-assumptions.md",
"gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md",
"gsd-core/workflows/discuss-phase-power.md",
"gsd-core/workflows/discuss-phase.md",
"gsd-core/workflows/discuss-phase/modes/advisor.md",
@@ -352,6 +362,7 @@
"gsd-core/workflows/discuss-phase/templates/discussion-log.md",
"gsd-core/workflows/do.md",
"gsd-core/workflows/docs-update.md",
"gsd-core/workflows/docs-update/steps/dispatch-monorepo-packages.md",
"gsd-core/workflows/edit-phase.md",
"gsd-core/workflows/eval-review.md",
"gsd-core/workflows/execute-phase.md",
@@ -388,7 +399,12 @@
"gsd-core/workflows/milestone-summary.md",
"gsd-core/workflows/mvp-phase.md",
"gsd-core/workflows/new-milestone.md",
"gsd-core/workflows/new-milestone/steps/project-md-milestone-write.md",
"gsd-core/workflows/new-milestone/steps/reset-phase-safety.md",
"gsd-core/workflows/new-project.md",
"gsd-core/workflows/new-project/steps/auto-mode-config.md",
"gsd-core/workflows/new-project/steps/auto-mode-detection.md",
"gsd-core/workflows/new-project/steps/codebase-map-offer.md",
"gsd-core/workflows/new-workspace.md",
"gsd-core/workflows/next.md",
"gsd-core/workflows/node-repair.md",
@@ -412,12 +428,21 @@
"gsd-core/workflows/pr-branch.md",
"gsd-core/workflows/profile-user.md",
"gsd-core/workflows/progress.md",
"gsd-core/workflows/progress/steps/forensic-audit.md",
"gsd-core/workflows/progress/steps/mvp-display.md",
"gsd-core/workflows/quick.md",
"gsd-core/workflows/quick/steps/discussion-phase.md",
"gsd-core/workflows/quick/steps/plan-checker-loop.md",
"gsd-core/workflows/quick/steps/quick-verification.md",
"gsd-core/workflows/quick/steps/research-phase.md",
"gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md",
"gsd-core/workflows/reapply-patches.md",
"gsd-core/workflows/remove-phase.md",
"gsd-core/workflows/remove-workspace.md",
"gsd-core/workflows/resume-project.md",
"gsd-core/workflows/review.md",
"gsd-core/workflows/review/steps/reviewer-instances-note-1.md",
"gsd-core/workflows/review/steps/reviewer-instances-note-2.md",
"gsd-core/workflows/scan.md",
"gsd-core/workflows/section-manifest.json",
"gsd-core/workflows/secure-phase.md",
@@ -436,14 +461,18 @@
"gsd-core/workflows/sync-skills.md",
"gsd-core/workflows/thread.md",
"gsd-core/workflows/transition.md",
"gsd-core/workflows/transition/steps/workstream-collision-check.md",
"gsd-core/workflows/ui-phase.md",
"gsd-core/workflows/ui-review.md",
"gsd-core/workflows/ultraplan-phase.md",
"gsd-core/workflows/undo.md",
"gsd-core/workflows/update.md",
"gsd-core/workflows/update/steps/channel-banner.md",
"gsd-core/workflows/validate-phase.md",
"gsd-core/workflows/verify-phase.md",
"gsd-core/workflows/verify-work.md",
"gsd-core/workflows/verify-work/steps/automated-ui-verification.md",
"gsd-core/workflows/verify-work/steps/mvp-uat-framing.md",
"hooks/gsd-check-update-worker.js",
"hooks/gsd-check-update.js",
"hooks/gsd-context-monitor.js",

View File

@@ -57,6 +57,7 @@
"gsd-core/references/api-coverage.md",
"gsd-core/references/artifact-types.md",
"gsd-core/references/autonomous-smart-discuss.md",
"gsd-core/references/autonomous-ui-design-contract.md",
"gsd-core/references/checkpoints.md",
"gsd-core/references/common-bug-patterns.md",
"gsd-core/references/context-budget.md",
@@ -222,15 +223,24 @@
"gsd-core/workflows/audit-milestone.md",
"gsd-core/workflows/audit-uat.md",
"gsd-core/workflows/autonomous.md",
"gsd-core/workflows/autonomous/steps/converge-banner.md",
"gsd-core/workflows/autonomous/steps/converge-dispatch-bg.md",
"gsd-core/workflows/autonomous/steps/converge-dispatch-inline.md",
"gsd-core/workflows/autonomous/steps/converge-fail-fast.md",
"gsd-core/workflows/autonomous/steps/converge-loop.md",
"gsd-core/workflows/check-todos.md",
"gsd-core/workflows/cleanup.md",
"gsd-core/workflows/code-review-fix.md",
"gsd-core/workflows/code-review.md",
"gsd-core/workflows/code-review/steps/dispatch-fix.md",
"gsd-core/workflows/code-review/steps/structural-pre-pass.md",
"gsd-core/workflows/complete-milestone.md",
"gsd-core/workflows/complete-milestone/steps/git-tag.md",
"gsd-core/workflows/debug.md",
"gsd-core/workflows/diagnose-issues.md",
"gsd-core/workflows/discovery-phase.md",
"gsd-core/workflows/discuss-phase-assumptions.md",
"gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md",
"gsd-core/workflows/discuss-phase-power.md",
"gsd-core/workflows/discuss-phase.md",
"gsd-core/workflows/discuss-phase/modes/advisor.md",
@@ -247,6 +257,7 @@
"gsd-core/workflows/discuss-phase/templates/discussion-log.md",
"gsd-core/workflows/do.md",
"gsd-core/workflows/docs-update.md",
"gsd-core/workflows/docs-update/steps/dispatch-monorepo-packages.md",
"gsd-core/workflows/edit-phase.md",
"gsd-core/workflows/eval-review.md",
"gsd-core/workflows/execute-phase.md",
@@ -283,7 +294,12 @@
"gsd-core/workflows/milestone-summary.md",
"gsd-core/workflows/mvp-phase.md",
"gsd-core/workflows/new-milestone.md",
"gsd-core/workflows/new-milestone/steps/project-md-milestone-write.md",
"gsd-core/workflows/new-milestone/steps/reset-phase-safety.md",
"gsd-core/workflows/new-project.md",
"gsd-core/workflows/new-project/steps/auto-mode-config.md",
"gsd-core/workflows/new-project/steps/auto-mode-detection.md",
"gsd-core/workflows/new-project/steps/codebase-map-offer.md",
"gsd-core/workflows/new-workspace.md",
"gsd-core/workflows/next.md",
"gsd-core/workflows/node-repair.md",
@@ -307,12 +323,21 @@
"gsd-core/workflows/pr-branch.md",
"gsd-core/workflows/profile-user.md",
"gsd-core/workflows/progress.md",
"gsd-core/workflows/progress/steps/forensic-audit.md",
"gsd-core/workflows/progress/steps/mvp-display.md",
"gsd-core/workflows/quick.md",
"gsd-core/workflows/quick/steps/discussion-phase.md",
"gsd-core/workflows/quick/steps/plan-checker-loop.md",
"gsd-core/workflows/quick/steps/quick-verification.md",
"gsd-core/workflows/quick/steps/research-phase.md",
"gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md",
"gsd-core/workflows/reapply-patches.md",
"gsd-core/workflows/remove-phase.md",
"gsd-core/workflows/remove-workspace.md",
"gsd-core/workflows/resume-project.md",
"gsd-core/workflows/review.md",
"gsd-core/workflows/review/steps/reviewer-instances-note-1.md",
"gsd-core/workflows/review/steps/reviewer-instances-note-2.md",
"gsd-core/workflows/scan.md",
"gsd-core/workflows/section-manifest.json",
"gsd-core/workflows/secure-phase.md",
@@ -331,14 +356,18 @@
"gsd-core/workflows/sync-skills.md",
"gsd-core/workflows/thread.md",
"gsd-core/workflows/transition.md",
"gsd-core/workflows/transition/steps/workstream-collision-check.md",
"gsd-core/workflows/ui-phase.md",
"gsd-core/workflows/ui-review.md",
"gsd-core/workflows/ultraplan-phase.md",
"gsd-core/workflows/undo.md",
"gsd-core/workflows/update.md",
"gsd-core/workflows/update/steps/channel-banner.md",
"gsd-core/workflows/validate-phase.md",
"gsd-core/workflows/verify-phase.md",
"gsd-core/workflows/verify-work.md",
"gsd-core/workflows/verify-work/steps/automated-ui-verification.md",
"gsd-core/workflows/verify-work/steps/mvp-uat-framing.md",
"hooks/gsd-session.json",
"scripts/changeset/README.md",
"scripts/changeset/cli.cjs",

View File

@@ -56,6 +56,7 @@
"gsd-core/references/api-coverage.md",
"gsd-core/references/artifact-types.md",
"gsd-core/references/autonomous-smart-discuss.md",
"gsd-core/references/autonomous-ui-design-contract.md",
"gsd-core/references/checkpoints.md",
"gsd-core/references/common-bug-patterns.md",
"gsd-core/references/context-budget.md",
@@ -221,15 +222,24 @@
"gsd-core/workflows/audit-milestone.md",
"gsd-core/workflows/audit-uat.md",
"gsd-core/workflows/autonomous.md",
"gsd-core/workflows/autonomous/steps/converge-banner.md",
"gsd-core/workflows/autonomous/steps/converge-dispatch-bg.md",
"gsd-core/workflows/autonomous/steps/converge-dispatch-inline.md",
"gsd-core/workflows/autonomous/steps/converge-fail-fast.md",
"gsd-core/workflows/autonomous/steps/converge-loop.md",
"gsd-core/workflows/check-todos.md",
"gsd-core/workflows/cleanup.md",
"gsd-core/workflows/code-review-fix.md",
"gsd-core/workflows/code-review.md",
"gsd-core/workflows/code-review/steps/dispatch-fix.md",
"gsd-core/workflows/code-review/steps/structural-pre-pass.md",
"gsd-core/workflows/complete-milestone.md",
"gsd-core/workflows/complete-milestone/steps/git-tag.md",
"gsd-core/workflows/debug.md",
"gsd-core/workflows/diagnose-issues.md",
"gsd-core/workflows/discovery-phase.md",
"gsd-core/workflows/discuss-phase-assumptions.md",
"gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md",
"gsd-core/workflows/discuss-phase-power.md",
"gsd-core/workflows/discuss-phase.md",
"gsd-core/workflows/discuss-phase/modes/advisor.md",
@@ -246,6 +256,7 @@
"gsd-core/workflows/discuss-phase/templates/discussion-log.md",
"gsd-core/workflows/do.md",
"gsd-core/workflows/docs-update.md",
"gsd-core/workflows/docs-update/steps/dispatch-monorepo-packages.md",
"gsd-core/workflows/edit-phase.md",
"gsd-core/workflows/eval-review.md",
"gsd-core/workflows/execute-phase.md",
@@ -282,7 +293,12 @@
"gsd-core/workflows/milestone-summary.md",
"gsd-core/workflows/mvp-phase.md",
"gsd-core/workflows/new-milestone.md",
"gsd-core/workflows/new-milestone/steps/project-md-milestone-write.md",
"gsd-core/workflows/new-milestone/steps/reset-phase-safety.md",
"gsd-core/workflows/new-project.md",
"gsd-core/workflows/new-project/steps/auto-mode-config.md",
"gsd-core/workflows/new-project/steps/auto-mode-detection.md",
"gsd-core/workflows/new-project/steps/codebase-map-offer.md",
"gsd-core/workflows/new-workspace.md",
"gsd-core/workflows/next.md",
"gsd-core/workflows/node-repair.md",
@@ -306,12 +322,21 @@
"gsd-core/workflows/pr-branch.md",
"gsd-core/workflows/profile-user.md",
"gsd-core/workflows/progress.md",
"gsd-core/workflows/progress/steps/forensic-audit.md",
"gsd-core/workflows/progress/steps/mvp-display.md",
"gsd-core/workflows/quick.md",
"gsd-core/workflows/quick/steps/discussion-phase.md",
"gsd-core/workflows/quick/steps/plan-checker-loop.md",
"gsd-core/workflows/quick/steps/quick-verification.md",
"gsd-core/workflows/quick/steps/research-phase.md",
"gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md",
"gsd-core/workflows/reapply-patches.md",
"gsd-core/workflows/remove-phase.md",
"gsd-core/workflows/remove-workspace.md",
"gsd-core/workflows/resume-project.md",
"gsd-core/workflows/review.md",
"gsd-core/workflows/review/steps/reviewer-instances-note-1.md",
"gsd-core/workflows/review/steps/reviewer-instances-note-2.md",
"gsd-core/workflows/scan.md",
"gsd-core/workflows/section-manifest.json",
"gsd-core/workflows/secure-phase.md",
@@ -330,14 +355,18 @@
"gsd-core/workflows/sync-skills.md",
"gsd-core/workflows/thread.md",
"gsd-core/workflows/transition.md",
"gsd-core/workflows/transition/steps/workstream-collision-check.md",
"gsd-core/workflows/ui-phase.md",
"gsd-core/workflows/ui-review.md",
"gsd-core/workflows/ultraplan-phase.md",
"gsd-core/workflows/undo.md",
"gsd-core/workflows/update.md",
"gsd-core/workflows/update/steps/channel-banner.md",
"gsd-core/workflows/validate-phase.md",
"gsd-core/workflows/verify-phase.md",
"gsd-core/workflows/verify-work.md",
"gsd-core/workflows/verify-work/steps/automated-ui-verification.md",
"gsd-core/workflows/verify-work/steps/mvp-uat-framing.md",
"hooks/gsd-cursor-post-tool.js",
"hooks/gsd-cursor-pre-tool.js",
"hooks/gsd-cursor-session-start.js",

View File

@@ -56,6 +56,7 @@
"gsd-core/references/api-coverage.md",
"gsd-core/references/artifact-types.md",
"gsd-core/references/autonomous-smart-discuss.md",
"gsd-core/references/autonomous-ui-design-contract.md",
"gsd-core/references/checkpoints.md",
"gsd-core/references/common-bug-patterns.md",
"gsd-core/references/context-budget.md",
@@ -221,15 +222,24 @@
"gsd-core/workflows/audit-milestone.md",
"gsd-core/workflows/audit-uat.md",
"gsd-core/workflows/autonomous.md",
"gsd-core/workflows/autonomous/steps/converge-banner.md",
"gsd-core/workflows/autonomous/steps/converge-dispatch-bg.md",
"gsd-core/workflows/autonomous/steps/converge-dispatch-inline.md",
"gsd-core/workflows/autonomous/steps/converge-fail-fast.md",
"gsd-core/workflows/autonomous/steps/converge-loop.md",
"gsd-core/workflows/check-todos.md",
"gsd-core/workflows/cleanup.md",
"gsd-core/workflows/code-review-fix.md",
"gsd-core/workflows/code-review.md",
"gsd-core/workflows/code-review/steps/dispatch-fix.md",
"gsd-core/workflows/code-review/steps/structural-pre-pass.md",
"gsd-core/workflows/complete-milestone.md",
"gsd-core/workflows/complete-milestone/steps/git-tag.md",
"gsd-core/workflows/debug.md",
"gsd-core/workflows/diagnose-issues.md",
"gsd-core/workflows/discovery-phase.md",
"gsd-core/workflows/discuss-phase-assumptions.md",
"gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md",
"gsd-core/workflows/discuss-phase-power.md",
"gsd-core/workflows/discuss-phase.md",
"gsd-core/workflows/discuss-phase/modes/advisor.md",
@@ -246,6 +256,7 @@
"gsd-core/workflows/discuss-phase/templates/discussion-log.md",
"gsd-core/workflows/do.md",
"gsd-core/workflows/docs-update.md",
"gsd-core/workflows/docs-update/steps/dispatch-monorepo-packages.md",
"gsd-core/workflows/edit-phase.md",
"gsd-core/workflows/eval-review.md",
"gsd-core/workflows/execute-phase.md",
@@ -282,7 +293,12 @@
"gsd-core/workflows/milestone-summary.md",
"gsd-core/workflows/mvp-phase.md",
"gsd-core/workflows/new-milestone.md",
"gsd-core/workflows/new-milestone/steps/project-md-milestone-write.md",
"gsd-core/workflows/new-milestone/steps/reset-phase-safety.md",
"gsd-core/workflows/new-project.md",
"gsd-core/workflows/new-project/steps/auto-mode-config.md",
"gsd-core/workflows/new-project/steps/auto-mode-detection.md",
"gsd-core/workflows/new-project/steps/codebase-map-offer.md",
"gsd-core/workflows/new-workspace.md",
"gsd-core/workflows/next.md",
"gsd-core/workflows/node-repair.md",
@@ -306,12 +322,21 @@
"gsd-core/workflows/pr-branch.md",
"gsd-core/workflows/profile-user.md",
"gsd-core/workflows/progress.md",
"gsd-core/workflows/progress/steps/forensic-audit.md",
"gsd-core/workflows/progress/steps/mvp-display.md",
"gsd-core/workflows/quick.md",
"gsd-core/workflows/quick/steps/discussion-phase.md",
"gsd-core/workflows/quick/steps/plan-checker-loop.md",
"gsd-core/workflows/quick/steps/quick-verification.md",
"gsd-core/workflows/quick/steps/research-phase.md",
"gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md",
"gsd-core/workflows/reapply-patches.md",
"gsd-core/workflows/remove-phase.md",
"gsd-core/workflows/remove-workspace.md",
"gsd-core/workflows/resume-project.md",
"gsd-core/workflows/review.md",
"gsd-core/workflows/review/steps/reviewer-instances-note-1.md",
"gsd-core/workflows/review/steps/reviewer-instances-note-2.md",
"gsd-core/workflows/scan.md",
"gsd-core/workflows/section-manifest.json",
"gsd-core/workflows/secure-phase.md",
@@ -330,14 +355,18 @@
"gsd-core/workflows/sync-skills.md",
"gsd-core/workflows/thread.md",
"gsd-core/workflows/transition.md",
"gsd-core/workflows/transition/steps/workstream-collision-check.md",
"gsd-core/workflows/ui-phase.md",
"gsd-core/workflows/ui-review.md",
"gsd-core/workflows/ultraplan-phase.md",
"gsd-core/workflows/undo.md",
"gsd-core/workflows/update.md",
"gsd-core/workflows/update/steps/channel-banner.md",
"gsd-core/workflows/validate-phase.md",
"gsd-core/workflows/verify-phase.md",
"gsd-core/workflows/verify-work.md",
"gsd-core/workflows/verify-work/steps/automated-ui-verification.md",
"gsd-core/workflows/verify-work/steps/mvp-uat-framing.md",
"hooks/gsd-check-update-worker.js",
"hooks/gsd-check-update.js",
"hooks/gsd-config-reload.js",

View File

@@ -127,6 +127,7 @@
"gsd-core/references/api-coverage.md",
"gsd-core/references/artifact-types.md",
"gsd-core/references/autonomous-smart-discuss.md",
"gsd-core/references/autonomous-ui-design-contract.md",
"gsd-core/references/checkpoints.md",
"gsd-core/references/common-bug-patterns.md",
"gsd-core/references/context-budget.md",
@@ -292,15 +293,24 @@
"gsd-core/workflows/audit-milestone.md",
"gsd-core/workflows/audit-uat.md",
"gsd-core/workflows/autonomous.md",
"gsd-core/workflows/autonomous/steps/converge-banner.md",
"gsd-core/workflows/autonomous/steps/converge-dispatch-bg.md",
"gsd-core/workflows/autonomous/steps/converge-dispatch-inline.md",
"gsd-core/workflows/autonomous/steps/converge-fail-fast.md",
"gsd-core/workflows/autonomous/steps/converge-loop.md",
"gsd-core/workflows/check-todos.md",
"gsd-core/workflows/cleanup.md",
"gsd-core/workflows/code-review-fix.md",
"gsd-core/workflows/code-review.md",
"gsd-core/workflows/code-review/steps/dispatch-fix.md",
"gsd-core/workflows/code-review/steps/structural-pre-pass.md",
"gsd-core/workflows/complete-milestone.md",
"gsd-core/workflows/complete-milestone/steps/git-tag.md",
"gsd-core/workflows/debug.md",
"gsd-core/workflows/diagnose-issues.md",
"gsd-core/workflows/discovery-phase.md",
"gsd-core/workflows/discuss-phase-assumptions.md",
"gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md",
"gsd-core/workflows/discuss-phase-power.md",
"gsd-core/workflows/discuss-phase.md",
"gsd-core/workflows/discuss-phase/modes/advisor.md",
@@ -317,6 +327,7 @@
"gsd-core/workflows/discuss-phase/templates/discussion-log.md",
"gsd-core/workflows/do.md",
"gsd-core/workflows/docs-update.md",
"gsd-core/workflows/docs-update/steps/dispatch-monorepo-packages.md",
"gsd-core/workflows/edit-phase.md",
"gsd-core/workflows/eval-review.md",
"gsd-core/workflows/execute-phase.md",
@@ -353,7 +364,12 @@
"gsd-core/workflows/milestone-summary.md",
"gsd-core/workflows/mvp-phase.md",
"gsd-core/workflows/new-milestone.md",
"gsd-core/workflows/new-milestone/steps/project-md-milestone-write.md",
"gsd-core/workflows/new-milestone/steps/reset-phase-safety.md",
"gsd-core/workflows/new-project.md",
"gsd-core/workflows/new-project/steps/auto-mode-config.md",
"gsd-core/workflows/new-project/steps/auto-mode-detection.md",
"gsd-core/workflows/new-project/steps/codebase-map-offer.md",
"gsd-core/workflows/new-workspace.md",
"gsd-core/workflows/next.md",
"gsd-core/workflows/node-repair.md",
@@ -377,12 +393,21 @@
"gsd-core/workflows/pr-branch.md",
"gsd-core/workflows/profile-user.md",
"gsd-core/workflows/progress.md",
"gsd-core/workflows/progress/steps/forensic-audit.md",
"gsd-core/workflows/progress/steps/mvp-display.md",
"gsd-core/workflows/quick.md",
"gsd-core/workflows/quick/steps/discussion-phase.md",
"gsd-core/workflows/quick/steps/plan-checker-loop.md",
"gsd-core/workflows/quick/steps/quick-verification.md",
"gsd-core/workflows/quick/steps/research-phase.md",
"gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md",
"gsd-core/workflows/reapply-patches.md",
"gsd-core/workflows/remove-phase.md",
"gsd-core/workflows/remove-workspace.md",
"gsd-core/workflows/resume-project.md",
"gsd-core/workflows/review.md",
"gsd-core/workflows/review/steps/reviewer-instances-note-1.md",
"gsd-core/workflows/review/steps/reviewer-instances-note-2.md",
"gsd-core/workflows/scan.md",
"gsd-core/workflows/section-manifest.json",
"gsd-core/workflows/secure-phase.md",
@@ -401,14 +426,18 @@
"gsd-core/workflows/sync-skills.md",
"gsd-core/workflows/thread.md",
"gsd-core/workflows/transition.md",
"gsd-core/workflows/transition/steps/workstream-collision-check.md",
"gsd-core/workflows/ui-phase.md",
"gsd-core/workflows/ui-review.md",
"gsd-core/workflows/ultraplan-phase.md",
"gsd-core/workflows/undo.md",
"gsd-core/workflows/update.md",
"gsd-core/workflows/update/steps/channel-banner.md",
"gsd-core/workflows/validate-phase.md",
"gsd-core/workflows/verify-phase.md",
"gsd-core/workflows/verify-work.md",
"gsd-core/workflows/verify-work/steps/automated-ui-verification.md",
"gsd-core/workflows/verify-work/steps/mvp-uat-framing.md",
"hooks/gsd-check-update-worker.js",
"hooks/gsd-check-update.js",
"hooks/gsd-config-reload.js",

View File

@@ -86,6 +86,7 @@
"gsd-core/references/api-coverage.md",
"gsd-core/references/artifact-types.md",
"gsd-core/references/autonomous-smart-discuss.md",
"gsd-core/references/autonomous-ui-design-contract.md",
"gsd-core/references/checkpoints.md",
"gsd-core/references/common-bug-patterns.md",
"gsd-core/references/context-budget.md",
@@ -251,15 +252,24 @@
"gsd-core/workflows/audit-milestone.md",
"gsd-core/workflows/audit-uat.md",
"gsd-core/workflows/autonomous.md",
"gsd-core/workflows/autonomous/steps/converge-banner.md",
"gsd-core/workflows/autonomous/steps/converge-dispatch-bg.md",
"gsd-core/workflows/autonomous/steps/converge-dispatch-inline.md",
"gsd-core/workflows/autonomous/steps/converge-fail-fast.md",
"gsd-core/workflows/autonomous/steps/converge-loop.md",
"gsd-core/workflows/check-todos.md",
"gsd-core/workflows/cleanup.md",
"gsd-core/workflows/code-review-fix.md",
"gsd-core/workflows/code-review.md",
"gsd-core/workflows/code-review/steps/dispatch-fix.md",
"gsd-core/workflows/code-review/steps/structural-pre-pass.md",
"gsd-core/workflows/complete-milestone.md",
"gsd-core/workflows/complete-milestone/steps/git-tag.md",
"gsd-core/workflows/debug.md",
"gsd-core/workflows/diagnose-issues.md",
"gsd-core/workflows/discovery-phase.md",
"gsd-core/workflows/discuss-phase-assumptions.md",
"gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md",
"gsd-core/workflows/discuss-phase-power.md",
"gsd-core/workflows/discuss-phase.md",
"gsd-core/workflows/discuss-phase/modes/advisor.md",
@@ -276,6 +286,7 @@
"gsd-core/workflows/discuss-phase/templates/discussion-log.md",
"gsd-core/workflows/do.md",
"gsd-core/workflows/docs-update.md",
"gsd-core/workflows/docs-update/steps/dispatch-monorepo-packages.md",
"gsd-core/workflows/edit-phase.md",
"gsd-core/workflows/eval-review.md",
"gsd-core/workflows/execute-phase.md",
@@ -312,7 +323,12 @@
"gsd-core/workflows/milestone-summary.md",
"gsd-core/workflows/mvp-phase.md",
"gsd-core/workflows/new-milestone.md",
"gsd-core/workflows/new-milestone/steps/project-md-milestone-write.md",
"gsd-core/workflows/new-milestone/steps/reset-phase-safety.md",
"gsd-core/workflows/new-project.md",
"gsd-core/workflows/new-project/steps/auto-mode-config.md",
"gsd-core/workflows/new-project/steps/auto-mode-detection.md",
"gsd-core/workflows/new-project/steps/codebase-map-offer.md",
"gsd-core/workflows/new-workspace.md",
"gsd-core/workflows/next.md",
"gsd-core/workflows/node-repair.md",
@@ -336,12 +352,21 @@
"gsd-core/workflows/pr-branch.md",
"gsd-core/workflows/profile-user.md",
"gsd-core/workflows/progress.md",
"gsd-core/workflows/progress/steps/forensic-audit.md",
"gsd-core/workflows/progress/steps/mvp-display.md",
"gsd-core/workflows/quick.md",
"gsd-core/workflows/quick/steps/discussion-phase.md",
"gsd-core/workflows/quick/steps/plan-checker-loop.md",
"gsd-core/workflows/quick/steps/quick-verification.md",
"gsd-core/workflows/quick/steps/research-phase.md",
"gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md",
"gsd-core/workflows/reapply-patches.md",
"gsd-core/workflows/remove-phase.md",
"gsd-core/workflows/remove-workspace.md",
"gsd-core/workflows/resume-project.md",
"gsd-core/workflows/review.md",
"gsd-core/workflows/review/steps/reviewer-instances-note-1.md",
"gsd-core/workflows/review/steps/reviewer-instances-note-2.md",
"gsd-core/workflows/scan.md",
"gsd-core/workflows/section-manifest.json",
"gsd-core/workflows/secure-phase.md",
@@ -360,14 +385,18 @@
"gsd-core/workflows/sync-skills.md",
"gsd-core/workflows/thread.md",
"gsd-core/workflows/transition.md",
"gsd-core/workflows/transition/steps/workstream-collision-check.md",
"gsd-core/workflows/ui-phase.md",
"gsd-core/workflows/ui-review.md",
"gsd-core/workflows/ultraplan-phase.md",
"gsd-core/workflows/undo.md",
"gsd-core/workflows/update.md",
"gsd-core/workflows/update/steps/channel-banner.md",
"gsd-core/workflows/validate-phase.md",
"gsd-core/workflows/verify-phase.md",
"gsd-core/workflows/verify-work.md",
"gsd-core/workflows/verify-work/steps/automated-ui-verification.md",
"gsd-core/workflows/verify-work/steps/mvp-uat-framing.md",
"scripts/changeset/README.md",
"scripts/changeset/cli.cjs",
"scripts/changeset/github-release-notes.cjs",

View File

@@ -122,6 +122,7 @@
"gsd-core/references/api-coverage.md",
"gsd-core/references/artifact-types.md",
"gsd-core/references/autonomous-smart-discuss.md",
"gsd-core/references/autonomous-ui-design-contract.md",
"gsd-core/references/checkpoints.md",
"gsd-core/references/common-bug-patterns.md",
"gsd-core/references/context-budget.md",
@@ -287,15 +288,24 @@
"gsd-core/workflows/audit-milestone.md",
"gsd-core/workflows/audit-uat.md",
"gsd-core/workflows/autonomous.md",
"gsd-core/workflows/autonomous/steps/converge-banner.md",
"gsd-core/workflows/autonomous/steps/converge-dispatch-bg.md",
"gsd-core/workflows/autonomous/steps/converge-dispatch-inline.md",
"gsd-core/workflows/autonomous/steps/converge-fail-fast.md",
"gsd-core/workflows/autonomous/steps/converge-loop.md",
"gsd-core/workflows/check-todos.md",
"gsd-core/workflows/cleanup.md",
"gsd-core/workflows/code-review-fix.md",
"gsd-core/workflows/code-review.md",
"gsd-core/workflows/code-review/steps/dispatch-fix.md",
"gsd-core/workflows/code-review/steps/structural-pre-pass.md",
"gsd-core/workflows/complete-milestone.md",
"gsd-core/workflows/complete-milestone/steps/git-tag.md",
"gsd-core/workflows/debug.md",
"gsd-core/workflows/diagnose-issues.md",
"gsd-core/workflows/discovery-phase.md",
"gsd-core/workflows/discuss-phase-assumptions.md",
"gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md",
"gsd-core/workflows/discuss-phase-power.md",
"gsd-core/workflows/discuss-phase.md",
"gsd-core/workflows/discuss-phase/modes/advisor.md",
@@ -312,6 +322,7 @@
"gsd-core/workflows/discuss-phase/templates/discussion-log.md",
"gsd-core/workflows/do.md",
"gsd-core/workflows/docs-update.md",
"gsd-core/workflows/docs-update/steps/dispatch-monorepo-packages.md",
"gsd-core/workflows/edit-phase.md",
"gsd-core/workflows/eval-review.md",
"gsd-core/workflows/execute-phase.md",
@@ -348,7 +359,12 @@
"gsd-core/workflows/milestone-summary.md",
"gsd-core/workflows/mvp-phase.md",
"gsd-core/workflows/new-milestone.md",
"gsd-core/workflows/new-milestone/steps/project-md-milestone-write.md",
"gsd-core/workflows/new-milestone/steps/reset-phase-safety.md",
"gsd-core/workflows/new-project.md",
"gsd-core/workflows/new-project/steps/auto-mode-config.md",
"gsd-core/workflows/new-project/steps/auto-mode-detection.md",
"gsd-core/workflows/new-project/steps/codebase-map-offer.md",
"gsd-core/workflows/new-workspace.md",
"gsd-core/workflows/next.md",
"gsd-core/workflows/node-repair.md",
@@ -372,12 +388,21 @@
"gsd-core/workflows/pr-branch.md",
"gsd-core/workflows/profile-user.md",
"gsd-core/workflows/progress.md",
"gsd-core/workflows/progress/steps/forensic-audit.md",
"gsd-core/workflows/progress/steps/mvp-display.md",
"gsd-core/workflows/quick.md",
"gsd-core/workflows/quick/steps/discussion-phase.md",
"gsd-core/workflows/quick/steps/plan-checker-loop.md",
"gsd-core/workflows/quick/steps/quick-verification.md",
"gsd-core/workflows/quick/steps/research-phase.md",
"gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md",
"gsd-core/workflows/reapply-patches.md",
"gsd-core/workflows/remove-phase.md",
"gsd-core/workflows/remove-workspace.md",
"gsd-core/workflows/resume-project.md",
"gsd-core/workflows/review.md",
"gsd-core/workflows/review/steps/reviewer-instances-note-1.md",
"gsd-core/workflows/review/steps/reviewer-instances-note-2.md",
"gsd-core/workflows/scan.md",
"gsd-core/workflows/section-manifest.json",
"gsd-core/workflows/secure-phase.md",
@@ -396,14 +421,18 @@
"gsd-core/workflows/sync-skills.md",
"gsd-core/workflows/thread.md",
"gsd-core/workflows/transition.md",
"gsd-core/workflows/transition/steps/workstream-collision-check.md",
"gsd-core/workflows/ui-phase.md",
"gsd-core/workflows/ui-review.md",
"gsd-core/workflows/ultraplan-phase.md",
"gsd-core/workflows/undo.md",
"gsd-core/workflows/update.md",
"gsd-core/workflows/update/steps/channel-banner.md",
"gsd-core/workflows/validate-phase.md",
"gsd-core/workflows/verify-phase.md",
"gsd-core/workflows/verify-work.md",
"gsd-core/workflows/verify-work/steps/automated-ui-verification.md",
"gsd-core/workflows/verify-work/steps/mvp-uat-framing.md",
"scripts/changeset/README.md",
"scripts/changeset/cli.cjs",
"scripts/changeset/github-release-notes.cjs",

View File

@@ -127,6 +127,7 @@
"gsd-core/references/api-coverage.md",
"gsd-core/references/artifact-types.md",
"gsd-core/references/autonomous-smart-discuss.md",
"gsd-core/references/autonomous-ui-design-contract.md",
"gsd-core/references/checkpoints.md",
"gsd-core/references/common-bug-patterns.md",
"gsd-core/references/context-budget.md",
@@ -292,15 +293,24 @@
"gsd-core/workflows/audit-milestone.md",
"gsd-core/workflows/audit-uat.md",
"gsd-core/workflows/autonomous.md",
"gsd-core/workflows/autonomous/steps/converge-banner.md",
"gsd-core/workflows/autonomous/steps/converge-dispatch-bg.md",
"gsd-core/workflows/autonomous/steps/converge-dispatch-inline.md",
"gsd-core/workflows/autonomous/steps/converge-fail-fast.md",
"gsd-core/workflows/autonomous/steps/converge-loop.md",
"gsd-core/workflows/check-todos.md",
"gsd-core/workflows/cleanup.md",
"gsd-core/workflows/code-review-fix.md",
"gsd-core/workflows/code-review.md",
"gsd-core/workflows/code-review/steps/dispatch-fix.md",
"gsd-core/workflows/code-review/steps/structural-pre-pass.md",
"gsd-core/workflows/complete-milestone.md",
"gsd-core/workflows/complete-milestone/steps/git-tag.md",
"gsd-core/workflows/debug.md",
"gsd-core/workflows/diagnose-issues.md",
"gsd-core/workflows/discovery-phase.md",
"gsd-core/workflows/discuss-phase-assumptions.md",
"gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md",
"gsd-core/workflows/discuss-phase-power.md",
"gsd-core/workflows/discuss-phase.md",
"gsd-core/workflows/discuss-phase/modes/advisor.md",
@@ -317,6 +327,7 @@
"gsd-core/workflows/discuss-phase/templates/discussion-log.md",
"gsd-core/workflows/do.md",
"gsd-core/workflows/docs-update.md",
"gsd-core/workflows/docs-update/steps/dispatch-monorepo-packages.md",
"gsd-core/workflows/edit-phase.md",
"gsd-core/workflows/eval-review.md",
"gsd-core/workflows/execute-phase.md",
@@ -353,7 +364,12 @@
"gsd-core/workflows/milestone-summary.md",
"gsd-core/workflows/mvp-phase.md",
"gsd-core/workflows/new-milestone.md",
"gsd-core/workflows/new-milestone/steps/project-md-milestone-write.md",
"gsd-core/workflows/new-milestone/steps/reset-phase-safety.md",
"gsd-core/workflows/new-project.md",
"gsd-core/workflows/new-project/steps/auto-mode-config.md",
"gsd-core/workflows/new-project/steps/auto-mode-detection.md",
"gsd-core/workflows/new-project/steps/codebase-map-offer.md",
"gsd-core/workflows/new-workspace.md",
"gsd-core/workflows/next.md",
"gsd-core/workflows/node-repair.md",
@@ -377,12 +393,21 @@
"gsd-core/workflows/pr-branch.md",
"gsd-core/workflows/profile-user.md",
"gsd-core/workflows/progress.md",
"gsd-core/workflows/progress/steps/forensic-audit.md",
"gsd-core/workflows/progress/steps/mvp-display.md",
"gsd-core/workflows/quick.md",
"gsd-core/workflows/quick/steps/discussion-phase.md",
"gsd-core/workflows/quick/steps/plan-checker-loop.md",
"gsd-core/workflows/quick/steps/quick-verification.md",
"gsd-core/workflows/quick/steps/research-phase.md",
"gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md",
"gsd-core/workflows/reapply-patches.md",
"gsd-core/workflows/remove-phase.md",
"gsd-core/workflows/remove-workspace.md",
"gsd-core/workflows/resume-project.md",
"gsd-core/workflows/review.md",
"gsd-core/workflows/review/steps/reviewer-instances-note-1.md",
"gsd-core/workflows/review/steps/reviewer-instances-note-2.md",
"gsd-core/workflows/scan.md",
"gsd-core/workflows/section-manifest.json",
"gsd-core/workflows/secure-phase.md",
@@ -401,14 +426,18 @@
"gsd-core/workflows/sync-skills.md",
"gsd-core/workflows/thread.md",
"gsd-core/workflows/transition.md",
"gsd-core/workflows/transition/steps/workstream-collision-check.md",
"gsd-core/workflows/ui-phase.md",
"gsd-core/workflows/ui-review.md",
"gsd-core/workflows/ultraplan-phase.md",
"gsd-core/workflows/undo.md",
"gsd-core/workflows/update.md",
"gsd-core/workflows/update/steps/channel-banner.md",
"gsd-core/workflows/validate-phase.md",
"gsd-core/workflows/verify-phase.md",
"gsd-core/workflows/verify-work.md",
"gsd-core/workflows/verify-work/steps/automated-ui-verification.md",
"gsd-core/workflows/verify-work/steps/mvp-uat-framing.md",
"hooks/gsd-check-update-worker.js",
"hooks/gsd-check-update.js",
"hooks/gsd-config-reload.js",

View File

@@ -24,6 +24,7 @@
"gsd-core/references/api-coverage.md",
"gsd-core/references/artifact-types.md",
"gsd-core/references/autonomous-smart-discuss.md",
"gsd-core/references/autonomous-ui-design-contract.md",
"gsd-core/references/checkpoints.md",
"gsd-core/references/common-bug-patterns.md",
"gsd-core/references/context-budget.md",
@@ -189,15 +190,24 @@
"gsd-core/workflows/audit-milestone.md",
"gsd-core/workflows/audit-uat.md",
"gsd-core/workflows/autonomous.md",
"gsd-core/workflows/autonomous/steps/converge-banner.md",
"gsd-core/workflows/autonomous/steps/converge-dispatch-bg.md",
"gsd-core/workflows/autonomous/steps/converge-dispatch-inline.md",
"gsd-core/workflows/autonomous/steps/converge-fail-fast.md",
"gsd-core/workflows/autonomous/steps/converge-loop.md",
"gsd-core/workflows/check-todos.md",
"gsd-core/workflows/cleanup.md",
"gsd-core/workflows/code-review-fix.md",
"gsd-core/workflows/code-review.md",
"gsd-core/workflows/code-review/steps/dispatch-fix.md",
"gsd-core/workflows/code-review/steps/structural-pre-pass.md",
"gsd-core/workflows/complete-milestone.md",
"gsd-core/workflows/complete-milestone/steps/git-tag.md",
"gsd-core/workflows/debug.md",
"gsd-core/workflows/diagnose-issues.md",
"gsd-core/workflows/discovery-phase.md",
"gsd-core/workflows/discuss-phase-assumptions.md",
"gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md",
"gsd-core/workflows/discuss-phase-power.md",
"gsd-core/workflows/discuss-phase.md",
"gsd-core/workflows/discuss-phase/modes/advisor.md",
@@ -214,6 +224,7 @@
"gsd-core/workflows/discuss-phase/templates/discussion-log.md",
"gsd-core/workflows/do.md",
"gsd-core/workflows/docs-update.md",
"gsd-core/workflows/docs-update/steps/dispatch-monorepo-packages.md",
"gsd-core/workflows/edit-phase.md",
"gsd-core/workflows/eval-review.md",
"gsd-core/workflows/execute-phase.md",
@@ -250,7 +261,12 @@
"gsd-core/workflows/milestone-summary.md",
"gsd-core/workflows/mvp-phase.md",
"gsd-core/workflows/new-milestone.md",
"gsd-core/workflows/new-milestone/steps/project-md-milestone-write.md",
"gsd-core/workflows/new-milestone/steps/reset-phase-safety.md",
"gsd-core/workflows/new-project.md",
"gsd-core/workflows/new-project/steps/auto-mode-config.md",
"gsd-core/workflows/new-project/steps/auto-mode-detection.md",
"gsd-core/workflows/new-project/steps/codebase-map-offer.md",
"gsd-core/workflows/new-workspace.md",
"gsd-core/workflows/next.md",
"gsd-core/workflows/node-repair.md",
@@ -274,12 +290,21 @@
"gsd-core/workflows/pr-branch.md",
"gsd-core/workflows/profile-user.md",
"gsd-core/workflows/progress.md",
"gsd-core/workflows/progress/steps/forensic-audit.md",
"gsd-core/workflows/progress/steps/mvp-display.md",
"gsd-core/workflows/quick.md",
"gsd-core/workflows/quick/steps/discussion-phase.md",
"gsd-core/workflows/quick/steps/plan-checker-loop.md",
"gsd-core/workflows/quick/steps/quick-verification.md",
"gsd-core/workflows/quick/steps/research-phase.md",
"gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md",
"gsd-core/workflows/reapply-patches.md",
"gsd-core/workflows/remove-phase.md",
"gsd-core/workflows/remove-workspace.md",
"gsd-core/workflows/resume-project.md",
"gsd-core/workflows/review.md",
"gsd-core/workflows/review/steps/reviewer-instances-note-1.md",
"gsd-core/workflows/review/steps/reviewer-instances-note-2.md",
"gsd-core/workflows/scan.md",
"gsd-core/workflows/section-manifest.json",
"gsd-core/workflows/secure-phase.md",
@@ -298,14 +323,18 @@
"gsd-core/workflows/sync-skills.md",
"gsd-core/workflows/thread.md",
"gsd-core/workflows/transition.md",
"gsd-core/workflows/transition/steps/workstream-collision-check.md",
"gsd-core/workflows/ui-phase.md",
"gsd-core/workflows/ui-review.md",
"gsd-core/workflows/ultraplan-phase.md",
"gsd-core/workflows/undo.md",
"gsd-core/workflows/update.md",
"gsd-core/workflows/update/steps/channel-banner.md",
"gsd-core/workflows/validate-phase.md",
"gsd-core/workflows/verify-phase.md",
"gsd-core/workflows/verify-work.md",
"gsd-core/workflows/verify-work/steps/automated-ui-verification.md",
"gsd-core/workflows/verify-work/steps/mvp-uat-framing.md",
"hooks/gsd-check-update-worker.js",
"hooks/gsd-check-update.js",
"hooks/gsd-config-reload.js",

View File

@@ -56,6 +56,7 @@
"gsd-core/references/api-coverage.md",
"gsd-core/references/artifact-types.md",
"gsd-core/references/autonomous-smart-discuss.md",
"gsd-core/references/autonomous-ui-design-contract.md",
"gsd-core/references/checkpoints.md",
"gsd-core/references/common-bug-patterns.md",
"gsd-core/references/context-budget.md",
@@ -221,15 +222,24 @@
"gsd-core/workflows/audit-milestone.md",
"gsd-core/workflows/audit-uat.md",
"gsd-core/workflows/autonomous.md",
"gsd-core/workflows/autonomous/steps/converge-banner.md",
"gsd-core/workflows/autonomous/steps/converge-dispatch-bg.md",
"gsd-core/workflows/autonomous/steps/converge-dispatch-inline.md",
"gsd-core/workflows/autonomous/steps/converge-fail-fast.md",
"gsd-core/workflows/autonomous/steps/converge-loop.md",
"gsd-core/workflows/check-todos.md",
"gsd-core/workflows/cleanup.md",
"gsd-core/workflows/code-review-fix.md",
"gsd-core/workflows/code-review.md",
"gsd-core/workflows/code-review/steps/dispatch-fix.md",
"gsd-core/workflows/code-review/steps/structural-pre-pass.md",
"gsd-core/workflows/complete-milestone.md",
"gsd-core/workflows/complete-milestone/steps/git-tag.md",
"gsd-core/workflows/debug.md",
"gsd-core/workflows/diagnose-issues.md",
"gsd-core/workflows/discovery-phase.md",
"gsd-core/workflows/discuss-phase-assumptions.md",
"gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md",
"gsd-core/workflows/discuss-phase-power.md",
"gsd-core/workflows/discuss-phase.md",
"gsd-core/workflows/discuss-phase/modes/advisor.md",
@@ -246,6 +256,7 @@
"gsd-core/workflows/discuss-phase/templates/discussion-log.md",
"gsd-core/workflows/do.md",
"gsd-core/workflows/docs-update.md",
"gsd-core/workflows/docs-update/steps/dispatch-monorepo-packages.md",
"gsd-core/workflows/edit-phase.md",
"gsd-core/workflows/eval-review.md",
"gsd-core/workflows/execute-phase.md",
@@ -282,7 +293,12 @@
"gsd-core/workflows/milestone-summary.md",
"gsd-core/workflows/mvp-phase.md",
"gsd-core/workflows/new-milestone.md",
"gsd-core/workflows/new-milestone/steps/project-md-milestone-write.md",
"gsd-core/workflows/new-milestone/steps/reset-phase-safety.md",
"gsd-core/workflows/new-project.md",
"gsd-core/workflows/new-project/steps/auto-mode-config.md",
"gsd-core/workflows/new-project/steps/auto-mode-detection.md",
"gsd-core/workflows/new-project/steps/codebase-map-offer.md",
"gsd-core/workflows/new-workspace.md",
"gsd-core/workflows/next.md",
"gsd-core/workflows/node-repair.md",
@@ -306,12 +322,21 @@
"gsd-core/workflows/pr-branch.md",
"gsd-core/workflows/profile-user.md",
"gsd-core/workflows/progress.md",
"gsd-core/workflows/progress/steps/forensic-audit.md",
"gsd-core/workflows/progress/steps/mvp-display.md",
"gsd-core/workflows/quick.md",
"gsd-core/workflows/quick/steps/discussion-phase.md",
"gsd-core/workflows/quick/steps/plan-checker-loop.md",
"gsd-core/workflows/quick/steps/quick-verification.md",
"gsd-core/workflows/quick/steps/research-phase.md",
"gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md",
"gsd-core/workflows/reapply-patches.md",
"gsd-core/workflows/remove-phase.md",
"gsd-core/workflows/remove-workspace.md",
"gsd-core/workflows/resume-project.md",
"gsd-core/workflows/review.md",
"gsd-core/workflows/review/steps/reviewer-instances-note-1.md",
"gsd-core/workflows/review/steps/reviewer-instances-note-2.md",
"gsd-core/workflows/scan.md",
"gsd-core/workflows/section-manifest.json",
"gsd-core/workflows/secure-phase.md",
@@ -330,14 +355,18 @@
"gsd-core/workflows/sync-skills.md",
"gsd-core/workflows/thread.md",
"gsd-core/workflows/transition.md",
"gsd-core/workflows/transition/steps/workstream-collision-check.md",
"gsd-core/workflows/ui-phase.md",
"gsd-core/workflows/ui-review.md",
"gsd-core/workflows/ultraplan-phase.md",
"gsd-core/workflows/undo.md",
"gsd-core/workflows/update.md",
"gsd-core/workflows/update/steps/channel-banner.md",
"gsd-core/workflows/validate-phase.md",
"gsd-core/workflows/verify-phase.md",
"gsd-core/workflows/verify-work.md",
"gsd-core/workflows/verify-work/steps/automated-ui-verification.md",
"gsd-core/workflows/verify-work/steps/mvp-uat-framing.md",
"hooks/gsd-check-update-worker.js",
"hooks/gsd-check-update.js",
"hooks/gsd-config-reload.js",

View File

@@ -56,6 +56,7 @@
"gsd-core/references/api-coverage.md",
"gsd-core/references/artifact-types.md",
"gsd-core/references/autonomous-smart-discuss.md",
"gsd-core/references/autonomous-ui-design-contract.md",
"gsd-core/references/checkpoints.md",
"gsd-core/references/common-bug-patterns.md",
"gsd-core/references/context-budget.md",
@@ -221,15 +222,24 @@
"gsd-core/workflows/audit-milestone.md",
"gsd-core/workflows/audit-uat.md",
"gsd-core/workflows/autonomous.md",
"gsd-core/workflows/autonomous/steps/converge-banner.md",
"gsd-core/workflows/autonomous/steps/converge-dispatch-bg.md",
"gsd-core/workflows/autonomous/steps/converge-dispatch-inline.md",
"gsd-core/workflows/autonomous/steps/converge-fail-fast.md",
"gsd-core/workflows/autonomous/steps/converge-loop.md",
"gsd-core/workflows/check-todos.md",
"gsd-core/workflows/cleanup.md",
"gsd-core/workflows/code-review-fix.md",
"gsd-core/workflows/code-review.md",
"gsd-core/workflows/code-review/steps/dispatch-fix.md",
"gsd-core/workflows/code-review/steps/structural-pre-pass.md",
"gsd-core/workflows/complete-milestone.md",
"gsd-core/workflows/complete-milestone/steps/git-tag.md",
"gsd-core/workflows/debug.md",
"gsd-core/workflows/diagnose-issues.md",
"gsd-core/workflows/discovery-phase.md",
"gsd-core/workflows/discuss-phase-assumptions.md",
"gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md",
"gsd-core/workflows/discuss-phase-power.md",
"gsd-core/workflows/discuss-phase.md",
"gsd-core/workflows/discuss-phase/modes/advisor.md",
@@ -246,6 +256,7 @@
"gsd-core/workflows/discuss-phase/templates/discussion-log.md",
"gsd-core/workflows/do.md",
"gsd-core/workflows/docs-update.md",
"gsd-core/workflows/docs-update/steps/dispatch-monorepo-packages.md",
"gsd-core/workflows/edit-phase.md",
"gsd-core/workflows/eval-review.md",
"gsd-core/workflows/execute-phase.md",
@@ -282,7 +293,12 @@
"gsd-core/workflows/milestone-summary.md",
"gsd-core/workflows/mvp-phase.md",
"gsd-core/workflows/new-milestone.md",
"gsd-core/workflows/new-milestone/steps/project-md-milestone-write.md",
"gsd-core/workflows/new-milestone/steps/reset-phase-safety.md",
"gsd-core/workflows/new-project.md",
"gsd-core/workflows/new-project/steps/auto-mode-config.md",
"gsd-core/workflows/new-project/steps/auto-mode-detection.md",
"gsd-core/workflows/new-project/steps/codebase-map-offer.md",
"gsd-core/workflows/new-workspace.md",
"gsd-core/workflows/next.md",
"gsd-core/workflows/node-repair.md",
@@ -306,12 +322,21 @@
"gsd-core/workflows/pr-branch.md",
"gsd-core/workflows/profile-user.md",
"gsd-core/workflows/progress.md",
"gsd-core/workflows/progress/steps/forensic-audit.md",
"gsd-core/workflows/progress/steps/mvp-display.md",
"gsd-core/workflows/quick.md",
"gsd-core/workflows/quick/steps/discussion-phase.md",
"gsd-core/workflows/quick/steps/plan-checker-loop.md",
"gsd-core/workflows/quick/steps/quick-verification.md",
"gsd-core/workflows/quick/steps/research-phase.md",
"gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md",
"gsd-core/workflows/reapply-patches.md",
"gsd-core/workflows/remove-phase.md",
"gsd-core/workflows/remove-workspace.md",
"gsd-core/workflows/resume-project.md",
"gsd-core/workflows/review.md",
"gsd-core/workflows/review/steps/reviewer-instances-note-1.md",
"gsd-core/workflows/review/steps/reviewer-instances-note-2.md",
"gsd-core/workflows/scan.md",
"gsd-core/workflows/section-manifest.json",
"gsd-core/workflows/secure-phase.md",
@@ -330,14 +355,18 @@
"gsd-core/workflows/sync-skills.md",
"gsd-core/workflows/thread.md",
"gsd-core/workflows/transition.md",
"gsd-core/workflows/transition/steps/workstream-collision-check.md",
"gsd-core/workflows/ui-phase.md",
"gsd-core/workflows/ui-review.md",
"gsd-core/workflows/ultraplan-phase.md",
"gsd-core/workflows/undo.md",
"gsd-core/workflows/update.md",
"gsd-core/workflows/update/steps/channel-banner.md",
"gsd-core/workflows/validate-phase.md",
"gsd-core/workflows/verify-phase.md",
"gsd-core/workflows/verify-work.md",
"gsd-core/workflows/verify-work/steps/automated-ui-verification.md",
"gsd-core/workflows/verify-work/steps/mvp-uat-framing.md",
"scripts/changeset/README.md",
"scripts/changeset/cli.cjs",
"scripts/changeset/github-release-notes.cjs",

View File

@@ -56,6 +56,7 @@
"gsd-core/references/api-coverage.md",
"gsd-core/references/artifact-types.md",
"gsd-core/references/autonomous-smart-discuss.md",
"gsd-core/references/autonomous-ui-design-contract.md",
"gsd-core/references/checkpoints.md",
"gsd-core/references/common-bug-patterns.md",
"gsd-core/references/context-budget.md",
@@ -221,15 +222,24 @@
"gsd-core/workflows/audit-milestone.md",
"gsd-core/workflows/audit-uat.md",
"gsd-core/workflows/autonomous.md",
"gsd-core/workflows/autonomous/steps/converge-banner.md",
"gsd-core/workflows/autonomous/steps/converge-dispatch-bg.md",
"gsd-core/workflows/autonomous/steps/converge-dispatch-inline.md",
"gsd-core/workflows/autonomous/steps/converge-fail-fast.md",
"gsd-core/workflows/autonomous/steps/converge-loop.md",
"gsd-core/workflows/check-todos.md",
"gsd-core/workflows/cleanup.md",
"gsd-core/workflows/code-review-fix.md",
"gsd-core/workflows/code-review.md",
"gsd-core/workflows/code-review/steps/dispatch-fix.md",
"gsd-core/workflows/code-review/steps/structural-pre-pass.md",
"gsd-core/workflows/complete-milestone.md",
"gsd-core/workflows/complete-milestone/steps/git-tag.md",
"gsd-core/workflows/debug.md",
"gsd-core/workflows/diagnose-issues.md",
"gsd-core/workflows/discovery-phase.md",
"gsd-core/workflows/discuss-phase-assumptions.md",
"gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md",
"gsd-core/workflows/discuss-phase-power.md",
"gsd-core/workflows/discuss-phase.md",
"gsd-core/workflows/discuss-phase/modes/advisor.md",
@@ -246,6 +256,7 @@
"gsd-core/workflows/discuss-phase/templates/discussion-log.md",
"gsd-core/workflows/do.md",
"gsd-core/workflows/docs-update.md",
"gsd-core/workflows/docs-update/steps/dispatch-monorepo-packages.md",
"gsd-core/workflows/edit-phase.md",
"gsd-core/workflows/eval-review.md",
"gsd-core/workflows/execute-phase.md",
@@ -282,7 +293,12 @@
"gsd-core/workflows/milestone-summary.md",
"gsd-core/workflows/mvp-phase.md",
"gsd-core/workflows/new-milestone.md",
"gsd-core/workflows/new-milestone/steps/project-md-milestone-write.md",
"gsd-core/workflows/new-milestone/steps/reset-phase-safety.md",
"gsd-core/workflows/new-project.md",
"gsd-core/workflows/new-project/steps/auto-mode-config.md",
"gsd-core/workflows/new-project/steps/auto-mode-detection.md",
"gsd-core/workflows/new-project/steps/codebase-map-offer.md",
"gsd-core/workflows/new-workspace.md",
"gsd-core/workflows/next.md",
"gsd-core/workflows/node-repair.md",
@@ -306,12 +322,21 @@
"gsd-core/workflows/pr-branch.md",
"gsd-core/workflows/profile-user.md",
"gsd-core/workflows/progress.md",
"gsd-core/workflows/progress/steps/forensic-audit.md",
"gsd-core/workflows/progress/steps/mvp-display.md",
"gsd-core/workflows/quick.md",
"gsd-core/workflows/quick/steps/discussion-phase.md",
"gsd-core/workflows/quick/steps/plan-checker-loop.md",
"gsd-core/workflows/quick/steps/quick-verification.md",
"gsd-core/workflows/quick/steps/research-phase.md",
"gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md",
"gsd-core/workflows/reapply-patches.md",
"gsd-core/workflows/remove-phase.md",
"gsd-core/workflows/remove-workspace.md",
"gsd-core/workflows/resume-project.md",
"gsd-core/workflows/review.md",
"gsd-core/workflows/review/steps/reviewer-instances-note-1.md",
"gsd-core/workflows/review/steps/reviewer-instances-note-2.md",
"gsd-core/workflows/scan.md",
"gsd-core/workflows/section-manifest.json",
"gsd-core/workflows/secure-phase.md",
@@ -330,14 +355,18 @@
"gsd-core/workflows/sync-skills.md",
"gsd-core/workflows/thread.md",
"gsd-core/workflows/transition.md",
"gsd-core/workflows/transition/steps/workstream-collision-check.md",
"gsd-core/workflows/ui-phase.md",
"gsd-core/workflows/ui-review.md",
"gsd-core/workflows/ultraplan-phase.md",
"gsd-core/workflows/undo.md",
"gsd-core/workflows/update.md",
"gsd-core/workflows/update/steps/channel-banner.md",
"gsd-core/workflows/validate-phase.md",
"gsd-core/workflows/verify-phase.md",
"gsd-core/workflows/verify-work.md",
"gsd-core/workflows/verify-work/steps/automated-ui-verification.md",
"gsd-core/workflows/verify-work/steps/mvp-uat-framing.md",
"hooks/gsd-windsurf-pre-command.js",
"hooks/gsd-windsurf-pre-write.js",
"hooks/package.json",

View File

@@ -127,6 +127,7 @@
"gsd-core/references/api-coverage.md",
"gsd-core/references/artifact-types.md",
"gsd-core/references/autonomous-smart-discuss.md",
"gsd-core/references/autonomous-ui-design-contract.md",
"gsd-core/references/checkpoints.md",
"gsd-core/references/common-bug-patterns.md",
"gsd-core/references/context-budget.md",
@@ -292,15 +293,24 @@
"gsd-core/workflows/audit-milestone.md",
"gsd-core/workflows/audit-uat.md",
"gsd-core/workflows/autonomous.md",
"gsd-core/workflows/autonomous/steps/converge-banner.md",
"gsd-core/workflows/autonomous/steps/converge-dispatch-bg.md",
"gsd-core/workflows/autonomous/steps/converge-dispatch-inline.md",
"gsd-core/workflows/autonomous/steps/converge-fail-fast.md",
"gsd-core/workflows/autonomous/steps/converge-loop.md",
"gsd-core/workflows/check-todos.md",
"gsd-core/workflows/cleanup.md",
"gsd-core/workflows/code-review-fix.md",
"gsd-core/workflows/code-review.md",
"gsd-core/workflows/code-review/steps/dispatch-fix.md",
"gsd-core/workflows/code-review/steps/structural-pre-pass.md",
"gsd-core/workflows/complete-milestone.md",
"gsd-core/workflows/complete-milestone/steps/git-tag.md",
"gsd-core/workflows/debug.md",
"gsd-core/workflows/diagnose-issues.md",
"gsd-core/workflows/discovery-phase.md",
"gsd-core/workflows/discuss-phase-assumptions.md",
"gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md",
"gsd-core/workflows/discuss-phase-power.md",
"gsd-core/workflows/discuss-phase.md",
"gsd-core/workflows/discuss-phase/modes/advisor.md",
@@ -317,6 +327,7 @@
"gsd-core/workflows/discuss-phase/templates/discussion-log.md",
"gsd-core/workflows/do.md",
"gsd-core/workflows/docs-update.md",
"gsd-core/workflows/docs-update/steps/dispatch-monorepo-packages.md",
"gsd-core/workflows/edit-phase.md",
"gsd-core/workflows/eval-review.md",
"gsd-core/workflows/execute-phase.md",
@@ -353,7 +364,12 @@
"gsd-core/workflows/milestone-summary.md",
"gsd-core/workflows/mvp-phase.md",
"gsd-core/workflows/new-milestone.md",
"gsd-core/workflows/new-milestone/steps/project-md-milestone-write.md",
"gsd-core/workflows/new-milestone/steps/reset-phase-safety.md",
"gsd-core/workflows/new-project.md",
"gsd-core/workflows/new-project/steps/auto-mode-config.md",
"gsd-core/workflows/new-project/steps/auto-mode-detection.md",
"gsd-core/workflows/new-project/steps/codebase-map-offer.md",
"gsd-core/workflows/new-workspace.md",
"gsd-core/workflows/next.md",
"gsd-core/workflows/node-repair.md",
@@ -377,12 +393,21 @@
"gsd-core/workflows/pr-branch.md",
"gsd-core/workflows/profile-user.md",
"gsd-core/workflows/progress.md",
"gsd-core/workflows/progress/steps/forensic-audit.md",
"gsd-core/workflows/progress/steps/mvp-display.md",
"gsd-core/workflows/quick.md",
"gsd-core/workflows/quick/steps/discussion-phase.md",
"gsd-core/workflows/quick/steps/plan-checker-loop.md",
"gsd-core/workflows/quick/steps/quick-verification.md",
"gsd-core/workflows/quick/steps/research-phase.md",
"gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md",
"gsd-core/workflows/reapply-patches.md",
"gsd-core/workflows/remove-phase.md",
"gsd-core/workflows/remove-workspace.md",
"gsd-core/workflows/resume-project.md",
"gsd-core/workflows/review.md",
"gsd-core/workflows/review/steps/reviewer-instances-note-1.md",
"gsd-core/workflows/review/steps/reviewer-instances-note-2.md",
"gsd-core/workflows/scan.md",
"gsd-core/workflows/section-manifest.json",
"gsd-core/workflows/secure-phase.md",
@@ -401,14 +426,18 @@
"gsd-core/workflows/sync-skills.md",
"gsd-core/workflows/thread.md",
"gsd-core/workflows/transition.md",
"gsd-core/workflows/transition/steps/workstream-collision-check.md",
"gsd-core/workflows/ui-phase.md",
"gsd-core/workflows/ui-review.md",
"gsd-core/workflows/ultraplan-phase.md",
"gsd-core/workflows/undo.md",
"gsd-core/workflows/update.md",
"gsd-core/workflows/update/steps/channel-banner.md",
"gsd-core/workflows/validate-phase.md",
"gsd-core/workflows/verify-phase.md",
"gsd-core/workflows/verify-work.md",
"gsd-core/workflows/verify-work/steps/automated-ui-verification.md",
"gsd-core/workflows/verify-work/steps/mvp-uat-framing.md",
"scripts/changeset/README.md",
"scripts/changeset/cli.cjs",
"scripts/changeset/github-release-notes.cjs",

View File

@@ -715,29 +715,40 @@ describe('workflow gsd_run query init.<workflow> invocations forward every flag:
const workflowMdPath = path.join(ROOT, 'gsd-core', 'workflows', `${workflow}.md`);
const content = fs.readFileSync(workflowMdPath, 'utf8');
const initLineRe = new RegExp(`^.*gsd_run query init\\.${workflow}\\b.*$`, 'm');
const initLineMatch = content.match(initLineRe);
assert.ok(initLineMatch, `${workflow}.md must contain a "gsd_run query init.${workflow}" invocation line`);
const initLine = initLineMatch[0];
// Everything up to and including the invocation line: real workflows
// parse $ARGUMENTS into a param variable earlier in the same bash
// block, then reference that variable on the INIT= line itself.
const scope = content.slice(0, content.indexOf(initLine) + initLine.length);
const referencedVars = varsReferencedIn(initLine);
// A workflow may call `gsd_run query init.<workflow>` more than once
// (e.g. new-milestone.md's early INIT_EARLY call, whose section_manifest
// gates ONLY project-md-milestone-write per its own prose, followed by
// a later full INIT call that gates every other flag-gated section and
// does forward the flag). Every occurrence is a candidate carrier —
// a flagAtom only needs forwarding by AT LEAST ONE invocation line,
// not necessarily the first.
const initLineRe = new RegExp(`^.*gsd_run query init\\.${workflow}\\b.*$`, 'mg');
const initLineMatches = [...content.matchAll(initLineRe)];
assert.ok(initLineMatches.length > 0, `${workflow}.md must contain a "gsd_run query init.${workflow}" invocation line`);
for (const atomFlag of flagAtoms) {
const directlyPresent = initLine
.split(/\s+/)
.includes(atomFlag);
const carriers = varsCarryingFlagToken(scope, atomFlag);
const forwardedViaVar = [...carriers].some((v) => referencedVars.has(v));
const forwardedByAnyInvocation = initLineMatches.some((match) => {
const initLine = match[0];
// Everything up to and including this invocation line: real
// workflows parse $ARGUMENTS into a param variable earlier in the
// same bash block, then reference that variable on the INIT= line
// itself.
const scope = content.slice(0, match.index + initLine.length);
const referencedVars = varsReferencedIn(initLine);
const directlyPresent = initLine
.split(/\s+/)
.includes(atomFlag);
const carriers = varsCarryingFlagToken(scope, atomFlag);
const forwardedViaVar = [...carriers].some((v) => referencedVars.has(v));
return directlyPresent || forwardedViaVar;
});
assert.ok(
directlyPresent || forwardedViaVar,
`${workflow}.md's "gsd_run query init.${workflow}" invocation line must forward a parameter for ` +
forwardedByAnyInvocation,
`${workflow}.md's "gsd_run query init.${workflow}" invocation line(s) must forward a parameter for ` +
`${atomFlag} (its gated section is otherwise permanently excluded — see BLOCKER, epic #1671 Phase 6.2). ` +
`Invocation line: ${initLine}`,
`Invocation line(s): ${initLineMatches.map((m) => m[0]).join(' | ')}`,
);
}
});

View File

@@ -13,7 +13,7 @@ const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const fc = require('./helpers/fast-check-setup.cjs');
const { runGsdTools, createTempProject, cleanup } = require('./helpers.cjs');
const { runGsdTools, createTempProject, cleanup, readWorkflowCombined } = require('./helpers.cjs');
const ROOT = path.resolve(__dirname, '..');
const WORKFLOWS = path.join(ROOT, 'gsd-core', 'workflows');
@@ -49,12 +49,20 @@ function statesOmitRule(content) {
* reported green across 15 non-compliant files for no better reason than that
* nobody had added them to it. Deriving the set is what makes a 16th file
* impossible to add silently.
*
* #2994: `content` is read via `readWorkflowCombined` (host + its
* `workflows/<wf>/steps/*.md` fragments), not the bare host file. The
* fragment model can move a workflow's own omit-rule prose (e.g.
* quick.md's rule lives in `quick/steps/research-phase.md` behind a
* `<!-- gsd:section -->` stub) out of the host without moving its
* `model="{…}"` dispatch site, so a host-only read would report a
* false positive for a workflow that still documents the rule.
*/
function workflowsThatDispatchWithAModel() {
return fs
.readdirSync(WORKFLOWS)
.filter((f) => f.endsWith('.md'))
.map((f) => ({ file: f, content: fs.readFileSync(path.join(WORKFLOWS, f), 'utf8') }))
.map((f) => ({ file: f, content: readWorkflowCombined(path.join(WORKFLOWS, f)) }))
.filter((w) => /model="\{/.test(w.content));
}

View File

@@ -580,6 +580,37 @@ describe('new-milestone.md: workstream-aware PROJECT.md guard (#2308)', () => {
// would break bash mid-script (DEFECT.TEST-SHELL-PIPELINE-NONPORTABLE, #2650).
const content = readFileNormalized(workflowPath);
// #2994 fragmentization moved the 7.5 reset-phase-safety section (including
// its "/gsd:new-milestone --reset-phase-numbers ${GSD_WS}" rerun hint) out
// of new-milestone.md into gsd-core/workflows/new-milestone/steps/reset-phase-safety.md
// behind a section marker. Only the routing-interpolation test below needs
// that moved text, so it reads host + step file combined instead of
// widening `content` (used for host-only fence extraction elsewhere in
// this describe block).
const contentWithSteps = content + '\n' + fs.readFileSync(
path.join(__dirname, '..', 'gsd-core', 'workflows', 'new-milestone', 'steps', 'reset-phase-safety.md'),
'utf8'
);
// #2994 final slice: Step 4's "Part A" milestone-state write moved out of
// new-milestone.md into gsd-core/workflows/new-milestone/steps/
// project-md-milestone-write.md behind a section marker. The
// "step 4 scopes the workstream skip" test below asserts on Part A's
// actual body (GSD_WS mentions, the workstream skip description) — rather
// than widen `content` for every test in this block, SPLICE the step
// file's content into the marker's exact position so partAIdx/partBIdx
// position-sensitive slicing below still works. A non-vacuity check
// (blank the step file, confirm the splice fails, restore) backs this.
const PROJECT_MD_STEP_PATH = path.join(
__dirname, '..', 'gsd-core', 'workflows', 'new-milestone', 'steps', 'project-md-milestone-write.md'
);
const PROJECT_MD_MARKER_RE = /<!-- gsd:section id="project-md-milestone-write"[\s\S]*?<!-- \/gsd:section -->/;
function contentWithProjectMdStepSpliced() {
const stepBody = fs.readFileSync(PROJECT_MD_STEP_PATH, 'utf8');
assert.match(content, PROJECT_MD_MARKER_RE, 'project-md-milestone-write marker not found in new-milestone.md');
return content.replace(PROJECT_MD_MARKER_RE, stepBody);
}
// Locate the first ```bash fence strictly between two headings.
function extractFenceBetween(markdown, startHeading, endHeading) {
const startIdx = markdown.indexOf(startHeading);
@@ -677,7 +708,7 @@ describe('new-milestone.md: workstream-aware PROJECT.md guard (#2308)', () => {
test('routing interpolations still propagate ${GSD_WS} at the documented lines', () => {
assert.ok(
content.includes('/gsd:new-milestone --reset-phase-numbers ${GSD_WS}'),
contentWithSteps.includes('/gsd:new-milestone --reset-phase-numbers ${GSD_WS}'),
'reset-phase-numbers rerun hint should propagate ${GSD_WS}'
);
assert.ok(
@@ -699,10 +730,11 @@ describe('new-milestone.md: workstream-aware PROJECT.md guard (#2308)', () => {
});
test('step 4 scopes the workstream skip to the milestone-state write only; Evolution repair always runs (finding 2)', () => {
const step4Idx = content.indexOf('## 4. Update PROJECT.md');
const step5Idx = content.indexOf('## 5. Update STATE.md');
const splicedContent = contentWithProjectMdStepSpliced();
const step4Idx = splicedContent.indexOf('## 4. Update PROJECT.md');
const step5Idx = splicedContent.indexOf('## 5. Update STATE.md');
assert.ok(step4Idx !== -1 && step5Idx !== -1 && step4Idx < step5Idx, 'steps 4 and 5 should be locatable');
const step4Body = content.slice(step4Idx, step5Idx);
const step4Body = splicedContent.slice(step4Idx, step5Idx);
const partAIdx = step4Body.indexOf('Part A');
const partBIdx = step4Body.indexOf('Part B');

View File

@@ -53,8 +53,27 @@ describe('new-project — MVP mode prompt', () => {
// the #3784 two-question split already shipped for /gsd:settings. new-project.md has no
// <step> tags, so blocks are located by the `header: "AI Models"` marker.
// #2994 fragmentization moved the Step 2a "AI Models" auto-mode prompt (and its
// config-new-project example payload) out of new-project.md into
// gsd-core/workflows/new-project/steps/auto-mode-config.md behind a section
// marker (Step 5's interactive prompt stays in the host). Read host + every
// step file combined so the "at least two AI Models prompts" / "both example
// payloads" counts are preserved across the split rather than reduced.
const NEW_PROJECT_STEPS_DIR = path.join(__dirname, '..', 'gsd-core', 'workflows', 'new-project', 'steps');
function readNewProjectCombined() {
let combined = fs.readFileSync(WORKFLOW, 'utf8');
if (fs.existsSync(NEW_PROJECT_STEPS_DIR)) {
for (const entry of fs.readdirSync(NEW_PROJECT_STEPS_DIR).sort()) {
if (entry.endsWith('.md')) {
combined += '\n' + fs.readFileSync(path.join(NEW_PROJECT_STEPS_DIR, entry), 'utf8');
}
}
}
return combined;
}
describe('bug #1516: new-project AI Models prompt exposes all 5 model profiles', () => {
const content = fs.readFileSync(WORKFLOW, 'utf-8');
const content = readNewProjectCombined();
// Locate every `header: "AI Models"` AskUserQuestion block and grab a window large
// enough to include its conditional Q2 successor (the standard-tier picker).

View File

@@ -116,7 +116,13 @@ describe('ADR-857 phase 6 verification and review capability migration', () => {
test('quick full-mode code review resolves execute:post hooks instead of inlining code_review config', () => {
const content = workflow('quick.md');
const section = sectionBetween(content, '**Step 6.25: Code review (auto)**', '**Step 6.5: Verification');
// #2994 fragmentization moved Step 6.5 (Verification) out of quick.md into
// gsd-core/workflows/quick/steps/quick-verification.md; the parent now marks
// that boundary with a `<!-- gsd:section id="quick-verification" -->` comment
// immediately after Step 6.25's content, so it remains the correct
// end-of-step delimiter (mirrors the execute-phase.md gap-closure-artifacts
// pattern above) instead of bleeding to end-of-file.
const section = sectionBetween(content, '**Step 6.25: Code review (auto)**', '<!-- gsd:section id="quick-verification"');
assert.ok(section.includes('loop render-hooks execute:post'));
assert.ok(section.includes('gsd-code-reviewer'));

View File

@@ -1161,12 +1161,22 @@ describe('feat-22-surfacing-docs', () => {
});
test('new-project workflow has Drift Guard default-yes option', () => {
const content = fs.readFileSync(NEW_PROJECT_PATH, 'utf-8');
// #2994 fragmentization moved the Step 2a auto-mode AI/Drift-Guard
// AskUserQuestion block (the sole holder of this exact phrasing, even
// pre-fragmentization — Step 5's interactive block uses different wording)
// out of new-project.md into
// gsd-core/workflows/new-project/steps/auto-mode-config.md. Read host +
// step combined so this count is preserved across the split.
const content = fs.readFileSync(NEW_PROJECT_PATH, 'utf-8') +
'\n' + fs.readFileSync(
path.join(ROOT, 'gsd-core', 'workflows', 'new-project', 'steps', 'auto-mode-config.md'),
'utf-8'
);
// Both question blocks (auto and interactive) should have the yes option
const count = (content.match(/Yes \(Recommended\).*catches hallucinated names/g) || []).length;
assert.ok(
count >= 1,
'new-project.md must have at least one Drift Guard "Yes (Recommended)" option'
'new-project.md (host + new-project/steps/*.md) must have at least one Drift Guard "Yes (Recommended)" option'
);
});

View File

@@ -7,14 +7,25 @@ const assert = require('node:assert/strict');
const fs = require('fs');
const path = require('path');
// #2994 fragmentization moved the automated_ui_verification step out of
// verify-work.md into gsd-core/workflows/verify-work/steps/automated-ui-verification.md
// behind a section marker (`state:ui-phase-active`). The host no longer
// contains any "playwright" mention at all — reading the host alone now
// only passes these two assertions via unrelated substring coincidences
// ("automated"/"UI" from the section-marker id and an unrelated "User-facing
// changes - UI" bullet; "fall back" from an unrelated subagent-dispatch
// line), which is vacuous. Read the step file directly — it is the sole
// remaining source of the real Playwright content.
const AUTOMATED_UI_VERIFICATION_STEP_PATH = path.join(
__dirname, '..', 'gsd-core', 'workflows', 'verify-work', 'steps', 'automated-ui-verification.md'
);
describe('Playwright-MCP UI verification integration', () => {
test('verify-work.md mentions automated UI verification', () => {
const content = fs.readFileSync(
path.join(__dirname, '..', 'gsd-core', 'workflows', 'verify-work.md'), 'utf-8'
);
const content = fs.readFileSync(AUTOMATED_UI_VERIFICATION_STEP_PATH, 'utf-8');
assert.ok(
content.toLowerCase().includes('playwright') || content.includes('automated') && content.includes('UI'),
'verify-work.md should mention automated UI verification option'
'verify-work.md (or its extracted verify-work/steps/automated-ui-verification.md) should mention automated UI verification option'
);
});
@@ -39,9 +50,7 @@ describe('Playwright-MCP UI verification integration', () => {
});
test('automated verification is optional/conditional (falls back to manual)', () => {
const verifyContent = fs.readFileSync(
path.join(__dirname, '..', 'gsd-core', 'workflows', 'verify-work.md'), 'utf-8'
);
const verifyContent = fs.readFileSync(AUTOMATED_UI_VERIFICATION_STEP_PATH, 'utf-8');
// Must include a fallback / "if available" conditional
const hasConditional =
verifyContent.includes('if available') ||

View File

@@ -14,6 +14,16 @@ const assert = require('node:assert/strict');
const fs = require('fs');
const path = require('path');
// #2994 fragmentization moved the --forensic-gated forensic_audit step out of
// progress.md into gsd-core/workflows/progress/steps/forensic-audit.md behind
// a section marker. Tests that need the step BODY read that step file
// directly (it is the sole remaining source of the step's content); the
// step-presence check below is the only one that must also confirm the host
// still wires up the marker that reads it.
const FORENSIC_AUDIT_STEP_PATH = path.join(
__dirname, '..', 'gsd-core', 'workflows', 'progress', 'steps', 'forensic-audit.md'
);
describe('#2189: progress --forensic flag', () => {
test('progress command argument-hint includes --forensic', () => {
const command = fs.readFileSync(
@@ -26,20 +36,19 @@ describe('#2189: progress --forensic flag', () => {
const workflow = fs.readFileSync(
path.join(__dirname, '..', 'gsd-core', 'workflows', 'progress.md'), 'utf8'
);
const step = fs.readFileSync(FORENSIC_AUDIT_STEP_PATH, 'utf8');
assert.ok(
workflow.includes('<step name="forensic_audit">'),
'workflow should have a forensic_audit step'
workflow.includes('id="forensic-audit"'),
'progress.md must wire up the forensic-audit section marker'
);
assert.ok(
step.includes('<step name="forensic_audit">'),
'progress/steps/forensic-audit.md should have a forensic_audit step'
);
});
test('forensic_audit step is only triggered when --forensic is present', () => {
const workflow = fs.readFileSync(
path.join(__dirname, '..', 'gsd-core', 'workflows', 'progress.md'), 'utf8'
);
const forensicStep = workflow.slice(
workflow.indexOf('<step name="forensic_audit">'),
workflow.indexOf('</step>', workflow.indexOf('<step name="forensic_audit">'))
);
const forensicStep = fs.readFileSync(FORENSIC_AUDIT_STEP_PATH, 'utf8');
assert.ok(
forensicStep.includes('--forensic'),
'forensic_audit step should be gated on --forensic flag'
@@ -51,13 +60,7 @@ describe('#2189: progress --forensic flag', () => {
});
test('forensic_audit step includes all 6 checks', () => {
const workflow = fs.readFileSync(
path.join(__dirname, '..', 'gsd-core', 'workflows', 'progress.md'), 'utf8'
);
const forensicStep = workflow.slice(
workflow.indexOf('<step name="forensic_audit">'),
workflow.indexOf('</step>', workflow.indexOf('<step name="forensic_audit">'))
);
const forensicStep = fs.readFileSync(FORENSIC_AUDIT_STEP_PATH, 'utf8');
// Check 1: STATE vs artifact consistency
assert.ok(
forensicStep.includes('STATE') && (forensicStep.includes('artifact') || forensicStep.includes('consistent')),
@@ -91,13 +94,7 @@ describe('#2189: progress --forensic flag', () => {
});
test('forensic_audit step produces a CLEAN or INTEGRITY ISSUE(S) FOUND verdict', () => {
const workflow = fs.readFileSync(
path.join(__dirname, '..', 'gsd-core', 'workflows', 'progress.md'), 'utf8'
);
const forensicStep = workflow.slice(
workflow.indexOf('<step name="forensic_audit">'),
workflow.indexOf('</step>', workflow.indexOf('<step name="forensic_audit">'))
);
const forensicStep = fs.readFileSync(FORENSIC_AUDIT_STEP_PATH, 'utf8');
assert.ok(
forensicStep.includes('CLEAN'),
'forensic step should produce a CLEAN verdict when all checks pass'
@@ -109,14 +106,8 @@ describe('#2189: progress --forensic flag', () => {
});
test('forensic_audit step does not change default progress behavior', () => {
const workflow = fs.readFileSync(
path.join(__dirname, '..', 'gsd-core', 'workflows', 'progress.md'), 'utf8'
);
// The forensic step must explicitly say default behavior is unchanged
const forensicStep = workflow.slice(
workflow.indexOf('<step name="forensic_audit">'),
workflow.indexOf('</step>', workflow.indexOf('<step name="forensic_audit">'))
);
const forensicStep = fs.readFileSync(FORENSIC_AUDIT_STEP_PATH, 'utf8');
assert.ok(
forensicStep.includes('unchanged') || forensicStep.includes('standard report'),
'forensic step should clarify that default behavior is unchanged'

View File

@@ -7,6 +7,27 @@ const fs = require('fs');
const path = require('path');
const WORKFLOW = path.join(__dirname, '..', 'gsd-core', 'workflows', 'progress.md');
const PROGRESS_STEPS_DIR = path.join(__dirname, '..', 'gsd-core', 'workflows', 'progress', 'steps');
/**
* progress.md was fragmented (#2994) into gsd-core/workflows/progress/steps/*.md.
* The mvp_display step (and its MVP-mode display contract) now lives in
* progress/steps/mvp-display.md, only read at all when state:phase-mvp-mode is
* true. Read host + every step file combined so this contract guard keeps
* seeing the full picture regardless of which file the content physically
* lives in.
*/
function readProgressCombined() {
let combined = fs.readFileSync(WORKFLOW, 'utf8');
if (fs.existsSync(PROGRESS_STEPS_DIR)) {
for (const entry of fs.readdirSync(PROGRESS_STEPS_DIR).sort()) {
if (entry.endsWith('.md')) {
combined += '\n' + fs.readFileSync(path.join(PROGRESS_STEPS_DIR, entry), 'utf8');
}
}
}
return combined;
}
function parseProgressContract(content) {
const lines = content.split(/\r?\n/);
@@ -24,7 +45,7 @@ function parseProgressContract(content) {
}
describe('progress — MVP mode display', () => {
const contract = parseProgressContract(fs.readFileSync(WORKFLOW, 'utf-8'));
const contract = parseProgressContract(readProgressCombined());
test('workflow declares MVP_MODE branch', () => {
assert.ok(contract.hasMvpModeVariable, 'must declare MVP_MODE');

View File

@@ -77,13 +77,39 @@ const fs = require('fs');
const path = require('path');
const QUICK_MD = path.join(__dirname, '..', 'gsd-core', 'workflows', 'quick.md');
const REPO_ROOT_FOR_SECTIONS = path.join(__dirname, '..');
/**
* #2994 fragmentization moved Steps 4.5/4.75/5.5/5.6/6.5 out of quick.md into
* gsd-core/workflows/quick/steps/*.md behind section markers. A plain
* `content.indexOf('Step 5.6')` against the host now matches an unrelated
* later CROSS-REFERENCE to "Step 5.6" (a mention inside the worktree-branch
* guard prose), not the real heading — silently vacuous, since that
* mis-anchored slice still happens to contain USE_WORKTREES/commit_docs/
* PLAN.md/git add/git commit substrings from unrelated surrounding text.
* Expand each `<!-- gsd:section id="X" ... -->...<!-- /gsd:section -->`
* marker IN PLACE with its step file's content so Step 5.5/5.6/6/6.5 land
* back at their original logical positions and ordering/slicing assertions
* see the real step bodies again.
*/
function expandWorkflowSections(workflowPath) {
const raw = fs.readFileSync(workflowPath, 'utf8');
const markerRe = /<!-- gsd:section id="[\w-]+" when="[^"]*" -->[\s\S]*?<!-- \/gsd:section -->/g;
return raw.replace(markerRe, (block) => {
const pathMatch = block.match(/`([^`]+\.md)`/);
if (!pathMatch) return block;
const stepPath = path.join(REPO_ROOT_FOR_SECTIONS, pathMatch[1]);
if (!fs.existsSync(stepPath)) return block;
return fs.readFileSync(stepPath, 'utf8');
});
}
describe('quick.md pre-dispatch PLAN.md commit (#2432)', () => {
let content;
test('quick.md exists', () => {
assert.ok(fs.existsSync(QUICK_MD), 'gsd-core/workflows/quick.md must exist');
content = fs.readFileSync(QUICK_MD, 'utf-8');
content = expandWorkflowSections(QUICK_MD);
});
test('Step 5.6 exists between Step 5.5 and Step 6', () => {

View File

@@ -16,6 +16,27 @@ const { runGsdTools, createTempProject, cleanup, absPlanningPath } = require('./
const COMMANDS_DIR = path.join(__dirname, '..', 'commands', 'gsd');
const WORKFLOWS_DIR = path.join(__dirname, '..', 'gsd-core', 'workflows');
const REPO_ROOT_FOR_SECTIONS = path.join(__dirname, '..');
/**
* #2994 fragmentization moved Steps 4.5/4.75/5.5/5.6/6.5 out of quick.md into
* gsd-core/workflows/quick/steps/*.md behind section markers. Position-based
* slicing here (`indexOf('Step 4.75')` .. `indexOf('Step 5:')`, etc.) needs
* those step headings back at their original logical position, not appended
* at the end — so expand each `<!-- gsd:section id="X" ... -->...
* <!-- /gsd:section -->` marker IN PLACE with its step file's content.
*/
function expandWorkflowSections(workflowPath) {
const raw = fs.readFileSync(workflowPath, 'utf8');
const markerRe = /<!-- gsd:section id="[\w-]+" when="[^"]*" -->[\s\S]*?<!-- \/gsd:section -->/g;
return raw.replace(markerRe, (block) => {
const pathMatch = block.match(/`([^`]+\.md)`/);
if (!pathMatch) return block;
const stepPath = path.join(REPO_ROOT_FOR_SECTIONS, pathMatch[1]);
if (!fs.existsSync(stepPath)) return block;
return fs.readFileSync(stepPath, 'utf8');
});
}
// ─────────────────────────────────────────────────────────────────────────────
// Command frontmatter: --research flag advertised
@@ -67,11 +88,11 @@ describe('quick workflow: research step', () => {
test('workflow file exists', () => {
assert.ok(fs.existsSync(workflowPath), 'workflows/quick.md should exist');
content = fs.readFileSync(workflowPath, 'utf-8');
content = expandWorkflowSections(workflowPath);
});
test('purpose mentions --research flag', () => {
content = fs.readFileSync(workflowPath, 'utf-8');
content = expandWorkflowSections(workflowPath);
const purposeMatch = content.match(/<purpose>([\s\S]*?)<\/purpose>/);
assert.ok(purposeMatch, 'should have <purpose> section');
assert.ok(
@@ -81,7 +102,7 @@ describe('quick workflow: research step', () => {
});
test('step 1 parses --research flag', () => {
content = fs.readFileSync(workflowPath, 'utf-8');
content = expandWorkflowSections(workflowPath);
assert.ok(
content.includes('$RESEARCH_MODE'),
'workflow should reference $RESEARCH_MODE variable'
@@ -89,7 +110,7 @@ describe('quick workflow: research step', () => {
});
test('step 4.75 research phase exists', () => {
content = fs.readFileSync(workflowPath, 'utf-8');
content = expandWorkflowSections(workflowPath);
assert.ok(
content.includes('Step 4.75'),
'workflow should contain Step 4.75 (research phase)'
@@ -97,7 +118,7 @@ describe('quick workflow: research step', () => {
});
test('research step spawns gsd-phase-researcher', () => {
content = fs.readFileSync(workflowPath, 'utf-8');
content = expandWorkflowSections(workflowPath);
const researchSection = content.substring(
content.indexOf('Step 4.75'),
content.indexOf('Step 5:')
@@ -109,7 +130,7 @@ describe('quick workflow: research step', () => {
});
test('research step writes RESEARCH.md', () => {
content = fs.readFileSync(workflowPath, 'utf-8');
content = expandWorkflowSections(workflowPath);
const researchSection = content.substring(
content.indexOf('Step 4.75'),
content.indexOf('Step 5:')
@@ -121,7 +142,7 @@ describe('quick workflow: research step', () => {
});
test('planner context includes RESEARCH.md when research mode', () => {
content = fs.readFileSync(workflowPath, 'utf-8');
content = expandWorkflowSections(workflowPath);
const plannerSection = content.substring(
content.indexOf('Step 5: Spawn planner'),
content.indexOf('Step 5.5')
@@ -133,7 +154,7 @@ describe('quick workflow: research step', () => {
});
test('file commit list includes RESEARCH.md', () => {
content = fs.readFileSync(workflowPath, 'utf-8');
content = expandWorkflowSections(workflowPath);
const commitSection = content.substring(
content.indexOf('Step 8:'),
content.indexOf('</process>')
@@ -145,7 +166,7 @@ describe('quick workflow: research step', () => {
});
test('success criteria includes research items', () => {
content = fs.readFileSync(workflowPath, 'utf-8');
content = expandWorkflowSections(workflowPath);
const criteriaMatch = content.match(/<success_criteria>([\s\S]*?)<\/success_criteria>/);
assert.ok(criteriaMatch, 'should have <success_criteria> section');
assert.ok(

View File

@@ -0,0 +1,163 @@
'use strict';
// allow-test-rule: source-text-is-the-product see #2994 — G5 below asserts that an
// extracted step file's raw text does not contain the literal resolver call
// for the SAME fact that gates its own admission (a circular, self-disabling
// section). There is no typed/structural representation of "this step body
// re-derives its own gate" other than the text itself; G2 asserts a specific
// resolver variable's source line is retained verbatim because a STILL-LIVE
// consumer elsewhere in the same host file reads it literally.
/**
* Resolver-hoist guard — #2994 (epic #1671 Phase 6.3) matrix §G
* (`.gsd/phase/chore-2994-fragment-model-workflows/50-test-matrix.md`):
* "did the hoist break the ungated path?" and "did the hoist actually
* happen?" — a gated section whose own extracted body STILL resolves its own
* gating fact is a circular, self-disabling section (the `plan-phase.md:125-158`
* precedent named in `40-design.md`'s negative space).
*
* G5 generalizes G1-G4 into ONE data-driven assertion over the SHIPPED
* `gsd-core/workflows/section-manifest.json`, so a FUTURE extraction cannot
* reintroduce the class without this test catching it: for every gated
* section whose `when=` atom has a documented, config-key-backed (or
* detector-backed) resolution — per `src/section-manifest.cts`'s
* `InvocationFacts` JSDoc and `src/init.cts`'s `detect*`/`buildSectionManifestField`
* comments, both read directly rather than re-guessed — its own step file
* must not contain the literal resolver call for that SAME fact. Where no
* generic rule is derivable (e.g. `state:workstream-active`'s env/pointer
* resolution, `state:is-monorepo`'s glob-detector, `state:next-channel`'s
* flag-only resolution), the atom is simply absent from the map below and
* skipped — per the matrix's own "where a generic rule is impossible, encode
* the specific known pairs" instruction.
*/
const { describe, test } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('fs');
const path = require('path');
const ROOT = path.join(__dirname, '..');
const SHIPPED_MANIFEST_PATH = path.join(ROOT, 'gsd-core', 'workflows', 'section-manifest.json');
function readShippedManifest() {
return JSON.parse(fs.readFileSync(SHIPPED_MANIFEST_PATH, 'utf8'));
}
/**
* Concatenated content of every ```-fenced code block in `content` (fence
* markers themselves excluded). A LIVE resolver call (an actual `gsd_run
* query ...`/`gsd_run loop ...` invocation the runtime would execute) only
* ever appears inside a fenced bash block; a PROSE mention of the same
* config key or command name (e.g. documenting why a resolver was
* deliberately removed, or naming a config key in a sentence) appears in
* single-backtick inline code within ordinary prose, never inside a fence.
* Restricting the forbidden-substring search below to fenced content is what
* lets G5 tell "this step file still executes the resolver" apart from
* "this step file's prose merely talks about the resolver" — both
* `mvp-display.md` and `reviewer-instances-note-1.md` do the latter
* (explaining, in prose, why they do NOT re-resolve), which a plain
* whole-file substring search cannot distinguish from the former.
*/
function fencedCodeOnly(content) {
const parts = content.split('```');
// parts[0] is text before the first fence, parts[1] is inside the first
// fence, parts[2] is between fence 1's close and fence 2's open, etc. —
// odd indices are fence interiors (matches this repo's other fence-aware
// helpers' `%2===1` convention).
return parts.filter((_, i) => i % 2 === 1).join('\n');
}
// ─── G5: no gated section's own step file resolves its own gating fact ────
/**
* Known {when atom -> forbidden literal substring(s)} pairs: the exact
* resolver invocation each atom's fact is documented as being hoisted FROM
* (`src/init.cts`'s `detectFallowConfig`/`detectGitCreateTag`/`readConfigJsonBoolean`
* call sites, and `src/section-manifest.cts`'s matching `InvocationFacts`
* JSDoc for each field). A step file gated on the atom must never contain
* that substring — if it does, the section re-derives the same fact its own
* marker already gates on, instead of consuming the init-resolved value.
*/
const ATOM_FORBIDDEN_RESOLVER_SUBSTRINGS = Object.freeze({
'state:fallow-enabled': ['code_quality.fallow.enabled'],
'state:git-create-tag': ['git.create_tag'],
'state:chunked-mode': ['workflow.plan_chunked'],
'state:worktrees-enabled': ['workflow.use_worktrees'],
'state:reviewer-instances-configured': ['review.reviewer_instances'],
'state:phase-mvp-mode': ['phase.mvp-mode'],
// #2994: detectUiPhaseActive (src/init.cts) resolves this fact via
// `resolveLoopHooks({point:'plan:pre', ...})` — the CLI-facing equivalent
// of that SAME resolution is `gsd_run loop render-hooks plan:pre`.
'state:ui-phase-active': ['render-hooks plan:pre'],
});
describe('no gated section resolves its own gating fact (matrix row G5, data-driven over the shipped manifest)', () => {
const manifest = readShippedManifest();
for (const [workflow, sections] of Object.entries(manifest.workflows)) {
for (const section of sections) {
const forbidden = ATOM_FORBIDDEN_RESOLVER_SUBSTRINGS[section.when];
if (!forbidden) continue; // no generic rule for this atom — see module doc comment.
test(`${workflow}.md § "${section.id}" (when="${section.when}") does not re-resolve its own gate`, () => {
const stepFilePath = path.join(ROOT, section.read);
const stepContent = fencedCodeOnly(fs.readFileSync(stepFilePath, 'utf8'));
for (const token of forbidden) {
assert.ok(
!stepContent.includes(token),
`${section.read} (gated on "${section.when}") must not itself call the resolver for that fact ` +
`(found forbidden substring "${token}") — this is a circular, self-disabling section: the init-resolved ` +
`fact already gates this file's own inclusion, so re-deriving it inside the file is dead weight at best ` +
`and a divergent second resolver at worst`,
);
}
});
}
}
test('the forbidden-substring map itself covers at least one atom actually present in the shipped manifest (anti-vacuity)', () => {
const usedAtoms = new Set();
for (const sections of Object.values(manifest.workflows)) {
for (const section of sections) usedAtoms.add(section.when);
}
const coveredAndUsed = Object.keys(ATOM_FORBIDDEN_RESOLVER_SUBSTRINGS).filter((atom) => usedAtoms.has(atom));
assert.ok(coveredAndUsed.length > 0, 'the forbidden-substring map must exercise at least one atom the shipped manifest actually gates a section on');
});
});
// ─── G2: autonomous.md's $PLAN_STRATEGY resolver survives the converge hoist
// because ungated "local" bullets still consume it literally ─────────────
describe('autonomous.md retains its $PLAN_STRATEGY resolver — still consumed by ungated content (matrix row G2)', () => {
const filePath = path.join(ROOT, 'gsd-core', 'workflows', 'autonomous.md');
const content = fs.readFileSync(filePath, 'utf8');
test('the PLAN_STRATEGY resolver (local/converge assignment) is present verbatim', () => {
assert.ok(content.includes('PLAN_STRATEGY="local"'), 'the default-local assignment must survive the converge-hoist');
assert.ok(content.includes('PLAN_STRATEGY="converge"'), 'the converge-branch assignment must survive the converge-hoist');
});
test('an ungated "PLAN_STRATEGY=local" consumer exists OUTSIDE any converge-* gated section', () => {
const { parseWorkflowSections } = require('../gsd-core/bin/lib/workflow-fragments.cjs');
const sections = parseWorkflowSections(content, 'gsd-core/workflows/autonomous.md');
const CONSUMER_TEXT = 'If `PLAN_STRATEGY=local`, run the regular planner:';
assert.ok(content.includes(CONSUMER_TEXT), 'the ungated local-strategy consumer text must exist in the host file');
const gatedConvergeSections = sections.filter((s) => s.explicit && s.when === 'state:plan-strategy-converge');
assert.ok(gatedConvergeSections.length > 0, 'sanity: autonomous.md must have at least one state:plan-strategy-converge gated section');
for (const gated of gatedConvergeSections) {
assert.ok(
!gated.body.includes(CONSUMER_TEXT),
`the ungated PLAN_STRATEGY=local consumer must not live inside gated section "${gated.id}" — deleting that section must never remove the local-planning path`,
);
}
// Deleting the resolver would break this ungated consumer — assert the
// resolver assignment and the consumer are BOTH present outside every
// converge-gated section (i.e. in the always-composed remainder).
const gapText = sections.filter((s) => !s.explicit).map((s) => s.body).join('');
assert.ok(gapText.includes('PLAN_STRATEGY="local"'));
assert.ok(gapText.includes(CONSUMER_TEXT));
});
});

View File

@@ -0,0 +1,310 @@
'use strict';
/**
* Prod-shape integration tests for `src/init.cts`'s `buildSectionManifestField`
* seam (compiled into `gsd-core/bin/lib/init.cjs`) — #2994 (epic #1671 Phase
* 6.3), `.gsd/phase/chore-2994-fragment-model-workflows/50-test-matrix.md`
* sections B (compound-fact folding), D (flag value shapes), and E (the
* `null` vs `[]` distinction).
*
* Every test drives the REAL CLI (`runGsdTools`, spawns `gsd-tools.cjs`) —
* never a direct `require()` of `cmdInit*`, since `buildSectionManifestField`
* itself is not exported and the whole point of this suite is proving the
* flag/config plumbing that only exists at the CLI seam (`init-command-router.cjs`'s
* `parseNamedArgs` calls) actually reaches `section_manifest`.
*
* Group E uses the `GSD_SECTION_MANIFEST` env override
* (`gsd-core/bin/lib/init.cjs`'s `_sectionManifestCandidatePath`) to point the
* CLI at a temp fixture manifest instead of the shipped
* `gsd-core/workflows/section-manifest.json` — this is the ONLY way to
* exercise "workflow absent from the manifest" today, because every
* `cmdInit*` this repo currently wires (#2994's own completion) already has
* an explicit key in the shipped artifact; Groups B and D deliberately do NOT
* override it, driving the shipped manifest directly per the matrix's own
* "drive from the shipped artifact wherever possible" rule.
*/
const { describe, test, beforeEach, afterEach } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('fs');
const path = require('path');
const { runGsdTools, cleanup, createTempDir, createTempProject } = require('./helpers.cjs');
const { seedPhase } = require('./fixtures/index.cjs');
function writeConfig(tmpDir, config) {
fs.mkdirSync(path.join(tmpDir, '.planning'), { recursive: true });
fs.writeFileSync(path.join(tmpDir, '.planning', 'config.json'), JSON.stringify(config, null, 2));
}
function runInitJson(argv, cwd, env = {}) {
const result = runGsdTools(argv, cwd, env);
assert.ok(result.success, `Command failed: ${result.error}`);
return JSON.parse(result.output);
}
// ─── Group E: the null vs [] distinction (matrix rows E1/E2/E3/E5/E6) ──────
describe('section_manifest: null (degraded) vs [] (computed, nothing applicable) — matrix §E', () => {
let tmpDir;
let manifestDir;
beforeEach(() => {
tmpDir = createTempProject('section-manifest-e-');
manifestDir = createTempDir('section-manifest-e-manifest-');
});
afterEach(() => {
cleanup(tmpDir);
cleanup(manifestDir);
});
test('a manifest with no "quick" key degrades section_manifest to null (row E1)', () => {
const manifestPath = path.join(manifestDir, 'manifest.json');
fs.writeFileSync(manifestPath, JSON.stringify({ workflows: {} }));
const output = runInitJson(['init', 'quick', 'desc'], tmpDir, { GSD_SECTION_MANIFEST: manifestPath });
assert.equal(output.section_manifest, null, 'an absent workflow key must degrade to null, never []');
});
test('a manifest with an explicit empty "quick" section list yields a computed [] (row E2)', () => {
const manifestPath = path.join(manifestDir, 'manifest.json');
fs.writeFileSync(manifestPath, JSON.stringify({ workflows: { quick: [] } }));
const output = runInitJson(['init', 'quick', 'desc'], tmpDir, { GSD_SECTION_MANIFEST: manifestPath });
assert.notEqual(output.section_manifest, null, 'a present (even empty) workflow key must NOT degrade to null');
assert.deepEqual(output.section_manifest.included, []);
assert.deepEqual(output.section_manifest.excluded, []);
assert.deepEqual(output.section_manifest.read, []);
});
test('null and the computed [] shape are DIFFERENT values, not interchangeable (row E3)', () => {
const absentKeyManifest = path.join(manifestDir, 'absent.json');
fs.writeFileSync(absentKeyManifest, JSON.stringify({ workflows: {} }));
const emptyListManifest = path.join(manifestDir, 'empty.json');
fs.writeFileSync(emptyListManifest, JSON.stringify({ workflows: { quick: [] } }));
const degraded = runInitJson(['init', 'quick', 'desc'], tmpDir, { GSD_SECTION_MANIFEST: absentKeyManifest });
const computed = runInitJson(['init', 'quick', 'desc'], tmpDir, { GSD_SECTION_MANIFEST: emptyListManifest });
assert.equal(degraded.section_manifest, null);
assert.notEqual(computed.section_manifest, null);
assert.notDeepEqual(degraded.section_manifest, computed.section_manifest, 'null must never compare equal to the computed {included:[],excluded:[],...} shape');
});
test('a section list where every when= evaluates false is still the computed [] shape, not null (row E2, second case)', () => {
// "Applicable" is a FACTS question, not a shape question: a real section
// whose gating flag was simply never passed on this invocation must still
// be the computed {included:[],...} object, never collapse to null.
const manifestPath = path.join(manifestDir, 'manifest.json');
fs.writeFileSync(manifestPath, JSON.stringify({
workflows: {
quick: [{ id: 'discussion-phase', when: 'flag:--discuss', read: 'gsd-core/workflows/quick/steps/discussion-phase.md' }],
},
}));
const output = runInitJson(['init', 'quick', 'desc'], tmpDir, { GSD_SECTION_MANIFEST: manifestPath });
assert.notEqual(output.section_manifest, null);
assert.deepEqual(output.section_manifest.included, []);
assert.deepEqual(output.section_manifest.excluded, ['discussion-phase']);
});
test('a missing manifest file degrades to null without throwing (row E5)', () => {
const neverCreatedPath = path.join(manifestDir, 'does-not-exist.json');
assert.equal(fs.existsSync(neverCreatedPath), false, 'sanity: file must not exist');
const result = runGsdTools(['init', 'quick', 'desc'], tmpDir, { GSD_SECTION_MANIFEST: neverCreatedPath });
assert.ok(result.success, `a missing manifest file must not crash the command: ${result.error}`);
const output = JSON.parse(result.output);
assert.equal(output.section_manifest, null);
});
test('a malformed-JSON manifest file degrades to null without throwing (row E6)', () => {
const manifestPath = path.join(manifestDir, 'malformed.json');
fs.writeFileSync(manifestPath, '{ this is not valid JSON');
const result = runGsdTools(['init', 'quick', 'desc'], tmpDir, { GSD_SECTION_MANIFEST: manifestPath });
assert.ok(result.success, `a malformed manifest file must not crash the command: ${result.error}`);
const output = JSON.parse(result.output);
assert.equal(output.section_manifest, null);
});
});
// ─── Group B: compound-fact folding lives in init.cts, never the grammar ──
describe('compound-fact folding at the init seam — matrix §B', () => {
let tmpDir;
beforeEach(() => {
tmpDir = createTempProject('section-manifest-b-');
});
afterEach(() => {
cleanup(tmpDir);
});
// B1-B4: cmdInitQuick folds --full into --discuss/--research/--validate BEFORE
// buildSectionManifestField evaluates flag:--discuss/flag:--research/flag:--validate.
describe('cmdInitQuick folds --full into each mode fact (rows B1-B4)', () => {
test('--discuss alone includes discussion-phase (row B1, baseline)', () => {
const output = runInitJson(['init', 'quick', 'desc', '--discuss'], tmpDir);
assert.ok(output.section_manifest.included.includes('discussion-phase'));
});
test('--full alone (no --discuss) STILL includes discussion-phase — the fold this PR exists to prove (row B2)', () => {
const output = runInitJson(['init', 'quick', 'desc', '--full'], tmpDir);
assert.ok(
output.section_manifest.included.includes('discussion-phase'),
'--full must imply --discuss for section-gating purposes, folded in cmdInitQuick before evaluation',
);
assert.ok(output.section_manifest.included.includes('research-phase'), '--full must also imply --research');
assert.ok(output.section_manifest.included.includes('plan-checker-loop'), '--full must also imply --validate (plan-checker-loop)');
assert.ok(output.section_manifest.included.includes('quick-verification'), '--full must also imply --validate (quick-verification)');
});
test('neither --discuss nor --full excludes discussion-phase (row B3)', () => {
const output = runInitJson(['init', 'quick', 'desc'], tmpDir);
assert.ok(output.section_manifest.excluded.includes('discussion-phase'));
});
test('--full AND --discuss together still include discussion-phase exactly once, no double-counting (row B4)', () => {
const output = runInitJson(['init', 'quick', 'desc', '--full', '--discuss'], tmpDir);
const occurrences = output.section_manifest.included.filter((id) => id === 'discussion-phase');
assert.equal(occurrences.length, 1, 'discussion-phase must appear exactly once in included, never duplicated by the fold');
});
});
// B5-B6: cmdInitAutonomous folds --converge OR --cross-ai into one plan_strategy_converge fact.
describe('cmdInitAutonomous folds --converge OR --cross-ai (rows B5-B6)', () => {
test('--converge alone drives state:plan-strategy-converge sections into included (row B5)', () => {
const output = runInitJson(['init', 'autonomous', '--converge'], tmpDir);
assert.equal(output.plan_strategy_converge, true);
assert.ok(output.section_manifest.included.includes('converge-fail-fast'));
assert.ok(output.section_manifest.included.includes('converge-banner'));
});
test('--cross-ai alone (the documented alias, no --converge) ALSO drives inclusion (row B6)', () => {
const output = runInitJson(['init', 'autonomous', '--cross-ai'], tmpDir);
assert.equal(output.plan_strategy_converge, true);
assert.ok(output.section_manifest.included.includes('converge-fail-fast'));
assert.ok(output.section_manifest.included.includes('converge-loop'));
});
test('neither flag excludes every converge-* section', () => {
const output = runInitJson(['init', 'autonomous'], tmpDir);
assert.equal(output.plan_strategy_converge, false);
for (const id of ['converge-fail-fast', 'converge-banner', 'converge-dispatch-bg', 'converge-dispatch-inline', 'converge-loop']) {
assert.ok(output.section_manifest.excluded.includes(id), `expected "${id}" excluded when neither --converge nor --cross-ai is set`);
}
});
});
// B7: cmdInitUpdate folds --next OR --rc into one next_channel fact.
describe('cmdInitUpdate folds --next OR --rc (row B7)', () => {
test('--rc alone (not --next) drives channel-banner into included', () => {
const output = runInitJson(['init', 'update', '--rc'], tmpDir);
assert.equal(output.next_channel, true);
assert.ok(output.section_manifest.included.includes('channel-banner'));
});
test('--next alone also drives channel-banner into included (the other half of the OR)', () => {
const output = runInitJson(['init', 'update', '--next'], tmpDir);
assert.equal(output.next_channel, true);
assert.ok(output.section_manifest.included.includes('channel-banner'));
});
test('neither flag excludes channel-banner', () => {
const output = runInitJson(['init', 'update'], tmpDir);
assert.equal(output.next_channel, false);
assert.ok(output.section_manifest.excluded.includes('channel-banner'));
});
});
// B8: cmdInitDiscussPhaseAssumptions folds --auto OR consolidated auto-mode config.
describe('cmdInitDiscussPhaseAssumptions folds --auto flag OR config auto-mode (row B8)', () => {
test('no --auto flag, but config workflow._auto_chain_active=true, still includes auto-advance-dispatch', () => {
writeConfig(tmpDir, { workflow: { _auto_chain_active: true } });
const output = runInitJson(['init', 'discuss-phase-assumptions', '99'], tmpDir);
assert.ok(output.section_manifest.included.includes('auto-advance-dispatch'));
});
test('no --auto flag, but config workflow.auto_advance=true, still includes auto-advance-dispatch (the other config source)', () => {
writeConfig(tmpDir, { workflow: { auto_advance: true } });
const output = runInitJson(['init', 'discuss-phase-assumptions', '99'], tmpDir);
assert.ok(output.section_manifest.included.includes('auto-advance-dispatch'));
});
test('--auto flag present with no config also includes auto-advance-dispatch', () => {
const output = runInitJson(['init', 'discuss-phase-assumptions', '99', '--auto'], tmpDir);
assert.ok(output.section_manifest.included.includes('auto-advance-dispatch'));
});
test('neither --auto nor either config key excludes auto-advance-dispatch', () => {
const output = runInitJson(['init', 'discuss-phase-assumptions', '99'], tmpDir);
assert.ok(output.section_manifest.excluded.includes('auto-advance-dispatch'));
});
});
});
// ─── Group D: flag value shapes — token presence, not value truthiness ────
describe('flag value shapes drive section_manifest by truthiness, not semantic value — matrix §D', () => {
let tmpDir;
beforeEach(() => {
tmpDir = createTempProject('section-manifest-d-');
seedPhase(tmpDir, '01-sample', { '01-CONTEXT.md': '# Phase Context\n' });
});
afterEach(() => {
cleanup(tmpDir);
});
test('absent boolean flag (--reviews never passed) does not mark reviews-prerequisite present (row D1)', () => {
const output = runInitJson(['init', 'plan-phase', '1'], tmpDir);
assert.ok(output.section_manifest.excluded.includes('reviews-prerequisite'));
assert.ok(!output.section_manifest.included.includes('reviews-prerequisite'));
});
test('empty-string value flag (--prd "") excludes prd-express-gate — an empty value is not a branch selection (row D2)', () => {
const output = runInitJson(['init', 'plan-phase', '1', '--prd', ''], tmpDir);
assert.ok(output.section_manifest.excluded.includes('prd-express-gate'));
});
test('string-"0" value flag (--prd "0") INCLUDES prd-express-gate — token-presence semantics, not value truthiness (row D3)', () => {
const output = runInitJson(['init', 'plan-phase', '1', '--prd', '0'], tmpDir);
assert.ok(output.section_manifest.included.includes('prd-express-gate'), '--prd "0" is a non-empty string and must be treated as present');
});
test('whitespace-only value flag (--prd " ") includes prd-express-gate, asserted deliberately (row D4)', () => {
const output = runInitJson(['init', 'plan-phase', '1', '--prd', ' '], tmpDir);
assert.ok(output.section_manifest.included.includes('prd-express-gate'));
});
test('duplicate flag tokens (--prd a --prd b) resolve to a single membership, no crash (row D5)', () => {
const result = runGsdTools(['init', 'plan-phase', '1', '--prd', 'a', '--prd', 'b'], tmpDir);
assert.ok(result.success, `duplicate --prd tokens must not crash the command: ${result.error}`);
const output = JSON.parse(result.output);
assert.ok(output.section_manifest.included.includes('prd-express-gate'));
const occurrences = output.section_manifest.included.filter((id) => id === 'prd-express-gate');
assert.equal(occurrences.length, 1, 'must be a single membership, not one entry per duplicate token');
});
test('flag-shaped value (--prd --weird) does not crash and deterministically excludes prd-express-gate (row D6)', () => {
// parseNamedArgs treats a token starting with "--" as the NEXT flag, never
// as this flag's value — so --prd here resolves to null (absent), not the
// literal string "--weird".
const result = runGsdTools(['init', 'plan-phase', '1', '--prd', '--weird'], tmpDir);
assert.ok(result.success, `a flag-shaped value must not crash the command: ${result.error}`);
const output = JSON.parse(result.output);
assert.ok(output.section_manifest.excluded.includes('prd-express-gate'), '--prd immediately followed by another --flag token must resolve to absent, per parseNamedArgs');
});
test('unicode / very long value does not crash and is included as present (row D7)', () => {
const longUnicodeValue = `产品需求文档-🚀-${'x'.repeat(2000)}`;
const result = runGsdTools(['init', 'plan-phase', '1', '--prd', longUnicodeValue], tmpDir);
assert.ok(result.success, `a unicode/very-long --prd value must not crash the command: ${result.error}`);
const output = JSON.parse(result.output);
assert.ok(output.section_manifest.included.includes('prd-express-gate'));
});
});

View File

@@ -86,6 +86,24 @@ const factsArb = fc.record({
// InvocationFacts — must also participate in the partition/totality
// properties below, not just the six pre-existing fact keys.
chunkedMode: fc.boolean(),
// #2994 (epic #1671 Phase 6.3, matrix §K gap-fill): ten further boolean
// facts shipped alongside the widened WHEN_VOCABULARY (30 atoms total).
// Without these, `whenArb` (drawn from the FULL WHEN_VOCABULARY) can still
// generate sections gated on e.g. `state:plan-strategy-converge`, but every
// generated `facts` value left that field `undefined` — falsy under
// totality — so those sections were NEVER driven into `included` by this
// property, silently narrowing the partition/order-preservation coverage
// to the pre-#2994 atom set even though `whenArb` claimed full coverage.
uiPhaseActive: fc.boolean(),
fallowEnabled: fc.boolean(),
gitCreateTag: fc.boolean(),
planStrategyConverge: fc.boolean(),
reviewerInstancesConfigured: fc.boolean(),
autoAdvanceActive: fc.boolean(),
isMonorepo: fc.boolean(),
nextChannel: fc.boolean(),
workstreamActive: fc.boolean(),
flatMode: fc.boolean(),
});
// ─── Row 25: exact partition ────────────────────────────────────────────────
@@ -136,8 +154,12 @@ describe('property: never throws for vocabulary-valid when and arbitrary facts',
test('neverThrowsWhenFactsAreMissingKeysEntirely', () => {
// Totality also over PARTIAL facts objects (row 19's property-level
// twin): dropping zero or more of the seven fact keys must never throw.
const factKeys = ['flags', 'phaseNumber', 'hasPriorPhases', 'needsCodebaseMap', 'phaseMvpMode', 'worktreesEnabled', 'chunkedMode'];
// twin): dropping zero or more of the fact keys must never throw.
const factKeys = [
'flags', 'phaseNumber', 'hasPriorPhases', 'needsCodebaseMap', 'phaseMvpMode', 'worktreesEnabled', 'chunkedMode',
'uiPhaseActive', 'fallowEnabled', 'gitCreateTag', 'planStrategyConverge', 'reviewerInstancesConfigured',
'autoAdvanceActive', 'isMonorepo', 'nextChannel', 'workstreamActive', 'flatMode',
];
fc.assert(
fc.property(sectionsArb, factsArb, fc.subarray(factKeys), (sections, facts, keysToKeep) => {
const partialFacts = {};

View File

@@ -15,6 +15,8 @@
const { describe, test } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const { selectSections, WHEN_PREDICATES, REASON } = require('../gsd-core/bin/lib/section-manifest.cjs');
const { WHEN_VOCABULARY } = require('../gsd-core/bin/lib/workflow-fragments.cjs');
@@ -262,7 +264,7 @@ describe('flags set cardinality (#2992 row B14)', () => {
const sections = Object.freeze([
{ id: 'a', when: 'flag:--auto' },
{ id: 'b', when: 'flag:--discuss' },
{ id: 'c', when: 'flag:--full' },
{ id: 'c', when: 'flag:--fix' },
]);
test('zeroFlagsExcludesEveryFlagSection', () => {
@@ -276,7 +278,7 @@ describe('flags set cardinality (#2992 row B14)', () => {
});
test('manyFlagsIncludeEveryMatchingSection', () => {
const result = selectSections(sections, facts({ flags: new Set(['--auto', '--discuss', '--full', '--irrelevant']) }));
const result = selectSections(sections, facts({ flags: new Set(['--auto', '--discuss', '--fix', '--irrelevant']) }));
assert.deepEqual(result, { included: ['a', 'b', 'c'], excluded: [] });
});
});
@@ -450,6 +452,161 @@ describe('state:chunked-mode predicate (row A8)', () => {
// they were predicates, silently including the section or throwing an
// untyped error instead of failing closed with REASON.UNKNOWN_WHEN.
// ─── #2994 (epic #1671 Phase 6.3) matrix §A: atom admission — the gate-2 hole ──
//
// The two load-time parity guards above (rows 21-23) prove WHEN_VOCABULARY <->
// WHEN_PREDICATES symmetry — every atom has SOME predicate function. Neither
// proves the predicate's underlying FACT is ever computed by a real cmdInit*
// caller: an atom whose fact no caller assembles evaluates false forever,
// which is the silent-wrong-answer class this PR exists to prevent (matrix
// row A/#8). This block closes that hole by driving every one of the 30
// frozen atoms to both a TRUE and a FALSE outcome via a constructed
// InvocationFacts, entirely at this module's own boundary (never reaching
// into src/init.cts, which is covered separately in
// tests/section-manifest-init-facts.test.cjs).
/**
* Per-atom recipe: how to build an `InvocationFacts` fragment that drives
* `WHEN_PREDICATES[atom]` to `true`, and a second fragment that drives it to
* `false`. `flag` atoms derive their token mechanically (`atom.slice('flag:'.length)`,
* the same derivation B11 above already established as test-only-legal); every
* `state:` atom's backing fact FIELD is a hand-written literal here — same
* discipline as `WHEN_PREDICATES` itself (module doc comment "The evaluator is
* a LOOKUP, not a parser"): there is no mechanical way to derive
* `chunkedMode` from `"state:chunked-mode"` without inventing a tokenizer.
* `'always'` is the one structural exception: its predicate is `() => true`
* unconditionally (it backs every synthesized gap fragment), so no `facts`
* value can ever drive it to `false` — handled as its own branch below, not
* silently dropped from the 30-atom set A5 checks against.
*/
const STATE_ATOM_FACT_FIELD = Object.freeze({
'state:has-prior-phases': 'hasPriorPhases',
'state:auto-advance-active': 'autoAdvanceActive',
'state:chunked-mode': 'chunkedMode',
'state:fallow-enabled': 'fallowEnabled',
'state:git-create-tag': 'gitCreateTag',
'state:needs-codebase-map': 'needsCodebaseMap',
'state:phase-mvp-mode': 'phaseMvpMode',
'state:plan-strategy-converge': 'planStrategyConverge',
'state:reviewer-instances-configured': 'reviewerInstancesConfigured',
'state:ui-phase-active': 'uiPhaseActive',
'state:worktrees-enabled': 'worktreesEnabled',
'state:is-monorepo': 'isMonorepo',
'state:next-channel': 'nextChannel',
'state:workstream-active': 'workstreamActive',
'state:flat-mode': 'flatMode',
});
/** `state:gap-closure-phase` is the one atom keyed off `phaseNumber`, not a plain boolean field. */
const GAP_CLOSURE_ATOM = 'state:gap-closure-phase';
/** @returns {{truthy: object, falsy: object | null}} fact FRAGMENTS (merged onto `facts({})` by the caller) for `atom`; `falsy: null` marks the `'always'` exception. */
function factFragmentsFor(atom) {
if (atom === 'always') {
return { truthy: {}, falsy: null };
}
if (atom === GAP_CLOSURE_ATOM) {
return { truthy: { phaseNumber: '3.1' }, falsy: { phaseNumber: '3' } };
}
if (atom.startsWith('flag:--')) {
const token = atom.slice('flag:'.length);
return { truthy: { flags: new Set([token]) }, falsy: { flags: new Set() } };
}
const field = STATE_ATOM_FACT_FIELD[atom];
if (!field) {
throw new Error(`factFragmentsFor: atom "${atom}" has no fact-field mapping — update STATE_ATOM_FACT_FIELD`);
}
return { truthy: { [field]: true }, falsy: { [field]: false } };
}
describe('every when= atom is satisfiable by some InvocationFacts (#2994 matrix rows A1/A2/A5)', () => {
// A5 anti-vacuity: the atom set this describe block iterates must be
// non-empty AND exactly equal to WHEN_VOCABULARY's full 29-entry set — an
// A1/A2 loop over an empty or partial collection would be a green test
// proving nothing (ADR-1671 eval-gate rot, named explicitly in the design).
test('atom satisfiability set is non-empty and equals the full frozen WHEN_VOCABULARY (row A5)', () => {
assert.ok(WHEN_VOCABULARY.length > 0, 'WHEN_VOCABULARY must not be empty');
const coveredAtoms = [...WHEN_VOCABULARY].sort();
// Every atom must resolve via factFragmentsFor without throwing — this is
// the completeness check on STATE_ATOM_FACT_FIELD itself: an atom added
// to WHEN_VOCABULARY without a corresponding entry here throws loudly
// instead of silently narrowing the set A1/A2 below actually cover.
for (const atom of WHEN_VOCABULARY) {
assert.doesNotThrow(() => factFragmentsFor(atom), `factFragmentsFor must resolve "${atom}"`);
}
assert.deepEqual(coveredAtoms, [...WHEN_VOCABULARY].sort(), 'the covered atom set must be exactly WHEN_VOCABULARY');
assert.equal(WHEN_VOCABULARY.length, 29, 'sanity: the frozen vocabulary is expected at 29 entries for #2994 — update this literal alongside a deliberate vocabulary widening');
});
for (const atom of WHEN_VOCABULARY) {
test(`atom "${atom}" is included when its fact is true, excluded when false (rows A1/A2)`, () => {
const { truthy, falsy } = factFragmentsFor(atom);
const sections = [{ id: 'target', when: atom }];
// A1: some constructed InvocationFacts drives this section into `included`.
const includedResult = selectSections(sections, facts(truthy));
assert.deepEqual(includedResult, { included: ['target'], excluded: [] }, `expected "${atom}" included under its truthy facts`);
if (falsy === null) {
// The 'always' exception: no facts value can ever exclude it — assert
// that deliberately, rather than silently skipping the atom (which
// would violate A5's "set is non-empty" guarantee for THIS assertion).
const stillIncluded = selectSections(sections, facts({}));
assert.deepEqual(stillIncluded, { included: ['target'], excluded: [] }, '"always" must remain included under every facts value — it has no false branch');
return;
}
// A2: some constructed InvocationFacts drives the SAME section into `excluded`.
const excludedResult = selectSections(sections, facts(falsy));
assert.deepEqual(excludedResult, { included: [], excluded: ['target'] }, `expected "${atom}" excluded under its falsy facts`);
});
}
});
// ─── #2994 matrix row A3: no admitted atom lacks a consuming section ───────
//
// An atom present in WHEN_VOCABULARY with zero consuming `when=` markers
// anywhere in the SHIPPED gsd-core/workflows/section-manifest.json is dead
// vocabulary: cardinality without a section, the exact rot the frozen list's
// "no atom is admitted without a consumer, in the same commit" discipline
// exists to prevent (40-design.md "Vocabulary growth"). Driven from the real
// installed-shape artifact — never a hand-built fixture — per the matrix's
// prime directive ("a hand-built manifest proves a property no real caller
// exercises").
describe('no admitted atom lacks a consuming section in the shipped manifest (#2994 matrix row A3)', () => {
const SHIPPED_MANIFEST_PATH = path.join(__dirname, '..', 'gsd-core', 'workflows', 'section-manifest.json');
test('every WHEN_VOCABULARY atom is used as a when= value by at least one shipped section', () => {
const manifest = JSON.parse(fs.readFileSync(SHIPPED_MANIFEST_PATH, 'utf8'));
const usedAtoms = new Set();
for (const sections of Object.values(manifest.workflows)) {
for (const section of sections) {
usedAtoms.add(section.when);
}
}
// `always` is a NAMED, single exemption — not a blanket skip. It is the
// implicit gap-fragment default (a section with no `when=` marker at all
// is treated as unconditionally included) and is structural, not a
// marker atom: `gen-section-manifest.cjs`'s `explicitSections` filter
// (`s.explicit`) deliberately EXCLUDES it from the generated
// section-manifest.json, so no shipped section's `when=` value can ever
// literally read `"always"` — the atom is real (WHEN_PREDICATES and
// section-marker-parsing both accept it) but by design has zero
// consuming JSON entries.
const unusedAtoms = WHEN_VOCABULARY.filter((atom) => atom !== 'always' && !usedAtoms.has(atom));
// Deliberately UNWEAKENED per 50-test-matrix.md row A3's own instruction:
// "if any atom legitimately has no consumer today, do NOT weaken this
// test — report it to me instead, it is a real finding." No exclusion
// list beyond the single named `always` exemption above is applied here
// — if this assertion is red, that redness IS the finding, not a defect
// in the test.
assert.deepEqual(unusedAtoms, [], `dead vocabulary: these WHEN_VOCABULARY atoms have no consuming section anywhere in ${SHIPPED_MANIFEST_PATH}`);
});
});
describe('Object.prototype-shaped when= values fail closed (REASON.UNKNOWN_WHEN)', () => {
const HOSTILE_WHEN_VALUES = Object.freeze([
'constructor',

View File

@@ -0,0 +1,151 @@
'use strict';
// allow-test-rule: source-text-is-the-product see #2994 — this file asserts that
// specific prose/resolver LINES remain physically outside a `<!-- gsd:section
// -->` marker pair in the HOST workflow .md files. The deployed contract IS
// the exact text and its position relative to the marker boundary (a runtime
// that reads only the gated fragment must still see the flag-absent
// fallback), so asserting on rendered text is the only meaningful check here
// — a typed/structural assertion on `parseWorkflowSections` output could not
// distinguish "the fallback text survived, in the right gap" from "the
// fallback text was deleted entirely along with the surrounding gap".
/**
* Inversion-guard tests — #2994 (epic #1671 Phase 6.3) matrix §F, the
* highest-risk edit in the PR (`.gsd/phase/chore-2994-fragment-model-workflows/
* 50-test-matrix.md`): gating `discuss-phase-assumptions.md`'s `auto_advance`
* step WHOLE would delete the flag-absent fallback ("End here — confirm_creation
* already ran; do not route back to it."), removing text needed EXACTLY when
* `--auto` is absent — the inverse of what a `when=` gate is supposed to do.
* The design's split is at `:651/652`: only `auto-advance-dispatch` itself is
* gated (`state:auto-advance-active`); the resolvers above it and the
* flag-absent fallback below it stay outside the marker pair, in the host
* file, unconditionally composed regardless of `section_manifest`.
*
* The same class — "an always-relevant note living immediately adjacent to a
* gated section must not itself be swallowed by the marker" — applies to
* `verify-work.md`'s MVP false-branch note (row "Same class applies").
*
* Parses each host file with the REAL `parseWorkflowSections` (never a
* second ad-hoc marker scan) to get the marker's exact byte boundaries, then
* asserts the fallback/resolver text is NOT inside the gated section's body
* and IS present in the surrounding (always-composed) gap text.
*/
const { describe, test } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('fs');
const path = require('path');
const { parseWorkflowSections } = require('../gsd-core/bin/lib/workflow-fragments.cjs');
const WORKFLOWS_DIR = path.join(__dirname, '..', 'gsd-core', 'workflows');
function readWorkflow(name) {
const filePath = path.join(WORKFLOWS_DIR, `${name}.md`);
return { filePath, content: fs.readFileSync(filePath, 'utf8') };
}
/** The gated section's own body, by id — must equal exactly one parsed section. */
function bodyOf(sections, id) {
const match = sections.filter((s) => s.id === id);
assert.equal(match.length, 1, `expected exactly one parsed section with id "${id}"`);
return match[0].body;
}
/** Every gap-section body concatenated (i.e., everything OUTSIDE any explicit marker), preserving the "never inside a gate" question. */
function outsideGatedText(sections) {
return sections.filter((s) => !s.explicit).map((s) => s.body).join('');
}
// ─── F1/F2/F3: discuss-phase-assumptions.md's auto-advance-dispatch split ──
describe('discuss-phase-assumptions.md: the flag-absent fallback survives exclusion (matrix §F, highest-risk edit)', () => {
const { content } = readWorkflow('discuss-phase-assumptions');
const sections = parseWorkflowSections(content, 'gsd-core/workflows/discuss-phase-assumptions.md');
const GATED_ID = 'auto-advance-dispatch';
const FALLBACK_TEXT = 'End here — `confirm_creation` already ran; do not route back to it.';
const RESOLVER_MARKERS = [
'Parse `--auto` flag from $ARGUMENTS',
'gsd_run query config-set workflow._auto_chain_active',
'AUTO_MODE=$(gsd_run query check auto-mode --pick active',
];
test('the gated section exists and is when="state:auto-advance-active" (sanity)', () => {
const gated = sections.find((s) => s.id === GATED_ID);
assert.ok(gated, `expected a "${GATED_ID}" section to exist`);
assert.equal(gated.when, 'state:auto-advance-active');
});
test('the flag-absent "End here" fallback is present in the composed HOST output (row F1 — the inversion)', () => {
assert.ok(content.includes(FALLBACK_TEXT), 'the fallback text must exist verbatim in the host file at all');
});
test('the flag-absent fallback is NOT inside the gated section\'s own body (row F1/F3)', () => {
const gatedBody = bodyOf(sections, GATED_ID);
assert.ok(
!gatedBody.includes(FALLBACK_TEXT),
'the fallback must live OUTSIDE the gated marker — gating it away would delete the text needed exactly when --auto is absent',
);
});
test('the flag-absent fallback IS present in the unconditionally-composed gap text (row F2 — dispatch body included path also composes the surrounding text unconditionally)', () => {
const gapText = outsideGatedText(sections);
assert.ok(gapText.includes(FALLBACK_TEXT), 'the fallback must be part of an always-composed gap section, not lost between gaps');
});
test('every resolver line (--auto parse, config-set, AUTO_MODE read) lies OUTSIDE the gated marker (row F3)', () => {
const gatedBody = bodyOf(sections, GATED_ID);
for (const marker of RESOLVER_MARKERS) {
assert.ok(!gatedBody.includes(marker), `resolver text "${marker}" must not live inside the gated "${GATED_ID}" section`);
}
const gapText = outsideGatedText(sections);
for (const marker of RESOLVER_MARKERS) {
assert.ok(gapText.includes(marker), `resolver text "${marker}" must be present in the unconditionally-composed surrounding text`);
}
});
test('composing the document with every section INCLUDED still contains the fallback (row F2 sanity — inclusion never deletes it either)', () => {
// The stub instructs conditional reading of steps/auto-advance-dispatch.md,
// but the fallback sentence itself is literal HOST text either way — this
// asserts the host's raw byte content carries it once, regardless of
// section_manifest, which composeWorkflow (verbatim strategy, #2930)
// preserves unconditionally.
const occurrences = content.split(FALLBACK_TEXT).length - 1;
assert.equal(occurrences, 1, 'the fallback text must appear exactly once in the host file');
});
});
// ─── Same class: verify-work.md's MVP false-branch note ────────────────────
describe('verify-work.md: the MVP false-branch note survives exclusion of mvp-uat-framing (same class as §F)', () => {
const { content } = readWorkflow('verify-work');
const sections = parseWorkflowSections(content, 'gsd-core/workflows/verify-work.md');
const GATED_ID = 'mvp-uat-framing';
const FALSE_BRANCH_NOTE = 'When `MVP_MODE=false` (mode is null, absent, or the phase has no `**Mode:**` line in ROADMAP.md), fall back to the standard UAT generation path — no behavioral change.';
test('the gated section exists and is when="state:phase-mvp-mode" (sanity)', () => {
const gated = sections.find((s) => s.id === GATED_ID);
assert.ok(gated, `expected a "${GATED_ID}" section to exist`);
assert.equal(gated.when, 'state:phase-mvp-mode');
});
test('the MVP=false fallback note is present in the host file at all', () => {
assert.ok(content.includes(FALSE_BRANCH_NOTE));
});
test('the MVP=false fallback note is NOT inside the gated mvp-uat-framing section body', () => {
const gatedBody = bodyOf(sections, GATED_ID);
assert.ok(
!gatedBody.includes(FALSE_BRANCH_NOTE),
'gating mvp-uat-framing away (MVP_MODE=false) must not delete the note that documents exactly that fallback',
);
});
test('the MVP=false fallback note IS present in the unconditionally-composed gap text', () => {
const gapText = outsideGatedText(sections);
assert.ok(gapText.includes(FALSE_BRANCH_NOTE));
});
});

View File

@@ -26,6 +26,7 @@ const { test, describe } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('fs');
const path = require('path');
const { readWorkflowCombined } = require('./helpers.cjs');
const WORKFLOWS_DIR = path.join(__dirname, '..', 'gsd-core', 'workflows');
const LIVENESS_PHRASE = 'runs in a subagent';
@@ -84,11 +85,24 @@ describe('spawn-liveness-banner', () => {
});
test('every workflow that dispatches a subagent contains the liveness phrase somewhere', () => {
const mdFiles = findMdFiles(WORKFLOWS_DIR);
// #2994: scoped to HOST workflow files only (top-level gsd-core/workflows/*.md),
// each read via readWorkflowCombined (host + its workflows/<wf>/steps/*.md
// fragments). The fragment model can move a subagent dispatch site into a step
// file while the host still carries the liveness phrase elsewhere for its other
// dispatches (e.g. docs-update.md). A raw recursive `findMdFiles` walk treats
// that step fragment as an independent "workflow" and reports a false positive
// — it is not a standalone workflow, it is part of its host's contract. The
// asserted property (every workflow that dispatches a subagent documents the
// liveness phrase somewhere in that workflow) is unchanged; only the file
// boundary the check honors is corrected.
const hostFiles = fs
.readdirSync(WORKFLOWS_DIR, { withFileTypes: true })
.filter((entry) => entry.isFile() && entry.name.endsWith('.md'))
.map((entry) => path.join(WORKFLOWS_DIR, entry.name));
const presenceViolations = [];
for (const filePath of mdFiles) {
const content = fs.readFileSync(filePath, 'utf-8');
for (const filePath of hostFiles) {
const content = readWorkflowCombined(filePath);
const rel = path.relative(WORKFLOWS_DIR, filePath);
if (content.includes('subagent_type') && !content.includes(LIVENESS_PHRASE)) {

Some files were not shown because too many files have changed in this diff Show More