feat(#1787): add /gsd:next smart entry workflow (#1798)

* docs: design spec for /gsd smart-entry command

Hybrid approach porting gsd-pi's smart-entry wizard to gsd-core:
deterministic classifier (gsd-tools smart-entry --json) + markdown
command/workflow with AskUserQuestion + --text fallback. Routing-first
('what now?' menu), 10 situations redesigned for gsd-core's phase loop.

* feat: add /gsd-start smart-entry command

State-aware front door adapted from gsd-pi's smart-entry wizard,
redesigned for gsd-core's markdown-first, multi-runtime architecture.

- src/smart-entry.cts: deterministic situation classifier (no-project,
  paused, blocked, verify-failed, needs-first-phase, planning, executing,
  verify-pending, idle-stranded, complete, unknown). Reads STATE.md,
  ROADMAP.md, git, and verify signals; emits JSON the workflow consumes.
- gsd-tools.cjs: wire  case + help listing.
- commands/gsd/start.md + gsd-core/workflows/gsd.md: thin markdown
  dispatcher presenting an AskUserQuestion menu (with --text fallback for
  non-Claude runtimes) and dispatching to existing commands. Falls back
  to /gsd:progress if detection is unavailable.
- help.md: document /gsd:start (parity with bug-2954).
- tests: smart-entry.unit.test.cjs (classifier behavior across all
  situations + priority + JSON shape) and gsd-workflow.structure.test.cjs
  (markdown-layer invariants + every emitted command resolves to a real
  slash command).

Spec: docs/superpowers/specs/2026-06-27-gsd-smart-entry-design.md
Note: command-contract (ADR-0002) requires a gsd:* prefix, so the bare
/gsd from the spec surfaces as /gsd-start.

* refactor: rename smart-entry command to /gsd:next

Rename the command from /gsd:start to /gsd:next per feedback. The
command file is now commands/gsd/next.md (name: gsd:next) and the
backing workflow is gsd-core/workflows/smart-entry.md (named for the
smart-entry classifier and gsd-tools smart-entry subcommand; does not
collide with the existing workflows/next.md, which is the progress
--next sub-workflow). help.md and the spec updated to match.

All affected tests (188) pass; lint:ci clean.

* fix: smart-entry reads real STATE.md schema (nested progress YAML + body Phase field)

Codex review found the classifier misread this repo's own STATE.md: it
looked only for scalar current_phase/total_phases frontmatter and body
fields named 'Current Phase'/'Total Phases', but real STATE.md stores
the phase as body 'Phase: N' and total_phases/percent under a nested
'progress:' YAML object. Both came back null, so active projects
(e.g. this repo at Phase 3 / verifying) wrongly classified as
needs-first-phase.

- detectSignals now reads total_phases + percent from nested progress{}
  first, then scalar fm, then body; current_phase falls back to the
  body 'Phase:' field (parseProsePhaseField lineage).
- Add regression tests against the real schema (nested progress YAML +
  body Phase field) covering verify-pending + executing situations.

Verified against this repo: now classifies verify-pending (was
needs-first-phase). Coverage 93.25% lines / 86.99% branches.

* fix(workflow): tiered fallback when gsd-tools is broken (not just smart-entry)

Live test exposed a self-defeating fallback: when smart-entry --json
failed because gsd-tools itself was broken (missing
markdown-sectionizer.cjs), the workflow fell back to /gsd:progress —
which also depends on gsd-tools and would dead-end too.

Replace the single /gsd:progress fallback with a tiered recovery:
1. Probe gsd_run state-snapshot. If it ALSO errors, the whole tool
   layer is down — read .planning/STATE.md directly with the Read tool
   and synthesize a minimal situation + actions menu so /gsd:next stays
   useful. Surface a rebuild hint.
2. Only if smart-entry alone is missing (older gsd-core), fall back to
   /gsd:progress as before.

Matches the direct-read resilience the live agent already did by hand.

* docs: add gsd-next skill surface

* chore: trigger no-mistakes validation

* no-mistakes(review): Fix smart-entry phase ordering

* no-mistakes(review): Fix decimal smart-entry phase ordering

* no-mistakes(test): Fix smart-entry next test contracts

* no-mistakes(document): Docs synced for smart entry

* chore: add changeset fragment for #1798 (/gsd:next smart-entry workflow)

Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>

* fix: shorten next.md description and update golden install parity fixtures

Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>

* fix: update /gsd-next refs to /gsd:next in docs and add Smart Entry topic alias

Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>

* chore: trigger no-mistakes validation

* fix: regenerate INVENTORY-MANIFEST.json for new /gsd-next files

Full CI caught that adding commands/gsd/next.md + gsd-core/workflows/smart-entry.md
left docs/INVENTORY-MANIFEST.json stale (not in the affected-test scope that
no-mistakes' test gate runs, so it surfaced in CI). Regenerated via
node scripts/gen-inventory-manifest.cjs --write; inventory-manifest-sync
test now passes.

* fix: add 'next' to core_loop cluster, update INVENTORY-MANIFEST, fix gates.md ref

Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>

* fix: regenerate golden install parity fixtures for /gsd:next

Full CI (shard 3/3) caught that adding commands/gsd/next.md + the
smart-entry workflow/lib made the per-runtime golden install parity
fixtures stale across all 16 runtimes. Regenerated via
UPDATE_GOLDEN=1 node --test tests/golden-install-parity.test.cjs.
All 16 fixtures + inventory-manifest-sync now pass.

* Fix smart-entry verify-failed phase scoping and empty resolve shim step

Scope detectVerifyFailed to STATE.md's current phase so leftover higher
phase directories cannot force verify-failed routing. Move the gsd_run
shim resolver into the workflow resolve step so agents define gsd_run
before the detect step runs smart-entry.

* fix: recapture golden fixtures with updated gates.md hash (/gsd:next)

Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>

* fix: recapture all 16 golden fixtures with updated smart-entry.md hash

Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>

* chore: regenerate fixtures + inventory manifest after rebase onto next

Rebased onto next which adopted #1837 (package-version normalization to
<VERSION> in golden-install-parity hashes). Recaptured the golden fixture
that needed it (hermes), re-sorted INVENTORY-MANIFEST.json, and regenerated
the gsd-next / ns-workflow skill descriptions to match the command surface.

Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>

* refactor(#1787): delegate /gsd:next in-project advancement to gated /gsd:progress --next

Reconciles the /gsd:next smart-entry front door with the existing
/gsd:progress --next engine (davesienkowski review on PR #1798). The
classifier previously recommended /gsd:execute-phase directly for the
`executing` situation, bypassing workflows/next.md Route 0
(resume-incomplete-phase invariant, #160) and Gates 1-3 — reproducing the
duplication that got the old flat /gsd-next removed (#3054), plus a
correctness hazard (executing the recorded current phase while an earlier
phase is silently incomplete).

Now planning/executing/verify-pending recommend `/gsd:progress --next`
(single gated engine); the specific command stays an explicit secondary.
Off-path states (no-project, paused, blocked, verify-failed,
idle-stranded, complete) keep direct recommendations — smart-entry's
distinct value over --next. Adds docs/adr/1787-gsd-next-smart-entry.md and
a regression test locking the delegation contract.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(#1787): avoid literal /gsd-next token in ADR (bug-3054 guard)

The repo-invariants #3054 guard bans the removed /gsd-next slash form in
docs surfaces. Refer to the removed command as `gsd-next` (prose) — the
historical reference is unchanged, just the banned token is dropped.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore: gitignore compiled host-integration-sdk + handshake-serialized .cjs

Pre-existing gap from #1683: these two src/*.cts modules compile to
gsd-core/bin/lib/*.cjs but were omitted from the per-file ignore list, so
`npm run build`/`npm test` left them as untracked build artifacts (dirty
tree + accidental-commit footgun). Adds them alongside their siblings
(host-integration.cjs, mcp-server.cjs, …). Found while finishing #1798.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* test(#1787): lock per-situation action invariants for all 11 situations + ADR typo

Adversarial-review follow-ups:
- Add a test asserting every situation's action set has exactly one
  recommended action, 1-4 unique-id /gsd:* actions (previously the
  one-recommended/1-4 invariant was only sampled for 6 of 11 situations).
- Fix ADR typo: /gsd-progress → /gsd:progress.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(#1798): split oversized test chunks so a slow shard can't trip the per-chunk timeout

Root-cause of the intermittent `full test (windows-latest, 22, shard 1/3)`
failure. It was NOT a leaked handle (the runner's kill message guesses that,
but --test-force-exit already exits leaks cleanly). Diagnosis:

- Ran every shard-1/3 file WITHOUT --test-force-exit + a 45s kill-timer:
  zero hangs, zero leaks — every file self-exits. So no leaked handle / hang.
- CI activity profile: output kept flowing (slowly) right up to the 600.0s
  kill — a dead hang would go silent. => pure slowness.
- Per-file timing: install-minimal-hooks.test.cjs is a 4987-line / 250-case
  consolidation file doing dozens of real installs — 41s even on a fast Mac
  (much worse on the slow Windows I/O path), plus an install-heavy cluster.

Mechanism: MAX_FILES_PER_CHUNK=180 packed the whole ~171-file shard into ONE
`node --test` chunk, so the entire shard's wall-clock ran against a single
600s per-chunk backstop. On slow Windows runners that single chunk crossed
600s and was killed mid-run — an intermittent false-negative gate that also
hits `next` directly.

Fix: lower MAX_FILES_PER_CHUNK 180 -> 90 so each shard splits into ~2 chunks,
each with its own fresh 600s budget and a fresh node process (also relieves
per-process memory pressure). Verified locally: shard 1/3 now runs as
chunk 1/2 (90 files) + chunk 2/2 (81 files), 5323 tests, 0 fail. Also made the
timeout kill-message name slowness as a cause instead of asserting a leak, so
the next debugger isn't sent hunting a nonexistent handle leak.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Tom Boucher <trekkie@nomorestars.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Jeremy McSpadden
2026-07-03 11:18:25 -05:00
committed by GitHub
parent b42a8f45de
commit e5ef323b15
44 changed files with 2025 additions and 93 deletions

View File

@@ -0,0 +1,5 @@
---
type: Added
pr: 1798
---
**`/gsd:next` smart-entry workflow** — adds a state-aware entry point that classifies the current project situation (no-project, blocked, verify-failed, planning, executing, verify-pending, complete, and more) and recommends the right next GSD command. The `gsd-tools smart-entry [--json]` classifier handles phase ordering including decimal phase IDs; the `/gsd:next` skill surfaces the workflow with tiered fallback behavior.

3
.gitignore vendored
View File

@@ -68,6 +68,8 @@ build/
# Published via prepublishOnly; built before test via pretest. Grows as modules migrate.
/tsconfig.build.tsbuildinfo
/gsd-core/bin/lib/host-integration.cjs
/gsd-core/bin/lib/host-integration-sdk.cjs
/gsd-core/bin/lib/handshake-serialized.cjs
/gsd-core/bin/lib/install-engine.cjs
/gsd-core/bin/lib/embedding-adapter.cjs
/gsd-core/bin/lib/adapter-declarative.cjs
@@ -110,6 +112,7 @@ build/
/gsd-core/bin/lib/installer-migration-report.cjs
/gsd-core/bin/lib/prompt-budget.cjs
/gsd-core/bin/lib/secrets.cjs
/gsd-core/bin/lib/smart-entry.cjs
/gsd-core/bin/lib/phase-lifecycle.cjs
/gsd-core/bin/lib/workstream-name-policy.cjs
/gsd-core/bin/lib/decisions.cjs

29
commands/gsd/next.md Normal file
View File

@@ -0,0 +1,29 @@
---
name: gsd:next
description: Smart entry — detect project state and route to the right next GSD action.
effort: low
allowed-tools:
- Read
- Bash
- Glob
- SlashCommand
- AskUserQuestion
---
<objective>
GSD smart entry — the state-aware front door. Detect what's going on in this project, then present a short menu of the right next actions and dispatch to one.
This is a launcher/router only. It never does the work itself. It reads project + workflow state via `gsd-tools smart-entry --json`, shows a situation-appropriate menu, and hands off to an existing GSD command.
</objective>
<execution_context>
@~/.claude/gsd-core/workflows/smart-entry.md
@~/.claude/gsd-core/references/ui-brand.md
</execution_context>
<context>
Arguments: $ARGUMENTS
</context>
<process>
Follow ~/.claude/gsd-core/workflows/smart-entry.md. Detect the situation, present the menu, and dispatch exactly one command. Then stop.
</process>

View File

@@ -5,13 +5,15 @@ argument-hint: ""
allowed-tools:
- Read
- Skill
requires: [discuss-phase, spec-phase, plan-phase, execute-phase, verify-work, phase, progress, ultraplan-phase, plan-review-convergence, add-tests, ai-integration-phase, autonomous, fast, mvp-phase, quick]
requires: [discuss-phase, spec-phase, plan-phase, execute-phase, verify-work, phase, progress, next, ultraplan-phase, plan-review-convergence, add-tests, ai-integration-phase, autonomous, fast, mvp-phase, quick]
---
Route to the appropriate phase-pipeline skill based on the user's intent.
Sub-skill names below are post-#2790 consolidated targets — `gsd-phase`
absorbs the former add/insert/remove/edit-phase commands and `gsd-progress`
absorbs the former next/do commands.
absorbs the former next/do workflow-advance commands. The reclaimed
`gsd-next` target is the state-aware smart-entry launcher, not the retired
workflow-advance command.
| User wants | Invoke |
|---|---|
@@ -22,6 +24,7 @@ absorbs the former next/do commands.
| Verify built features through UAT | gsd-verify-work |
| Add / insert / remove / edit a phase | gsd-phase |
| Advance to the next logical step | gsd-progress |
| Open the state-aware smart-entry launcher | gsd-next |
| Offload planning to the ultraplan cloud | gsd-ultraplan-phase |
| Cross-AI plan review convergence loop | gsd-plan-review-convergence |
| Generate tests for a completed phase | gsd-add-tests |

View File

@@ -93,6 +93,17 @@ node gsd-tools.cjs state-snapshot
Returns JSON with: current position, phase, plan, status, decisions, blockers, metrics, last activity.
### Smart Entry
Read-only situation classifier used by `/gsd:next`.
```bash
node gsd-tools.cjs smart-entry # Human summary + recommended route
node gsd-tools.cjs smart-entry --json # Machine-readable result for workflows
```
The JSON result contains `situation`, `recommended`, `summary`, `signals`, and ordered `actions[]`. Detection reads `.planning/STATE.md`, `ROADMAP.md`, latest verification/summary artifacts, and git status; it does not write files or dispatch commands.
---
## Phase Commands

View File

@@ -28,7 +28,7 @@ Six namespace routers ship as the first-stage entry points in v1.40. They keep t
| Command | Routes to |
|---------|-----------|
| `/gsd-workflow` | Phase pipeline — discuss / plan / execute / verify / phase / progress |
| `/gsd-workflow` | Phase pipeline — discuss / plan / execute / verify / phase / progress / next |
| `/gsd-project` | Project lifecycle — milestones, audits, summary |
| `/gsd-quality` | Quality gates — code review, debug, audit, security, eval, ui |
| `/gsd-context` | Codebase intelligence — map, graphify, docs, learnings |
@@ -578,9 +578,21 @@ node gsd-tools.cjs phase uat-passed 3 --raw # Machine-readable
## Navigation Commands
### `/gsd:next`
Open the state-aware smart-entry launcher. It reads `.planning/STATE.md`, `ROADMAP.md`, verification artifacts, and git status, classifies the current situation, shows a short menu, then dispatches exactly one existing GSD command.
This is a launcher/router only — it never performs project work directly. Detection is handled by `gsd-tools smart-entry --json`; the markdown workflow presents the menu with `AskUserQuestion` or a numbered `--text` fallback.
**Situations detected:** no project, paused work, blockers, failed verification, first-phase setup, planning, executing, pending verification, idle stranded work, complete milestone, or unknown state.
```bash
/gsd:next # Detect state and route to the best next action
```
### `/gsd-progress`
Show status, next steps, and automatically advance to the next logical workflow step. Reads project state and determines the appropriate action.
Show status, next steps, and automatically advance to the next logical workflow step. Reads project state and determines the appropriate action. Use `/gsd:next` when you want an interactive smart-entry menu before dispatch; use `/gsd-progress --next` when you want GSD to advance directly.
| Flag | Description |
|------|-------------|
@@ -1419,6 +1431,7 @@ Reviewers are prompted to verify the plan's claims against the actual repository
**Default reviewer behavior (no flags):**
- If `review.default_reviewers` is **unset**, `/gsd-review` runs all detected reviewers (current default behavior).
- If `review.default_reviewers` is **set**, `/gsd-review` runs only that subset (for example `["gemini","codex"]`).
- `review.default_reviewers` may include names from `review.reviewer_instances`; each instance runs as its own reviewer identity using its configured adapter/model. Instance names are not CLI flags.
- `--all` always overrides config and runs the full detected set.
- Explicit flags (for example `--cursor`) override both `--all` and config defaults for that run.

View File

@@ -82,6 +82,7 @@ GSD stores project settings in `.planning/config.json`. Created during `/gsd-new
},
"review": {
"default_reviewers": null,
"reviewer_instances": {},
"models": {}
},
"parallelization": {
@@ -220,7 +221,7 @@ Use `review.default_reviewers` to scope the no-flag `/gsd-review` run to a subse
| Setting | Type | Default | Description |
|---------|------|---------|-------------|
| `review.default_reviewers` | string[] \| null | `null` (all detected reviewers) | Optional default subset for no-flag `/gsd-review`, e.g. `["gemini","codex"]`. Precedence is: explicit reviewer flags > `--all` > `review.default_reviewers` > all detected. Unknown slugs are ignored with a warning; known-but-undetected slugs are ignored with an info note; empty arrays are rejected by `config-set`. |
| `review.default_reviewers` | string[] \| null | `null` (all detected reviewers) | Optional default subset for no-flag `/gsd-review`, e.g. `["gemini","codex"]`. Entries may be built-in reviewer slugs or configured `review.reviewer_instances` names. Precedence is: explicit reviewer flags > `--all` > `review.default_reviewers` > all detected. Unknown slugs are ignored with a warning when no instances are configured; with `review.reviewer_instances` present, unknown entries are hard errors to catch typoed instance names. Known-but-undetected slugs are ignored with an info note; empty arrays are rejected by `config-set`. |
Example:
@@ -972,7 +973,7 @@ Configure per-CLI model selection for `/gsd-review`. When set, overrides the CLI
| `review.models.ollama` | string | (server default) | Model name passed to Ollama when `--ollama` reviewer is invoked. If unset, the first available model reported by the server is used (e.g. `llama3`). Set to a specific tag: `gsd config-set review.models.ollama codellama` |
| `review.models.lm_studio` | string | (server default) | Model name passed to LM Studio when `--lm-studio` reviewer is invoked. If unset, the first available model reported by the server is used. |
| `review.models.llama_cpp` | string | (server default) | Model name passed to llama.cpp when `--llama-cpp` reviewer is invoked. If unset, the first model reported by `/v1/models` is used. |
| `review.default_reviewers` | string[] \| null | (all detected reviewers) | Default reviewer subset for no-flag `/gsd-review`. Example: `["gemini","codex"]`. Explicit flags and `--all` override this setting. |
| `review.default_reviewers` | string[] \| null | (all detected reviewers) | Default reviewer subset for no-flag `/gsd-review`. Example: `["gemini","codex"]`. May include configured `review.reviewer_instances` names. Explicit flags and `--all` override this setting. |
| `review.max_prompt_tokens` | number\|null | null | Default maximum estimated tokens for the assembled review prompt. When set, the prompt is deterministically trimmed before being sent to each reviewer. Per-reviewer overrides via `review.max_prompt_tokens_per_reviewer` take precedence. null = no trim (current behavior). |
| `review.max_prompt_tokens_per_reviewer` | object | {} | Per-reviewer token budget overrides. Keys are reviewer slugs (ollama, llama_cpp, lm_studio, gemini, claude, codex, opencode, qwen, cursor). Values override `review.max_prompt_tokens` for that reviewer. Recommended for local model servers. |
| `review.ollama_host` | string | `http://localhost:11434` | Base URL of the Ollama server. Override when running Ollama on a non-default port or remote host: `gsd config-set review.ollama_host http://192.168.1.10:11434` |

View File

@@ -1223,6 +1223,7 @@ When verification returns `human_needed`, items are persisted as a trackable HUM
**User configuration note:**
- Set `review.default_reviewers` in `.planning/config.json` (or via `gsd config-set`) to control no-flag `/gsd-review` fan-out.
- `review.default_reviewers` may include configured `review.reviewer_instances` names; each instance runs as an independent reviewer identity backed by its configured adapter/model. Instance names are not CLI flags.
- Use `--all` for a full pre-merge sweep without changing project defaults.
- For local model servers with small context windows, set `review.max_prompt_tokens_per_reviewer` to auto-trim prompts per reviewer — see [Prompt budgets for small-context reviewers](../docs/CONFIGURATION.md#prompt-budgets-for-small-context-reviewers) in CONFIGURATION.md.
@@ -2638,7 +2639,7 @@ Users who run a memory / knowledge-base MCP server (for example, ExoCortex-style
### 122. Skill Surface Consolidation
**Purpose:** Cut the eager skill-listing overhead by folding 31 micro-skills into 4 new grouped parents and 6 existing parents that absorb sub-operations as flags. Zero functional loss — every removed micro-skill's behavior survives via a flag on a consolidated parent. After consolidation, `commands/gsd/*.md` ships 59 sub-skills (plus 6 namespace meta-skills, see #123).
**Purpose:** Cut the eager skill-listing overhead by folding 31 micro-skills into 4 new grouped parents and 6 existing parents that absorb sub-operations as flags. Zero functional loss — every removed micro-skill's behavior survives via a flag on a consolidated parent. After consolidation, `commands/gsd/*.md` ships 60 sub-skills (plus 6 namespace meta-skills, see #123).
**Requirements:**
- REQ-CONSOLIDATE-01: Four new grouped skills replace clusters of micro-skills:
@@ -2647,8 +2648,9 @@ Users who run a memory / knowledge-base MCP server (for example, ExoCortex-style
- `/gsd-config` — folds settings-advanced (`--advanced`), settings-integrations (`--integrations`), set-profile (`--profile`)
- `/gsd-workspace` — folds new-workspace (`--new`), list-workspaces (`--list`), remove-workspace (`--remove`)
- REQ-CONSOLIDATE-02: Six existing parents absorb wrap-up / sub-operations as flags: `/gsd-update --sync`, `/gsd-update --reapply`, `/gsd-sketch --wrap-up`, `/gsd-spike --wrap-up`, `/gsd-map-codebase --fast`, `/gsd-map-codebase --query`, `/gsd-code-review --fix`, `/gsd-progress --do`, `/gsd-progress --next`.
- REQ-CONSOLIDATE-03: Deleted micro-skill slash forms (the bare `gsd-add-todo`, `gsd-add-backlog`, `gsd-plant-seed`, `gsd-check-todos`, `gsd-add-phase`, `gsd-insert-phase`, `gsd-remove-phase`, `gsd-edit-phase`, `gsd-new-workspace`, `gsd-list-workspaces`, `gsd-remove-workspace`, `gsd-settings-advanced`, `gsd-settings-integrations`, `gsd-set-profile`, `gsd-sketch-wrap-up`, `gsd-spike-wrap-up`, `gsd-reapply-patches`, `gsd-code-review-fix`, …) MUST resolve to "Unknown command" — no shadow stubs.
- REQ-CONSOLIDATE-04: `autonomous.md` invokes `/gsd-code-review --fix` (was previously calling the deleted `gsd-code-review-fix`).
- REQ-CONSOLIDATE-03: `/gsd:next` is not the retired workflow-advance command; it is reserved for the state-aware smart-entry launcher. Workflow advancement remains under `/gsd-progress --next`.
- REQ-CONSOLIDATE-04: Deleted micro-skill slash forms (the bare `gsd-add-todo`, `gsd-add-backlog`, `gsd-plant-seed`, `gsd-check-todos`, `gsd-add-phase`, `gsd-insert-phase`, `gsd-remove-phase`, `gsd-edit-phase`, `gsd-new-workspace`, `gsd-list-workspaces`, `gsd-remove-workspace`, `gsd-settings-advanced`, `gsd-settings-integrations`, `gsd-set-profile`, `gsd-sketch-wrap-up`, `gsd-spike-wrap-up`, `gsd-reapply-patches`, `gsd-code-review-fix`, …) MUST resolve to "Unknown command" — no shadow stubs.
- REQ-CONSOLIDATE-05: `autonomous.md` invokes `/gsd-code-review --fix` (was previously calling the deleted `gsd-code-review-fix`).
**Reference issue:** [#2790](https://github.com/open-gsd/gsd-core/issues/2790)
@@ -2659,7 +2661,7 @@ Users who run a memory / knowledge-base MCP server (for example, ExoCortex-style
**Purpose:** Replace the flat eager skill listing with a two-stage hierarchical routing layer. The model sees 6 namespace routers instead of 86 entries, selects a namespace, then routes to the sub-skill. Descriptions use pipe-separated keyword tags (≤ 60 chars) for routing density.
**Commands:**
- `/gsd-workflow` — phase pipeline router (discuss / plan / execute / verify / phase / progress)
- `/gsd-workflow` — phase pipeline router (discuss / plan / execute / verify / phase / progress / next)
- `/gsd-project` — project lifecycle (milestones, audits, summary)
- `/gsd-quality` — quality gates (code review, debug, audit, security, eval, ui)
- `/gsd-context` — codebase intelligence (map, graphify, docs, learnings)
@@ -2677,11 +2679,13 @@ Users who run a memory / knowledge-base MCP server (for example, ExoCortex-style
- REQ-NS-01: Six `commands/gsd/ns-*.md` namespace routers ship with pipe-separated keyword-tag descriptions (≤ 60 chars).
- REQ-NS-02: Existing sub-skills are unchanged and still invocable directly — namespace skills are additive, not a replacement for direct slash forms.
- REQ-NS-03: The body of each namespace router contains a routing table that maps user intent to the correct concrete sub-skill on the post-#2790 consolidated surface.
- REQ-NS-04: Tests validate namespace files exist, include matching command `requires`, and reference only existing sub-skill files.
**Reference issue:** [#2792](https://github.com/open-gsd/gsd-core/issues/2792)
---
### 124. Context-Window Utilization Guard
**Command:** `/gsd-health --context`
@@ -3192,6 +3196,7 @@ The load-bearing wire is the `plan-phase` lift into `must_haves.prohibitions`, s
**Reference:** [Prohibition Probe](../gsd-core/references/prohibition-probe.md)
### 147. Capability Management Command
**Command:** `gsd capability install | update | remove | list | outdated | disable | enable`
@@ -3209,3 +3214,23 @@ The load-bearing wire is the `plan-phase` lift into `must_haves.prohibitions`, s
**Trust boundary:** install never executes capability code (copy-only staging); executable surfaces require explicit consent; sources are gated by the **project-scoped** `capabilities.strict_known_registries` policy (fail-closed on a malformed/unparseable value); every shared-config write/delete is realpath-confined to the scope root, and a name collision with a user's `mcpServers` entry is never clobbered.
**Reference:** [`gsd capability` command reference](reference/gsd-capability-command.md) · [ADR-1244](adr/1244-capability-ecosystem.md)
### 148. Smart Entry Launcher
**Command:** `/gsd:next`
**Tool:** `gsd-tools smart-entry [--json]`
**Purpose:** Provide a state-aware front door that reads project/workflow state, classifies the user's situation, presents a short menu, and dispatches exactly one existing GSD command.
**Requirements:**
- REQ-SMART-ENTRY-01: Detection MUST be read-only and deterministic; classification lives in `gsd-tools smart-entry`.
- REQ-SMART-ENTRY-02: The launcher MUST never perform project work directly; it only displays a menu and dispatches one command.
- REQ-SMART-ENTRY-03: The workflow MUST fall back to `/gsd-progress` if detection fails.
- REQ-SMART-ENTRY-04: Each classified situation MUST provide exactly one recommended action and valid slash commands.
- REQ-SMART-ENTRY-05: Text-mode runtimes MUST receive a numbered-list fallback instead of being stranded by interactive UI assumptions.
**Situations:** no project, paused, blocked, verify failed, needs first phase, planning, executing, verify pending, idle stranded, complete, unknown.
**Reference:** [Smart Entry Design](superpowers/specs/2026-06-27-gsd-smart-entry-design.md)
---

View File

@@ -71,6 +71,7 @@
"/gsd-mvp-phase",
"/gsd-new-milestone",
"/gsd-new-project",
"/gsd-next",
"/gsd-ns-context",
"/gsd-ns-ideate",
"/gsd-ns-manage",
@@ -182,6 +183,7 @@
"ship.md",
"sketch-wrap-up.md",
"sketch.md",
"smart-entry.md",
"spec-phase.md",
"spike-wrap-up.md",
"spike.md",
@@ -340,8 +342,8 @@
"gsd2-import.cjs",
"handshake-serialized.cjs",
"hook-bus.cjs",
"host-integration.cjs",
"host-integration-sdk.cjs",
"host-integration.cjs",
"init-command-router.cjs",
"init.cjs",
"install-engine.cjs",
@@ -402,6 +404,7 @@
"security.cjs",
"semver-compare.cjs",
"shell-command-projection.cjs",
"smart-entry.cjs",
"stale-bake-guard.cjs",
"state-command-router.cjs",
"state-document.cjs",

View File

@@ -67,7 +67,7 @@ These six routers are descriptor-only entries that the model picks first; the bo
| Command | Role | Source |
|---------|------|--------|
| `/gsd-workflow` | Phase pipeline router — discuss / plan / execute / verify / phase / progress. | [commands/gsd/ns-workflow.md](../commands/gsd/ns-workflow.md) |
| `/gsd-workflow` | Phase pipeline router — discuss / plan / execute / verify / phase / progress / next. | [commands/gsd/ns-workflow.md](../commands/gsd/ns-workflow.md) |
| `/gsd-project` | Project lifecycle router — milestones, audits, summary. | [commands/gsd/ns-project.md](../commands/gsd/ns-project.md) |
| `/gsd-quality` | Quality-gate router — code review, debug, audit, security, eval, ui. | [commands/gsd/ns-review.md](../commands/gsd/ns-review.md) |
| `/gsd-context` | Codebase-intelligence router — map, graphify, docs, learnings. | [commands/gsd/ns-context.md](../commands/gsd/ns-context.md) |
@@ -123,6 +123,7 @@ These six routers are descriptor-only entries that the model picks first; the bo
| Command | Role | Source |
|---------|------|--------|
| `/gsd:next` | State-aware smart-entry launcher — reads project state, shows a contextual menu, and dispatches one existing GSD command. | [commands/gsd/next.md](../commands/gsd/next.md) |
| `/gsd-progress` | Check project progress, show context, and route to next action; use `--next` to advance automatically or `--do` to run a freeform task. | [commands/gsd/progress.md](../commands/gsd/progress.md) |
| `/gsd-capture` | Capture ideas, tasks, notes, and seeds — todo (default), `--note`, `--backlog`, `--seed`, or `--list` pending todos. | [commands/gsd/capture.md](../commands/gsd/capture.md) |
| `/gsd-stats` | Display project statistics — phases, plans, requirements, git metrics, timeline. | [commands/gsd/stats.md](../commands/gsd/stats.md) |

View File

@@ -0,0 +1,123 @@
# ADR 1787: `/gsd:next` smart-entry front door delegates advancement to `/gsd:progress --next`
- **Status:** Accepted
- **Date:** 2026-07-03
- **Issue:** #1787
- **Implementation:** PR #1798 (`feat(#1787): add /gsd:next smart entry workflow`)
- **Supersedes context:** the removal of the flat `gsd-next` command (#3054)
## Context
gsd-core has no state-aware front door. A user must already know whether to
reach for `/gsd:progress`, `/gsd:plan-phase`, `/gsd:execute-phase`, `/gsd:quick`,
or `/gsd:new-project`. gsd-pi ships a `/gsd` smart-entry wizard (a state-aware
menu with one recommended action) that users run first; gsd-core wants the same
"run one command, get told what to do next" feel without gsd-pi's TUI, since
gsd-core is a markdown prompt framework installed into AI agents, not a Node app.
Two facts constrain the design:
1. **A `gsd-next` command already existed and was deliberately removed (#3054),**
with `/gsd:progress --next` established as the canonical "advance to the next
logical step" engine. `tests/bug-3054-stale-gsd-next-references.test.cjs`
guards against user-facing surfaces re-referencing the removed flat command.
Re-introducing a `next` entry point re-opens a settled question: it must not
recreate the duplication that justified the removal.
2. **`/gsd:progress` is the "unified GSD situational command."** Its `--next`
mode (`gsd-core/workflows/next.md`) is a *gated* advancement engine:
- **Route 0** — the resume-incomplete-phase invariant (#160): if a session
died mid-execution and `STATE.md`'s `current_phase` advanced past a phase
that still has `PLAN.md` files without matching `SUMMARY.md`, `--next`
resumes the *incomplete earlier* phase rather than the recorded current one.
- **Gates 1–3** — unresolved checkpoint, error/failed state, and unchecked
verification failures each hard-stop advancement.
The initial implementation of the new `smart-entry` classifier
(`src/smart-entry.cts`) re-derived in-project forward routing itself. For the
`executing` situation it recommended dispatching `/gsd:execute-phase` **directly**,
bypassing Route 0 and Gates 1–3. That reproduced exactly the duplication that got
`gsd-next` removed — two front doors that can route the *same* in-project state
to *different* phases — and introduced a correctness hazard (executing the
recorded current phase while an earlier phase is silently incomplete). A
maintainer (davesienkowski) flagged the overlap on PR #1798.
## Decision
Ship `/gsd:next` as a **menu front door only**, with a hard boundary against
re-implementing advancement:
1. **Detection + classification** live in Node as `gsd-tools smart-entry [--json]`
(`src/smart-entry.cts`): a pure, unit-tested classifier over `.planning/STATE.md`,
`ROADMAP.md`, and read-only git signals, producing one of 11 situations with a
recommended action and an action menu. **Presentation + dispatch** live in the
markdown layer (`commands/gsd/next.md` → `gsd-core/workflows/smart-entry.md`),
using `AskUserQuestion` with a `--text` numbered-list fallback for non-Claude
runtimes. The command carries no `requires` field so it works pre-project.
2. **In-project forward motion delegates to the single gated engine.** For the
`planning`, `executing`, and `verify-pending` situations, the *recommended*
action is `/gsd:progress --next`. smart-entry never re-derives forward routing;
it hands linear advancement to `workflows/next.md` so Route 0 and Gates 1–3 are
always honored. The specific command (`/gsd:plan-phase`, `/gsd:execute-phase`,
`/gsd:verify-work`) remains available as an explicit secondary menu option for a
user who deliberately wants to bypass advancement gating.
3. **smart-entry's distinct value is the states `--next` cannot reach.** For
situations *off* the linear advance path it keeps direct recommendations, since
these are precisely what `/gsd:progress --next` does not (or cannot, given its
`requires: [phase]`) handle: `no-project` → `/gsd:new-project`, `paused` →
`/gsd:resume-work`, `blocked` → `/gsd:debug`, `verify-failed` →
`/gsd:verify-work`, `needs-first-phase` → `/gsd:discuss-phase`, `idle-stranded`
→ `/gsd:ship`, `complete` → `/gsd:new-milestone`, `unknown` → `/gsd:progress`.
This makes the spec's stated decision #4 ("complementary, not redundant") true in
the implementation, not just the prose: there is exactly one advancement engine,
and `/gsd:next` is a menu over it plus the off-path states.
## Consequences
### Positive
- **One advancement engine.** `/gsd:next` and `/gsd:progress --next` can never
disagree about the next in-project step, and Route 0 / Gates 1–3 cannot be
bypassed through the new front door. The #3054 duplication does not return.
- **Genuine new value, no overlap.** The front door adds pre-project, remediation,
and lifecycle-exit routing that `--next` structurally cannot serve.
- **Testable boundary.** `tests/smart-entry.unit.test.cjs` asserts that every
forward-motion situation recommends `/gsd:progress --next` and every off-path
situation keeps its direct recommendation — the delegation is a regression-locked
contract, not a convention.
### Negative / trade-offs
- One extra indirection hop for the common "just continue" case (`/gsd:next` →
`/gsd:progress --next` → dispatched command) versus dispatching the phase command
directly. Accepted: the hop is what buys gate-safety and single-engine behavior.
- The classifier reads STATE.md via shared primitives (`frontmatter.cjs`,
`state-document.cjs`, `phase-id.cjs`) rather than through `workflows/next.md`'s
own detection, so detection logic exists in two places. Accepted: they share
parsing primitives and only the *routing decision* is centralized (in `--next`),
which is where divergence would actually harm the user.
## Alternatives considered
1. **Keep smart-entry as an independent in-project router (as first implemented).**
Rejected: reproduces the #3054 duplication and the Route 0 / Gates 1–3 bypass
hazard.
2. **Fold everything into `/gsd:progress` (add a menu mode) and ship no new
command.** Rejected: `/gsd:progress` carries `requires: [phase]` and cannot
serve the pre-project `no-project` front-door case, which is a primary goal.
3. **Replace `workflows/next.md`'s inline detection with the new classifier so
there is one detection *and* routing engine.** Rejected for this PR: `--next`
couples detection to safety gates and convergence flags the classifier does not
model; swapping its detection wholesale would risk regressing those invariants.
Left as possible future consolidation.
## References
- Spec: `docs/superpowers/specs/2026-06-27-gsd-smart-entry-design.md`
- Removed flat command guard: `tests/bug-3054-stale-gsd-next-references.test.cjs`
- Gated engine: `gsd-core/workflows/next.md` (Route 0 = resume-incomplete-phase, #160)
- Classifier: `src/smart-entry.cts` → `gsd-core/bin/lib/smart-entry.cjs`
- Command contract naming (`gsd:*`): ADR-0002

View File

@@ -45,6 +45,8 @@ gsd config-set review.default_reviewers '["gemini","codex"]'
For the full integration settings schema (API keys, model overrides per reviewer, local server host addresses), see [Configuration](../CONFIGURATION.md).
If you need multiple independent reviewer voices from the same adapter, configure `review.reviewer_instances` and add those instance names to `review.default_reviewers`. Instance names run only through `review.default_reviewers`; they are not valid `/gsd-review` flags. See [Reviewer instances](../CONFIGURATION.md#reviewer-instances) for the schema.
---
## Run a review

View File

@@ -0,0 +1,310 @@
# Design: /gsd Smart Entry
**Date:** 2026-06-27
**Status:** Approved — ready for implementation planning
**Origin:** Adapted from the `/gsd` smart-entry wizard in `open-gsd/gsd-pi`, redesigned for gsd-core's markdown-first, multi-runtime architecture.
---
## Summary
A `/gsd:next` command that acts as gsd-core's **state-aware front door**. It reads project + workflow state, classifies the user's situation, and presents a small menu of the right next actions — then dispatches to an existing command. The "smart" part is deterministic detection living in Node (a new `gsd-tools smart-entry` subcommand); the presentation is an idiomatic markdown command + workflow using `AskUserQuestion` with a `--text` fallback for non-Claude runtimes.
This is a **launcher / router**, not an executor. It never does the work itself.
> **Implementation note (command name):** the command-contract (ADR-0002) requires `name:` to be `gsd:*` or `gsd-*` prefixed; a bare `/gsd` is not expressible. The command is therefore `gsd:next` → `/gsd:next` (file `commands/gsd/next.md`, backed by `gsd-core/workflows/smart-entry.md`). The "smart entry" concept and behavior are unchanged; only the surfaced name differs from the original `/gsd` sketch.
---
## Motivation
gsd-pi ships a `/gsd` smart-entry wizard — a state-aware menu that branches on detected project state (phase loop, blockers, stranded work) and surfaces one well-chosen set of options with a single recommended action. It is the command users run first. gsd-core has no equivalent front door: users must already know whether to reach for `progress`, `plan-phase`, `execute-phase`, `quick`, or `new-project`.
We want the same daily-driver feel — "run `/gsd`, get told what to do next" — without fighting gsd-core's nature. gsd-core is a **markdown prompt framework installed into AI agents**, not a Node CLI app. So gsd-pi's full-screen TUI and its imperative TypeScript branch tree do not port directly. What ports is the **behavior**: detect state → classify situation → offer contextual options → dispatch.
---
## Resolved design decisions
These were chosen during brainstorming and are fixed inputs to this spec:
1. **Approach: Hybrid.** Detection + classification as a new `gsd-tools smart-entry --json` subcommand (deterministic, unit-tested in Node); presentation + dispatch as a markdown command + workflow that shells out to it via the existing `gsd_run` shim. This mirrors how `gsd-core/workflows/do.md` already drives tooling. Rationale: code-driven detection is reliable and testable; the markdown surface keeps multi-runtime reach (Codex, Gemini, Copilot) and adds zero dependencies.
2. **Priority: workflow routing ("what now?").** The wizard optimizes for the ongoing-work menu (gsd-pi's `showSmartEntry`), not first-run onboarding (gsd-pi's `showProjectInit`). Onboarding routes to the existing `/gsd-new-project`, which already handles project detection and setup. We are **not** building an init wizard.
3. **Richness: phase + smart signals.** The classifier branches on gsd-core's phase loop **and** richer gsd-pi-style signals (blocked/recover, idle/stranded, paused, complete). All 10 situations below are in scope.
4. **Relationship to `/gsd-progress`: complementary, not redundant.** `/gsd:next` is the **front door / launcher** — a state-aware *menu* the user picks the next action from. `/gsd-progress` remains the **detailed situational report + auto-advance** (`--next` chaining). `/gsd:next` will frequently recommend `/gsd:progress`; it does not replace or deprecate it.
---
## Non-goals
- **No init/onboarding wizard.** `/gsd-new-project` already owns first-run project setup. `/gsd:next` routes to it.
- **No new prompt/TUI library.** `AskUserQuestion` (Claude) + `--text` numbered-list fallback (other runtimes) — matching repo convention. No inquirer/clack/ink.
- **No copy of gsd-pi's branch tree.** gsd-pi's milestone/slice/task model does not exist here. The situation table is **redesigned for gsd-core's phase loop** (`.planning/`).
- **No execution.** Pure launcher. Picked action dispatches to an existing command and stops.
- **No new state storage.** Reads existing artifacts (`.planning/STATE.md`, `ROADMAP.md`, git). Writes nothing.
---
## Architecture
```text
/gsd:next (commands/gsd/next.md — thin markdown dispatcher)
│
▼
workflow: gsd-core/workflows/smart-entry.md ◄── presentation + dispatch
│ step 1: resolve gsd_run shim
│ step 2: gsd_run smart-entry --json
│ step 3: render AskUserQuestion (or TEXT_MODE list)
│ step 4: show GSD ► ROUTING banner
│ step 5: dispatch + stop
▼
gsd-tools smart-entry --json ◄── NEW deterministic detection
│ reads: state-snapshot (.planning/STATE.md)
│ + .planning/ existence
│ + git status / branch / unpushed
│ + verify signals
│ emits: { situation, recommended, summary, actions[] }
▼
(existing command: progress / plan-phase / execute-phase / quick / ship …)
```
Two artifacts, one contract — the JSON shape in §"JSON contract". The workflow is thin because all branching logic lives in Node.
---
## The classifier — `gsd-tools smart-entry`
New `src/smart-entry.cts` → compiled (build-at-publish, ADR-457) to `gsd-core/bin/lib/smart-entry.cjs`. Registered in `gsd-tools.cjs` as `case 'smart-entry':` (≈2 lines, delegating to `smartEntry.run(cwd, { json: true }, raw)`).
### Contract
- **Pure detection → classification.** No side effects. No writes. No `process.exit()` (throw `ExitError` per repo convention; the `runMain` wrapper translates exit codes).
- **Output modes:** `--json` (machine, used by the workflow) and default (human-readable summary line, for `gsd-tools` users / debugging).
- **Idempotent, fast, no network.** Read-only filesystem + `git` calls only.
- **Never throws in `--json` mode when `.planning/` is absent** — it returns `situation: "no-project"` so the workflow always has a menu to render.
### Inputs
| input | source | what we read |
|---|---|---|
| workflow state | `gsd_run query state.load` / `cmdStateSnapshot` | `current_phase`, `total_phases`, `current_plan`, `total_plans_in_phase`, `status`, `progress`, `blockers[]`, `paused_at`, `last_activity`, session |
| planning dir | filesystem at cwd | existence of `.planning/`, `.planning/STATE.md`, `.planning/ROADMAP.md` |
| git signals | `git status --porcelain`, `git branch`, `git log @{u}..` (guarded) | dirty tree, branch, unpushed commits |
| verify signals | filesystem | latest phase's verify report presence; `STATUS:` marker on most recent summary |
Git calls are **guarded** — any git error (not a repo, no upstream, detached HEAD) is swallowed and treated as "no git signal," never fatal.
### Situations (priority order — first match wins)
This is the gsd-core analog of gsd-pi's phase enum. Evaluated top-down; the first matching row is the situation.
| # | situation | when (predicate over inputs) | recommended action |
|---|---|---|---|
| 1 | `no-project` | `.planning/` absent | `new-project` |
| 2 | `paused` | `paused_at` set (non-empty) | `resume-work` |
| 3 | `blocked` | `blockers[]` non-empty | `debug` |
| 4 | `verify-failed` | latest verify report `STATUS:` indicates failure/blocked | `verify-work` |
| 5 | `needs-first-phase` | STATE exists but `total_phases` ≤ 0 or no `ROADMAP.md` | `discuss-phase` |
| 6 | `planning` | `status` = planning (phase has no plan yet) | `plan-phase` |
| 7 | `executing` | `status` = executing / active | `execute-phase` |
| 8 | `verify-pending` | `status` = needs-verify / review-pending | `verify-work` |
| 9 | `idle-stranded` | clean tree + unpushed/stranded commits OR stale `last_activity` with committed-but-unshipped work | `ship` |
| 10 | `complete` | `total_phases` > 0 and current phase ≥ total and `status` = complete | `new-milestone` |
| — | `unknown` | fallback (no predicate matched) | `progress` |
**Note on `idle-stranded`:** this is the richest heuristic and the most likely to need tuning. Predicates: working tree clean **AND** (`git log @{u}..` non-empty **OR** `last_activity` older than threshold with non-complete `status`). Threshold: 72h (configurable later via config; hardcoded for v1). If this proves brittle in testing it is the first situation to relax — but it is in scope per the richness decision.
### Action set per situation
Each situation produces an ordered `actions[]` array. The recommended action is always first and carries `recommended: true`; the workflow shows the top 4 (`AskUserQuestion` cap). Every situation **always** includes `progress` ("Show progress") and `quick` ("Quick task") as escape hatches, and `help` is appended when room remains.
```
no-project → new-project*, map-codebase, quick, help
paused → resume-work*, progress, quick, help
blocked → debug*, verify-work, capture, progress
verify-failed → verify-work*, debug, code-review, progress
needs-first-phase→ discuss-phase*, plan-phase, quick, progress
planning → plan-phase*, discuss-phase, quick, progress
executing → execute-phase*, "progress --next", quick, code-review
verify-pending → verify-work*, code-review, "ship", progress
idle-stranded → ship*, complete-milestone, progress, capture
complete → new-milestone*, extract-learnings, quick, progress
unknown → progress*, "progress --next", quick, help
(* = recommended)
```
### JSON contract (machine output, `--json`)
```json
{
"situation": "executing",
"recommended": "execute-phase",
"summary": "Phase 2 of 5 · plan 1/3 · 60% · active",
"signals": {
"current_phase": 2,
"total_phases": 5,
"status": "executing",
"progress": 60,
"has_planning": true,
"git_dirty": false,
"paused": false,
"blockers": []
},
"actions": [
{ "id": "execute-phase", "label": "Continue executing phase 2", "command": "/gsd:execute-phase", "recommended": true },
{ "id": "progress-next", "label": "Advance to the next step", "command": "/gsd:progress --next", "recommended": false },
{ "id": "quick", "label": "Quick task", "command": "/gsd:quick", "recommended": false },
{ "id": "code-review", "label": "Review recent work", "command": "/gsd:code-review", "recommended": false }
]
}
```
- `situation`, `recommended`, `actions[]` are the contract the workflow depends on.
- `signals` is informational (shown in the summary banner); the workflow does not branch on it.
- `summary` is a one-line human string; the workflow may show it verbatim or reformat.
- `actions[].command` is the full slash command string the workflow dispatches, including flags (e.g. `/gsd:progress --next`).
---
## The markdown layer
### Command — `commands/gsd/next.md` (NEW)
Thin dispatcher, modeled on `commands/gsd/progress.md` and `commands/gsd/help.md`. Backed by `gsd-core/workflows/smart-entry.md` (named for the `smart-entry` classifier + `gsd-tools smart-entry` subcommand; does not collide with the existing `workflows/next.md`, which is the progress `--next` sub-workflow).
Frontmatter:
- `name: gsd:next` (surfaces as `/gsd:next`; the command-contract requires a `gsd:*`/`gsd-*` prefix — a bare `/gsd` is not expressible, see ADR-0002)
- `description:` "GSD smart entry — the state-aware front door. Reads your project state and routes you to the right next action."
- `argument-hint: ""` (no args for v1; reserved)
- `effort: low`
- `allowed-tools:` `Read, Bash, Glob, SlashCommand, AskUserQuestion`
- **No `requires: [phase]`** (unlike `progress`) — must work pre-project.
- `<execution_context>` → `@~/.claude/gsd-core/workflows/smart-entry.md` + `@~/.claude/gsd-core/references/ui-brand.md`
Body: a short `<objective>` stating this is a state-aware launcher, then `<process>` delegating entirely to the workflow. No inline logic.
### Workflow — `gsd-core/workflows/smart-entry.md` (NEW)
Five steps. **Must stay under 32 KiB (NEW_FILE_CAP)** — lean, because all branching is in Node.
**Step 1 — `resolve` (resolve the gsd_run shim):** Copy the `check_project`-style shim-resolution block verbatim from `gsd-core/workflows/do.md:29` (the long `_GSD_SHIM_NAME` resolver). This finds `gsd-tools.cjs` across all supported runtime homes. It is a proven, required block; do not paraphrase.
**Step 2 — `detect` (run the classifier):**
```bash
SNAPSHOT=$(gsd_run smart-entry --json 2>/dev/null)
```
Parse `SNAPSHOT` as JSON. If missing or unparseable → fall back to `/gsd:progress` (Step 5, with a one-line note "smart-entry unavailable — showing progress"). The agent never gets stuck.
**Step 3 — `present` (render the menu):**
TEXT_MODE handling copied verbatim from `do.md:15` (set `TEXT_MODE=true` when `--text` in `$ARGUMENTS` or `text_mode` from init JSON is true; replace every `AskUserQuestion` with a numbered list).
Present via `AskUserQuestion`:
- `header`: derived from `situation` (e.g. `executing` → "Continue work").
- `question`: the `summary` line + "What next?"
- `options`: the first 4 of `actions[]`, label = action `label`, recommended first. (AskUserQuestion shows the first option as recommended.)
- Always allow the user to type a custom command ("Other" is provided automatically by the tool).
In TEXT_MODE: print `summary`, then a numbered list of all `actions[]` (not capped — text has no 4-option limit), ask the user to type a number.
**Step 4 — `display` (routing banner):** Copy the `display` step from `do.md:77-89` verbatim — the `GSD ► ROUTING` banner showing input / routing-to / reason. Input here is the chosen `label`; routing-to is the chosen `command`.
**Step 5 — `dispatch`:** Invoke the chosen `command`. Pass `$ARGUMENTS` through if the user typed a custom command. Then **stop** — the dispatched command owns everything from here. (Same contract as `do.md:91-99`.)
### TEXT_MODE / multi-runtime
The `--text` fallback is mandatory and is the reason we keep menus small and logic in Node. The fallback is copied from `do.md`, not reinvented.
---
## Error handling
| failure | behavior |
|---|---|
| `gsd_run` shim not found | the shim block itself errors with the standard install hint (from `do.md:29`); not our concern |
| `smart-entry` command missing (older gsd-core) | workflow sees empty/unparseable output → falls back to `/gsd:progress` with a note |
| `smart-entry` throws | same: caught by the `2>/dev/null` + parse check → fallback to `/gsd:progress` |
| `.planning/` absent | `smart-entry` returns `situation: "no-project"` → menu offers `new-project` |
| git unavailable / not a repo | classifier swallows git errors; works without git signals |
| `AskUserQuestion` unavailable (non-Claude) | TEXT_MODE numbered list |
**Invariant:** `/gsd` always produces *some* actionable menu and never strands the user. The ultimate fallback is `/gsd:progress`, which is always safe and always exists.
---
## Testing
Per CONTRIBUTING: `node:test` + `node:assert/strict`, behavior assertions only, no source-grep tests.
### `tests/smart-entry.unit.test.cjs` (NEW)
Fixture-driven: create temp dirs with crafted `.planning/STATE.md` + optional git repo, run the classifier, assert situation + recommended + action set. Cases (one per situation at minimum):
- `no-project` — empty cwd → situation `no-project`, recommended `new-project`, actions include `map-codebase`.
- `paused` — STATE.md with `paused_at` set → situation `paused`, recommended `resume-work`.
- `blocked` — STATE.md with blockers → situation `blocked`, recommended `debug`.
- `verify-failed` — latest summary `STATUS: blocked` → situation `verify-failed`.
- `needs-first-phase` — STATE.md present, `total_phases: 0` → situation `needs-first-phase`.
- `planning` / `executing` / `verify-pending` — respective `status` values.
- `idle-stranded` — clean tree + unpushed commits → situation `idle-stranded`, recommended `ship`.
- `complete` — current ≥ total, status complete → situation `complete`, recommended `new-milestone`.
- `unknown` — malformed state → situation `unknown`, recommended `progress`.
- **Priority ordering** — a STATE.md that is both paused AND blocked resolves to `paused` (earlier row wins).
- **JSON shape** — `actions[].command` always starts with `/gsd:`; exactly one action has `recommended: true`.
### `tests/gsd-workflow.structure.test.cjs` (NEW)
Invariants over the markdown layer (these are structural/format assertions on shipped artifacts, not source-grep of logic — permitted since they test the *contract* the workflow exposes):
- `commands/gsd/next.md` exists with frontmatter `name: gsd:next`, no `requires` field, `allowed-tools` includes `AskUserQuestion`.
- `gsd-core/workflows/smart-entry.md` exists and is **under 32 KiB** (NEW_FILE_CAP).
- Every `command` string referenced by the classifier's action table resolves to a real existing slash command file in `commands/gsd/` (guard against dead routes).
- The workflow contains the TEXT_MODE fallback clause and the shim-resolution block (contract assertions).
- The workflow dispatches exactly one command and then stops (no inline execution).
### Coverage & baseline
- The new `.cjs` enters the `c8` coverage gate (`--lines 70 --branches 60`).
- After adding `workflows/smart-entry.md`, run `npm run size:baseline` to update `tests/workflow-size-baseline.json`; justify the new entry in the PR.
---
## File changes
| file | change | size budget |
|---|---|---|
| `src/smart-entry.cts` | NEW — detection + classifier; `--json` + human output | — |
| `gsd-core/bin/lib/smart-entry.cjs` | generated by `build:lib` (gitignored) | — |
| `gsd-core/bin/gsd-tools.cjs` | add `case 'smart-entry':` (~2 lines) | — |
| `commands/gsd/next.md` | NEW — thin dispatcher command (`gsd:next` → `/gsd:next`) | small |
| `gsd-core/workflows/smart-entry.md` | NEW — presentation + dispatch | < 32 KiB |
| `tests/smart-entry.unit.test.cjs` | NEW — classifier behavior | — |
| `tests/gsd-workflow.structure.test.cjs` | NEW — markdown-layer invariants | — |
| `tests/workflow-size-baseline.json` | regenerate via `npm run size:baseline` | — |
| `docs/superpowers/specs/2026-06-27-gsd-smart-entry-design.md` | this document | — |
No existing command or workflow is modified. No new npm dependencies.
---
## Open questions for implementation
None blocking. Two noted for the implementer's judgment (not spec-level):
1. **`idle-stranded` threshold** — 72h hardcoded for v1. If brittle in practice, relax to "unpushed commits only" (drop the staleness clause).
2. **Action label wording** — exact strings are an implementation/tuning detail; the contract is `id` + `command`.
---
## Success criteria
- [ ] `gsd-tools smart-entry --json` classifies all 10 situations + `unknown` correctly from fixtures.
- [ ] `/gsd` in a real project shows a situation-appropriate menu and dispatches the chosen command.
- [ ] `/gsd` pre-project offers `new-project`.
- [ ] `/gsd` works under TEXT_MODE (no `AskUserQuestion`).
- [ ] Any `smart-entry` failure falls back to `/gsd:progress` without erroring.
- [ ] New workflow under 32 KiB; `size:baseline` updated; coverage gate passes.
- [ ] No new dependencies; no existing command modified.

View File

@@ -89,6 +89,7 @@ export default tseslint.config(
'gsd-core/bin/lib/installer-migration-report.cjs',
'gsd-core/bin/lib/prompt-budget.cjs',
'gsd-core/bin/lib/secrets.cjs',
'gsd-core/bin/lib/smart-entry.cjs',
'gsd-core/bin/lib/phase-lifecycle.cjs',
'gsd-core/bin/lib/workstream-name-policy.cjs',
'gsd-core/bin/lib/decisions.cjs',

View File

@@ -259,6 +259,7 @@ const { routePhasesCommand } = require('./lib/phases-command-router.cjs');
const { routeValidateCommand } = require('./lib/validate-command-router.cjs');
const { routeRoadmapCommand } = require('./lib/roadmap-command-router.cjs');
const { routeAgentCommand } = require('./lib/agent-command-router.cjs');
const smartEntryMod = require('./lib/smart-entry.cjs');
const { routeCheckCommand } = require('./lib/check-command-router.cjs');
const { routeTaskCommand } = require('./lib/task-command-router.cjs');
const { parseNamedArgs, parseMultiwordArg } = require('./lib/command-arg-projection.cjs');
@@ -665,7 +666,7 @@ async function main() {
'from-gsd2, frontmatter, gap-analysis, generate-claude-md, generate-claude-profile, ' +
'generate-dev-preferences, generate-slug, graphify, history-digest, init, intel, ' +
'capability, classify-confidence, git, learnings, list-seeds, list-todos, loop, milestone, package-legitimacy, phase, phase-plan-index, phases, profile-questionnaire, ' +
'profile-sample, progress, project-instruction-file, prompt-budget, requirements, research-plan, research-store, resolve-granularity, resolve-model, roadmap, scaffold, state, ' +
'profile-sample, progress, project-instruction-file, prompt-budget, requirements, research-plan, research-store, resolve-granularity, resolve-model, roadmap, scaffold, smart-entry, state, ' +
'task, template, user-story, validate, verify, verify-path-exists, verify-summary, eval, workstream, worktree\n\n' +
'Global flags:\n' +
' --raw Emit raw output without post-processing\n' +
@@ -833,6 +834,11 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
break;
}
case 'smart-entry': {
smartEntryMod.runSmartEntry(cwd, args, raw);
break;
}
case 'check': {
routeCheckCommand({ args, cwd, raw });
break;

View File

@@ -38,7 +38,7 @@ Canonical gate types used across GSD workflows. Every validation checkpoint maps
**Recovery:** Developer investigates root cause, fixes, restarts from checkpoint.
**Examples:**
- Context window critically low during execution
- STATE.md in error state blocking /gsd-next
- STATE.md in error state blocking /gsd:next
- Verification finds critical missing deliverables
---

View File

@@ -13,6 +13,20 @@ Display the complete GSD Core command reference. Output ONLY the reference conte
2. `/gsd:plan-phase 1` - Create detailed plan for first phase
3. `/gsd:execute-phase 1` - Execute the phase
Not sure where to start? `/gsd:next` reads your project state and routes you to the right next action.
### Smart Entry
**`/gsd:next`**
The state-aware front door. Detects your current situation and presents a short menu of the right next actions.
- Reads `.planning/STATE.md`, git state, and verification signals via `gsd-tools smart-entry`
- Classifies your situation (no-project, paused, blocked, planning, executing, needs-verify, idle, complete, …)
- Shows a situation-appropriate menu with one recommended action, then dispatches
- Launcher/router only — it never does the work itself; falls back to `/gsd:progress` if detection is unavailable
Usage: `/gsd:next`
## Staying Updated
GSD evolves fast. Update periodically:

View File

@@ -7,6 +7,7 @@ Emit a section from the full reference for the topic in `$ARGUMENTS`. Read `work
| Topic alias(es) | Section heading in `full.md` |
|---|---|
| `next`, `smart-entry` | `### Smart Entry` |
| `workflow`, `core`, `core-workflow` | `## Core Workflow` (entire section through end of `### Quick Mode`) |
| `init`, `new-project` | `### Project Initialization` |
| `map`, `map-codebase` | The `/gsd:map-codebase` block under `### Project Initialization` |

View File

@@ -0,0 +1,120 @@
<purpose>
GSD smart entry — the state-aware front door. Detect the current project situation via `gsd-tools smart-entry --json`, present a short menu of the right next actions, and dispatch to exactly one existing GSD command. This is a launcher/router only; it never does the work itself.
This is a *menu* front door, not a second router. For in-project forward motion (planning → executing → verify-pending) the recommended action is `/gsd:progress --next`, which delegates to the single gated advancement engine (`workflows/next.md`: Route 0 resume-incomplete-phase + Gates 1-3). smart-entry adds value only where `--next` cannot reach: pre-project, remediation (paused/blocked/verify-failed), and lifecycle exits (idle-stranded/complete). See `docs/adr/1787-gsd-next-smart-entry.md`.
</purpose>
<required_reading>
Read all files referenced by the invoking prompt's `execution_context` before starting.
</required_reading>
<process>
<step name="text_mode">
**TEXT_MODE handling (non-Claude runtimes).**
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.
</step>
<step name="resolve">
**Resolve the gsd_run shim.**
Run this resolver block exactly. It locates `gsd-tools.cjs` across every supported runtime home and defines a `gsd_run` function. If it cannot find the tool, it prints the standard install hint and exits non-zero.
```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 "$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="$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
```
</step>
<step name="detect">
**Detect the situation.**
```bash
SNAPSHOT=$(gsd_run smart-entry --json 2>/dev/null)
```
Parse `SNAPSHOT` as JSON. It has the shape:
```json
{
"situation": "executing",
"recommended": "progress-next",
"summary": "Phase 2 of 5 · 60% · executing",
"signals": { "...": "..." },
"actions": [
{ "id": "progress-next", "label": "Advance to the next step", "command": "/gsd:progress --next", "recommended": true },
{ "id": "execute-phase", "label": "Continue executing phase 2", "command": "/gsd:execute-phase", "recommended": false }
]
}
```
`situation` is one of: `no-project`, `paused`, `blocked`, `verify-failed`, `needs-first-phase`, `planning`, `executing`, `verify-pending`, `idle-stranded`, `complete`, `unknown`.
**Fallback (never strand the user):** `smart-entry --json` can fail for two reasons, and each has a different recovery. Parse `SNAPSHOT`; if it is empty, not valid JSON, or missing `actions`, apply the first matching recovery below — do NOT error.
1. **`gsd-tools` itself is broken** (the failure is a `Cannot find module ...` / Node crash, not just an empty result). Probe by running `gsd_run state-snapshot` — if THAT also errors, the whole tool layer is down and routing to `/gsd:progress` would dead-end too (it also needs gsd-tools). **Recover by reading state directly:**
- Read `.planning/STATE.md` (frontmatter + body) with the Read tool. Extract: `status` (frontmatter `status:` or body `**Status:**`), `Phase:` from the body, `total_phases`/`percent` from a nested `progress:` frontmatter object if present, and any `## Blockers` items.
- Synthesize a minimal result: `situation` = your best guess from the status text (`executing`/`verifying`/`planning`/`complete`/`paused`), `summary` = a one-line read ("Phase N of M · status"), and an `actions` list built from status (e.g. verifying → `/gsd:verify-work`, executing → `/gsd:execute-phase`, else `/gsd:progress`), always including `/gsd:quick` and `/gsd:help`.
- Print one line first: `smart-entry unavailable (gsd-tools error) — reading state directly. The gsd-tools layer may need a rebuild (rm tsconfig.build.tsbuildinfo && npm run build).`
- Proceed to the `present` step with this synthesized result.
2. **Only `smart-entry` is unavailable** (e.g. older gsd-core without the subcommand; `state-snapshot` still works). Run `/gsd:progress` and stop. Print one line first: `smart-entry unavailable — showing progress.`
</step>
<step name="present">
**Present the menu.**
Show the `summary` line to orient the user, then offer the actions.
**If TEXT_MODE is false:** call `AskUserQuestion` with:
- `header`: a short label derived from `situation` (e.g. `executing` → "Continue work", `blocked` → "Unblock", `no-project` → "Get started", `complete` → "What next?").
- `question`: the `summary` line, then "What would you like to do?"
- `options`: the first 4 entries of `actions[]` in order. For each, `label` = the action's `label`, `description` = the action's `command`. The recommended action is already first; surface it as the first option. The user may also type a custom command (handled automatically).
**If TEXT_MODE is true:** print the `summary`, then a numbered list of ALL `actions[]` (not capped to 4 — text has no limit), then ask the user to type the number of their choice:
```
{summary}
1. {actions[0].label} ({actions[0].command})
2. {actions[1].label} ({actions[1].command})
...
Type a number, or describe what you want to do.
```
Wait for the user's response before continuing. Map the chosen number to the corresponding action.
</step>
<step name="display">
**Show the routing decision.**
```
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
GSD ► SMART ENTRY
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
**Situation:** {situation}
**Routing to:** {chosen command}
```
</step>
<step name="dispatch">
**Dispatch and stop.**
Invoke the chosen action's `command`. If the user typed a free-form response instead of picking an action, treat it as freeform intent and route via `/gsd:progress --do "<their text>"`.
After invoking the command, **stop**. The dispatched command owns everything from here. Do not continue, do not chain, do not re-enter this workflow.
</step>
</process>
<success_criteria>
- [ ] Situation detected via `gsd_run smart-entry --json`
- [ ] Summary shown to orient the user
- [ ] Menu offered (AskUserQuestion, or numbered list under TEXT_MODE)
- [ ] Routing decision displayed before dispatch
- [ ] Exactly one command dispatched
- [ ] Any detection failure falls back to /gsd:progress (never strands the user)
- [ ] No work done directly — launcher only
</success_criteria>

View File

@@ -562,9 +562,19 @@ function main() {
const MAX_CMDLINE_CHARS = process.env.RUN_TESTS_MAX_CMDLINE_CHARS
? Number(process.env.RUN_TESTS_MAX_CMDLINE_CHARS)
: 28000; // headroom below the 32,767 Windows ceiling
// A full-lane shard (~171 files) fit in ONE chunk at the old cap of 180, so the
// entire shard's wall-clock ran against a single per-chunk timeout. On the slow
// Windows runner the install-heavy files in a shard (e.g. install-minimal-hooks
// .test.cjs alone runs ~250 cases doing dozens of real installs) push that single
// chunk past the 600s per-chunk backstop — killed mid-run while still making slow
// progress (verified: no leaked handle / hang; --test-force-exit exits leaks
// cleanly, so the timeout was pure slowness, NOT the leak the kill message guesses).
// The per-chunk timeout is sized for a "healthy chunk (~4-5 min)"; keep chunks at
// roughly half a shard so each gets its own fresh 600s budget and a fresh node
// process (also relieving per-process memory pressure from 170+ files at once).
const MAX_FILES_PER_CHUNK = process.env.RUN_TESTS_MAX_FILES_PER_CHUNK
? Number(process.env.RUN_TESTS_MAX_FILES_PER_CHUNK)
: 180;
: 90;
// node:test does not exit until the event loop drains. A unit test that leaks
// an open handle (un-terminated Worker, un-killed child_process, ref'd timer)
@@ -630,9 +640,12 @@ function main() {
if (timedOut) {
console.error(
`run-tests: chunk ${i + 1}/${chunks.length} exceeded the per-chunk timeout ` +
`of ${chunkTimeoutMs}ms and was killed — a test in this chunk is likely leaking ` +
`of ${chunkTimeoutMs}ms and was killed. Two possible causes: (1) a test leaks ` +
`an open handle (un-terminated Worker, un-killed child process, or ref'd timer) ` +
`so node --test never exits. Files: ${chunks[i]
`so node --test never exits — but --test-force-exit already guards that, so if it ` +
`is enabled suspect (2) the chunk is legitimately too slow for the budget (too ` +
`many/too-heavy files packed together). Check whether output kept flowing until ` +
`the kill (slow) vs stopped early (hang) before assuming a leak. Files: ${chunks[i]
.map(f => f.split(/[\\/]/).pop())
.join(' ')}`,
);

30
skills/gsd-next/SKILL.md Normal file
View File

@@ -0,0 +1,30 @@
---
name: gsd-next
description: "Smart entry — detect project state and route to the right next GSD action."
effort: low
allowed-tools:
- Read
- Bash
- Glob
- SlashCommand
- AskUserQuestion
---
<objective>
GSD smart entry — the state-aware front door. Detect what's going on in this project, then present a short menu of the right next actions and dispatch to one.
This is a launcher/router only. It never does the work itself. It reads project + workflow state via `gsd-tools smart-entry --json`, shows a situation-appropriate menu, and hands off to an existing GSD command.
</objective>
<execution_context>
@~/.claude/gsd-core/workflows/smart-entry.md
@~/.claude/gsd-core/references/ui-brand.md
</execution_context>
<context>
Arguments: $ARGUMENTS
</context>
<process>
Follow ~/.claude/gsd-core/workflows/smart-entry.md. Detect the situation, present the menu, and dispatch exactly one command. Then stop.
</process>

View File

@@ -10,7 +10,9 @@ allowed-tools:
Route to the appropriate phase-pipeline skill based on the user's intent.
Sub-skill names below are post-#2790 consolidated targets — `gsd-phase`
absorbs the former add/insert/remove/edit-phase commands and `gsd-progress`
absorbs the former next/do commands.
absorbs the former next/do workflow-advance commands. The reclaimed
`gsd-next` target is the state-aware smart-entry launcher, not the retired
workflow-advance command.
| User wants | Invoke |
|---|---|
@@ -21,6 +23,7 @@ absorbs the former next/do commands.
| Verify built features through UAT | gsd-verify-work |
| Add / insert / remove / edit a phase | gsd-phase |
| Advance to the next logical step | gsd-progress |
| Open the state-aware smart-entry launcher | gsd-next |
| Offload planning to the ultraplan cloud | gsd-ultraplan-phase |
| Cross-AI plan review convergence loop | gsd-plan-review-convergence |
| Generate tests for a completed phase | gsd-add-tests |

View File

@@ -31,6 +31,7 @@ export type ClusterMap = Readonly<Record<ClusterName, ReadonlyArray<string>>>;
export const CLUSTERS: ClusterMap = Object.freeze({
core_loop: Object.freeze([
'next',
'new-project',
'discuss-phase',
'plan-phase',

586
src/smart-entry.cts Normal file
View File

@@ -0,0 +1,586 @@
/**
* Smart Entry — state-aware situation classifier for the /gsd front door.
*
* Reads project + workflow state (`.planning/STATE.md`, ROADMAP.md, git) and
* classifies the user's situation into one of a small set of enumerated values,
* each carrying a recommended next action and a menu of alternatives. Pure
* detection → classification; no side effects, no writes.
*
* Surfaced as `gsd-tools smart-entry [--json]`. The markdown `/gsd` command +
* workflow shells out to `smart-entry --json`, renders an AskUserQuestion menu
* (with a --text numbered-list fallback for non-Claude runtimes), and dispatches
* the chosen action to an existing slash command. See
* docs/superpowers/specs/2026-06-27-gsd-smart-entry-design.md and
* docs/adr/1787-gsd-next-smart-entry.md.
*
* Relationship to `/gsd:progress --next`: this classifier is a *menu* front door,
* not a second router. For in-project forward motion (planning → executing →
* verify-pending) the recommended action delegates to `/gsd:progress --next`
* (workflows/next.md) — the single, gated advancement engine (Route 0 resume-
* incomplete-phase invariant + Gates 1-3). smart-entry never re-derives forward
* routing itself; a standalone advancement command that bypassed those gates is
* exactly what got the old flat `/gsd-next` removed (#3054). Its distinct value is
* the states `--next` cannot reach: pre-project (no-project), remediation (paused,
* blocked, verify-failed), and lifecycle exits (idle-stranded, complete).
*
* Design note: this is the gsd-core analog of gsd-pi's `showSmartEntry` branch
* tree, redesigned for gsd-core's `.planning/` phase loop. gsd-pi's
* milestone/slice/task model does not exist here; the situation table is the
* translation, not a port.
*/
import fs from 'node:fs';
import path from 'node:path';
import { execFileSync } from 'node:child_process';
// eslint-disable-next-line @typescript-eslint/no-require-imports
import ioMod = require('./io.cjs');
const { output } = ioMod;
// eslint-disable-next-line @typescript-eslint/no-require-imports
import planningWorkspace = require('./planning-workspace.cjs');
const { planningPaths } = planningWorkspace;
// eslint-disable-next-line @typescript-eslint/no-require-imports
import frontmatter = require('./frontmatter.cjs');
const { extractFrontmatter } = frontmatter;
// eslint-disable-next-line @typescript-eslint/no-require-imports
import stateDocument = require('./state-document.cjs');
const { stateExtractField } = stateDocument;
// eslint-disable-next-line @typescript-eslint/no-require-imports
import phaseId = require('./phase-id.cjs');
const { comparePhaseNum, extractPhaseToken, normalizePhaseName, phaseTokenMatches } = phaseId;
// ─── Types ────────────────────────────────────────────────────────────────────
/** Situation id — the gsd-core analog of gsd-pi's phase enum. */
export type Situation =
| 'no-project'
| 'paused'
| 'blocked'
| 'verify-failed'
| 'needs-first-phase'
| 'planning'
| 'executing'
| 'verify-pending'
| 'idle-stranded'
| 'complete'
| 'unknown';
export interface SmartEntryAction {
id: string;
label: string;
/** Full slash command string the workflow dispatches, including flags. */
command: string;
recommended: boolean;
}
export interface SmartEntrySignals {
current_phase: number | null;
total_phases: number | null;
status: string;
progress: number | null;
has_planning: boolean;
has_roadmap: boolean;
git_dirty: boolean;
git_unpushed: boolean;
paused: boolean;
blockers: string[];
has_git: boolean;
/** Latest verify report failed (STATUS: blocked/failed) or status says so. */
verify_failed: boolean;
/** last_activity older than IDLE_STALE_MS (computed with the clock seam). */
stale_activity: boolean;
}
export interface SmartEntryResult {
situation: Situation;
recommended: string;
summary: string;
signals: SmartEntrySignals;
actions: SmartEntryAction[];
}
// ─── Constants ────────────────────────────────────────────────────────────────
/**
* Staleness threshold for idle-stranded detection. A clean tree whose last
* activity is older than this (with non-complete status) is considered stranded
* committed-but-unshipped work. Hardcoded for v1; configurable later.
*/
const IDLE_STALE_MS = 72 * 60 * 60 * 1000; // 72h
/** @internal a single source of truth so the structure test can assert coverage. */
export const SITUATIONS: readonly Situation[] = Object.freeze([
'no-project',
'paused',
'blocked',
'verify-failed',
'needs-first-phase',
'planning',
'executing',
'verify-pending',
'idle-stranded',
'complete',
'unknown',
]);
// ─── Detection ─────────────────────────────────────────────────────────────────
/** Frontmatter scalar helper: prefer YAML frontmatter, fall back to body field. */
function fmScalar(fm: Record<string, unknown>, body: string, key: string, bodyField: string): string | null {
const v = fm[key];
if (typeof v === 'string' && v.trim()) return v.trim();
if (typeof v === 'number' || typeof v === 'boolean') return String(v);
return stateExtractField(body, bodyField);
}
/** Read a scalar value from a nested frontmatter object (e.g. progress.total_phases). */
function fmScalarKey(obj: unknown, key: string): string | null {
if (!obj || typeof obj !== 'object') return null;
const v = (obj as Record<string, unknown>)[key];
if (typeof v === 'string' && v.trim()) return v.trim();
if (typeof v === 'number' || typeof v === 'boolean') return String(v);
return null;
}
function parseIntOrNull(s: string | null): number | null {
if (s === null) return null;
const cleaned = s.replace('%', '').trim();
const n = Number.parseInt(cleaned, 10);
return Number.isFinite(n) ? n : null;
}
function phaseTokenFromDirName(name: string): string | null {
const token = extractPhaseToken(name);
return /^\d+(?:[A-Z])?(?:\.\d+)*(?:-|$)/i.test(token) ? token : null;
}
/**
* Parse a `last_activity` value that may be an ISO date or a free-form string
* into an epoch-ms timestamp. Returns null when unparseable.
*/
function parseActivityTimestamp(raw: string | null): number | null {
if (!raw) return null;
const ms = Date.parse(raw);
return Number.isNaN(ms) ? null : ms;
}
interface GitSignals {
has_git: boolean;
dirty: boolean;
unpushed: boolean;
}
/** Read-only git signals. Any git error is swallowed → "no git signal". */
function readGitSignals(cwd: string): GitSignals {
const run = (args: string[]): string => {
try {
return execFileSync('git', args, {
cwd,
encoding: 'utf-8',
maxBuffer: 4 * 1024 * 1024,
windowsHide: true,
stdio: ['pipe', 'pipe', 'pipe'],
});
} catch {
return '';
}
};
// Cheap "is this a git repo" probe that also doubles as the dirty check.
const status = run(['status', '--porcelain']);
if (status === '' && !run(['rev-parse', '--is-inside-work-tree']).trim()) {
return { has_git: false, dirty: false, unpushed: false };
}
const dirty = status.trim().length > 0;
// `git log @{u}..HEAD` lists commits on this branch not on its upstream.
// Non-empty ⇒ unpushed. Errors (no upstream, detached HEAD) ⇒ not unpushed.
const unpushed = run(['log', '@{u}..HEAD', '--oneline']).trim().length > 0;
return { has_git: true, dirty, unpushed };
}
/** Leading numeric phase token from a STATE.md scalar or body `Phase:` value. */
function phaseTokenFromState(raw: string | null): string | null {
if (!raw?.trim()) return null;
const match = raw.trim().match(/^(\d+(?:[A-Z])?(?:\.\d+)*)/i);
return match ? match[1] : null;
}
/**
* Detect whether the current phase's verify report indicates failure. The
* canonical signal is a phase summary/verify artifact whose STATUS: marker reads
* blocked, failed, or fail. Scoped to STATE.md's current phase so a leftover or
* newer phase tree cannot skew routing. Returns true only on a clear positive signal.
*/
function detectVerifyFailed(cwd: string, currentPhaseRaw: string | null): boolean {
const phasesDir = path.join(planningPaths(cwd).phases);
let entries: string[] = [];
try {
entries = fs.readdirSync(phasesDir)
.map((name) => ({ name, phaseToken: phaseTokenFromDirName(name) }))
.filter((entry): entry is { name: string; phaseToken: string } => entry.phaseToken !== null)
.sort((a, b) => comparePhaseNum(a.phaseToken, b.phaseToken) || a.name.localeCompare(b.name))
.map((entry) => entry.name);
} catch {
return false;
}
if (entries.length === 0) return false;
const phaseToken = phaseTokenFromState(currentPhaseRaw);
let targetDir: string | undefined;
if (phaseToken) {
const normalized = normalizePhaseName(phaseToken);
targetDir = entries.find((name) => phaseTokenMatches(name, normalized));
if (!targetDir) return false;
} else {
// No current phase in state — fall back to the highest-numbered phase dir.
targetDir = entries[entries.length - 1];
}
const latestDir = path.join(phasesDir, targetDir);
let files: string[] = [];
try {
files = fs.readdirSync(latestDir);
} catch {
return false;
}
const candidates = files.filter((f) => /summary|verif(?:y|ication)|uat/i.test(f));
for (const name of candidates) {
let content = '';
try {
content = fs.readFileSync(path.join(latestDir, name), 'utf-8');
} catch {
continue;
}
const statusMatch = content.match(/STATUS:\s*([A-Za-z_-]+)/i);
if (statusMatch && /\b(blocked|fail(ed)?)\b/i.test(statusMatch[1])) {
return true;
}
}
return false;
}
/** Parse `.planning/STATE.md` (frontmatter + body) into the raw signals we need. */
function readStateFile(statePath: string): {
fm: Record<string, unknown>;
body: string;
} | null {
let content: string;
try {
content = fs.readFileSync(statePath, 'utf-8');
} catch {
return null;
}
const fm = extractFrontmatter(content) as Record<string, unknown>;
const body = content.replace(/^---[\s\S]*?---\s*/, '');
return { fm, body };
}
/** Collect the full signal set from disk for `cwd`. Exported for unit tests. */
export function detectSignals(cwd: string, now: () => number = Date.now): SmartEntrySignals {
const paths = planningPaths(cwd);
const hasPlanning = fs.existsSync(paths.planning);
const hasRoadmap = fs.existsSync(paths.roadmap);
const git = readGitSignals(cwd);
const empty: SmartEntrySignals = {
current_phase: null,
total_phases: null,
status: '',
progress: null,
has_planning: hasPlanning,
has_roadmap: hasRoadmap,
git_dirty: git.dirty,
git_unpushed: git.unpushed,
paused: false,
blockers: [],
has_git: git.has_git,
verify_failed: false,
stale_activity: false,
};
if (!hasPlanning) return empty;
const parsed = readStateFile(paths.state);
if (!parsed) return empty;
const { fm, body } = parsed;
// STATE.md has two lineages of schemas (see state.cjs parseProsePhaseField):
// - scalar frontmatter: `current_phase: 3`, `total_phases: 5`
// - nested frontmatter: `progress: { total_phases: 5, percent: 40 }`
// - body prose: `Phase: 3`, `**Status:** verifying`, `Total Phases: 5`
// Read each field across every form, scalar-first then nested then body, so
// the classifier works on real STATE.md files written by current GSD.
const statusRaw = fmScalar(fm, body, 'status', 'Status');
const pausedAtRaw = fmScalar(fm, body, 'paused_at', 'Paused At');
const lastActivityRaw = fmScalar(fm, body, 'last_activity', 'Last Activity');
// current_phase: scalar fm → nested (none) → body "Current Phase" → body "Phase".
// The body `Phase:` field is the canonical location in prose-form STATE.md
// (e.g. "Phase: 3" or "Phase: 3 — ui-review"); parse the leading number.
const currentPhaseRaw =
fmScalar(fm, body, 'current_phase', 'Current Phase') ??
stateExtractField(body, 'Phase');
// total_phases & percent: nested `progress:` object takes precedence in the
// nested schema; scalar fm / body fields cover the flat schema.
const progressFm = typeof fm.progress === 'object' ? fm.progress : null;
const totalPhasesRaw: string | null =
fmScalarKey(progressFm, 'total_phases') ??
fmScalar(fm, body, 'total_phases', 'Total Phases');
const progressRaw: string | null =
fmScalarKey(progressFm, 'percent') ??
fmScalar(fm, body, 'progress', 'Progress');
// Blockers list: `- <text>` items under a `## Blockers` heading.
const blockers: string[] = [];
const blockersMatch = body.match(/##\s*Blockers\s*\n([\s\S]*?)(?=\n##|$)/i); // allow-adhoc-markdown: read-only blockers section-collect in smart-entry.cts; mirrors state.cts (#1372), pending collectSection migration
if (blockersMatch) {
const items = blockersMatch[1].match(/^-\s+(.+)$/gm) || [];
for (const item of items) blockers.push(item.replace(/^-\s+/, '').trim());
}
const paused = Boolean(pausedAtRaw && pausedAtRaw.trim());
// Stale = no recorded activity for IDLE_STALE_MS. Used only by idle-stranded.
// Computed here (with the clock seam) so the pure classify() stays a function
// of (signals, staleActivity) and detectSignals owns all disk reads.
const lastActivityMs = parseActivityTimestamp(lastActivityRaw);
const staleActivity = lastActivityMs !== null && now() - lastActivityMs > IDLE_STALE_MS;
// Verify-failed may be signalled either by STATE.md status or by a failed
// STATUS: marker on the current phase's summary/verify artifact.
const verifyFailed =
/\bverify-fail(ed)?|verification-fail|uat-fail\b/i.test(statusRaw || '') ||
detectVerifyFailed(cwd, currentPhaseRaw);
return {
current_phase: parseIntOrNull(currentPhaseRaw),
total_phases: parseIntOrNull(totalPhasesRaw),
status: (statusRaw || '').toLowerCase(),
progress: parseIntOrNull(progressRaw),
has_planning: hasPlanning,
has_roadmap: hasRoadmap,
git_dirty: git.dirty,
git_unpushed: git.unpushed,
paused,
blockers,
has_git: git.has_git,
verify_failed: verifyFailed,
stale_activity: staleActivity,
};
}
// ─── Situation classification ─────────────────────────────────────────────────
/** True when the workflow has fully completed all phases. */
function isComplete(s: SmartEntrySignals): boolean {
if (s.total_phases === null || s.current_phase === null) return false;
return s.current_phase >= s.total_phases && /\bcomplete(d)?|done|shipped\b/i.test(s.status);
}
/** Idle-stranded: clean tree, committed work not shipped, optionally stale. */
function isIdleStranded(s: SmartEntrySignals): boolean {
if (/\bcomplete(d)?|done|shipped|paused\b/i.test(s.status)) return false;
if (s.git_dirty) return false;
// Unpushed commits are the strongest signal. Otherwise require staleness.
return s.git_unpushed || s.stale_activity;
}
/**
* Classify signals into a situation. Priority order matters — the first
* matching predicate wins (e.g. paused beats blocked). Exported for unit tests.
* Pure function of signals (staleness + verify-failed are precomputed on signals).
*/
export function classify(s: SmartEntrySignals): Situation {
if (!s.has_planning) return 'no-project';
if (s.paused) return 'paused';
if (s.blockers.length > 0) return 'blocked';
if (s.verify_failed) return 'verify-failed';
if (s.total_phases === null || s.total_phases <= 0 || !s.has_roadmap) return 'needs-first-phase';
if (isComplete(s)) return 'complete';
if (/\bplanning|planned\b/i.test(s.status)) return 'planning';
if (/\bexecut(e|ing)|active|in.progress|building\b/i.test(s.status)) return 'executing';
if (/\bverify|review|needs.review|pending.review\b/i.test(s.status)) return 'verify-pending';
if (isIdleStranded(s)) return 'idle-stranded';
return 'unknown';
}
// ─── Action sets ──────────────────────────────────────────────────────────────
const action = (
id: string,
label: string,
command: string,
recommended = false,
): SmartEntryAction => ({ id, label, command, recommended });
function rec(id: string, label: string, command: string): SmartEntryAction {
return action(id, label, command, true);
}
/** Per-situation ordered action list; recommended action first. */
export function actionsFor(situation: Situation, s: SmartEntrySignals): SmartEntryAction[] {
const phaseN = s.current_phase ?? '';
const execLabel = phaseN === '' ? 'Continue executing' : `Continue executing phase ${phaseN}`;
switch (situation) {
case 'no-project':
return [
rec('new-project', 'Start a new project', '/gsd:new-project'),
action('map-codebase', 'Map an existing codebase', '/gsd:map-codebase'),
action('quick', 'Quick task', '/gsd:quick'),
action('help', 'Show help', '/gsd:help'),
];
case 'paused':
return [
rec('resume-work', 'Resume work', '/gsd:resume-work'),
action('progress', 'Show progress', '/gsd:progress'),
action('quick', 'Quick task', '/gsd:quick'),
action('help', 'Show help', '/gsd:help'),
];
case 'blocked':
return [
rec('debug', 'Debug the blocker', '/gsd:debug'),
action('verify-work', 'Verify current work', '/gsd:verify-work'),
action('capture', 'Capture a note', '/gsd:capture'),
action('progress', 'Show progress', '/gsd:progress'),
];
case 'verify-failed':
return [
rec('verify-work', 'Re-verify work', '/gsd:verify-work'),
action('debug', 'Debug the failure', '/gsd:debug'),
action('code-review', 'Review recent work', '/gsd:code-review'),
action('progress', 'Show progress', '/gsd:progress'),
];
case 'needs-first-phase':
return [
rec('discuss-phase', 'Discuss the first phase', '/gsd:discuss-phase'),
action('plan-phase', 'Plan phase 1', '/gsd:plan-phase'),
action('quick', 'Quick task', '/gsd:quick'),
action('progress', 'Show progress', '/gsd:progress'),
];
case 'planning':
// Forward motion → delegate to the single gated engine (see 'executing').
return [
rec('progress-next', `Advance to the next step (plan phase ${phaseN || 1})`, '/gsd:progress --next'),
action('plan-phase', `Plan phase ${phaseN || 1}`, '/gsd:plan-phase'),
action('discuss-phase', 'Discuss before planning', '/gsd:discuss-phase'),
action('progress', 'Show progress', '/gsd:progress'),
];
case 'executing':
// In-project forward motion delegates to the single gated engine
// (/gsd:progress --next → workflows/next.md). Its Route 0 resume-incomplete
// -phase invariant + Gates 1-3 must not be bypassed by dispatching a raw
// /gsd:execute-phase here — that divergence is why the old flat /gsd-next
// was removed (#3054). Direct execute stays as an explicit secondary choice.
return [
rec('progress-next', 'Advance to the next step', '/gsd:progress --next'),
action('execute-phase', execLabel, '/gsd:execute-phase'),
action('quick', 'Quick task', '/gsd:quick'),
action('code-review', 'Review recent work', '/gsd:code-review'),
];
case 'verify-pending':
// Forward motion → delegate to the single gated engine (see 'executing').
return [
rec('progress-next', 'Advance to the next step (verify)', '/gsd:progress --next'),
action('verify-work', 'Verify work', '/gsd:verify-work'),
action('code-review', 'Review recent work', '/gsd:code-review'),
action('ship', 'Ship completed work', '/gsd:ship'),
];
case 'idle-stranded':
return [
rec('ship', 'Ship committed work', '/gsd:ship'),
action('complete-milestone', 'Complete the milestone', '/gsd:complete-milestone'),
action('progress', 'Show progress', '/gsd:progress'),
action('capture', 'Capture a note', '/gsd:capture'),
];
case 'complete':
return [
rec('new-milestone', 'Start a new milestone', '/gsd:new-milestone'),
action('extract-learnings', 'Extract learnings', '/gsd:extract-learnings'),
action('quick', 'Quick task', '/gsd:quick'),
action('progress', 'Show progress', '/gsd:progress'),
];
case 'unknown':
default:
return [
rec('progress', 'Show progress', '/gsd:progress'),
action('progress-next', 'Advance to the next step', '/gsd:progress --next'),
action('quick', 'Quick task', '/gsd:quick'),
action('help', 'Show help', '/gsd:help'),
];
}
}
// ─── Summary line ─────────────────────────────────────────────────────────────
function buildSummary(situation: Situation, s: SmartEntrySignals): string {
switch (situation) {
case 'no-project':
return 'No project yet — start fresh or map an existing codebase';
case 'paused':
return 'Work is paused — pick up where you left off';
case 'blocked':
return `Blocked${s.blockers.length ? ` · ${s.blockers.length} blocker(s)` : ''} — resolve before continuing`;
case 'verify-failed':
return 'Recent verification failed — re-verify or debug';
case 'needs-first-phase':
return 'Project initialized — plan your first phase';
case 'planning':
return `Phase ${s.current_phase ?? '?'} of ${s.total_phases ?? '?'} — needs a plan`;
case 'executing':
return progressLine('executing', s);
case 'verify-pending':
return progressLine('ready to verify', s);
case 'idle-stranded':
return 'Committed work not shipped — time to ship';
case 'complete':
return 'All phases complete — start a new milestone';
case 'unknown':
default:
return 'Unsure of state — showing progress';
}
}
function progressLine(tail: string, s: SmartEntrySignals): string {
const parts: string[] = [];
if (s.current_phase !== null && s.total_phases !== null) {
parts.push(`Phase ${s.current_phase} of ${s.total_phases}`);
} else if (s.current_phase !== null) {
parts.push(`Phase ${s.current_phase}`);
}
if (s.progress !== null) parts.push(`${s.progress}%`);
parts.push(tail);
return parts.join(' · ');
}
// ─── Orchestration ─────────────────────────────────────────────────────────────
/**
* Run the full detect → classify → assemble pipeline. Pure: no stdout, no
* writes. Exported for direct unit testing and reuse. The clock seam drives
* staleness detection inside detectSignals.
*/
export function classifyProject(cwd: string, now: () => number = Date.now): SmartEntryResult {
const signals = detectSignals(cwd, now);
const situation = classify(signals);
const actions = actionsFor(situation, signals);
const recommended = actions.find((a) => a.recommended)?.id ?? 'progress';
const summary = buildSummary(situation, signals);
return { situation, recommended, summary, signals, actions };
}
/**
* `gsd-tools smart-entry` entry point. `--json` emits machine JSON; default
* emits a human-readable summary line. Never throws for a missing `.planning/`
* (returns `no-project`); other internal failures surface a typed error via
* `output()` so the workflow can fall back to `/gsd:progress`.
*/
export function runSmartEntry(cwd: string, args: string[], raw: boolean): void {
const json = args.includes('--json');
const result = classifyProject(cwd);
if (json) {
output(result, raw, undefined);
return;
}
// Human mode: a compact one-liner plus the recommended action.
const human = `${result.summary}\nRecommended: ${result.recommended} → ${
result.actions.find((a) => a.id === result.recommended)?.command ?? '/gsd:progress'
}`;
output(null, true, human);
}

View File

@@ -37,7 +37,7 @@
"agents/gsd-verifier.md": "d2bd23b62f95a64f",
"gsd-core/VERSION": "ef0deccd81a6723c",
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
"gsd-core/bin/gsd-tools.cjs": "815b92a0e7b7693e",
"gsd-core/bin/gsd-tools.cjs": "e839e52071d5baa1",
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "7d398e94c44e5268",
@@ -243,8 +243,8 @@
"gsd-core/workflows/help.md": "5d040504b9ab35e3",
"gsd-core/workflows/help/modes/brief.md": "2e923b3ed635d495",
"gsd-core/workflows/help/modes/default.md": "b544113eb9aa54fa",
"gsd-core/workflows/help/modes/full.md": "3c3b67b4eb28cd82",
"gsd-core/workflows/help/modes/topic.md": "a404990fcf2d7df7",
"gsd-core/workflows/help/modes/full.md": "0e253248c1c2e063",
"gsd-core/workflows/help/modes/topic.md": "19a279fe91922d3b",
"gsd-core/workflows/import.md": "3cbe85643922fd3b",
"gsd-core/workflows/inbox.md": "a448220c548f27bc",
"gsd-core/workflows/ingest-docs.md": "783e0380797478cc",
@@ -288,6 +288,7 @@
"gsd-core/workflows/ship.md": "438fbd3ec509c1f1",
"gsd-core/workflows/sketch-wrap-up.md": "ab9fbb7371ef36bf",
"gsd-core/workflows/sketch.md": "114ca2455c2eb6fc",
"gsd-core/workflows/smart-entry.md": "0f468aa4e70bcc23",
"gsd-core/workflows/spec-phase.md": "5d6f0c480c0b251d",
"gsd-core/workflows/spike-wrap-up.md": "e2f251b7c8bfa2b2",
"gsd-core/workflows/spike.md": "0f9a81bcf4573195",
@@ -372,12 +373,13 @@
"skills/gsd-mvp-phase/SKILL.md": "1cf7622d655840ac",
"skills/gsd-new-milestone/SKILL.md": "f97a28dd1e2b9317",
"skills/gsd-new-project/SKILL.md": "31b46764278d1c1a",
"skills/gsd-next/SKILL.md": "e7e03391c0e3a235",
"skills/gsd-ns-context/SKILL.md": "e791bc60e4cf124e",
"skills/gsd-ns-ideate/SKILL.md": "b85f270e8415f595",
"skills/gsd-ns-manage/SKILL.md": "d55c53b5b0513033",
"skills/gsd-ns-project/SKILL.md": "64844073a6115c45",
"skills/gsd-ns-review/SKILL.md": "2e30158e112d173c",
"skills/gsd-ns-workflow/SKILL.md": "79373d30cd17ed49",
"skills/gsd-ns-workflow/SKILL.md": "529e21833a6a8831",
"skills/gsd-pause-work/SKILL.md": "9d3cc6bd70b03df1",
"skills/gsd-phase/SKILL.md": "00676bbea61410bf",
"skills/gsd-plan-phase/SKILL.md": "168046ccf9702532",

View File

@@ -69,12 +69,13 @@
"commands/gsd-mvp-phase.md": "ffc5905aed8f450a",
"commands/gsd-new-milestone.md": "e9ddaec1dd29d023",
"commands/gsd-new-project.md": "56552a324cff669e",
"commands/gsd-next.md": "d9f43d56e1bca2ca",
"commands/gsd-ns-context.md": "011c44e7aa46e64a",
"commands/gsd-ns-ideate.md": "edc5e543512dd48a",
"commands/gsd-ns-manage.md": "0409d810e499357f",
"commands/gsd-ns-project.md": "8dfd1b9a2ffe86ac",
"commands/gsd-ns-review.md": "3766ed10827882a0",
"commands/gsd-ns-workflow.md": "87910975ac92d103",
"commands/gsd-ns-workflow.md": "c3b3c046a74ec0ee",
"commands/gsd-pause-work.md": "4fb032f72238fe33",
"commands/gsd-phase.md": "4920d15d779329eb",
"commands/gsd-plan-phase.md": "e74f3cb7a10cbb83",
@@ -106,7 +107,7 @@
"commands/gsd-workstreams.md": "52ab9c585d3a00f3",
"gsd-core/VERSION": "ef0deccd81a6723c",
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
"gsd-core/bin/gsd-tools.cjs": "4456f5432471af00",
"gsd-core/bin/gsd-tools.cjs": "e504d3b82da834ae",
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "7d398e94c44e5268",
@@ -152,7 +153,7 @@
"gsd-core/references/few-shot-examples/plan-checker.md": "2574808188ac9de4",
"gsd-core/references/few-shot-examples/verifier.md": "5badee4560b14ae8",
"gsd-core/references/gate-prompts.md": "e69f5993ab944d80",
"gsd-core/references/gates.md": "7dc9fd3a3d6217c6",
"gsd-core/references/gates.md": "bd79c7f90c8cb8d7",
"gsd-core/references/git-integration.md": "77bf9dff38b2c9d4",
"gsd-core/references/git-planning-commit.md": "f897a15ebfc3f5a7",
"gsd-core/references/honest-verifier.md": "8815c9fc18c35719",
@@ -312,8 +313,8 @@
"gsd-core/workflows/help.md": "5d040504b9ab35e3",
"gsd-core/workflows/help/modes/brief.md": "5fe077fbbdb5d9f7",
"gsd-core/workflows/help/modes/default.md": "cc8ef97de634c995",
"gsd-core/workflows/help/modes/full.md": "5d60d11d63089892",
"gsd-core/workflows/help/modes/topic.md": "d7c60d488810fbd6",
"gsd-core/workflows/help/modes/full.md": "3ac1c1bb851da7a5",
"gsd-core/workflows/help/modes/topic.md": "6762e41e3291ae24",
"gsd-core/workflows/import.md": "97861d522319d56e",
"gsd-core/workflows/inbox.md": "437f981ef9ae7b26",
"gsd-core/workflows/ingest-docs.md": "77d9103d8d5432f3",
@@ -357,6 +358,7 @@
"gsd-core/workflows/ship.md": "d26fb5d3e965642a",
"gsd-core/workflows/sketch-wrap-up.md": "c03f64e834b69540",
"gsd-core/workflows/sketch.md": "04e0758c1a58881e",
"gsd-core/workflows/smart-entry.md": "62456d35c69d7437",
"gsd-core/workflows/spec-phase.md": "2aa0353147153776",
"gsd-core/workflows/spike-wrap-up.md": "c86e04a0feb220a5",
"gsd-core/workflows/spike.md": "53127654e77256bf",
@@ -461,7 +463,7 @@
"skills/gsd-ns-review/skills/ui-phase/SKILL.md": "d0e75ee06eaa0165",
"skills/gsd-ns-review/skills/ui-review/SKILL.md": "bdab072171d79877",
"skills/gsd-ns-review/skills/validate-phase/SKILL.md": "8972b413eb712cae",
"skills/gsd-ns-workflow/SKILL.md": "e0711a5522acc3ca",
"skills/gsd-ns-workflow/SKILL.md": "9c03c167c9af062a",
"skills/gsd-ns-workflow/skills/add-tests/SKILL.md": "851084b695ab8328",
"skills/gsd-ns-workflow/skills/ai-integration-phase/SKILL.md": "34fa7aff585eb78f",
"skills/gsd-ns-workflow/skills/autonomous/SKILL.md": "1565a532889d3ea3",
@@ -469,6 +471,7 @@
"skills/gsd-ns-workflow/skills/execute-phase/SKILL.md": "67c9a6189f045de0",
"skills/gsd-ns-workflow/skills/fast/SKILL.md": "34b741cc323f5f15",
"skills/gsd-ns-workflow/skills/mvp-phase/SKILL.md": "f259f089a8d07a54",
"skills/gsd-ns-workflow/skills/next/SKILL.md": "e7409245f1a0f9de",
"skills/gsd-ns-workflow/skills/phase/SKILL.md": "fe5b26417ee466be",
"skills/gsd-ns-workflow/skills/plan-phase/SKILL.md": "8cf23ecd6ac98069",
"skills/gsd-ns-workflow/skills/plan-review-convergence/SKILL.md": "93f68cc6a36de7f2",

View File

@@ -36,7 +36,7 @@
"agents/gsd-verifier.md": "d62ffb1f520520c0",
"gsd-core/VERSION": "ef0deccd81a6723c",
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
"gsd-core/bin/gsd-tools.cjs": "4456f5432471af00",
"gsd-core/bin/gsd-tools.cjs": "e504d3b82da834ae",
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "7d398e94c44e5268",
@@ -242,8 +242,8 @@
"gsd-core/workflows/help.md": "5d040504b9ab35e3",
"gsd-core/workflows/help/modes/brief.md": "2e923b3ed635d495",
"gsd-core/workflows/help/modes/default.md": "b544113eb9aa54fa",
"gsd-core/workflows/help/modes/full.md": "a731f1b3a744be08",
"gsd-core/workflows/help/modes/topic.md": "bd2e4cc8e460f5bd",
"gsd-core/workflows/help/modes/full.md": "93cbdf7e542115a1",
"gsd-core/workflows/help/modes/topic.md": "899d7168a485297f",
"gsd-core/workflows/import.md": "b40557b17ca31024",
"gsd-core/workflows/inbox.md": "91aac6360e1a8672",
"gsd-core/workflows/ingest-docs.md": "a0a58450d457ae35",
@@ -287,6 +287,7 @@
"gsd-core/workflows/ship.md": "f8eece9ef821fc7c",
"gsd-core/workflows/sketch-wrap-up.md": "fcef0ef795f8bfc5",
"gsd-core/workflows/sketch.md": "dc9645b2ee28559d",
"gsd-core/workflows/smart-entry.md": "9d35a3094fb64ea0",
"gsd-core/workflows/spec-phase.md": "bab99d00772a5cd0",
"gsd-core/workflows/spike-wrap-up.md": "89a8d7fbc74ce8f2",
"gsd-core/workflows/spike.md": "aae8bcad15642645",
@@ -371,12 +372,13 @@
"skills/gsd-mvp-phase/SKILL.md": "f9a1348c6c297579",
"skills/gsd-new-milestone/SKILL.md": "ac99ffb8a966fe9d",
"skills/gsd-new-project/SKILL.md": "e4d930027074d3b6",
"skills/gsd-next/SKILL.md": "13e394affe675498",
"skills/gsd-ns-context/SKILL.md": "3102e7ad9b60f182",
"skills/gsd-ns-ideate/SKILL.md": "c2c129408a046d22",
"skills/gsd-ns-manage/SKILL.md": "8d786e01fa28a7bb",
"skills/gsd-ns-project/SKILL.md": "cc0c4bd9feeab147",
"skills/gsd-ns-review/SKILL.md": "4111ea56e820479f",
"skills/gsd-ns-workflow/SKILL.md": "13c0d84b25545331",
"skills/gsd-ns-workflow/SKILL.md": "4282c40a5e6e8bbe",
"skills/gsd-pause-work/SKILL.md": "35e8a148e44f5361",
"skills/gsd-phase/SKILL.md": "00be96e7ae36c6f0",
"skills/gsd-plan-phase/SKILL.md": "0c9e87da048acfb7",

View File

@@ -40,7 +40,7 @@
"agents/gsd-verifier.md": "5645dfb20b787f01",
"gsd-core/VERSION": "ef0deccd81a6723c",
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
"gsd-core/bin/gsd-tools.cjs": "4456f5432471af00",
"gsd-core/bin/gsd-tools.cjs": "e504d3b82da834ae",
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "7d398e94c44e5268",
@@ -86,7 +86,7 @@
"gsd-core/references/few-shot-examples/plan-checker.md": "2574808188ac9de4",
"gsd-core/references/few-shot-examples/verifier.md": "5badee4560b14ae8",
"gsd-core/references/gate-prompts.md": "e69f5993ab944d80",
"gsd-core/references/gates.md": "7dc9fd3a3d6217c6",
"gsd-core/references/gates.md": "bd79c7f90c8cb8d7",
"gsd-core/references/git-integration.md": "f5403e1470e46e69",
"gsd-core/references/git-planning-commit.md": "f897a15ebfc3f5a7",
"gsd-core/references/honest-verifier.md": "8815c9fc18c35719",
@@ -246,8 +246,8 @@
"gsd-core/workflows/help.md": "5d040504b9ab35e3",
"gsd-core/workflows/help/modes/brief.md": "5fe077fbbdb5d9f7",
"gsd-core/workflows/help/modes/default.md": "77c34f4a77dec02c",
"gsd-core/workflows/help/modes/full.md": "a123dd946319a258",
"gsd-core/workflows/help/modes/topic.md": "d7c60d488810fbd6",
"gsd-core/workflows/help/modes/full.md": "1bf502890828ada8",
"gsd-core/workflows/help/modes/topic.md": "6762e41e3291ae24",
"gsd-core/workflows/import.md": "64c8031374b39376",
"gsd-core/workflows/inbox.md": "437f981ef9ae7b26",
"gsd-core/workflows/ingest-docs.md": "62761071f8391015",
@@ -291,6 +291,7 @@
"gsd-core/workflows/ship.md": "c3db35c8d1b398c1",
"gsd-core/workflows/sketch-wrap-up.md": "91f40fc816293e3c",
"gsd-core/workflows/sketch.md": "92a8bab2605469fb",
"gsd-core/workflows/smart-entry.md": "4ca661156b9c274f",
"gsd-core/workflows/spec-phase.md": "e06346c9c626a2d2",
"gsd-core/workflows/spike-wrap-up.md": "693949a4e10e66bd",
"gsd-core/workflows/spike.md": "204e742c846ee0d9",
@@ -373,7 +374,7 @@
"skills/gsd-ns-review/skills/ui-phase/SKILL.md": "c35f175ccc747d56",
"skills/gsd-ns-review/skills/ui-review/SKILL.md": "48267dc071481a89",
"skills/gsd-ns-review/skills/validate-phase/SKILL.md": "62dc6ae62bb38c16",
"skills/gsd-ns-workflow/SKILL.md": "7dbd699b5130c0d5",
"skills/gsd-ns-workflow/SKILL.md": "0cb9c23c065fe448",
"skills/gsd-ns-workflow/skills/add-tests/SKILL.md": "addc3a220961a9c7",
"skills/gsd-ns-workflow/skills/ai-integration-phase/SKILL.md": "e74b83a991dc9fc7",
"skills/gsd-ns-workflow/skills/autonomous/SKILL.md": "37dc7b78ceb74803",
@@ -381,6 +382,7 @@
"skills/gsd-ns-workflow/skills/execute-phase/SKILL.md": "e249a35959e49e99",
"skills/gsd-ns-workflow/skills/fast/SKILL.md": "3eae6536d09c2532",
"skills/gsd-ns-workflow/skills/mvp-phase/SKILL.md": "6c2beccceb46fda1",
"skills/gsd-ns-workflow/skills/next/SKILL.md": "3856471d0f64bf09",
"skills/gsd-ns-workflow/skills/phase/SKILL.md": "4e1363db6013a1e5",
"skills/gsd-ns-workflow/skills/plan-phase/SKILL.md": "b7a6ff2837b41143",
"skills/gsd-ns-workflow/skills/plan-review-convergence/SKILL.md": "b7d213288df9aa96",

View File

@@ -69,12 +69,13 @@
"commands/gsd-mvp-phase.md": "671791ba61c4654a",
"commands/gsd-new-milestone.md": "00b5d757991941c7",
"commands/gsd-new-project.md": "490195d5003d3d17",
"commands/gsd-next.md": "dbec71acc9d5d2a2",
"commands/gsd-ns-context.md": "cc8954f405ae7916",
"commands/gsd-ns-ideate.md": "fd1dd80d705e6e36",
"commands/gsd-ns-manage.md": "5cc012d0be1caabb",
"commands/gsd-ns-project.md": "52e30c8d84bb1ee5",
"commands/gsd-ns-review.md": "a28dbfd4aba5f74b",
"commands/gsd-ns-workflow.md": "dcd214fc50008d42",
"commands/gsd-ns-workflow.md": "a1b874be9cf16caf",
"commands/gsd-pause-work.md": "6caa75a7c2b4dd2d",
"commands/gsd-phase.md": "e3ca4958ea20a935",
"commands/gsd-plan-phase.md": "6de27d54ac191539",
@@ -106,7 +107,7 @@
"commands/gsd-workstreams.md": "884b6c8d648422c7",
"gsd-core/VERSION": "ef0deccd81a6723c",
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
"gsd-core/bin/gsd-tools.cjs": "4456f5432471af00",
"gsd-core/bin/gsd-tools.cjs": "e504d3b82da834ae",
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "7d398e94c44e5268",
@@ -152,7 +153,7 @@
"gsd-core/references/few-shot-examples/plan-checker.md": "2574808188ac9de4",
"gsd-core/references/few-shot-examples/verifier.md": "5badee4560b14ae8",
"gsd-core/references/gate-prompts.md": "e69f5993ab944d80",
"gsd-core/references/gates.md": "7dc9fd3a3d6217c6",
"gsd-core/references/gates.md": "bd79c7f90c8cb8d7",
"gsd-core/references/git-integration.md": "77bf9dff38b2c9d4",
"gsd-core/references/git-planning-commit.md": "f897a15ebfc3f5a7",
"gsd-core/references/honest-verifier.md": "8815c9fc18c35719",
@@ -312,8 +313,8 @@
"gsd-core/workflows/help.md": "5d040504b9ab35e3",
"gsd-core/workflows/help/modes/brief.md": "5fe077fbbdb5d9f7",
"gsd-core/workflows/help/modes/default.md": "cc8ef97de634c995",
"gsd-core/workflows/help/modes/full.md": "de202c9910773a1d",
"gsd-core/workflows/help/modes/topic.md": "d7c60d488810fbd6",
"gsd-core/workflows/help/modes/full.md": "4c018bcc9ed4e55c",
"gsd-core/workflows/help/modes/topic.md": "6762e41e3291ae24",
"gsd-core/workflows/import.md": "97861d522319d56e",
"gsd-core/workflows/inbox.md": "437f981ef9ae7b26",
"gsd-core/workflows/ingest-docs.md": "77d9103d8d5432f3",
@@ -357,6 +358,7 @@
"gsd-core/workflows/ship.md": "d26fb5d3e965642a",
"gsd-core/workflows/sketch-wrap-up.md": "7d52aa95ee69d47a",
"gsd-core/workflows/sketch.md": "55cee885b4add548",
"gsd-core/workflows/smart-entry.md": "62456d35c69d7437",
"gsd-core/workflows/spec-phase.md": "2aa0353147153776",
"gsd-core/workflows/spike-wrap-up.md": "2fde13092ba1af57",
"gsd-core/workflows/spike.md": "0051a7e2193a7522",
@@ -441,12 +443,13 @@
"skills/gsd-mvp-phase/SKILL.md": "236fcdd69f47d101",
"skills/gsd-new-milestone/SKILL.md": "be371ad30631385e",
"skills/gsd-new-project/SKILL.md": "793f7177d3bd026b",
"skills/gsd-next/SKILL.md": "43c805425b88bf75",
"skills/gsd-ns-context/SKILL.md": "305b9b6248e8cc95",
"skills/gsd-ns-ideate/SKILL.md": "c63a1aed61888b35",
"skills/gsd-ns-manage/SKILL.md": "6a5b711b5103c566",
"skills/gsd-ns-project/SKILL.md": "345ea11ccffd21b0",
"skills/gsd-ns-review/SKILL.md": "98f1f3b3caf1a279",
"skills/gsd-ns-workflow/SKILL.md": "eda03f7d99eb2f8e",
"skills/gsd-ns-workflow/SKILL.md": "b77682820700300b",
"skills/gsd-pause-work/SKILL.md": "e2de20b7539e78d4",
"skills/gsd-phase/SKILL.md": "f87211f779315ced",
"skills/gsd-plan-phase/SKILL.md": "6134e837750034ab",

View File

@@ -72,7 +72,7 @@
"config.toml": "a34316b9ac61a620",
"gsd-core/VERSION": "ef0deccd81a6723c",
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
"gsd-core/bin/gsd-tools.cjs": "4456f5432471af00",
"gsd-core/bin/gsd-tools.cjs": "e504d3b82da834ae",
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "7d398e94c44e5268",
@@ -278,8 +278,8 @@
"gsd-core/workflows/help.md": "08e1349950c5602a",
"gsd-core/workflows/help/modes/brief.md": "becceb85b25dca89",
"gsd-core/workflows/help/modes/default.md": "18c7dc50066f5cd6",
"gsd-core/workflows/help/modes/full.md": "072c20fe275d54df",
"gsd-core/workflows/help/modes/topic.md": "fc6697bdb47df8b3",
"gsd-core/workflows/help/modes/full.md": "8a7854a34df89bd8",
"gsd-core/workflows/help/modes/topic.md": "303613ba8b123b9f",
"gsd-core/workflows/import.md": "8b8c3a25526c0d41",
"gsd-core/workflows/inbox.md": "61b8b10e7a74b2e9",
"gsd-core/workflows/ingest-docs.md": "3680699c20c3ac7d",
@@ -323,6 +323,7 @@
"gsd-core/workflows/ship.md": "125045072ab5017d",
"gsd-core/workflows/sketch-wrap-up.md": "7709bdd7a70f736c",
"gsd-core/workflows/sketch.md": "bfe5a781d6da3ce8",
"gsd-core/workflows/smart-entry.md": "72d698957173fd33",
"gsd-core/workflows/spec-phase.md": "47cea2b5efffa6b0",
"gsd-core/workflows/spike-wrap-up.md": "53a2c51a8d9e6b08",
"gsd-core/workflows/spike.md": "c2f115f0d3251654",
@@ -387,12 +388,13 @@
"skills/gsd-mvp-phase/SKILL.md": "72b65ae927b280fe",
"skills/gsd-new-milestone/SKILL.md": "587574d2d475bb10",
"skills/gsd-new-project/SKILL.md": "129ac16e6a1f04ba",
"skills/gsd-next/SKILL.md": "7855fd70e387087e",
"skills/gsd-ns-context/SKILL.md": "9b496da79789b3f9",
"skills/gsd-ns-ideate/SKILL.md": "84ca1cde06110981",
"skills/gsd-ns-manage/SKILL.md": "909dafa0cd19ba5a",
"skills/gsd-ns-project/SKILL.md": "fb8ad6c2223be3c3",
"skills/gsd-ns-review/SKILL.md": "022253010db0e072",
"skills/gsd-ns-workflow/SKILL.md": "14b6b1915178163f",
"skills/gsd-ns-workflow/SKILL.md": "cba075bc819b539e",
"skills/gsd-pause-work/SKILL.md": "b379469eed78a196",
"skills/gsd-phase/SKILL.md": "25477edc97a90c91",
"skills/gsd-plan-phase/SKILL.md": "19ead1acb151a868",

View File

@@ -38,7 +38,7 @@
"copilot-instructions.md": "1fb04111759f1645",
"gsd-core/VERSION": "ef0deccd81a6723c",
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
"gsd-core/bin/gsd-tools.cjs": "815b92a0e7b7693e",
"gsd-core/bin/gsd-tools.cjs": "e839e52071d5baa1",
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "7d398e94c44e5268",
@@ -244,8 +244,8 @@
"gsd-core/workflows/help.md": "5d040504b9ab35e3",
"gsd-core/workflows/help/modes/brief.md": "2e923b3ed635d495",
"gsd-core/workflows/help/modes/default.md": "b544113eb9aa54fa",
"gsd-core/workflows/help/modes/full.md": "6985d700bbb1cbe5",
"gsd-core/workflows/help/modes/topic.md": "a404990fcf2d7df7",
"gsd-core/workflows/help/modes/full.md": "0e9381367d6257bd",
"gsd-core/workflows/help/modes/topic.md": "19a279fe91922d3b",
"gsd-core/workflows/import.md": "0e4a8880126d196b",
"gsd-core/workflows/inbox.md": "a448220c548f27bc",
"gsd-core/workflows/ingest-docs.md": "c34b9511b9ea92d8",
@@ -289,6 +289,7 @@
"gsd-core/workflows/ship.md": "0ee4d6bc7e34f716",
"gsd-core/workflows/sketch-wrap-up.md": "804bb69e6c3590a0",
"gsd-core/workflows/sketch.md": "b4d17647e0fb9e5d",
"gsd-core/workflows/smart-entry.md": "d4c882ebeec21f34",
"gsd-core/workflows/spec-phase.md": "117abebab62d6df7",
"gsd-core/workflows/spike-wrap-up.md": "e203ed8e057f654c",
"gsd-core/workflows/spike.md": "716d74cdb2e39a3e",
@@ -352,12 +353,13 @@
"skills/gsd-mvp-phase/SKILL.md": "60ac3c3fa6a6a19b",
"skills/gsd-new-milestone/SKILL.md": "e5eafe44e271bb92",
"skills/gsd-new-project/SKILL.md": "4ce68352221126c6",
"skills/gsd-next/SKILL.md": "16507fc39a3a0684",
"skills/gsd-ns-context/SKILL.md": "d61edde87ae53105",
"skills/gsd-ns-ideate/SKILL.md": "ca62cdf78ebe3585",
"skills/gsd-ns-manage/SKILL.md": "9b1b7f6d9b877fcd",
"skills/gsd-ns-project/SKILL.md": "f1a666fb76527e6b",
"skills/gsd-ns-review/SKILL.md": "6405d0b1cf3f245d",
"skills/gsd-ns-workflow/SKILL.md": "4f8a304f83832794",
"skills/gsd-ns-workflow/SKILL.md": "243287d21663ba87",
"skills/gsd-pause-work/SKILL.md": "4f0caa008a8001ff",
"skills/gsd-phase/SKILL.md": "d38c7f9b1d0d2360",
"skills/gsd-plan-phase/SKILL.md": "d257bb9b1786039d",

View File

@@ -69,12 +69,13 @@
"commands/gsd-mvp-phase.md": "e8debeee627a0bf2",
"commands/gsd-new-milestone.md": "64b997f99b1e6293",
"commands/gsd-new-project.md": "f1a6288e80cc16b0",
"commands/gsd-next.md": "e9d8b7c71627e03b",
"commands/gsd-ns-context.md": "b551dcc549c6a23f",
"commands/gsd-ns-ideate.md": "32f561de74b1bc7b",
"commands/gsd-ns-manage.md": "24445a84bd817c5b",
"commands/gsd-ns-project.md": "b39b509ac4161d28",
"commands/gsd-ns-review.md": "c8550463fbab5e76",
"commands/gsd-ns-workflow.md": "8226be5dc9e99b90",
"commands/gsd-ns-workflow.md": "2c1f1fde3c46bb40",
"commands/gsd-pause-work.md": "59630f05f95fff68",
"commands/gsd-phase.md": "9a073dcd0f934f90",
"commands/gsd-plan-phase.md": "57de92131fcb16b8",
@@ -106,7 +107,7 @@
"commands/gsd-workstreams.md": "112660ceb663c750",
"gsd-core/VERSION": "ef0deccd81a6723c",
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
"gsd-core/bin/gsd-tools.cjs": "815b92a0e7b7693e",
"gsd-core/bin/gsd-tools.cjs": "e839e52071d5baa1",
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "7d398e94c44e5268",
@@ -312,8 +313,8 @@
"gsd-core/workflows/help.md": "08e1349950c5602a",
"gsd-core/workflows/help/modes/brief.md": "2e923b3ed635d495",
"gsd-core/workflows/help/modes/default.md": "37c5149aae232e61",
"gsd-core/workflows/help/modes/full.md": "b893a940160dbcb9",
"gsd-core/workflows/help/modes/topic.md": "9f00f5f94c5497aa",
"gsd-core/workflows/help/modes/full.md": "ed31d1afcbd3591f",
"gsd-core/workflows/help/modes/topic.md": "177d351f7465787d",
"gsd-core/workflows/import.md": "791424ae67c9cf32",
"gsd-core/workflows/inbox.md": "797c287852eb8957",
"gsd-core/workflows/ingest-docs.md": "b8b06f0561e5e68f",
@@ -357,6 +358,7 @@
"gsd-core/workflows/ship.md": "597f9423dd372337",
"gsd-core/workflows/sketch-wrap-up.md": "6780757b2db165ac",
"gsd-core/workflows/sketch.md": "4221996fa3d87f7c",
"gsd-core/workflows/smart-entry.md": "f8c615009ab00a22",
"gsd-core/workflows/spec-phase.md": "fca5397bf040156d",
"gsd-core/workflows/spike-wrap-up.md": "ec64f0f7ab03ef9b",
"gsd-core/workflows/spike.md": "9e37f8067adf87b7",
@@ -421,12 +423,13 @@
"skills/gsd-mvp-phase/SKILL.md": "9d8e2999fa9f7830",
"skills/gsd-new-milestone/SKILL.md": "642d0741c89b911a",
"skills/gsd-new-project/SKILL.md": "18f587ac0b390d94",
"skills/gsd-next/SKILL.md": "75ef1905cb7425e1",
"skills/gsd-ns-context/SKILL.md": "dcf5711653d57354",
"skills/gsd-ns-ideate/SKILL.md": "0114f0bc5501c1e3",
"skills/gsd-ns-manage/SKILL.md": "f3232d05d5263f5b",
"skills/gsd-ns-project/SKILL.md": "9f737a544e8d3f31",
"skills/gsd-ns-review/SKILL.md": "8c686e92f293bfdd",
"skills/gsd-ns-workflow/SKILL.md": "180031baca2f6d24",
"skills/gsd-ns-workflow/SKILL.md": "c379192bd17d47d7",
"skills/gsd-pause-work/SKILL.md": "10e7531a2cb392ad",
"skills/gsd-phase/SKILL.md": "1a2c9b64c1af7ffc",
"skills/gsd-plan-phase/SKILL.md": "0784da05d41dcfe7",

View File

@@ -69,12 +69,13 @@
"commands/gsd/mvp-phase.toml": "fbaa7f45ec213bb5",
"commands/gsd/new-milestone.toml": "ca13145a1b414535",
"commands/gsd/new-project.toml": "ed9c9d44e2bad612",
"commands/gsd/next.toml": "f434ec4e11d96141",
"commands/gsd/ns-context.toml": "fc29ba4b8e8cd6ca",
"commands/gsd/ns-ideate.toml": "dd813a2d4dd91ade",
"commands/gsd/ns-manage.toml": "2fdb297bc681288e",
"commands/gsd/ns-project.toml": "81146d2a189556bc",
"commands/gsd/ns-review.toml": "2e48bcc682bb0c6a",
"commands/gsd/ns-workflow.toml": "409dd5a1848ef755",
"commands/gsd/ns-workflow.toml": "8968e5b15969cb1f",
"commands/gsd/pause-work.toml": "11899f0727c0ffe9",
"commands/gsd/phase.toml": "e062b8c130d3e954",
"commands/gsd/plan-phase.toml": "7d1c635bbdbd1f8b",
@@ -106,7 +107,7 @@
"commands/gsd/workstreams.toml": "95f0c349b808a84c",
"gsd-core/VERSION": "ef0deccd81a6723c",
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
"gsd-core/bin/gsd-tools.cjs": "4456f5432471af00",
"gsd-core/bin/gsd-tools.cjs": "e504d3b82da834ae",
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "7d398e94c44e5268",
@@ -152,7 +153,7 @@
"gsd-core/references/few-shot-examples/plan-checker.md": "2574808188ac9de4",
"gsd-core/references/few-shot-examples/verifier.md": "5badee4560b14ae8",
"gsd-core/references/gate-prompts.md": "e69f5993ab944d80",
"gsd-core/references/gates.md": "7dc9fd3a3d6217c6",
"gsd-core/references/gates.md": "bd79c7f90c8cb8d7",
"gsd-core/references/git-integration.md": "77bf9dff38b2c9d4",
"gsd-core/references/git-planning-commit.md": "f897a15ebfc3f5a7",
"gsd-core/references/honest-verifier.md": "8815c9fc18c35719",
@@ -312,8 +313,8 @@
"gsd-core/workflows/help.md": "5d040504b9ab35e3",
"gsd-core/workflows/help/modes/brief.md": "5fe077fbbdb5d9f7",
"gsd-core/workflows/help/modes/default.md": "cc8ef97de634c995",
"gsd-core/workflows/help/modes/full.md": "c7068334fba84dd8",
"gsd-core/workflows/help/modes/topic.md": "d7c60d488810fbd6",
"gsd-core/workflows/help/modes/full.md": "bc96c127412c4171",
"gsd-core/workflows/help/modes/topic.md": "6762e41e3291ae24",
"gsd-core/workflows/import.md": "c740cfabacf7b70a",
"gsd-core/workflows/inbox.md": "41371f790ef06101",
"gsd-core/workflows/ingest-docs.md": "1ee3890d42d95576",
@@ -357,6 +358,7 @@
"gsd-core/workflows/ship.md": "dfbf2069ce4b276d",
"gsd-core/workflows/sketch-wrap-up.md": "067e2654f3fdaba2",
"gsd-core/workflows/sketch.md": "e1a544c83bb6e5be",
"gsd-core/workflows/smart-entry.md": "aead1f97e32cc345",
"gsd-core/workflows/spec-phase.md": "0d67fa7de33933c0",
"gsd-core/workflows/spike-wrap-up.md": "99a9e23d1c6cf66e",
"gsd-core/workflows/spike.md": "c9482514e665bcac",

View File

@@ -37,7 +37,7 @@
"agents/gsd-verifier.md": "c5916e463df6306e",
"gsd-core/VERSION": "ef0deccd81a6723c",
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
"gsd-core/bin/gsd-tools.cjs": "4456f5432471af00",
"gsd-core/bin/gsd-tools.cjs": "e504d3b82da834ae",
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "7d398e94c44e5268",
@@ -243,8 +243,8 @@
"gsd-core/workflows/help.md": "5d040504b9ab35e3",
"gsd-core/workflows/help/modes/brief.md": "2e923b3ed635d495",
"gsd-core/workflows/help/modes/default.md": "bb6876c976996dbd",
"gsd-core/workflows/help/modes/full.md": "2df924dd3815ff0e",
"gsd-core/workflows/help/modes/topic.md": "bd2e4cc8e460f5bd",
"gsd-core/workflows/help/modes/full.md": "fdab4b3690bdd11d",
"gsd-core/workflows/help/modes/topic.md": "899d7168a485297f",
"gsd-core/workflows/import.md": "da7ca19f487c6b60",
"gsd-core/workflows/inbox.md": "91aac6360e1a8672",
"gsd-core/workflows/ingest-docs.md": "8adc1e35097abce2",
@@ -288,6 +288,7 @@
"gsd-core/workflows/ship.md": "3f6e6424d089d4d0",
"gsd-core/workflows/sketch-wrap-up.md": "eb5f0849f07479aa",
"gsd-core/workflows/sketch.md": "9a712af64fc2fff8",
"gsd-core/workflows/smart-entry.md": "bf38fd489b27231e",
"gsd-core/workflows/spec-phase.md": "79f136ab71edf595",
"gsd-core/workflows/spike-wrap-up.md": "79e23a81bb74bc95",
"gsd-core/workflows/spike.md": "80844a3c05339fdb",
@@ -393,7 +394,7 @@
"skills/gsd/gsd-ns-review/skills/ui-phase/SKILL.md": "3207854a4f0fab67",
"skills/gsd/gsd-ns-review/skills/ui-review/SKILL.md": "512c54662616c3f9",
"skills/gsd/gsd-ns-review/skills/validate-phase/SKILL.md": "f2c31c34fc8e24c4",
"skills/gsd/gsd-ns-workflow/SKILL.md": "a5c0c299df224d10",
"skills/gsd/gsd-ns-workflow/SKILL.md": "f54502a2c6bf5495",
"skills/gsd/gsd-ns-workflow/skills/add-tests/SKILL.md": "29e5863322ad6f2d",
"skills/gsd/gsd-ns-workflow/skills/ai-integration-phase/SKILL.md": "317912c0488d701b",
"skills/gsd/gsd-ns-workflow/skills/autonomous/SKILL.md": "3c9bc726e13c4916",
@@ -401,6 +402,7 @@
"skills/gsd/gsd-ns-workflow/skills/execute-phase/SKILL.md": "a2e4e6211e09b3c0",
"skills/gsd/gsd-ns-workflow/skills/fast/SKILL.md": "c8517ba44da99147",
"skills/gsd/gsd-ns-workflow/skills/mvp-phase/SKILL.md": "0804dfd4124526ba",
"skills/gsd/gsd-ns-workflow/skills/next/SKILL.md": "1d30cf6061d15168",
"skills/gsd/gsd-ns-workflow/skills/phase/SKILL.md": "610bd06d71198849",
"skills/gsd/gsd-ns-workflow/skills/plan-phase/SKILL.md": "f06f0e2fc8def3f0",
"skills/gsd/gsd-ns-workflow/skills/plan-review-convergence/SKILL.md": "fddf8bd7b3ae1f10",

View File

@@ -69,12 +69,13 @@
"command/gsd-mvp-phase.md": "273a1c26ca13274b",
"command/gsd-new-milestone.md": "a315b3205c5d3470",
"command/gsd-new-project.md": "f2763e2ccbb70ebe",
"command/gsd-next.md": "dfdcfb2fd79957fa",
"command/gsd-ns-context.md": "9a2b55b64f2e1e4b",
"command/gsd-ns-ideate.md": "27e7fcdc69eeeb5f",
"command/gsd-ns-manage.md": "eded59052ca4b240",
"command/gsd-ns-project.md": "91417cf7b76581dd",
"command/gsd-ns-review.md": "61fe33e28eb1b961",
"command/gsd-ns-workflow.md": "3352c292c27fe50b",
"command/gsd-ns-workflow.md": "13c1ab91080c0cc3",
"command/gsd-pause-work.md": "04e993b1c9f8322b",
"command/gsd-phase.md": "8f0e98dc6c223229",
"command/gsd-plan-phase.md": "b37e9a61f946c975",
@@ -106,7 +107,7 @@
"command/gsd-workstreams.md": "5e57eed1881c3891",
"gsd-core/VERSION": "ef0deccd81a6723c",
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
"gsd-core/bin/gsd-tools.cjs": "4456f5432471af00",
"gsd-core/bin/gsd-tools.cjs": "e504d3b82da834ae",
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "7d398e94c44e5268",
@@ -312,8 +313,8 @@
"gsd-core/workflows/help.md": "5d040504b9ab35e3",
"gsd-core/workflows/help/modes/brief.md": "2e923b3ed635d495",
"gsd-core/workflows/help/modes/default.md": "b544113eb9aa54fa",
"gsd-core/workflows/help/modes/full.md": "89044c816457cf48",
"gsd-core/workflows/help/modes/topic.md": "a404990fcf2d7df7",
"gsd-core/workflows/help/modes/full.md": "051f9633f45558b4",
"gsd-core/workflows/help/modes/topic.md": "19a279fe91922d3b",
"gsd-core/workflows/import.md": "95734bf6d48ef776",
"gsd-core/workflows/inbox.md": "a073396097c89c01",
"gsd-core/workflows/ingest-docs.md": "270a4dc138d239c4",
@@ -357,6 +358,7 @@
"gsd-core/workflows/ship.md": "b6149b0bc45cc759",
"gsd-core/workflows/sketch-wrap-up.md": "7673797d65af2377",
"gsd-core/workflows/sketch.md": "201772f9c5c02e97",
"gsd-core/workflows/smart-entry.md": "8b865b82ff6d44d8",
"gsd-core/workflows/spec-phase.md": "2c67506aeb25a7e1",
"gsd-core/workflows/spike-wrap-up.md": "b7ee3d961b5792c6",
"gsd-core/workflows/spike.md": "a782dd863771fe0a",
@@ -442,12 +444,13 @@
"skills/gsd-mvp-phase/SKILL.md": "eeb0ac0f88647475",
"skills/gsd-new-milestone/SKILL.md": "bc8989d97f3161ad",
"skills/gsd-new-project/SKILL.md": "c08047e101dbac9c",
"skills/gsd-next/SKILL.md": "3856471d0f64bf09",
"skills/gsd-ns-context/SKILL.md": "110fb2227bcde535",
"skills/gsd-ns-ideate/SKILL.md": "d093d59a8519decc",
"skills/gsd-ns-manage/SKILL.md": "2756981de613022e",
"skills/gsd-ns-project/SKILL.md": "757976d48e8b7a26",
"skills/gsd-ns-review/SKILL.md": "680bdd33c882b049",
"skills/gsd-ns-workflow/SKILL.md": "68effe2fc2c65317",
"skills/gsd-ns-workflow/SKILL.md": "579b7f0dac74e1fb",
"skills/gsd-pause-work/SKILL.md": "34366b18a392a717",
"skills/gsd-phase/SKILL.md": "64a241d4f8665aa2",
"skills/gsd-plan-phase/SKILL.md": "c73cba04a26f0bfe",

View File

@@ -73,7 +73,7 @@
"agents/subagents/gsd-verifier.yaml": "2d2bd6b37626f382",
"gsd-core/VERSION": "ef0deccd81a6723c",
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
"gsd-core/bin/gsd-tools.cjs": "4456f5432471af00",
"gsd-core/bin/gsd-tools.cjs": "e504d3b82da834ae",
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "7d398e94c44e5268",
@@ -119,7 +119,7 @@
"gsd-core/references/few-shot-examples/plan-checker.md": "2574808188ac9de4",
"gsd-core/references/few-shot-examples/verifier.md": "5badee4560b14ae8",
"gsd-core/references/gate-prompts.md": "e69f5993ab944d80",
"gsd-core/references/gates.md": "7dc9fd3a3d6217c6",
"gsd-core/references/gates.md": "bd79c7f90c8cb8d7",
"gsd-core/references/git-integration.md": "77bf9dff38b2c9d4",
"gsd-core/references/git-planning-commit.md": "f897a15ebfc3f5a7",
"gsd-core/references/honest-verifier.md": "8815c9fc18c35719",
@@ -279,8 +279,8 @@
"gsd-core/workflows/help.md": "5d040504b9ab35e3",
"gsd-core/workflows/help/modes/brief.md": "5fe077fbbdb5d9f7",
"gsd-core/workflows/help/modes/default.md": "cc8ef97de634c995",
"gsd-core/workflows/help/modes/full.md": "d7432c8631f3cae8",
"gsd-core/workflows/help/modes/topic.md": "d7c60d488810fbd6",
"gsd-core/workflows/help/modes/full.md": "94c18706768e5a41",
"gsd-core/workflows/help/modes/topic.md": "6762e41e3291ae24",
"gsd-core/workflows/import.md": "97861d522319d56e",
"gsd-core/workflows/inbox.md": "437f981ef9ae7b26",
"gsd-core/workflows/ingest-docs.md": "77d9103d8d5432f3",
@@ -324,6 +324,7 @@
"gsd-core/workflows/ship.md": "d26fb5d3e965642a",
"gsd-core/workflows/sketch-wrap-up.md": "19046704ae337d73",
"gsd-core/workflows/sketch.md": "e42a914206c152ef",
"gsd-core/workflows/smart-entry.md": "62456d35c69d7437",
"gsd-core/workflows/spec-phase.md": "2aa0353147153776",
"gsd-core/workflows/spike-wrap-up.md": "3f3e7f0018284059",
"gsd-core/workflows/spike.md": "be2295fe2b32956f",
@@ -386,12 +387,13 @@
"skills/gsd-mvp-phase/SKILL.md": "d2f98f1e955797b6",
"skills/gsd-new-milestone/SKILL.md": "52c3cbe5b931985b",
"skills/gsd-new-project/SKILL.md": "d74e7583377e3563",
"skills/gsd-next/SKILL.md": "0a5109eb81fbeb4b",
"skills/gsd-ns-context/SKILL.md": "89f5bffe6702c0c5",
"skills/gsd-ns-ideate/SKILL.md": "e0c0420f6878906a",
"skills/gsd-ns-manage/SKILL.md": "b7fbe29e45e75f54",
"skills/gsd-ns-project/SKILL.md": "1292d4a6a4280ce9",
"skills/gsd-ns-review/SKILL.md": "2ff62330379f947f",
"skills/gsd-ns-workflow/SKILL.md": "3f5fcfec9da730a2",
"skills/gsd-ns-workflow/SKILL.md": "86272fd655c206bc",
"skills/gsd-pause-work/SKILL.md": "95017c70ae9dca0d",
"skills/gsd-phase/SKILL.md": "31578c329cc2583e",
"skills/gsd-plan-phase/SKILL.md": "54798eddf30056b5",

View File

@@ -69,12 +69,13 @@
"command/gsd-mvp-phase.md": "d45fd76cb9cff3ef",
"command/gsd-new-milestone.md": "64c9dc73425a66a2",
"command/gsd-new-project.md": "0086f0de26002219",
"command/gsd-next.md": "b0ef81652d72d894",
"command/gsd-ns-context.md": "9a2b55b64f2e1e4b",
"command/gsd-ns-ideate.md": "27e7fcdc69eeeb5f",
"command/gsd-ns-manage.md": "eded59052ca4b240",
"command/gsd-ns-project.md": "91417cf7b76581dd",
"command/gsd-ns-review.md": "61fe33e28eb1b961",
"command/gsd-ns-workflow.md": "3352c292c27fe50b",
"command/gsd-ns-workflow.md": "13c1ab91080c0cc3",
"command/gsd-pause-work.md": "bb5bf91a2e3e480e",
"command/gsd-phase.md": "6bcda1539f949d5a",
"command/gsd-plan-phase.md": "7ee44086b59862b9",
@@ -106,7 +107,7 @@
"command/gsd-workstreams.md": "5e57eed1881c3891",
"gsd-core/VERSION": "ef0deccd81a6723c",
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
"gsd-core/bin/gsd-tools.cjs": "4456f5432471af00",
"gsd-core/bin/gsd-tools.cjs": "e504d3b82da834ae",
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "7d398e94c44e5268",
@@ -312,8 +313,8 @@
"gsd-core/workflows/help.md": "5d040504b9ab35e3",
"gsd-core/workflows/help/modes/brief.md": "2e923b3ed635d495",
"gsd-core/workflows/help/modes/default.md": "b544113eb9aa54fa",
"gsd-core/workflows/help/modes/full.md": "d43cbea8b5e6e290",
"gsd-core/workflows/help/modes/topic.md": "a404990fcf2d7df7",
"gsd-core/workflows/help/modes/full.md": "604993c20f4e4b20",
"gsd-core/workflows/help/modes/topic.md": "19a279fe91922d3b",
"gsd-core/workflows/import.md": "46337b37d375f4da",
"gsd-core/workflows/inbox.md": "a073396097c89c01",
"gsd-core/workflows/ingest-docs.md": "bbb4f5fb3ab75113",
@@ -357,6 +358,7 @@
"gsd-core/workflows/ship.md": "5ffb515478d78cb6",
"gsd-core/workflows/sketch-wrap-up.md": "1a9f5b8e01a43506",
"gsd-core/workflows/sketch.md": "89acaae8de9bf8c9",
"gsd-core/workflows/smart-entry.md": "0660f42e2d646f7e",
"gsd-core/workflows/spec-phase.md": "eb94ceb386b2a328",
"gsd-core/workflows/spike-wrap-up.md": "3609a57dbd8b5ac0",
"gsd-core/workflows/spike.md": "df52b9f31a72d204",
@@ -443,12 +445,13 @@
"skills/gsd-mvp-phase/SKILL.md": "0531d0e907af41ce",
"skills/gsd-new-milestone/SKILL.md": "45688741cc4bab41",
"skills/gsd-new-project/SKILL.md": "feef18d69466fdf9",
"skills/gsd-next/SKILL.md": "85fb881a8ac65081",
"skills/gsd-ns-context/SKILL.md": "110fb2227bcde535",
"skills/gsd-ns-ideate/SKILL.md": "d093d59a8519decc",
"skills/gsd-ns-manage/SKILL.md": "2756981de613022e",
"skills/gsd-ns-project/SKILL.md": "757976d48e8b7a26",
"skills/gsd-ns-review/SKILL.md": "680bdd33c882b049",
"skills/gsd-ns-workflow/SKILL.md": "68effe2fc2c65317",
"skills/gsd-ns-workflow/SKILL.md": "579b7f0dac74e1fb",
"skills/gsd-pause-work/SKILL.md": "c7e9ba4f242c4648",
"skills/gsd-phase/SKILL.md": "ee78c0c56c814d84",
"skills/gsd-plan-phase/SKILL.md": "6abcc3a1568a6bb9",

View File

@@ -37,7 +37,7 @@
"agents/gsd-verifier.md": "7286d42f73e54be1",
"gsd-core/VERSION": "ef0deccd81a6723c",
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
"gsd-core/bin/gsd-tools.cjs": "4456f5432471af00",
"gsd-core/bin/gsd-tools.cjs": "e504d3b82da834ae",
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "7d398e94c44e5268",
@@ -243,8 +243,8 @@
"gsd-core/workflows/help.md": "5d040504b9ab35e3",
"gsd-core/workflows/help/modes/brief.md": "2e923b3ed635d495",
"gsd-core/workflows/help/modes/default.md": "cef457f954a9991e",
"gsd-core/workflows/help/modes/full.md": "8f70d84fd3549aa0",
"gsd-core/workflows/help/modes/topic.md": "bd2e4cc8e460f5bd",
"gsd-core/workflows/help/modes/full.md": "1e1e826ba1da0bc5",
"gsd-core/workflows/help/modes/topic.md": "899d7168a485297f",
"gsd-core/workflows/import.md": "5c0e3e97b0a59a06",
"gsd-core/workflows/inbox.md": "91aac6360e1a8672",
"gsd-core/workflows/ingest-docs.md": "9cbe45380eed4a0e",
@@ -288,6 +288,7 @@
"gsd-core/workflows/ship.md": "e10d21edabf201a7",
"gsd-core/workflows/sketch-wrap-up.md": "7630ee69ab22462d",
"gsd-core/workflows/sketch.md": "68c337d92c79dc9b",
"gsd-core/workflows/smart-entry.md": "c90d5ee83befd3ea",
"gsd-core/workflows/spec-phase.md": "dbe5f223d7bc026c",
"gsd-core/workflows/spike-wrap-up.md": "96b8244988d651fd",
"gsd-core/workflows/spike.md": "52fcd95f7b343232",
@@ -392,7 +393,7 @@
"skills/gsd-ns-review/skills/ui-phase/SKILL.md": "7c9404102a9b9d74",
"skills/gsd-ns-review/skills/ui-review/SKILL.md": "ef8f643abff1486b",
"skills/gsd-ns-review/skills/validate-phase/SKILL.md": "70375e2381319d2b",
"skills/gsd-ns-workflow/SKILL.md": "46f5e3e89eed3743",
"skills/gsd-ns-workflow/SKILL.md": "5a65f9fb93a71cec",
"skills/gsd-ns-workflow/skills/add-tests/SKILL.md": "6661195a66f25ae8",
"skills/gsd-ns-workflow/skills/ai-integration-phase/SKILL.md": "96c8151779ad510e",
"skills/gsd-ns-workflow/skills/autonomous/SKILL.md": "fb57d2c08ea9e8bc",
@@ -400,6 +401,7 @@
"skills/gsd-ns-workflow/skills/execute-phase/SKILL.md": "13518a22e020e1bf",
"skills/gsd-ns-workflow/skills/fast/SKILL.md": "5f3f8b0c3f564d14",
"skills/gsd-ns-workflow/skills/mvp-phase/SKILL.md": "f9a1348c6c297579",
"skills/gsd-ns-workflow/skills/next/SKILL.md": "13e394affe675498",
"skills/gsd-ns-workflow/skills/phase/SKILL.md": "1ed640e5f06c7be6",
"skills/gsd-ns-workflow/skills/plan-phase/SKILL.md": "75979bb1db5557af",
"skills/gsd-ns-workflow/skills/plan-review-convergence/SKILL.md": "c3dd8bfa877eaed5",

View File

@@ -37,7 +37,7 @@
"agents/gsd-verifier.md": "2fef6afe676b0e34",
"gsd-core/VERSION": "ef0deccd81a6723c",
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
"gsd-core/bin/gsd-tools.cjs": "815b92a0e7b7693e",
"gsd-core/bin/gsd-tools.cjs": "e839e52071d5baa1",
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "7d398e94c44e5268",
@@ -243,8 +243,8 @@
"gsd-core/workflows/help.md": "08e1349950c5602a",
"gsd-core/workflows/help/modes/brief.md": "2e923b3ed635d495",
"gsd-core/workflows/help/modes/default.md": "1b3c315342701265",
"gsd-core/workflows/help/modes/full.md": "6d4aacaec9858e28",
"gsd-core/workflows/help/modes/topic.md": "5419498effee148f",
"gsd-core/workflows/help/modes/full.md": "62c71f57f047999b",
"gsd-core/workflows/help/modes/topic.md": "229e3f3d85bda04b",
"gsd-core/workflows/import.md": "47cc99b635e45f82",
"gsd-core/workflows/inbox.md": "e9ea37b2d46dc5b4",
"gsd-core/workflows/ingest-docs.md": "9554d457d64b7c17",
@@ -288,6 +288,7 @@
"gsd-core/workflows/ship.md": "0647d19b5487b1a3",
"gsd-core/workflows/sketch-wrap-up.md": "4894dac77fc42655",
"gsd-core/workflows/sketch.md": "52f4a04f511e205e",
"gsd-core/workflows/smart-entry.md": "0687dcb3cf5b3699",
"gsd-core/workflows/spec-phase.md": "56cecc44b2cc0bc4",
"gsd-core/workflows/spike-wrap-up.md": "1f57905138a648ac",
"gsd-core/workflows/spike.md": "48df8b626cbd378d",
@@ -370,7 +371,7 @@
"skills/gsd-ns-review/skills/ui-phase/SKILL.md": "89a29a2fa62b177e",
"skills/gsd-ns-review/skills/ui-review/SKILL.md": "3e23efe03dafd691",
"skills/gsd-ns-review/skills/validate-phase/SKILL.md": "fa1ab5c9a869f6cd",
"skills/gsd-ns-workflow/SKILL.md": "7dbd699b5130c0d5",
"skills/gsd-ns-workflow/SKILL.md": "0cb9c23c065fe448",
"skills/gsd-ns-workflow/skills/add-tests/SKILL.md": "6d64ae85b590274f",
"skills/gsd-ns-workflow/skills/ai-integration-phase/SKILL.md": "c458b792bdf5b1f2",
"skills/gsd-ns-workflow/skills/autonomous/SKILL.md": "37dc7b78ceb74803",
@@ -378,6 +379,7 @@
"skills/gsd-ns-workflow/skills/execute-phase/SKILL.md": "eb0158de64f22a55",
"skills/gsd-ns-workflow/skills/fast/SKILL.md": "3eae6536d09c2532",
"skills/gsd-ns-workflow/skills/mvp-phase/SKILL.md": "81c98f4436888cb8",
"skills/gsd-ns-workflow/skills/next/SKILL.md": "02993a5aa046eadd",
"skills/gsd-ns-workflow/skills/phase/SKILL.md": "50f6cc4df79a26b7",
"skills/gsd-ns-workflow/skills/plan-phase/SKILL.md": "44d42fa41a094ad9",
"skills/gsd-ns-workflow/skills/plan-review-convergence/SKILL.md": "fee26bc1daedbff5",

View File

@@ -37,7 +37,7 @@
"agents/gsd-verifier.md": "b16b34a580941124",
"gsd-core/VERSION": "ef0deccd81a6723c",
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
"gsd-core/bin/gsd-tools.cjs": "815b92a0e7b7693e",
"gsd-core/bin/gsd-tools.cjs": "e839e52071d5baa1",
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "7d398e94c44e5268",
@@ -243,8 +243,8 @@
"gsd-core/workflows/help.md": "08e1349950c5602a",
"gsd-core/workflows/help/modes/brief.md": "2e923b3ed635d495",
"gsd-core/workflows/help/modes/default.md": "91509fe03bad9795",
"gsd-core/workflows/help/modes/full.md": "8fb3c93df5883f56",
"gsd-core/workflows/help/modes/topic.md": "9f00f5f94c5497aa",
"gsd-core/workflows/help/modes/full.md": "4c1a43f7095647e1",
"gsd-core/workflows/help/modes/topic.md": "177d351f7465787d",
"gsd-core/workflows/import.md": "f5b3826c4cfb2682",
"gsd-core/workflows/inbox.md": "797c287852eb8957",
"gsd-core/workflows/ingest-docs.md": "6f0ddb1130216a7d",
@@ -288,6 +288,7 @@
"gsd-core/workflows/ship.md": "ddfd8e847836dd94",
"gsd-core/workflows/sketch-wrap-up.md": "67e90c60062d0d5f",
"gsd-core/workflows/sketch.md": "7418628ba45dd6c0",
"gsd-core/workflows/smart-entry.md": "963c0a363fb0cd6a",
"gsd-core/workflows/spec-phase.md": "e8ed811cd67076b7",
"gsd-core/workflows/spike-wrap-up.md": "f8e39782c6e42ef7",
"gsd-core/workflows/spike.md": "aa5934044317ba7a",

View File

@@ -0,0 +1,154 @@
'use strict';
/**
* Structure / contract tests for the /gsd smart-entry command + workflow.
*
* Spec: docs/superpowers/specs/2026-06-27-gsd-smart-entry-design.md
*
* These assert the CONTRACT the markdown layer exposes — not implementation
* logic — over the shipped artifacts: frontmatter completeness, size caps, the
* TEXT_MODE fallback clause, the /gsd:progress fallback, and that every command
* the classifier can emit resolves to a real existing slash command file.
*/
const { describe, test } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const ROOT = path.join(__dirname, '..');
const CMD = path.join(ROOT, 'commands', 'gsd', 'next.md');
const WF = path.join(ROOT, 'gsd-core', 'workflows', 'smart-entry.md');
const CMDS_DIR = path.join(ROOT, 'commands', 'gsd');
// NEW_FILE_CAP from tests/workflow-size-budget.test.cjs (#1074).
const NEW_FILE_CAP = 32768;
function read(p) {
return fs.readFileSync(p, 'utf-8');
}
/** Parse YAML-ish frontmatter (key: value lines between --- fences). */
function parseFrontmatter(content) {
const m = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
if (!m) return {};
const fm = {};
for (const line of m[1].split(/\r?\n/)) {
const mm = line.match(/^([a-z_-]+):\s*(.*)$/);
if (mm) fm[mm[1]] = mm[2];
}
return fm;
}
describe('/gsd:next command file (commands/gsd/next.md)', () => {
const content = read(CMD);
test('exists', () => {
assert.ok(fs.existsSync(CMD));
});
test('frontmatter has name: gsd:next (surfaces as /gsd:next)', () => {
const fm = parseFrontmatter(content);
assert.equal(fm.name, 'gsd:next');
});
test('has NO requires field (must work pre-project)', () => {
const fm = parseFrontmatter(content);
assert.equal(fm.requires, undefined);
});
test('allowed-tools includes AskUserQuestion, Bash, SlashCommand', () => {
const fmBlock = content.match(/^---\r?\n[\s\S]*?\r?\n---/)[0];
assert.match(fmBlock, /AskUserQuestion/);
assert.match(fmBlock, /Bash/);
assert.match(fmBlock, /SlashCommand/);
});
test('references the smart-entry workflow', () => {
assert.match(content, /workflows\/smart-entry\.md/);
});
});
describe('/gsd:next workflow (gsd-core/workflows/smart-entry.md)', () => {
const content = read(WF);
test('exists and is under the NEW_FILE_CAP (32 KiB)', () => {
assert.ok(fs.existsSync(WF));
const bytes = fs.statSync(WF).size;
assert.ok(bytes < NEW_FILE_CAP, `workflow is ${bytes} bytes, must be < ${NEW_FILE_CAP}`);
});
test('contains the TEXT_MODE fallback clause', () => {
assert.match(content, /TEXT_MODE/);
assert.match(content, /numbered list/i);
});
test('contains the gsd_run shim resolver block', () => {
assert.match(content, /_GSD_SHIM_NAME/);
assert.match(content, /gsd_run\(\)/);
});
test('runs smart-entry --json to detect', () => {
assert.match(content, /smart-entry --json/);
});
test('falls back to /gsd:progress on detection failure (never strands)', () => {
assert.match(content, /\/gsd:progress/);
assert.match(content, /unavailable/i);
});
test('dispatches exactly one command then stops', () => {
assert.match(content, /dispatch/i);
// "stop" / "do not chain" guard language present
assert.match(content, /(stop|do not chain|do not re-enter)/i);
});
});
describe('smart-entry: every emitted command resolves to a real slash command', () => {
// Drive the classifier across all situations and collect every command string
// it can emit, then assert each resolves to commands/gsd/<name>.md.
const smartEntry = require('../gsd-core/bin/lib/smart-entry.cjs');
const { SITUATIONS, actionsFor } = smartEntry;
// A minimal signal shape sufficient for actionsFor (which only reads
// current_phase for label text). actionsFor ignores other fields.
const sampleSignals = {
current_phase: 2,
total_phases: 5,
status: 'executing',
progress: 60,
has_planning: true,
has_roadmap: true,
git_dirty: false,
git_unpushed: false,
paused: false,
blockers: [],
has_git: true,
verify_failed: false,
stale_activity: false,
};
const allCommands = new Set();
for (const situation of SITUATIONS) {
for (const a of actionsFor(situation, sampleSignals)) {
allCommands.add(a.command);
}
}
test(`collected commands from all ${SITUATIONS.length} situations`, () => {
assert.ok(allCommands.size >= 8, `expected a broad command set, got ${allCommands.size}`);
});
for (const cmd of allCommands) {
test(`command "${cmd}" resolves to a real commands/gsd/*.md file`, () => {
// Strip leading "/gsd:" and any trailing flags, map to a file stem.
const stem = cmd.replace(/^\/gsd:/, '').split(/\s+/)[0];
// Special-case: "progress --next" / "progress --do" still → progress.md.
const file = path.join(CMDS_DIR, `${stem}.md`);
assert.ok(
fs.existsSync(file),
`command "${cmd}" → expected file commands/gsd/${stem}.md (not found)`,
);
});
}
});

View File

@@ -476,6 +476,10 @@ const KNOWN_SKILLS = new Set([
'mvp-phase.md',
'new-milestone.md',
'new-project.md',
// `next.md` was legitimately reclaimed (#1787): the old workflow-advance
// command stays absorbed into `progress.md --next`, while the current
// `/gsd:next` is a different state-aware smart-entry launcher.
'next.md',
'pause-work.md',
'phase.md',
'plan-phase.md',
@@ -591,7 +595,10 @@ describe('absorbed skills are removed', () => {
['scan', 'absorbed into map-codebase.md'],
['intel', 'absorbed into map-codebase.md'],
['code-review-fix', 'absorbed into code-review.md'],
['next', 'absorbed into progress.md'],
// NOTE: `next` is intentionally absent here — the workflow-advance behavior
// stays absorbed into `progress.md --next`, but `/gsd:next` was reintroduced
// (#1787) as a distinct state-aware smart-entry launcher, so `next.md` is a
// KNOWN_SKILL again rather than an absorbed/removed command.
['do', 'absorbed into progress.md'],
];
@@ -832,7 +839,7 @@ describe('skill set', () => {
current: currentBasenames,
known: KNOWN_SKILLS,
fail: assert.fail,
pruneHint: 'edit KNOWN_SKILLS in tests/enh-2790-skill-consolidation.test.cjs',
pruneHint: 'edit KNOWN_SKILLS in tests/skill-frontmatter-contract.test.cjs',
});
});
});

View File

@@ -0,0 +1,430 @@
'use strict';
/**
* Unit tests for the smart-entry situation classifier.
*
* Spec: docs/superpowers/specs/2026-06-27-gsd-smart-entry-design.md
*
* Covers: all 11 situations, priority ordering (paused beats blocked), JSON
* shape invariants (exactly one recommended, commands are /gsd:* slash forms),
* and the --json / human output modes.
*/
const { describe, test, afterEach } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const os = require('node:os');
const path = require('node:path');
const { execFileSync } = require('node:child_process');
const { cleanup } = require('./helpers.cjs');
const smartEntry = require('../gsd-core/bin/lib/smart-entry.cjs');
const { classify, classifyProject, detectSignals, SITUATIONS } = smartEntry;
const TOOLS = path.join(__dirname, '..', 'gsd-core', 'bin', 'gsd-tools.cjs');
// ─── Fixture helpers ─────────────────────────────────────────────────────────
/**
* Create a temp dir with a `.planning/` and optional STATE.md / ROADMAP.md.
* @returns {string} tmpDir path
*/
function makeProject({ state, roadmap = false, git = false, verifyFail = false } = {}) {
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-se-'));
fs.mkdirSync(path.join(tmpDir, '.planning', 'phases'), { recursive: true });
if (state !== undefined) {
fs.writeFileSync(path.join(tmpDir, '.planning', 'STATE.md'), state);
}
if (roadmap) {
fs.writeFileSync(path.join(tmpDir, '.planning', 'ROADMAP.md'), '# Roadmap\n');
}
if (git) {
execFileSync('git', ['init'], { cwd: tmpDir, stdio: 'pipe' });
execFileSync('git', ['config', 'user.email', 't@t.com'], { cwd: tmpDir, stdio: 'pipe' });
execFileSync('git', ['config', 'user.name', 'T'], { cwd: tmpDir, stdio: 'pipe' });
}
if (verifyFail) {
const phaseDir = path.join(tmpDir, '.planning', 'phases', '01-feat');
fs.mkdirSync(phaseDir, { recursive: true });
fs.writeFileSync(
path.join(phaseDir, 'SUMMARY.md'),
'# Summary\n\nSTATUS: blocked\n',
);
}
return tmpDir;
}
/** Minimal STATE.md frontmatter body. */
function state(opts) {
const fm = { ...opts };
// status goes in frontmatter as `status`; we also mirror as a body table for
// robustness against either format the real STATE.md uses.
const lines = ['---'];
for (const [k, v] of Object.entries(fm)) lines.push(`${k}: ${v}`);
lines.push('---', '', '# State', '');
if (fm.status) lines.push(`**Status:** ${fm.status}`);
return lines.join('\n') + '\n';
}
const CLEANUP = [];
function track(dir) {
CLEANUP.push(dir);
return dir;
}
function removeAll() {
for (const dir of CLEANUP) {
cleanup(dir);
}
CLEANUP.length = 0;
}
// ─── Tests ─────────────────────────────────────────────────────────────────────
describe('smart-entry: situation coverage', () => {
afterEach(removeAll);
const CASES = [
['no-project', () => {
// Truly empty dir — no .planning at all.
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-se-none-'));
return track(d);
}],
['paused', () => track(makeProject({ state: state({ status: 'planning', paused_at: '2026-06-01T00:00:00Z' }), roadmap: true }))],
['blocked', () => track(makeProject({ state: state({ status: 'executing', total_phases: 5, current_phase: 2 }) + '\n## Blockers\n\n- Need API key\n', roadmap: true }))],
['verify-failed', () => track(makeProject({ state: state({ status: 'verify-failed', total_phases: 5, current_phase: 2 }), roadmap: true, verifyFail: true }))],
['needs-first-phase', () => track(makeProject({ state: state({ status: 'planning', total_phases: 0, current_phase: 0 }) }))],
['planning', () => track(makeProject({ state: state({ status: 'planning', total_phases: 5, current_phase: 2 }), roadmap: true }))],
['executing', () => track(makeProject({ state: state({ status: 'executing', total_phases: 5, current_phase: 2, progress: 60 }), roadmap: true }))],
['verify-pending', () => track(makeProject({ state: state({ status: 'needs-review', total_phases: 5, current_phase: 2 }), roadmap: true }))],
['complete', () => track(makeProject({ state: state({ status: 'complete', total_phases: 5, current_phase: 5 }), roadmap: true }))],
['unknown', () => track(makeProject({ state: state({ status: '', total_phases: 5, current_phase: 2 }), roadmap: true }))],
];
for (const [expected, factory] of CASES) {
test(`classifies "${expected}"`, () => {
const dir = factory();
const result = classifyProject(dir);
assert.equal(result.situation, expected);
});
}
test('SITUATIONS constant lists all 11 (incl unknown) and is frozen', () => {
assert.equal(SITUATIONS.length, 11);
assert.ok(SITUATIONS.includes('unknown'));
assert.ok(Object.isFrozen(SITUATIONS));
});
});
describe('smart-entry: idle-stranded (git-dependent)', () => {
afterEach(removeAll);
test('clean tree + unpushed commits → idle-stranded, recommended ship', () => {
// idle-stranded is the fallback AFTER the status predicates: it fires for an
// ambiguous status (matches none of planning/executing/verify/complete) with
// unpushed committed work. Use an empty status to land here deterministically.
const dir = track(makeProject({
state: state({ status: '', total_phases: 5, current_phase: 2 }),
roadmap: true,
git: true,
}));
// Commit the .planning files so the working tree is clean (untracked files
// would make git_dirty true and mask the stranded signal).
execFileSync('git', ['add', '-A'], { cwd: dir, stdio: 'pipe' });
execFileSync('git', ['commit', '-m', 'init'], { cwd: dir, stdio: 'pipe' });
const base = detectSignals(dir);
assert.equal(base.git_dirty, false);
assert.equal(base.git_unpushed, false);
// Force the stranded signal and assert the situation + recommendation.
const forced = { ...base, git_unpushed: true };
assert.equal(classify(forced), 'idle-stranded');
});
test('idle-stranded action set recommends ship', () => {
const dir = track(makeProject({
state: state({ status: '', total_phases: 5, current_phase: 2 }),
roadmap: true,
}));
const base = detectSignals(dir);
const forced = { ...base, git_unpushed: true };
const situation = classify(forced);
assert.equal(situation, 'idle-stranded');
const actions = smartEntry.actionsFor(situation, forced);
assert.equal(actions[0].id, 'ship');
assert.equal(actions[0].recommended, true);
assert.equal(actions[0].command, '/gsd:ship');
});
});
describe('smart-entry: priority ordering', () => {
afterEach(removeAll);
test('verify-failed inspects current phase verify artifact', () => {
const dir = track(makeProject({
state: state({ status: 'executing', total_phases: 10, current_phase: 100 }),
roadmap: true,
}));
const phaseNinetyNine = path.join(dir, '.planning', 'phases', '99-old-phase');
const phaseOneHundred = path.join(dir, '.planning', 'phases', '100-current-phase');
fs.mkdirSync(phaseNinetyNine, { recursive: true });
fs.mkdirSync(phaseOneHundred, { recursive: true });
fs.writeFileSync(path.join(phaseNinetyNine, '99-VERIFICATION.md'), 'STATUS: passed\n');
fs.writeFileSync(path.join(phaseOneHundred, '100-VERIFICATION.md'), 'STATUS: failed\n');
const result = classifyProject(dir);
assert.equal(result.situation, 'verify-failed');
assert.equal(result.signals.verify_failed, true);
});
test('verify-failed ignores failure in a higher phase when state is on an earlier phase', () => {
const dir = track(makeProject({
state: state({ status: 'executing', total_phases: 10, current_phase: 2 }),
roadmap: true,
}));
const phaseTwo = path.join(dir, '.planning', 'phases', '02-active-phase');
const phaseOneHundred = path.join(dir, '.planning', 'phases', '100-leftover-phase');
fs.mkdirSync(phaseTwo, { recursive: true });
fs.mkdirSync(phaseOneHundred, { recursive: true });
fs.writeFileSync(path.join(phaseTwo, '02-VERIFICATION.md'), 'STATUS: passed\n');
fs.writeFileSync(path.join(phaseOneHundred, '100-VERIFICATION.md'), 'STATUS: failed\n');
const result = classifyProject(dir);
assert.equal(result.situation, 'executing');
assert.equal(result.signals.verify_failed, false);
});
test('verify-failed includes decimal phase directories for the current phase', () => {
const dir = track(makeProject({
state: state({ status: 'executing', total_phases: 10, current_phase: '7.1' }),
roadmap: true,
}));
const phaseSeven = path.join(dir, '.planning', 'phases', '07-base-phase');
const phaseSevenOne = path.join(dir, '.planning', 'phases', '07.1-inserted-phase');
fs.mkdirSync(phaseSeven, { recursive: true });
fs.mkdirSync(phaseSevenOne, { recursive: true });
fs.writeFileSync(path.join(phaseSeven, '07-VERIFICATION.md'), 'STATUS: passed\n');
fs.writeFileSync(path.join(phaseSevenOne, '07.1-VERIFICATION.md'), 'STATUS: failed\n');
const result = classifyProject(dir);
assert.equal(result.situation, 'verify-failed');
assert.equal(result.signals.verify_failed, true);
});
test('paused beats blocked (earlier row wins)', () => {
const dir = track(makeProject({
// paused_at set AND blockers present AND a phase loop: must resolve paused.
state: state({ status: 'executing', total_phases: 5, current_phase: 2, paused_at: '2026-06-01T00:00:00Z' })
+ '\n## Blockers\n\n- blocker one\n',
roadmap: true,
}));
const result = classifyProject(dir);
assert.equal(result.situation, 'paused');
});
test('blocked beats planning (earlier row wins)', () => {
const dir = track(makeProject({
state: state({ status: 'planning', total_phases: 5, current_phase: 2 })
+ '\n## Blockers\n\n- blocker one\n',
roadmap: true,
}));
const result = classifyProject(dir);
assert.equal(result.situation, 'blocked');
});
test('no-project beats everything (no .planning)', () => {
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-se-empty-'));
track(tmpDir);
const result = classifyProject(tmpDir);
assert.equal(result.situation, 'no-project');
});
});
describe('smart-entry: real STATE.md schema (nested progress YAML + body Phase field)', () => {
afterEach(removeAll);
// Mirrors this repo's actual .planning/STATE.md: status + nested progress{}
// in frontmatter, phase number in the body as `Phase: N`. Codex review found
// the classifier originally misread this as needs-first-phase (#P1).
function realState({ status, phase, totalPhases, percent }) {
const fm = [
'---',
'gsd_state_version: 1.0',
`status: ${status}`,
'last_activity: 2026-06-13',
'progress:',
` total_phases: ${totalPhases}`,
` percent: ${percent}`,
'---',
'',
'# Project State',
'',
`Phase: ${phase}`,
'',
`**Status:** ${status}`,
'',
].join('\n');
return fm;
}
test('reads current_phase from body `Phase:` + total_phases/percent from nested progress{}', () => {
const dir = track(makeProject({
state: realState({ status: 'verifying', phase: 3, totalPhases: 5, percent: 40 }),
roadmap: true,
}));
const signals = detectSignals(dir);
assert.equal(signals.current_phase, 3, 'current_phase from body Phase: field');
assert.equal(signals.total_phases, 5, 'total_phases from nested progress.total_phases');
assert.equal(signals.progress, 40, 'percent from nested progress.percent');
assert.equal(signals.status, 'verifying');
});
test('classifies verify-pending (not needs-first-phase) for an active real-schema project', () => {
const dir = track(makeProject({
state: realState({ status: 'verifying', phase: 3, totalPhases: 5, percent: 40 }),
roadmap: true,
}));
const result = classifyProject(dir);
assert.equal(result.situation, 'verify-pending');
// Forward motion delegates to the gated engine; verify-work stays available.
assert.equal(result.recommended, 'progress-next');
assert.match(result.summary, /Phase 3 of 5/);
});
test('executing status with nested progress schema classifies executing', () => {
const dir = track(makeProject({
state: realState({ status: 'executing', phase: 2, totalPhases: 5, percent: 60 }),
roadmap: true,
}));
const result = classifyProject(dir);
assert.equal(result.situation, 'executing');
// Forward motion delegates to /gsd:progress --next, not a raw execute-phase.
assert.equal(result.recommended, 'progress-next');
});
});
describe('smart-entry: in-project advancement delegates to the gated engine', () => {
afterEach(removeAll);
// Reconciliation guard (#1787): /gsd:next must not re-implement forward routing.
// For every in-project forward-motion situation the recommended action is
// `/gsd:progress --next` (workflows/next.md), so Route 0's resume-incomplete
// -phase invariant + Gates 1-3 are never bypassed. Re-deriving advancement here
// is what got the old flat /gsd-next removed (#3054). The specific command
// (execute-phase / plan-phase / verify-work) stays as an explicit secondary.
for (const situation of ['planning', 'executing', 'verify-pending']) {
test(`${situation}: recommended action is /gsd:progress --next`, () => {
const signals = {
current_phase: 2, total_phases: 5, status: situation, progress: 40,
has_planning: true, has_roadmap: true, git_dirty: false, git_unpushed: false,
paused: false, blockers: [], has_git: true, verify_failed: false, stale_activity: false,
};
const actions = smartEntry.actionsFor(situation, signals);
const recommended = actions.filter((a) => a.recommended);
assert.equal(recommended.length, 1, 'exactly one recommended action');
assert.equal(recommended[0].command, '/gsd:progress --next');
});
}
// Remediation / lifecycle situations are OFF the linear advance path — they
// keep direct specific recommendations (their distinct value over --next).
const DIRECT = {
'no-project': '/gsd:new-project',
paused: '/gsd:resume-work',
blocked: '/gsd:debug',
'verify-failed': '/gsd:verify-work',
'idle-stranded': '/gsd:ship',
complete: '/gsd:new-milestone',
};
for (const [situation, command] of Object.entries(DIRECT)) {
test(`${situation}: keeps its direct recommendation (${command})`, () => {
const signals = {
current_phase: 2, total_phases: 5, status: situation, progress: 40,
has_planning: true, has_roadmap: true, git_dirty: false, git_unpushed: false,
paused: false, blockers: [], has_git: true, verify_failed: false, stale_activity: false,
};
const actions = smartEntry.actionsFor(situation, signals);
const recommended = actions.find((a) => a.recommended);
assert.equal(recommended.command, command);
});
}
});
describe('smart-entry: per-situation action invariants (all 11)', () => {
// Lock the action-set contract for EVERY situation, not just the 6 sampled by
// the JSON-shape test: exactly one recommended, 1-4 actions, unique ids, and
// /gsd:* command forms. Guards the reconciliation (and future edits) against
// silently breaking these for a less-common situation.
const sampleSignals = {
current_phase: 2, total_phases: 5, status: 'executing', progress: 60,
has_planning: true, has_roadmap: true, git_dirty: false, git_unpushed: false,
paused: false, blockers: [], has_git: true, verify_failed: false, stale_activity: false,
};
for (const situation of SITUATIONS) {
test(`${situation}: exactly one recommended, 1-4 unique-id /gsd:* actions`, () => {
const actions = smartEntry.actionsFor(situation, sampleSignals);
assert.ok(actions.length >= 1 && actions.length <= 4, `${situation}: 1-4 actions (got ${actions.length})`);
assert.equal(actions.filter((a) => a.recommended).length, 1, `${situation}: exactly one recommended`);
const ids = actions.map((a) => a.id);
assert.equal(new Set(ids).size, ids.length, `${situation}: action ids are unique`);
for (const a of actions) {
assert.ok(a.command.startsWith('/gsd:'), `${situation}/${a.id}: command is a /gsd: slash form`);
}
});
}
});
describe('smart-entry: JSON shape invariants', () => {
afterEach(removeAll);
test('every situation yields exactly one recommended action and /gsd:* commands', () => {
const dirs = [
track(makeProject()), // no-project
track(makeProject({ state: state({ status: 'planning', paused_at: '2026-06-01T00:00:00Z' }), roadmap: true })), // paused
track(makeProject({ state: state({ status: 'executing', total_phases: 5, current_phase: 2 }) + '\n## Blockers\n- b\n', roadmap: true })), // blocked
track(makeProject({ state: state({ status: 'executing', total_phases: 5, current_phase: 2, progress: 60 }), roadmap: true })), // executing
track(makeProject({ state: state({ status: 'complete', total_phases: 5, current_phase: 5 }), roadmap: true })), // complete
track(makeProject({ state: state({ status: '', total_phases: 5, current_phase: 2 }), roadmap: true })), // unknown
];
for (const dir of dirs) {
const result = classifyProject(dir);
const recommended = result.actions.filter((a) => a.recommended);
assert.equal(recommended.length, 1, `${result.situation}: exactly one recommended`);
assert.equal(recommended[0].id, result.recommended, `${result.situation}: recommended id matches`);
for (const a of result.actions) {
assert.ok(a.command.startsWith('/gsd:'), `${result.situation}/${a.id}: command is a slash form`);
}
assert.ok(result.summary.length > 0, `${result.situation}: summary non-empty`);
assert.ok(result.actions.length >= 1 && result.actions.length <= 4, `${result.situation}: 1-4 actions`);
}
});
});
describe('smart-entry: CLI dispatch (gsd-tools smart-entry)', () => {
afterEach(removeAll);
test('--json in an empty dir returns no-project machine JSON', () => {
// A bare tmpdir with no .planning is a true no-project.
const bare = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-se-bare-'));
track(bare);
const out = execFileSync(process.execPath, [TOOLS, 'smart-entry', '--json', '--cwd', bare], {
encoding: 'utf-8',
});
const j = JSON.parse(out);
assert.equal(j.situation, 'no-project');
assert.equal(j.recommended, 'new-project');
assert.equal(j.actions[0].command, '/gsd:new-project');
});
test('default (human) mode prints a plain summary line, not JSON', () => {
const bare = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-se-human-'));
track(bare);
const out = execFileSync(process.execPath, [TOOLS, 'smart-entry', '--cwd', bare], {
encoding: 'utf-8',
});
assert.ok(!out.startsWith('{'), 'human mode is not JSON');
assert.match(out, /No project yet/);
assert.match(out, /Recommended:/);
});
});

View File

@@ -73,6 +73,7 @@
"ship.md": 24647,
"sketch-wrap-up.md": 14223,
"sketch.md": 19960,
"smart-entry.md": 11080,
"spec-phase.md": 31752,
"spike-wrap-up.md": 15092,
"spike.md": 24517,