Mechanical rename produced by scripts/msd-rename.cjs: gsd/Gsd/GSD -> msd/Msd/MSD across contents and paths, upstream package/repo coordinates -> @golem15/msd-core and golem15com/msd-core. Deep links into upstream history, sibling upstream packages, the GSD-2 import feature, CHANGELOG.md and .changeset/ are kept as-is. Hand edits on top: MSD block-letter banner and logos, LICENSE copyright line, package/plugin identity, regenerated lockfile, install-tree fixtures, derived registries and benchmark baseline; migration checksum baseline re-locked (MSD keeps its own install state, so no install had applied the old sums); sort-order and regex-escaped expectations in tests adjusted.
19 KiB
Design: /msd Smart Entry
Date: 2026-06-27
Status: Approved — ready for implementation planning
Origin: Adapted from the /msd smart-entry wizard in open-gsd/gsd-pi, redesigned for msd-core's markdown-first, multi-runtime architecture.
Summary
A /msd-next command that acts as msd-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 msd-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 bemsd:*ormsd-*prefixed; a bare/msdis not expressible. The frontmatter is thereforename: msd:next, surfacing as/msd-next(filecommands/msd/next.md, backed bymsd-core/workflows/smart-entry.md). The "smart entry" concept and behavior are unchanged; only the surfaced name differs from the original/msdsketch.
Motivation
gsd-pi ships a /msd 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. msd-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 /msd, get told what to do next" — without fighting msd-core's nature. msd-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:
-
Approach: Hybrid. Detection + classification as a new
msd-tools smart-entry --jsonsubcommand (deterministic, unit-tested in Node); presentation + dispatch as a markdown command + workflow that shells out to it via the existingmsd_runshim. This mirrors howmsd-core/workflows/do.mdalready drives tooling. Rationale: code-driven detection is reliable and testable; the markdown surface keeps multi-runtime reach (Codex, Gemini, Copilot) and adds zero dependencies. -
Priority: workflow routing ("what now?"). The wizard optimizes for the ongoing-work menu (gsd-pi's
showSmartEntry), not first-run onboarding (gsd-pi'sshowProjectInit). Onboarding routes to the existing/msd-new-project, which already handles project detection and setup. We are not building an init wizard. -
Richness: phase + smart signals. The classifier branches on msd-core's phase loop and richer gsd-pi-style signals (blocked/recover, idle/stranded, paused, complete). All 10 situations below are in scope.
-
Relationship to
/msd-progress: complementary, not redundant./msd-nextis the front door / launcher — a state-aware menu the user picks the next action from./msd-progressremains the detailed situational report + auto-advance (--nextchaining)./msd-nextwill frequently recommend/msd-progress; it does not replace or deprecate it.
Non-goals
- No init/onboarding wizard.
/msd-new-projectalready owns first-run project setup./msd-nextroutes to it. - No new prompt/TUI library.
AskUserQuestion(Claude) +--textnumbered-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 msd-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
/msd-next (commands/msd/next.md — thin markdown dispatcher)
│
▼
workflow: msd-core/workflows/smart-entry.md ◄── presentation + dispatch
│ step 1: resolve msd_run shim
│ step 2: msd_run smart-entry --json
│ step 3: render AskUserQuestion (or TEXT_MODE list)
│ step 4: show MSD ► ROUTING banner
│ step 5: dispatch + stop
▼
msd-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 — msd-tools smart-entry
New src/smart-entry.cts → compiled (build-at-publish, ADR-457) to msd-core/bin/lib/smart-entry.cjs. Registered in msd-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()(throwExitErrorper repo convention; therunMainwrapper translates exit codes). - Output modes:
--json(machine, used by the workflow) and default (human-readable summary line, formsd-toolsusers / debugging). - Idempotent, fast, no network. Read-only filesystem +
gitcalls only. - Never throws in
--jsonmode when.planning/is absent — it returnssituation: "no-project"so the workflow always has a menu to render.
Inputs
| input | source | what we read |
|---|---|---|
| workflow state | msd_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 msd-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)
{
"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": "/msd-execute-phase", "recommended": true },
{ "id": "progress-next", "label": "Advance to the next step", "command": "/msd-progress --next", "recommended": false },
{ "id": "quick", "label": "Quick task", "command": "/msd-quick", "recommended": false },
{ "id": "code-review", "label": "Review recent work", "command": "/msd-code-review", "recommended": false }
]
}
situation,recommended,actions[]are the contract the workflow depends on.signalsis informational (shown in the summary banner); the workflow does not branch on it.summaryis a one-line human string; the workflow may show it verbatim or reformat.actions[].commandis the full slash command string the workflow dispatches, including flags (e.g./msd-progress --next).
The markdown layer
Command — commands/msd/next.md (NEW)
Thin dispatcher, modeled on commands/msd/progress.md and commands/msd/help.md. Backed by msd-core/workflows/smart-entry.md (named for the smart-entry classifier + msd-tools smart-entry subcommand; does not collide with the existing workflows/next.md, which is the progress --next sub-workflow).
Frontmatter:
name: msd:next(surfaces as/msd-next; the command-contract requires amsd:*/msd-*prefix — a bare/msdis not expressible, see ADR-0002)description:"MSD 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: lowallowed-tools:Read, Bash, Glob, SlashCommand, AskUserQuestion- No
requires: [phase](unlikeprogress) — must work pre-project. <execution_context>→@~/.claude/msd-core/workflows/smart-entry.md+@~/.claude/msd-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 — msd-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 msd_run shim): Copy the check_project-style shim-resolution block verbatim from msd-core/workflows/do.md:29 (the long _MSD_SHIM_NAME resolver). This finds msd-tools.cjs across all supported runtime homes. It is a proven, required block; do not paraphrase.
Step 2 — detect (run the classifier):
SNAPSHOT=$(msd_run smart-entry --json 2>/dev/null)
Parse SNAPSHOT as JSON. If missing or unparseable → fall back to /msd-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 fromsituation(e.g.executing→ "Continue work").question: thesummaryline + "What next?"options: the first 4 ofactions[], label = actionlabel, 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 MSD ► 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 |
|---|---|
msd_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 msd-core) |
workflow sees empty/unparseable output → falls back to /msd-progress with a note |
smart-entry throws |
same: caught by the 2>/dev/null + parse check → fallback to /msd-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: /msd always produces some actionable menu and never strands the user. The ultimate fallback is /msd-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 → situationno-project, recommendednew-project, actions includemap-codebase.paused— STATE.md withpaused_atset → situationpaused, recommendedresume-work.blocked— STATE.md with blockers → situationblocked, recommendeddebug.verify-failed— latest summarySTATUS: blocked→ situationverify-failed.needs-first-phase— STATE.md present,total_phases: 0→ situationneeds-first-phase.planning/executing/verify-pending— respectivestatusvalues.idle-stranded— clean tree + unpushed commits → situationidle-stranded, recommendedship.complete— current ≥ total, status complete → situationcomplete, recommendednew-milestone.unknown— malformed state → situationunknown, recommendedprogress.- Priority ordering — a STATE.md that is both paused AND blocked resolves to
paused(earlier row wins). - JSON shape —
actions[].commandalways starts with/msd:; exactly one action hasrecommended: true.
tests/msd-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/msd/next.mdexists with frontmattername: msd:next, norequiresfield,allowed-toolsincludesAskUserQuestion.msd-core/workflows/smart-entry.mdexists and is under 32 KiB (NEW_FILE_CAP).- Every
commandstring referenced by the classifier's action table resolves to a real existing slash command file incommands/msd/(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
.cjsenters thec8coverage gate (--lines 70 --branches 60). - After adding
workflows/smart-entry.md, runnpm run size:baselineto updatetests/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 |
— |
msd-core/bin/lib/smart-entry.cjs |
generated by build:lib (gitignored) |
— |
msd-core/bin/msd-tools.cjs |
add case 'smart-entry': (~2 lines) |
— |
commands/msd/next.md |
NEW — thin dispatcher command (name: msd:next → /msd-next) |
small |
msd-core/workflows/smart-entry.md |
NEW — presentation + dispatch | < 32 KiB |
tests/smart-entry.unit.test.cjs |
NEW — classifier behavior | — |
tests/msd-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-msd-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):
idle-strandedthreshold — 72h hardcoded for v1. If brittle in practice, relax to "unpushed commits only" (drop the staleness clause).- Action label wording — exact strings are an implementation/tuning detail; the contract is
id+command.
Success criteria
msd-tools smart-entry --jsonclassifies all 10 situations +unknowncorrectly from fixtures./msdin a real project shows a situation-appropriate menu and dispatches the chosen command./msdpre-project offersnew-project./msdworks under TEXT_MODE (noAskUserQuestion).- Any
smart-entryfailure falls back to/msd-progresswithout erroring. - New workflow under 32 KiB;
size:baselineupdated; coverage gate passes. - No new dependencies; no existing command modified.