mirror of
https://github.com/glittercowboy/get-shit-done
synced 2026-05-02 20:42:30 +02:00
Compare commits
43 Commits
fix/2997-s
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bd99d3dcb8 | ||
|
|
4d534cf03f | ||
|
|
b956a596ee | ||
|
|
4d4f02bd60 | ||
|
|
6eae18415d | ||
|
|
ca72c08eb5 | ||
|
|
9548a96a9b | ||
|
|
a8007e857f | ||
|
|
76b587cba2 | ||
|
|
5d4d928250 | ||
|
|
5e808b233a | ||
|
|
38dcf1ec47 | ||
|
|
f2c7f730ce | ||
|
|
37d3c9337a | ||
|
|
6a7ecb5344 | ||
|
|
8be1af4f98 | ||
|
|
87951950fd | ||
|
|
f9d892946a | ||
|
|
3591c09fa9 | ||
|
|
c1fcb6075d | ||
|
|
40ecf263b1 | ||
|
|
a370f299f3 | ||
|
|
37d207312f | ||
|
|
278d440ca8 | ||
|
|
b5385cffb3 | ||
|
|
20e5cbda75 | ||
|
|
0d07c350df | ||
|
|
43fdb0334b | ||
|
|
78c60a9cc0 | ||
|
|
a871db4222 | ||
|
|
c26d4dc863 | ||
|
|
7a812ab812 | ||
|
|
8fa4692d46 | ||
|
|
f73945aa73 | ||
|
|
1cc688367a | ||
|
|
7db6786ec8 | ||
|
|
ad6e2e81ca | ||
|
|
bf401f1a4c | ||
|
|
d732c4fdb7 | ||
|
|
c055e23e55 | ||
|
|
3c4b701afe | ||
|
|
b1d221a42b | ||
|
|
834e57dfe1 |
8
.github/workflows/canary.yml
vendored
8
.github/workflows/canary.yml
vendored
@@ -80,7 +80,7 @@ jobs:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
- name: Tag and push
|
||||
if: ${{ github.ref == 'refs/heads/main' && !inputs.dry_run }}
|
||||
if: ${{ github.ref == 'refs/heads/dev' && !inputs.dry_run }}
|
||||
env:
|
||||
CANARY_VERSION: ${{ steps.canary.outputs.canary_version }}
|
||||
run: |
|
||||
@@ -88,19 +88,19 @@ jobs:
|
||||
git push origin "v${CANARY_VERSION}"
|
||||
|
||||
- name: Publish to npm (canary)
|
||||
if: ${{ github.ref == 'refs/heads/main' && !inputs.dry_run }}
|
||||
if: ${{ github.ref == 'refs/heads/dev' && !inputs.dry_run }}
|
||||
run: npm publish --provenance --access public --tag canary
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
- name: Publish SDK to npm (canary)
|
||||
if: ${{ github.ref == 'refs/heads/main' && !inputs.dry_run }}
|
||||
if: ${{ github.ref == 'refs/heads/dev' && !inputs.dry_run }}
|
||||
run: cd sdk && npm publish --provenance --access public --tag canary
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
- name: Verify publish
|
||||
if: ${{ github.ref == 'refs/heads/main' && !inputs.dry_run }}
|
||||
if: ${{ github.ref == 'refs/heads/dev' && !inputs.dry_run }}
|
||||
env:
|
||||
CANARY_VERSION: ${{ steps.canary.outputs.canary_version }}
|
||||
run: |
|
||||
|
||||
344
.github/workflows/release-sdk.yml
vendored
Normal file
344
.github/workflows/release-sdk.yml
vendored
Normal file
@@ -0,0 +1,344 @@
|
||||
# Release SDK Bundle
|
||||
#
|
||||
# Stopgap workflow_dispatch publish path: builds get-shit-done-cc with the
|
||||
# compiled SDK and the SDK .tgz bundled inside the CC tarball, then
|
||||
# publishes the CC package to ONE chosen dist-tag (dev | next | latest)
|
||||
# per run.
|
||||
#
|
||||
# Why this exists: @gsd-build/sdk publishes from canary.yml and release.yml
|
||||
# fail because the @gsd-build npm token is currently unavailable. CC users
|
||||
# do not consume @gsd-build/sdk directly — bin/gsd-sdk.js resolves
|
||||
# sdk/dist/cli.js from inside the installed CC package, so the bundled
|
||||
# copy is sufficient for full functionality. This workflow ships CC alone
|
||||
# (no separate @gsd-build/sdk publish attempt) and additionally bakes a
|
||||
# bundled gsd-sdk-<version>.tgz at sdk-bundle/gsd-sdk.tgz inside the CC
|
||||
# tarball as a recoverable npm-installable artifact.
|
||||
#
|
||||
# Existing canary.yml and release.yml are intentionally untouched. They
|
||||
# remain the canonical two-package publish path; restore them to primary
|
||||
# use once @gsd-build/sdk ownership is recovered.
|
||||
#
|
||||
# Tracking issues: #2925 (initial workflow), #2929 (CI-gate parity with release.yml)
|
||||
|
||||
name: Release SDK Bundle
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'npm dist-tag to publish under'
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- dev
|
||||
- next
|
||||
- latest
|
||||
version:
|
||||
description: 'Explicit version (e.g. 1.50.0-dev.3, 1.50.0-rc.2, 1.50.0). Empty = derive from package.json base + tag-appropriate suffix.'
|
||||
required: false
|
||||
type: string
|
||||
ref:
|
||||
description: 'Branch or ref to build from (default: the workflow-dispatch ref, typically dev)'
|
||||
required: false
|
||||
type: string
|
||||
dry_run:
|
||||
description: 'Dry run (skip npm publish, git tag, and push)'
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
# Per dist-tag, no concurrent publishes for the same stream. Different streams
|
||||
# can publish in parallel because they target different dist-tags.
|
||||
concurrency:
|
||||
group: release-sdk-${{ inputs.tag }}
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
NODE_VERSION: 24
|
||||
|
||||
jobs:
|
||||
# Cross-platform install validation gate (parity with release.yml).
|
||||
# Publish job depends on this — won't proceed if the package fails to
|
||||
# install cleanly across the supported matrix.
|
||||
install-smoke:
|
||||
permissions:
|
||||
contents: read
|
||||
uses: ./.github/workflows/install-smoke.yml
|
||||
with:
|
||||
ref: ${{ inputs.ref }}
|
||||
|
||||
release:
|
||||
needs: install-smoke
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
permissions:
|
||||
contents: write # tag + push + GitHub Release
|
||||
id-token: write # provenance
|
||||
environment: npm-publish
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ inputs.ref }}
|
||||
|
||||
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Determine version
|
||||
id: ver
|
||||
env:
|
||||
INPUT_TAG: ${{ inputs.tag }}
|
||||
INPUT_OVERRIDE: ${{ inputs.version }}
|
||||
run: |
|
||||
set -e
|
||||
RAW=$(node -p "require('./package.json').version")
|
||||
BASE=$(echo "$RAW" | sed 's/-.*//')
|
||||
if [ -n "$INPUT_OVERRIDE" ]; then
|
||||
VERSION="$INPUT_OVERRIDE"
|
||||
else
|
||||
case "$INPUT_TAG" in
|
||||
dev)
|
||||
N=1
|
||||
while git tag -l "v${BASE}-dev.${N}" | grep -q .; do
|
||||
N=$((N + 1))
|
||||
done
|
||||
VERSION="${BASE}-dev.${N}"
|
||||
;;
|
||||
next)
|
||||
N=1
|
||||
while git tag -l "v${BASE}-rc.${N}" | grep -q .; do
|
||||
N=$((N + 1))
|
||||
done
|
||||
VERSION="${BASE}-rc.${N}"
|
||||
;;
|
||||
latest)
|
||||
VERSION="$BASE"
|
||||
;;
|
||||
*)
|
||||
echo "::error::Unknown tag '$INPUT_TAG' (expected dev|next|latest)"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
||||
echo "tag=$INPUT_TAG" >> "$GITHUB_OUTPUT"
|
||||
echo "→ Will publish v${VERSION} to dist-tag '${INPUT_TAG}'"
|
||||
|
||||
- name: Refuse if version already exists on npm
|
||||
env:
|
||||
VERSION: ${{ steps.ver.outputs.version }}
|
||||
run: |
|
||||
EXISTING=$(npm view get-shit-done-cc@"$VERSION" version 2>/dev/null || true)
|
||||
if [ -n "$EXISTING" ]; then
|
||||
echo "::error::get-shit-done-cc@${VERSION} is already published. Bump version or pass an explicit override input."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Tolerant tag-existence check (matches release.yml pattern). An
|
||||
# operator re-running after a mid-flight publish-step failure should
|
||||
# not be blocked just because the tag step succeeded last time. Only
|
||||
# error if the existing tag points at a different commit than HEAD.
|
||||
- name: Check git tag (skip if matches HEAD, error if mismatched)
|
||||
env:
|
||||
VERSION: ${{ steps.ver.outputs.version }}
|
||||
run: |
|
||||
if git rev-parse -q --verify "refs/tags/v${VERSION}" >/dev/null; then
|
||||
EXISTING_SHA=$(git rev-parse "refs/tags/v${VERSION}")
|
||||
HEAD_SHA=$(git rev-parse HEAD)
|
||||
if [ "$EXISTING_SHA" != "$HEAD_SHA" ]; then
|
||||
echo "::error::git tag v${VERSION} already exists pointing at ${EXISTING_SHA}, but HEAD is ${HEAD_SHA}"
|
||||
exit 1
|
||||
fi
|
||||
echo "::notice::tag v${VERSION} already exists at HEAD; tag step will skip"
|
||||
fi
|
||||
|
||||
- name: Configure git identity
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
- name: Bump in-tree version (not committed)
|
||||
env:
|
||||
VERSION: ${{ steps.ver.outputs.version }}
|
||||
run: |
|
||||
npm version "$VERSION" --no-git-tag-version
|
||||
cd sdk && npm version "$VERSION" --no-git-tag-version
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Run full test suite with coverage (parity with release.yml)
|
||||
run: npm run test:coverage
|
||||
|
||||
- name: Build SDK dist for tarball
|
||||
run: npm run build:sdk
|
||||
|
||||
- name: Verify CC tarball ships sdk/dist/cli.js (bug #2647 guard)
|
||||
run: bash scripts/verify-tarball-sdk-dist.sh
|
||||
|
||||
- name: Pack SDK as tarball and bundle into CC source tree
|
||||
env:
|
||||
VERSION: ${{ steps.ver.outputs.version }}
|
||||
run: |
|
||||
set -e
|
||||
cd sdk
|
||||
npm pack
|
||||
# npm pack emits gsd-build-sdk-<version>.tgz in the cwd
|
||||
TARBALL="gsd-build-sdk-${VERSION}.tgz"
|
||||
if [ ! -f "$TARBALL" ]; then
|
||||
echo "::error::Expected $TARBALL but npm pack did not produce it. Listing sdk/:"
|
||||
ls -la
|
||||
exit 1
|
||||
fi
|
||||
mkdir -p ../sdk-bundle
|
||||
mv "$TARBALL" ../sdk-bundle/gsd-sdk.tgz
|
||||
cd ..
|
||||
ls -la sdk-bundle/
|
||||
|
||||
- name: Add sdk-bundle to CC files whitelist (in-tree, not committed)
|
||||
run: |
|
||||
node <<'NODE'
|
||||
const fs = require('fs');
|
||||
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
|
||||
if (!Array.isArray(pkg.files)) {
|
||||
console.error('::error::package.json files is not an array');
|
||||
process.exit(1);
|
||||
}
|
||||
if (!pkg.files.includes('sdk-bundle')) {
|
||||
pkg.files.push('sdk-bundle');
|
||||
fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2) + '\n');
|
||||
console.log('Added sdk-bundle/ to package.json files whitelist');
|
||||
} else {
|
||||
console.log('sdk-bundle/ already in files whitelist');
|
||||
}
|
||||
NODE
|
||||
|
||||
- name: Verify CC tarball will contain sdk-bundle/gsd-sdk.tgz
|
||||
run: |
|
||||
set -e
|
||||
TARBALL=$(npm pack --ignore-scripts 2>/dev/null | tail -1)
|
||||
if [ -z "$TARBALL" ] || [ ! -f "$TARBALL" ]; then
|
||||
echo "::error::npm pack produced no tarball"
|
||||
exit 1
|
||||
fi
|
||||
echo "Inspecting $TARBALL for sdk-bundle/gsd-sdk.tgz:"
|
||||
if ! tar -tzf "$TARBALL" | grep -q "package/sdk-bundle/gsd-sdk.tgz"; then
|
||||
echo "::error::CC tarball is missing package/sdk-bundle/gsd-sdk.tgz"
|
||||
tar -tzf "$TARBALL" | grep -E "sdk-bundle|sdk/dist" | head -20
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ CC tarball contains sdk-bundle/gsd-sdk.tgz"
|
||||
rm -f "$TARBALL"
|
||||
|
||||
- name: Dry-run publish validation
|
||||
env:
|
||||
TAG: ${{ steps.ver.outputs.tag }}
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
run: npm publish --dry-run --tag "$TAG"
|
||||
|
||||
- name: Tag and push
|
||||
if: ${{ !inputs.dry_run }}
|
||||
env:
|
||||
VERSION: ${{ steps.ver.outputs.version }}
|
||||
run: |
|
||||
if git rev-parse -q --verify "refs/tags/v${VERSION}" >/dev/null; then
|
||||
echo "Tag v${VERSION} already exists at HEAD (per pre-flight check); skipping git tag step"
|
||||
else
|
||||
git tag "v${VERSION}"
|
||||
fi
|
||||
git push origin "v${VERSION}"
|
||||
|
||||
- name: Publish to npm (CC bundle, SDK included as both loose tree and .tgz)
|
||||
if: ${{ !inputs.dry_run }}
|
||||
env:
|
||||
TAG: ${{ steps.ver.outputs.tag }}
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
run: npm publish --provenance --access public --tag "$TAG"
|
||||
|
||||
# Keep `next` from going stale relative to `latest`. When publishing a
|
||||
# stable release, also point `next` at it so users on `@next` don't
|
||||
# get stuck on an older pre-release than what's now stable. Parity
|
||||
# with release.yml#finalize "Clean up next dist-tag" step.
|
||||
- name: Re-point next dist-tag at the new latest (only when tag=latest)
|
||||
if: ${{ !inputs.dry_run && steps.ver.outputs.tag == 'latest' }}
|
||||
env:
|
||||
VERSION: ${{ steps.ver.outputs.version }}
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
run: |
|
||||
npm dist-tag add "get-shit-done-cc@${VERSION}" next
|
||||
echo "✅ next dist-tag re-pointed to v${VERSION} (matches latest)"
|
||||
|
||||
- name: Create GitHub Release
|
||||
if: ${{ !inputs.dry_run }}
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
VERSION: ${{ steps.ver.outputs.version }}
|
||||
TAG: ${{ steps.ver.outputs.tag }}
|
||||
run: |
|
||||
# Per-tag release flags:
|
||||
# dev, next → --prerelease (won't be highlighted as the latest release on the repo page)
|
||||
# latest → --latest (becomes the highlighted release)
|
||||
if [ "$TAG" = "latest" ]; then
|
||||
gh release create "v${VERSION}" \
|
||||
--title "v${VERSION}" \
|
||||
--generate-notes \
|
||||
--latest
|
||||
else
|
||||
gh release create "v${VERSION}" \
|
||||
--title "v${VERSION}" \
|
||||
--generate-notes \
|
||||
--prerelease
|
||||
fi
|
||||
echo "✅ GitHub Release v${VERSION} created"
|
||||
|
||||
- name: Verify publish landed on registry
|
||||
if: ${{ !inputs.dry_run }}
|
||||
env:
|
||||
VERSION: ${{ steps.ver.outputs.version }}
|
||||
TAG: ${{ steps.ver.outputs.tag }}
|
||||
run: |
|
||||
PUBLISHED="NOT_FOUND"
|
||||
for delay in 5 10 20 30 45; do
|
||||
PUBLISHED=$(npm view get-shit-done-cc@"$VERSION" version 2>/dev/null || echo "NOT_FOUND")
|
||||
if [ "$PUBLISHED" = "$VERSION" ]; then
|
||||
break
|
||||
fi
|
||||
echo "Waiting ${delay}s for registry to catch up (saw: $PUBLISHED)..."
|
||||
sleep "$delay"
|
||||
done
|
||||
if [ "$PUBLISHED" != "$VERSION" ]; then
|
||||
echo "::error::Version $VERSION did not appear on the registry within timeout"
|
||||
exit 1
|
||||
fi
|
||||
TAG_VERSION=$(npm view get-shit-done-cc dist-tags."$TAG" 2>/dev/null || echo "NOT_FOUND")
|
||||
if [ "$TAG_VERSION" != "$VERSION" ]; then
|
||||
echo "::error::dist-tag '$TAG' resolves to '$TAG_VERSION', expected '$VERSION'"
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ get-shit-done-cc@${VERSION} live on dist-tag '${TAG}'"
|
||||
|
||||
- name: Summary
|
||||
env:
|
||||
VERSION: ${{ steps.ver.outputs.version }}
|
||||
TAG: ${{ steps.ver.outputs.tag }}
|
||||
DRY_RUN: ${{ inputs.dry_run }}
|
||||
run: |
|
||||
echo "## Release SDK Bundle: v${VERSION} → @${TAG}" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "" >> "$GITHUB_STEP_SUMMARY"
|
||||
if [ "$DRY_RUN" = "true" ]; then
|
||||
echo "**DRY RUN** — npm publish, git tag, push, and GitHub Release were skipped." >> "$GITHUB_STEP_SUMMARY"
|
||||
else
|
||||
echo "- Published \`get-shit-done-cc@${VERSION}\` to dist-tag \`${TAG}\`" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "- SDK bundled inside the CC tarball at:" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo " - \`sdk/dist/cli.js\` (loose tree, consumed by \`bin/gsd-sdk.js\` shim)" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo " - \`sdk-bundle/gsd-sdk.tgz\` (npm-installable artifact)" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "- Git tag \`v${VERSION}\` pushed" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "- GitHub Release \`v${VERSION}\` created" >> "$GITHUB_STEP_SUMMARY"
|
||||
if [ "$TAG" = "latest" ]; then
|
||||
echo "- \`next\` dist-tag re-pointed at \`v${VERSION}\` (kept current with \`latest\`)" >> "$GITHUB_STEP_SUMMARY"
|
||||
fi
|
||||
echo "- Install: \`npm install -g get-shit-done-cc@${TAG}\`" >> "$GITHUB_STEP_SUMMARY"
|
||||
fi
|
||||
11
CHANGELOG.md
11
CHANGELOG.md
@@ -7,6 +7,17 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
||||
## [Unreleased](https://github.com/gsd-build/get-shit-done/compare/v1.38.5...HEAD)
|
||||
|
||||
### Added
|
||||
- **Vertical MVP discovery & progress surfaces.** `/gsd new-project` now prompts the user to choose between **Vertical MVP** (each phase delivers an end-to-end user capability — recommended for new products) and **Horizontal Layers** (build complete technical layers, assemble at the end). Picking Vertical MVP writes `**Mode:** mvp` on every initial roadmap phase. `/gsd progress` adds a user-flow status sub-block sourced from PLAN.md task names when a phase has `**Mode:** mvp`. `/gsd stats` adds a 'Phases: N total | M MVP | K standard' summary line when at least one MVP phase exists. `/gsd graphify` renders MVP-mode phase nodes with a distinct green fill (`#22c55e`) and a ` (MVP)` label suffix — two-channel signaling for color-blind and grayscale renders. Closes the umbrella PRD #2826. (#2826)
|
||||
- **MVP-mode UAT framing in `/gsd verify-work`** — when the phase under verification has `**Mode:** mvp` in ROADMAP.md, the generated UAT script asks "can a real user complete the feature?" before any technical checks. User-flow steps (open, fill, click, observe) run first; technical checks (endpoint schemas, error states) only run AFTER the user flow passes. Adds a goal-backward "User Flow Coverage" section to VERIFICATION.md that maps user-story steps to evidence in the codebase. User-story-format guard refuses to verify a `mode: mvp` phase whose `**Goal:**` line is not in user-story format. (#2826)
|
||||
- **MVP+TDD runtime gate in `/gsd execute-phase`** — when both `MVP_MODE` and `TDD_MODE` are active for a phase, the executor refuses to advance a behavior-adding task until a failing-test commit exists for it. The existing end-of-phase TDD review (advisory by default) escalates to **blocking** under the same condition: phases with missing RED→GREEN commits cannot be marked complete without `--force-mvp-gate`. Pure doc-only / config-only / test-only tasks are exempt. (#2826)
|
||||
- **`/gsd mvp-phase <N>` command** — guided MVP planning entry point. Prompts for an "As a / I want to / So that" user story (three structured fields), runs SPIDR splitting check (full interactive flow per PRD Q3) if the story is too large, writes `**Mode:** mvp` and the formatted goal to ROADMAP.md, then delegates to `/gsd plan-phase <N>` (which auto-detects MVP via the roadmap mode field shipped in PRD Phase 1). The `gsd-planner` agent now emits a `## Phase Goal` section with bolded **As a** / **I want to** / **so that** keywords as the first content under the phase header in PLAN.md when MVP_MODE is active. (#2826)
|
||||
- **`--mvp` flag on `/gsd plan-phase`** — opt-in vertical-slice planning. Plans are
|
||||
organized as feature slices (UI→API→DB) instead of horizontal layers, so each task
|
||||
moves a real user-visible capability forward. Persistable per-phase via `**Mode:** mvp`
|
||||
in ROADMAP.md. New-project Phase 1 + `--mvp` triggers Walking Skeleton output
|
||||
(`SKELETON.md`) capturing architectural decisions for subsequent phases. Single
|
||||
planner agent, mode-switched (no new agent surface). PRD Phases 2–4 (`mvp-phase`
|
||||
command, TDD wiring, discovery/UX) deferred to follow-up plans. (#2826)
|
||||
- `--minimal` install flag (alias `--core-only`) writes only the main-loop core skills
|
||||
(`new-project`, `discuss-phase`, `plan-phase`, `execute-phase`, `help`, `update`) and
|
||||
zero `gsd-*` subagents. Cuts cold-start system-prompt overhead from ~12k tokens to
|
||||
|
||||
@@ -355,6 +355,21 @@ When the plan frontmatter has `type: tdd`, the entire plan follows the RED/GREEN
|
||||
If RED or GREEN gate commits are missing, add a warning to SUMMARY.md under a `## TDD Gate Compliance` section.
|
||||
</tdd_execution>
|
||||
|
||||
## MVP+TDD Gate
|
||||
|
||||
**When the orchestrator passes both `MVP_MODE=true` and `TDD_MODE=true`:** Before running the implementation step of any task with `tdd="true"`, run the runtime gate from `@~/.claude/get-shit-done/references/execute-mvp-tdd.md`. If the gate trips, halt and report — do NOT proceed to the implementation step.
|
||||
|
||||
**Halt-and-report protocol:**
|
||||
|
||||
1. Stop. Do not run the task's implementation step.
|
||||
2. Emit the structured halt report defined in `references/execute-mvp-tdd.md` (header line, reason code, expected behavior, required next step).
|
||||
3. Update `STATE.md` with `last_gate_trip: {plan_id}/{task_id}`.
|
||||
4. Exit the current execution wave cleanly. Prior commits in the same wave stay — do not roll back.
|
||||
|
||||
**Behavior-adding task detection** (the gate only fires for behavior-adding tasks): see `references/execute-mvp-tdd.md` for the precise definition. Pure doc-only / config-only / test-only tasks are exempt.
|
||||
|
||||
**Mode is all-or-nothing per phase** (PRD decision Q1, inherited from Phase 1). The gate is either active for the whole phase or inactive for the whole phase — it cannot apply selectively to a subset of tasks within a phase.
|
||||
|
||||
<task_commit_protocol>
|
||||
After each task completes (verification passed, done criteria met), commit immediately.
|
||||
|
||||
|
||||
@@ -302,6 +302,35 @@ This prevents the "scavenger hunt" anti-pattern where executors explore the code
|
||||
|
||||
Exceptions where `tdd="true"` is not needed: `type="checkpoint:*"` tasks, configuration-only files, documentation, migration scripts, glue code wiring existing tested components, styling-only changes.
|
||||
|
||||
## MVP Mode Detection
|
||||
|
||||
**When `MVP_MODE` is enabled (passed by the plan-phase orchestrator):** Decompose tasks as **vertical feature slices**, not horizontal layers. Required reading: `@~/.claude/get-shit-done/references/planner-mvp-mode.md` (loaded conditionally by the orchestrator).
|
||||
|
||||
**Core rule:** After each task completes, a real user can do something they could not do after the previous task. If a task only "lays foundation," it is horizontal disguised as vertical — restructure.
|
||||
|
||||
**Plan structure under MVP_MODE:**
|
||||
|
||||
1. Frame the phase goal as a user story at the top of `PLAN.md`. The user story is sourced from the `**Goal:**` line in ROADMAP.md (set by `mvp-phase`). Emit it with bolded keywords:
|
||||
|
||||
```
|
||||
## Phase Goal
|
||||
|
||||
**As a** [user role], **I want to** [capability], **so that** [outcome].
|
||||
```
|
||||
|
||||
Format rules from `@~/.claude/get-shit-done/references/user-story-template.md`:
|
||||
- All three slots required. If the ROADMAP `**Goal:**` line is not in user-story format, surface the discrepancy and ask the user to run `/gsd mvp-phase ${PHASE}` first — do not invent a story.
|
||||
- Bold the three keywords (`**As a**`, `**I want to**`, `**so that**`) when emitting to PLAN.md. The ROADMAP form does not use bolded keywords; the PLAN form does.
|
||||
2. First task: failing end-to-end test for the happy path.
|
||||
3. Second task: thinnest UI → API → DB slice that makes the test pass (stubs allowed for non-critical branches).
|
||||
4. Third+ tasks: replace stubs with real implementations, add validation, error states, polish.
|
||||
|
||||
**Mode is all-or-nothing per phase** (PRD decision Q1). Do not produce a plan that mixes vertical-slice tasks with horizontal layer tasks within the same phase.
|
||||
|
||||
**Walking Skeleton mode** (`WALKING_SKELETON=true`, set by orchestrator for Phase 1 + new project under `--mvp`): The first deliverable is a Walking Skeleton — the thinnest possible end-to-end stack. In addition to `PLAN.md`, produce `SKELETON.md` using the template at `@~/.claude/get-shit-done/references/skeleton-template.md`. `SKELETON.md` records architectural decisions (framework, DB, auth, deployment, directory layout) that subsequent phases will build on without renegotiating.
|
||||
|
||||
**Compatibility with TDD detection:** When both `MVP_MODE=true` and `workflow.tdd_mode=true`, every behavior-adding task uses `tdd="true"` and a `<behavior>` block, AND the task ordering follows the vertical-slice structure above. The first task is always a failing end-to-end test.
|
||||
|
||||
## User Setup Detection
|
||||
|
||||
For tasks involving external services, identify human-required configuration:
|
||||
|
||||
@@ -585,6 +585,27 @@ Deferred items are informational only — they do not require closure plans.
|
||||
|
||||
</verification_process>
|
||||
|
||||
<mvp_mode_verification>
|
||||
|
||||
## MVP Mode Verification
|
||||
|
||||
**When the phase under verification has `mode: mvp` in ROADMAP.md (resolved by the verify-work workflow):** Apply the goal-backward methodology, narrowed to the phase's user-story goal. Required reading: `@~/.claude/get-shit-done/references/verify-mvp-mode.md`.
|
||||
|
||||
**Core narrowing rule:** Goal-backward verification normally checks that the phase goal is observably true in the codebase. Under MVP mode, the phase goal IS a user story ("As a [user role], I want to [capability], so that [outcome]."). Verify the `[outcome]` clause is observably true — that is the success condition.
|
||||
|
||||
**VERIFICATION.md output structure under MVP mode:**
|
||||
|
||||
1. Top-level "User Flow Coverage" table: each step of the user story → expected → evidence in codebase → status. (Format defined in `references/verify-mvp-mode.md`.)
|
||||
2. Standard technical-check sections (API verification, error handling, etc.) follow below — only if the user flow coverage is complete.
|
||||
|
||||
**User-story-format guard:** If the phase has `mode: mvp` but the `**Goal:**` line is not in user-story format, refuse to verify. Surface the discrepancy and ask the user to run `/gsd mvp-phase ${PHASE}` to set a proper user-story goal. Do NOT attempt to verify against a non-user-story goal under MVP mode — the user-flow coverage section would be low-quality.
|
||||
|
||||
**Mode is all-or-nothing per phase** (PRD decision Q1, inherited from Phase 1). The MVP Mode Verification rules apply to the whole phase or not at all.
|
||||
|
||||
**Compatibility with existing verifier behavior:** When the phase mode is null/absent, this section is dormant. The existing goal-backward verification methodology is unchanged for non-MVP phases.
|
||||
|
||||
</mvp_mode_verification>
|
||||
|
||||
<output>
|
||||
|
||||
## Create VERIFICATION.md
|
||||
|
||||
@@ -193,6 +193,19 @@ Wait for the agent to complete.
|
||||
|
||||
---
|
||||
|
||||
## MVP-Mode Node Rendering
|
||||
|
||||
**MVP-mode rendering.** When a phase has `**Mode:** mvp` in ROADMAP.md (resolved via `gsd-sdk query roadmap.get-phase --pick mode`), render its graph node with two distinct visual signals:
|
||||
|
||||
1. **Distinct fill color.** Use `#22c55e` (green) for MVP-mode phase nodes. Standard phases keep the default fill color. Two-channel signaling (color + label) handles color-blind and grayscale renders.
|
||||
2. **`MVP` label suffix.** Append ` (MVP)` to the node's label text. Example: a phase originally labeled `Phase 1: User Auth` renders as `Phase 1: User Auth (MVP)`.
|
||||
|
||||
Both signals fire together — never just one. Per PRD Q5 decision, the goal is unambiguous visual distinction in any render context.
|
||||
|
||||
When the phase mode is null/absent, render with the standard color and label — no behavioral change for non-MVP phases.
|
||||
|
||||
---
|
||||
|
||||
## Anti-Patterns
|
||||
|
||||
1. DO NOT spawn an agent for query/status/diff operations -- these are inline CLI calls
|
||||
|
||||
45
commands/gsd/mvp-phase.md
Normal file
45
commands/gsd/mvp-phase.md
Normal file
@@ -0,0 +1,45 @@
|
||||
---
|
||||
name: gsd:mvp-phase
|
||||
description: Plan a phase as a vertical MVP slice — user story, SPIDR splitting, then plan-phase
|
||||
argument-hint: "<phase-number>"
|
||||
agent: gsd-planner
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Write
|
||||
- Bash
|
||||
- Glob
|
||||
- Grep
|
||||
- Task
|
||||
- AskUserQuestion
|
||||
---
|
||||
<objective>
|
||||
Guide the user through MVP-mode planning for a phase. The command:
|
||||
|
||||
1. Prompts for an "As a / I want to / So that" user story (three structured questions)
|
||||
2. Runs SPIDR splitting check — if the story is too large, walks through Spike/Paths/Interfaces/Data/Rules and offers to split into multiple phases
|
||||
3. Writes `**Mode:** mvp` and the reformatted `**Goal:**` to the phase's ROADMAP.md section
|
||||
4. Delegates to `/gsd plan-phase <N>` which auto-detects MVP mode via the roadmap field
|
||||
|
||||
Phase 1 of the vertical-mvp-slice PRD shipped the planner-side machinery; this command is the user entry point for it.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@~/.claude/get-shit-done/workflows/mvp-phase.md
|
||||
@~/.claude/get-shit-done/references/spidr-splitting.md
|
||||
@~/.claude/get-shit-done/references/user-story-template.md
|
||||
</execution_context>
|
||||
|
||||
<runtime_note>
|
||||
**Copilot (VS Code):** Use `vscode_askquestions` wherever this workflow calls `AskUserQuestion`. Equivalent API.
|
||||
</runtime_note>
|
||||
|
||||
<context>
|
||||
Phase number: $ARGUMENTS (required — integer or decimal like `2.1`)
|
||||
|
||||
The phase must already exist in ROADMAP.md (created via `/gsd new-project`, `/gsd add-phase`, or `/gsd insert-phase`). This command does not create new phases — it converts an existing phase to MVP mode.
|
||||
</context>
|
||||
|
||||
<process>
|
||||
Execute the mvp-phase workflow from @~/.claude/get-shit-done/workflows/mvp-phase.md end-to-end.
|
||||
Preserve all gates: phase existence, status guard (refuse in_progress/completed), user-story format validation, SPIDR splitting check, ROADMAP write confirmation, plan-phase delegation.
|
||||
</process>
|
||||
66
docs/CANARY.md
Normal file
66
docs/CANARY.md
Normal file
@@ -0,0 +1,66 @@
|
||||
# Canary Stream
|
||||
|
||||
The **canary** dist-tag is GSD's earliest preview channel. It exists so contributors and willing early adopters can exercise in-flight features against the long-lived `dev` integration branch before they have any expectation of stability.
|
||||
|
||||
## Stream policy
|
||||
|
||||
GSD ships through three npm dist-tags, each fed by exactly one git branch. **Streams do not mix.**
|
||||
|
||||
| Branch | dist-tag | Audience | Stability |
|
||||
|---|---|---|---|
|
||||
| `dev` | `canary` | Contributors, willing early adopters | Best-effort. May regress between cuts. Roll-forward only. |
|
||||
| `main` | `next` | Maintainers, RC testers | Release-candidate quality. Bug-bar enforced. |
|
||||
| `main` | `latest` | Everyone else | Production stable. The default `npm install` target. |
|
||||
|
||||
`dev` is the integration branch for in-flight feature work (typically multi-PR vertical slices like the MVP/TDD/UAT track in 1.50.0). When the dev work stabilizes, it promotes to `main` as an RC train (`vX.Y.Z-rc.N` published to `next`), and after the RC train bakes, the same train promotes again to `latest`.
|
||||
|
||||
A canary build NEVER becomes a `next` build directly, and a `next` build NEVER becomes a `latest` build directly — every promotion goes through a fresh tag and a fresh release.
|
||||
|
||||
## Installing canary
|
||||
|
||||
```bash
|
||||
# One-off invocation (npx)
|
||||
npx get-shit-done-cc@canary
|
||||
|
||||
# Pin to the canary dist-tag globally
|
||||
npm install -g get-shit-done-cc@canary
|
||||
|
||||
# Pin to an exact canary version
|
||||
npm install -g get-shit-done-cc@1.50.0-canary.1
|
||||
```
|
||||
|
||||
The CC installer's defensive purge rewrites stale config blocks left by older GSD versions, so reinstalling on top of an existing project is safe.
|
||||
|
||||
## When to install canary
|
||||
|
||||
✅ **Do** install canary when you want to:
|
||||
- Exercise in-flight planning/execution/verification features early and report findings
|
||||
- Validate a fix you've contributed to `dev` is reachable end-to-end
|
||||
- Help shake out canary-bake items (rough edges that won't ship to `next` until resolved)
|
||||
|
||||
❌ **Do NOT** install canary on:
|
||||
- Production projects you depend on for delivery
|
||||
- A machine where rolling back means recreating GSD state (use a profile or a workspace instead)
|
||||
- A demo or onboarding setup — pin to `@latest` so audiences see the stable surface
|
||||
|
||||
## Rolling back from canary
|
||||
|
||||
```bash
|
||||
# Back to the current stable
|
||||
npm install -g get-shit-done-cc@latest
|
||||
|
||||
# Or to the next/RC train
|
||||
npm install -g get-shit-done-cc@next
|
||||
```
|
||||
|
||||
If you have a local project that interacted with canary-only features (for instance, an MVP-mode phase planned by 1.50.0-canary), the planner artifacts in `.planning/` remain valid — older GSD versions will just ignore the `**Mode:** mvp` field on phases.
|
||||
|
||||
## Reporting issues against canary
|
||||
|
||||
File against the [issue tracker](https://github.com/gsd-build/get-shit-done/issues) with the `bug` template. Include the exact canary version (`get-shit-done-cc --version` reports it) so triage can route the report back into the `dev` stream rather than the stable stream.
|
||||
|
||||
## Where to look next
|
||||
|
||||
- Active canary release notes: [`docs/RELEASE-v1.50.0-canary.1.md`](RELEASE-v1.50.0-canary.1.md)
|
||||
- Stable release notes: [`CHANGELOG.md`](../CHANGELOG.md)
|
||||
- Stream architecture rationale: discussed across [#2727](https://github.com/gsd-build/get-shit-done/issues/2727), [#2773](https://github.com/gsd-build/get-shit-done/issues/2773) (codex schema-break and the resulting promotion bottleneck that motivated explicit stream isolation)
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"generated": "2026-04-27",
|
||||
"generated": "2026-04-30",
|
||||
"families": {
|
||||
"agents": [
|
||||
"gsd-advisor-researcher",
|
||||
@@ -78,6 +78,7 @@
|
||||
"/gsd-manager",
|
||||
"/gsd-map-codebase",
|
||||
"/gsd-milestone-summary",
|
||||
"/gsd-mvp-phase",
|
||||
"/gsd-new-milestone",
|
||||
"/gsd-new-project",
|
||||
"/gsd-new-workspace",
|
||||
@@ -166,6 +167,7 @@
|
||||
"manager.md",
|
||||
"map-codebase.md",
|
||||
"milestone-summary.md",
|
||||
"mvp-phase.md",
|
||||
"new-milestone.md",
|
||||
"new-project.md",
|
||||
"new-workspace.md",
|
||||
@@ -224,6 +226,7 @@
|
||||
"decimal-phase-calculation.md",
|
||||
"doc-conflict-engine.md",
|
||||
"domain-probes.md",
|
||||
"execute-mvp-tdd.md",
|
||||
"executor-examples.md",
|
||||
"gate-prompts.md",
|
||||
"gates.md",
|
||||
@@ -237,6 +240,7 @@
|
||||
"planner-antipatterns.md",
|
||||
"planner-chunked.md",
|
||||
"planner-gap-closure.md",
|
||||
"planner-mvp-mode.md",
|
||||
"planner-reviews.md",
|
||||
"planner-revision.md",
|
||||
"planner-source-audit.md",
|
||||
@@ -245,10 +249,12 @@
|
||||
"questioning.md",
|
||||
"revision-loop.md",
|
||||
"scout-codebase.md",
|
||||
"skeleton-template.md",
|
||||
"sketch-interactivity.md",
|
||||
"sketch-theme-system.md",
|
||||
"sketch-tooling.md",
|
||||
"sketch-variant-patterns.md",
|
||||
"spidr-splitting.md",
|
||||
"tdd.md",
|
||||
"thinking-models-debug.md",
|
||||
"thinking-models-execution.md",
|
||||
@@ -259,8 +265,10 @@
|
||||
"ui-brand.md",
|
||||
"universal-anti-patterns.md",
|
||||
"user-profiling.md",
|
||||
"user-story-template.md",
|
||||
"verification-overrides.md",
|
||||
"verification-patterns.md",
|
||||
"verify-mvp-mode.md",
|
||||
"workstream-flag.md"
|
||||
],
|
||||
"cli_modules": [
|
||||
|
||||
@@ -54,7 +54,7 @@ Full roster at `agents/gsd-*.md`. The "Primary doc" column flags whether [`docs/
|
||||
|
||||
---
|
||||
|
||||
## Commands (86 shipped)
|
||||
## Commands (87 shipped)
|
||||
|
||||
Full roster at `commands/gsd/*.md`. The groupings below mirror `docs/COMMANDS.md` section order; each row carries the command name, a one-line role derived from the command's frontmatter `description:`, and a link to the source file. `tests/command-count-sync.test.cjs` locks the count against the filesystem.
|
||||
|
||||
@@ -67,6 +67,7 @@ Full roster at `commands/gsd/*.md`. The groupings below mirror `docs/COMMANDS.md
|
||||
| `/gsd-list-workspaces` | List active GSD workspaces and their status. | [commands/gsd/list-workspaces.md](../commands/gsd/list-workspaces.md) |
|
||||
| `/gsd-remove-workspace` | Remove a GSD workspace and clean up worktrees. | [commands/gsd/remove-workspace.md](../commands/gsd/remove-workspace.md) |
|
||||
| `/gsd-discuss-phase` | Gather phase context through adaptive questioning before planning. | [commands/gsd/discuss-phase.md](../commands/gsd/discuss-phase.md) |
|
||||
| `/gsd-mvp-phase` | Plan a phase as a vertical MVP slice — user story, SPIDR splitting, then plan-phase. | [commands/gsd/mvp-phase.md](../commands/gsd/mvp-phase.md) |
|
||||
| `/gsd-spec-phase` | Socratic spec refinement producing a SPEC.md with falsifiable requirements. | [commands/gsd/spec-phase.md](../commands/gsd/spec-phase.md) |
|
||||
| `/gsd-ui-phase` | Generate UI design contract (UI-SPEC.md) for frontend phases. | [commands/gsd/ui-phase.md](../commands/gsd/ui-phase.md) |
|
||||
| `/gsd-ai-integration-phase` | Generate AI design contract (AI-SPEC.md) via framework selection, research, and eval planning. | [commands/gsd/ai-integration-phase.md](../commands/gsd/ai-integration-phase.md) |
|
||||
@@ -176,7 +177,7 @@ Full roster at `commands/gsd/*.md`. The groupings below mirror `docs/COMMANDS.md
|
||||
|
||||
---
|
||||
|
||||
## Workflows (84 shipped)
|
||||
## Workflows (85 shipped)
|
||||
|
||||
Full roster at `get-shit-done/workflows/*.md`. Workflows are thin orchestrators that commands reference internally; most are not read directly by end users. Rows below map each workflow file to its role (derived from the `<purpose>` block) and, where applicable, to the command that invokes it.
|
||||
|
||||
@@ -201,6 +202,7 @@ Full roster at `get-shit-done/workflows/*.md`. Workflows are thin orchestrators
|
||||
| `discuss-phase-assumptions.md` | Assumptions-mode discuss — extract implementation decisions via codebase-first analysis. | `/gsd-discuss-phase` (when `discuss_mode=assumptions`) |
|
||||
| `discuss-phase-power.md` | Power-user discuss — pre-generate all questions into a JSON state file + HTML UI. | `/gsd-discuss-phase --power` |
|
||||
| `discuss-phase.md` | Extract implementation decisions through iterative gray-area discussion. | `/gsd-discuss-phase` |
|
||||
| `mvp-phase.md` | Plan a phase as a vertical MVP slice — user story, SPIDR splitting, then plan-phase. | `/gsd-mvp-phase` |
|
||||
| `do.md` | Route freeform text from the user to the best matching GSD command. | `/gsd-do` |
|
||||
| `docs-update.md` | Generate, update, and verify canonical and hand-written project documentation. | `/gsd-docs-update` |
|
||||
| `edit-phase.md` | Edit any field of an existing phase in ROADMAP.md in place, preserving number and position. | `/gsd-edit-phase` |
|
||||
@@ -271,7 +273,7 @@ Full roster at `get-shit-done/workflows/*.md`. Workflows are thin orchestrators
|
||||
|
||||
---
|
||||
|
||||
## References (51 shipped)
|
||||
## References (57 shipped)
|
||||
|
||||
Full roster at `get-shit-done/references/*.md`. References are shared knowledge documents that workflows and agents `@-reference`. The groupings below match [`docs/ARCHITECTURE.md`](ARCHITECTURE.md#references-get-shit-donereferencesmd) — core, workflow, thinking-model clusters, and the modular planner decomposition.
|
||||
|
||||
@@ -320,6 +322,8 @@ Full roster at `get-shit-done/references/*.md`. References are shared knowledge
|
||||
| `ai-frameworks.md` | AI framework decision-matrix reference for `gsd-framework-selector`. |
|
||||
| `executor-examples.md` | Worked examples for the gsd-executor agent. |
|
||||
| `doc-conflict-engine.md` | Shared conflict-detection contract for ingest/import workflows. |
|
||||
| `execute-mvp-tdd.md` | Runtime gate semantics for execute-phase under MVP+TDD — pre-task failing-test verification, end-of-phase blocking review. |
|
||||
| `verify-mvp-mode.md` | UAT framing rules for MVP-mode phases — user-flow-first ordering, deferred technical checks, user-story-format guard. |
|
||||
|
||||
### Sketch References
|
||||
|
||||
@@ -356,8 +360,12 @@ The `gsd-planner` agent is decomposed into a core agent plus reference modules t
|
||||
| `planner-reviews.md` | Cross-AI review integration (reads REVIEWS.md from `/gsd-review`). |
|
||||
| `planner-revision.md` | Plan revision patterns for iterative refinement. |
|
||||
| `planner-source-audit.md` | Planner source-audit and authority-limit rules. |
|
||||
| `planner-mvp-mode.md` | Vertical-slice planning rules for MVP mode. |
|
||||
| `skeleton-template.md` | SKELETON.md template emitted for new-project Walking Skeleton (Phase 1 + `--mvp`). |
|
||||
| `user-story-template.md` | User story format for MVP planning — "As a / I want to / So that" structured fields. |
|
||||
| `spidr-splitting.md` | SPIDR splitting decomposition rules for handling large user stories in MVP mode. |
|
||||
|
||||
> **Subdirectory:** `get-shit-done/references/few-shot-examples/` contains additional few-shot examples (`plan-checker.md`, `verifier.md`) that are referenced from specific agents. These are not counted in the 51 top-level references.
|
||||
> **Subdirectory:** `get-shit-done/references/few-shot-examples/` contains additional few-shot examples (`plan-checker.md`, `verifier.md`) that are referenced from specific agents. These are not counted in the 53 top-level references.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -17,10 +17,12 @@ Language versions: [English](README.md) · [Português (pt-BR)](pt-BR/README.md)
|
||||
| [User Guide](USER-GUIDE.md) | All users | Workflow walkthroughs, troubleshooting, and recovery |
|
||||
| [Context Monitor](context-monitor.md) | All users | Context window monitoring hook architecture |
|
||||
| [Discuss Mode](workflow-discuss-mode.md) | All users | Assumptions vs interview mode for discuss-phase |
|
||||
| [Canary Stream](CANARY.md) | Contributors, early adopters | `dev` → `@canary` dist-tag policy, when to install, rollback path |
|
||||
|
||||
## Quick Links
|
||||
|
||||
- **What's new:** see [CHANGELOG](../CHANGELOG.md) for current release notes, and upstream [README](../README.md) for release highlights
|
||||
- **Canary preview:** [`docs/CANARY.md`](CANARY.md) — opt into the early-preview stream from `dev`. Active cut: [`v1.50.0-canary.1`](RELEASE-v1.50.0-canary.1.md)
|
||||
- **Getting started:** [README](../README.md) → install → `/gsd-new-project`
|
||||
- **Full workflow walkthrough:** [User Guide](USER-GUIDE.md)
|
||||
- **All commands at a glance:** [Command Reference](COMMANDS.md)
|
||||
|
||||
94
docs/RELEASE-v1.50.0-canary.1.md
Normal file
94
docs/RELEASE-v1.50.0-canary.1.md
Normal file
@@ -0,0 +1,94 @@
|
||||
# v1.50.0-canary.1 Release Notes
|
||||
|
||||
First canary cut for the **1.50.0** train. Published to npm under the `canary` dist-tag.
|
||||
|
||||
```bash
|
||||
npx get-shit-done-cc@canary
|
||||
# or pin exact:
|
||||
npm install -g get-shit-done-cc@1.50.0-canary.1
|
||||
```
|
||||
|
||||
> **Canary stream caveat.** Canary builds come from the long-lived `dev` integration branch and may carry rough edges that the `next` (RC) and `latest` (stable) channels never see. Use canary when you want to exercise in-flight features early and report findings; do NOT pin production projects to it. See [CANARY.md](CANARY.md) for the stream policy and rollback path.
|
||||
|
||||
---
|
||||
|
||||
## Headline: Vertical MVP / TDD / UAT planning track
|
||||
|
||||
The 1.50.0 train opens with a four-phase vertical slice that adds an end-to-end "MVP mode" to the GSD planning pipeline — from project kickoff, through phase planning, through execution, through verification. Issue [#2826](https://github.com/gsd-build/get-shit-done/issues/2826) is the umbrella PRD.
|
||||
|
||||
### What's new
|
||||
|
||||
#### `/gsd plan-phase --mvp` — vertical-slice planning ([#2867](https://github.com/gsd-build/get-shit-done/pull/2867))
|
||||
|
||||
`/gsd plan-phase` learns a `--mvp` flag that flips the planner into vertical-slice mode. The planner reads `**Mode:** mvp` from a phase's ROADMAP entry, an explicit `--mvp` CLI override, or `workflow.mvp_mode` in `.planning/config.json` (precedence in that order, with the CLI flag winning). Under MVP mode the planner:
|
||||
|
||||
- Surfaces a "Walking Skeleton" template for the very first phase of a new project — a thin end-to-end vertical slice that proves the wiring before any horizontal layer is built
|
||||
- Suppresses horizontal-layer language ("data layer first, then business logic, then UI") in favor of user-flow-driven decomposition
|
||||
- Emits the user story as a header at the top of `PLAN.md`
|
||||
|
||||
New required-reading injection: `references/planner-mvp-mode.md`. New parser surface: `roadmap.cjs` extracts a `mode` field on every phase lookup.
|
||||
|
||||
#### `/gsd mvp-phase <N>` — guided user-story phase framing ([#2874](https://github.com/gsd-build/get-shit-done/pull/2874))
|
||||
|
||||
A new top-level command that walks the user through framing a phase as a vertical MVP slice before planning. Three structured prompts capture an "As a / I want to / So that" user story. If the story is too large, an interactive SPIDR (Spike / Path / Interface / Data / Rule) splitting flow surfaces a list of `/gsd add-phase` invocations to break the work apart. The command then:
|
||||
|
||||
- Mutates the ROADMAP entry to set `**Mode:** mvp` and replaces `**Goal:**` with the assembled user story
|
||||
- Delegates to `/gsd plan-phase --mvp <N>` to produce the plan
|
||||
|
||||
Two new references: [`spidr-splitting.md`](../get-shit-done/references/spidr-splitting.md), [`user-story-template.md`](../get-shit-done/references/user-story-template.md).
|
||||
|
||||
#### Execute-phase MVP+TDD runtime gate ([#2878](https://github.com/gsd-build/get-shit-done/pull/2878))
|
||||
|
||||
When `MVP_MODE` and `TDD_MODE` are both true at execution time, `execute-phase` adds a per-task gate that requires a `test(<phase>-<plan>):` commit to exist before the corresponding `feat(...)` commit. The reference [`execute-mvp-tdd.md`](../get-shit-done/references/execute-mvp-tdd.md) documents the contract; the executor agent (`agents/gsd-executor.md`) gains an MVP+TDD Gate section that explains when the gate trips, what evidence it expects, and how to escalate via the documented escape hatch.
|
||||
|
||||
> **Known canary-bake item.** The current bash gate snippet uses some workflow variables that aren't fully wired (`${PLAN_ID}`, `${TASK_TDD}`) and the documented `--force-mvp-gate` escape hatch is referenced in the user-facing error message but not yet implemented in the argument parser. These are tracked as canary-bake follow-ups; the gate itself is functional for the dominant code path.
|
||||
|
||||
#### Verify-work MVP-mode UAT framing ([#2880](https://github.com/gsd-build/get-shit-done/pull/2880))
|
||||
|
||||
Under MVP mode, `verify-work` flips the UAT script's framing so user-flow steps come **before** technical correctness checks — the inverse of the default order. The verifier agent gains a `mvp_mode_verification` section. New reference: [`verify-mvp-mode.md`](../get-shit-done/references/verify-mvp-mode.md).
|
||||
|
||||
A user-story format guard at the top of `extract_tests` will halt verification if a phase claims `**Mode:** mvp` but its `**Goal:**` doesn't parse as `As a … I want to … so that …` — pointing the user at `/gsd mvp-phase <N>` to repair.
|
||||
|
||||
#### Discovery & progress surfaces ([#2883](https://github.com/gsd-build/get-shit-done/pull/2883))
|
||||
|
||||
The MVP slice closes out with read-side surfaces:
|
||||
|
||||
- **`/gsd new-project`** prompts up front for **Vertical MVP** vs **Horizontal Layers** mode and seeds the milestone accordingly
|
||||
- **`/gsd-progress`** emits a "User-flow next up" panel for MVP-mode phases, surfacing user-visible task names ahead of internal scaffolding
|
||||
- **`/gsd-stats`** adds an "MVP phases: N" summary line when the roadmap contains any
|
||||
- **`/gsd-graphify`** visually differentiates MVP-mode phase nodes from horizontal-layer phases in the rendered graph
|
||||
|
||||
---
|
||||
|
||||
## Bonus fixes also in this canary
|
||||
|
||||
- **`/gsd-progress` no longer cites stale CLAUDE.md project blocks** as the source for the "Next Up" section ([#2912](https://github.com/gsd-build/get-shit-done/issues/2912)) — explicit context-authority directive added to the report step.
|
||||
|
||||
(Other recent main-stream fixes — agent-skills CLI JSON wrap, audit-open ReferenceError, execute-phase branching, Hermes runtime — target the `next` stream and will arrive in the canary when they land in `dev`.)
|
||||
|
||||
---
|
||||
|
||||
## Install / upgrade
|
||||
|
||||
```bash
|
||||
# Try the canary
|
||||
npx get-shit-done-cc@canary
|
||||
|
||||
# Or pin exact
|
||||
npm install -g get-shit-done-cc@1.50.0-canary.1
|
||||
```
|
||||
|
||||
The installer's defensive purge will rewrite stale config blocks left by older GSD versions on first run. No manual cleanup needed.
|
||||
|
||||
## Reporting issues
|
||||
|
||||
If something breaks on canary, file against [the issue tracker](https://github.com/gsd-build/get-shit-done/issues) with the `bug` template and mention `1.50.0-canary.1` so it gets routed back into the dev stream rather than the stable stream.
|
||||
|
||||
## What ships next in this train
|
||||
|
||||
Pending dev-stream merges that should land before promotion to `next`:
|
||||
- Resolve canary-bake items in the MVP+TDD gate (variable wiring + `--force-mvp-gate` parser)
|
||||
- Sync recent main-stream fixes (`#2918`, `#2919`, `#2921`, `#2917`, `#2920`) into dev
|
||||
- Ride a few canary cycles for real-user MVP/TDD/UAT feedback
|
||||
|
||||
When the dev stream stabilizes, the train promotes to `main` as `v1.50.0-rc.1` (the `next` channel).
|
||||
@@ -85,6 +85,11 @@ function searchPhaseInContent(content, escapedPhase, phaseNum) {
|
||||
const goalMatch = section.match(/\*\*Goal(?::\*\*|\*\*:)\s*([^\n]+)/i);
|
||||
const goal = goalMatch ? goalMatch[1].trim() : null;
|
||||
|
||||
// Mode: vertical-MVP slice mode flag. Lowercased + trimmed for canonical
|
||||
// comparison; unrecognized values are preserved verbatim for forward-compat.
|
||||
const modeMatch = section.match(/\*\*Mode(?::\*\*|\*\*:)\s*([^\n]+)/i);
|
||||
const mode = modeMatch ? modeMatch[1].trim().toLowerCase() : null;
|
||||
|
||||
// Extract success criteria as structured array
|
||||
const criteriaMatch = section.match(/\*\*Success Criteria\*\*[^\n]*:\s*\n((?:\s*\d+\.\s*[^\n]+\n?)+)/i);
|
||||
const success_criteria = criteriaMatch
|
||||
@@ -96,6 +101,7 @@ function searchPhaseInContent(content, escapedPhase, phaseNum) {
|
||||
phase_number: phaseNum,
|
||||
phase_name: phaseName,
|
||||
goal,
|
||||
mode,
|
||||
success_criteria,
|
||||
section,
|
||||
};
|
||||
@@ -181,6 +187,9 @@ function cmdRoadmapAnalyze(cwd, raw) {
|
||||
const goalMatch = section.match(/\*\*Goal(?::\*\*|\*\*:)\s*([^\n]+)/i);
|
||||
const goal = goalMatch ? goalMatch[1].trim() : null;
|
||||
|
||||
const modeMatch = section.match(/\*\*Mode(?::\*\*|\*\*:)\s*([^\n]+)/i);
|
||||
const mode = modeMatch ? modeMatch[1].trim().toLowerCase() : null;
|
||||
|
||||
const dependsMatch = section.match(/\*\*Depends on(?::\*\*|\*\*:)\s*([^\n]+)/i);
|
||||
const depends_on = dependsMatch ? dependsMatch[1].trim() : null;
|
||||
|
||||
@@ -227,6 +236,7 @@ function cmdRoadmapAnalyze(cwd, raw) {
|
||||
number: phaseNum,
|
||||
name: phaseName,
|
||||
goal,
|
||||
mode,
|
||||
depends_on,
|
||||
plan_count: planCount,
|
||||
summary_count: summaryCount,
|
||||
|
||||
81
get-shit-done/references/execute-mvp-tdd.md
Normal file
81
get-shit-done/references/execute-mvp-tdd.md
Normal file
@@ -0,0 +1,81 @@
|
||||
# Execute-Phase — MVP+TDD Gate (Runtime Enforcement)
|
||||
|
||||
> Loaded by `execute-phase` workflow and `gsd-executor` agent only when **both** `MVP_MODE=true` AND `TDD_MODE=true` for the phase. Defines the runtime gate that blocks behavior-adding tasks until a failing-test commit exists.
|
||||
|
||||
## When this gate fires
|
||||
|
||||
- `MVP_MODE` is `true` (resolved from CLI flag → ROADMAP `**Mode:**` field → config; see `references/planner-mvp-mode.md`).
|
||||
- `TDD_MODE` is `true` (resolved from `--tdd` flag → `workflow.tdd_mode` config).
|
||||
- The current task being executed has `tdd="true"` in its `<task>` frontmatter (set by the planner per Phase 1).
|
||||
- The task's `<behavior>` block lists at least one expected behavior.
|
||||
|
||||
If any of these is false, the gate is inactive — execution proceeds normally.
|
||||
|
||||
## What the gate checks
|
||||
|
||||
For each task gated by MVP+TDD, the executor MUST verify (before running the implementation step):
|
||||
|
||||
1. **A failing-test commit exists.** Search git log on the current branch for a commit matching `test({phase}-{plan})` whose subject mentions the same plan as the current task. The commit must touch a test file (`*.test.*`, `*.spec.*`, `tests/**`).
|
||||
2. **The test was actually red.** The commit message body or the executor's recent shell history must show the test failed when first run. Acceptable evidence:
|
||||
- Commit message contains `RED:` prefix or `(RED)` tag
|
||||
- Recent terminal output shows `FAIL` or non-zero exit on the new test before any implementation commit
|
||||
3. **No implementation commit yet.** No `feat({phase}-{plan})` commit may exist for the same plan ID before the failing-test commit.
|
||||
|
||||
If any check fails, the gate trips.
|
||||
|
||||
## What "behavior-adding task" means
|
||||
|
||||
A task is behavior-adding when:
|
||||
- Its frontmatter has `tdd="true"` AND
|
||||
- Its `<behavior>` block names at least one user-visible outcome (not a config-only or doc-only task) AND
|
||||
- Its `<files>` list includes at least one source file (not exclusively `*.md`, `*.json`, or `*.test.*`)
|
||||
|
||||
Pure documentation, configuration, or test-only tasks are skipped by this gate even when both modes are active.
|
||||
|
||||
## What happens when the gate trips
|
||||
|
||||
The executor MUST:
|
||||
|
||||
1. Halt before running the task's implementation step.
|
||||
2. Emit a structured halt report:
|
||||
|
||||
```
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
MVP+TDD GATE TRIPPED — Plan {plan_id}, Task {task_id}
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
Reason: {missing_red_commit | red_commit_not_failing | feat_before_test}
|
||||
|
||||
Behavior expected to be tested:
|
||||
- {first behavior bullet}
|
||||
|
||||
Required next step:
|
||||
1. Write a failing test for the behavior above.
|
||||
2. Commit it as: test({phase}-{plan}): {short description}
|
||||
3. Re-run /gsd execute-phase
|
||||
```
|
||||
|
||||
3. Exit the current execution wave cleanly. Do NOT roll back any prior commits in the same wave.
|
||||
4. Update `STATE.md` with `last_gate_trip: {plan_id}/{task_id}` so the user can resume after writing the test.
|
||||
|
||||
## Escalation: end-of-phase TDD review under MVP+TDD
|
||||
|
||||
The existing end-of-phase TDD review (in `workflows/execute-phase.md`'s `tdd_review_checkpoint` step) is normally **advisory** — it surfaces gate violations but does not block phase completion.
|
||||
|
||||
Under MVP+TDD, escalate this to **blocking**:
|
||||
- If any TDD plan is missing a RED or GREEN commit, the executor MUST refuse to mark the phase complete.
|
||||
- The user is shown the same review table, but the verdict line reads:
|
||||
> "Phase blocked: {N} TDD plan(s) violate the RED→GREEN gate sequence under MVP+TDD. Resolve and re-run /gsd execute-phase, or override with `/gsd execute-phase {phase} --force-mvp-gate` to ship anyway."
|
||||
|
||||
The `--force-mvp-gate` flag is documented but not introduced by this plan — it is the escape hatch the spec mentions; if the user later builds it, the workflow already references the contract.
|
||||
|
||||
## What this gate does NOT do
|
||||
|
||||
- It does not enforce REFACTOR commits. REFACTOR remains optional (per `references/tdd.md`).
|
||||
- It does not check test quality (the test could be trivially passing). That's the planner's job.
|
||||
- It does not run tests. The executor only inspects git log + file system. Running tests is the implementation step's job.
|
||||
- It does not gate config-only or doc-only tasks (see "behavior-adding task" definition).
|
||||
|
||||
## Compatibility with existing TDD discipline
|
||||
|
||||
This gate is additive to `references/tdd.md`. Tasks not under MVP+TDD continue to use the existing advisory TDD discipline (RED/GREEN/REFACTOR commits with end-of-phase review checkpoint). Only the runtime gate and the blocking escalation are new.
|
||||
53
get-shit-done/references/planner-mvp-mode.md
Normal file
53
get-shit-done/references/planner-mvp-mode.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# Planner — MVP Mode (Vertical Slice Strategy)
|
||||
|
||||
> Loaded by `gsd-planner` only when `MVP_MODE=true`. Standard horizontal-layer planning rules continue to apply for all other phases.
|
||||
|
||||
## Core Rule
|
||||
|
||||
**Decompose by feature slice, not by technical layer.** Every task must move the user-facing capability forward. After each task, a real user can click through more of the feature than they could before.
|
||||
|
||||
**Forbidden** in MVP mode:
|
||||
- "Create the database schema" as a standalone task
|
||||
- "Build the API layer" as a standalone task
|
||||
- "Wire up the UI" as a final integration task
|
||||
|
||||
**Required** in MVP mode:
|
||||
- The first non-test task produces a working end-to-end path. Stubs are allowed for non-critical branches; the happy path must be real.
|
||||
- Each subsequent task either adds a new slice OR refines an existing slice (validation, error states, edge cases).
|
||||
- The phase goal is framed as a user story: "**As a** [user], **I want to** [do X], **so that** [Y]."
|
||||
|
||||
## Task Order Pattern
|
||||
|
||||
For a feature `F`:
|
||||
|
||||
1. **Failing end-to-end test** for the happy path of `F`.
|
||||
2. **Thinnest viable slice** — UI form → API endpoint → DB read/write — that makes the test pass. Hard-coded values, missing validation, no error states are fine here.
|
||||
3. **Real data layer** — replace any stubs from Task 2 with real queries.
|
||||
4. **Validation + error states** — invalid input, network failure, empty states.
|
||||
5. **Production polish** — loading indicators, edge cases, accessibility checks.
|
||||
|
||||
Tasks 3-5 are not always all needed; gate by the phase's acceptance criteria.
|
||||
|
||||
## Walking Skeleton Mode (`WALKING_SKELETON=true`)
|
||||
|
||||
When the orchestrator sets `WALKING_SKELETON=true` (Phase 1 of a new project under `--mvp`), the plan changes shape:
|
||||
|
||||
- The "feature" is the application itself. Pick the smallest meaningful capability that proves the full stack works (e.g., "user can sign up and see their name on a dashboard").
|
||||
- The plan **must include**:
|
||||
- Project scaffold (framework init, routing, build, lint)
|
||||
- One real DB read/write
|
||||
- One real UI interaction wired to the API
|
||||
- Deployment to a dev environment (or a documented local-run command that exercises the full stack)
|
||||
- The plan **must produce** `SKELETON.md` in the phase directory alongside `PLAN.md`. Use the template at `@~/.claude/get-shit-done/references/skeleton-template.md`. `SKELETON.md` records the architectural decisions that subsequent phases will build on (chosen framework, DB, deployment target, auth approach, directory layout).
|
||||
|
||||
`SKELETON.md` is the architectural backbone for every later vertical slice; treat it as a contract, not a scratchpad.
|
||||
|
||||
## Anti-Patterns to Reject
|
||||
|
||||
- **Layer cake disguised as slices.** Three "vertical" tasks where Task 1 is "all the schemas", Task 2 is "all the endpoints", Task 3 is "all the UI" — that is horizontal planning with new labels. Reject.
|
||||
- **Skeleton bloat.** Walking Skeleton is the *thinnest* working stack, not "Phase 1 of a normal app." If Skeleton has more than ~5 tasks, you are not skeletonizing.
|
||||
- **Premature SPIDR splitting.** SPIDR splitting is the `mvp-phase` command's job (Phase 2 of the PRD), not the planner's. If the phase scope feels too large, surface it via the verification loop, do not split silently.
|
||||
|
||||
## Acceptance Test for Your Plan
|
||||
|
||||
Before emitting the plan, ask: **after Task N completes, can a real user *do* something they could not do after Task N-1?** If the answer is "no, but the foundation is laid", you have a horizontal task disguised as a slice. Restructure.
|
||||
48
get-shit-done/references/skeleton-template.md
Normal file
48
get-shit-done/references/skeleton-template.md
Normal file
@@ -0,0 +1,48 @@
|
||||
# SKELETON.md Template
|
||||
|
||||
> Emitted by `gsd-planner` when `WALKING_SKELETON=true` (Phase 1 + `--mvp` + new project). Records the architectural decisions the rest of the project will build on.
|
||||
|
||||
```markdown
|
||||
# Walking Skeleton — [Project Name]
|
||||
|
||||
**Phase:** 1
|
||||
**Generated:** {ISO date}
|
||||
|
||||
## Capability Proven End-to-End
|
||||
|
||||
> One sentence: the smallest user-visible capability that exercises the full stack.
|
||||
|
||||
Example: "A signed-in user can view their email on a dashboard page served by the deployed app."
|
||||
|
||||
## Architectural Decisions
|
||||
|
||||
| Decision | Choice | Rationale |
|
||||
|---|---|---|
|
||||
| Framework | (e.g., Next.js 15 App Router) | Why this fits the project |
|
||||
| Data layer | (e.g., Postgres + Drizzle) | Why |
|
||||
| Auth | (e.g., session cookies + bcrypt) | Why |
|
||||
| Deployment target | (e.g., Vercel preview) | Why |
|
||||
| Directory layout | (e.g., feature-folders under src/features/*) | Why |
|
||||
|
||||
## Stack Touched in Phase 1
|
||||
|
||||
- [ ] Project scaffold (framework, build, lint, test runner)
|
||||
- [ ] Routing — at least one real route
|
||||
- [ ] Database — at least one real read AND one real write
|
||||
- [ ] UI — at least one interactive element wired to the API
|
||||
- [ ] Deployment — running on dev environment OR documented local full-stack run command
|
||||
|
||||
## Out of Scope (Deferred to Later Slices)
|
||||
|
||||
> Anything that is *not* in the skeleton. Be explicit — this list prevents future phases from re-litigating Phase 1's minimalism.
|
||||
|
||||
- (e.g., password reset, email verification, multi-tenancy)
|
||||
|
||||
## Subsequent Slice Plan
|
||||
|
||||
Each later phase adds one vertical slice on top of this skeleton without altering its architectural decisions:
|
||||
|
||||
- Phase 2: [next user capability]
|
||||
- Phase 3: [next user capability]
|
||||
- ...
|
||||
```
|
||||
69
get-shit-done/references/spidr-splitting.md
Normal file
69
get-shit-done/references/spidr-splitting.md
Normal file
@@ -0,0 +1,69 @@
|
||||
# SPIDR Story Splitting Rules
|
||||
|
||||
> Used by `mvp-phase` workflow when the user-supplied story is too large for a single phase. Per PRD decision Q3, SPIDR runs as a **full interactive flow** — not a lightweight check.
|
||||
|
||||
## When SPIDR triggers
|
||||
|
||||
Trigger SPIDR splitting if **any** of these size signals fire on the user story:
|
||||
|
||||
1. **Compound capabilities.** The story names two or more independent user actions joined by "and" (e.g., "register **and** log in **and** reset their password"). Each "and" is a candidate split point.
|
||||
2. **Multi-actor.** The story names more than one `[user role]` (e.g., "As a user or admin..."). Each role is a candidate split.
|
||||
3. **Length.** The assembled story exceeds ~120 chars on a single line.
|
||||
4. **Vague capability.** The capability is a noun phrase, not a verb-noun pair (e.g., "I want to use the dashboard" — needs to specify *which interaction* with the dashboard).
|
||||
|
||||
If none of these fire, skip SPIDR entirely and proceed to ROADMAP write.
|
||||
|
||||
## The five SPIDR axes
|
||||
|
||||
For each axis, ask one targeted question. The user picks the axis that best fits their story; only one axis is applied per split.
|
||||
|
||||
### Spike
|
||||
|
||||
> "Is there an unknown that needs research before this can be implemented? If so, the spike is its own phase."
|
||||
|
||||
If yes: split out a research phase (no acceptance criteria except "we know enough to plan the rest"). The remaining story becomes a follow-up phase.
|
||||
|
||||
### Paths
|
||||
|
||||
> "Does this feature have a happy path and one or more error/edge paths?"
|
||||
|
||||
If yes: split happy path into the first phase, edge paths into follow-ups. Order: happy path first (it proves the slice works), then progressively edge cases.
|
||||
|
||||
### Interfaces
|
||||
|
||||
> "Does this feature need to work on more than one interface (web, mobile, API, CLI)?"
|
||||
|
||||
If yes: split by interface. Web first if user-facing; API first if integration-driven; mobile last unless it's the primary platform.
|
||||
|
||||
### Data
|
||||
|
||||
> "Does this feature touch multiple data scopes (one user vs. many, single team vs. multi-tenant, small CSV vs. large dataset)?"
|
||||
|
||||
If yes: split by scope. Smallest scope first (one user, single team, small data), then expand.
|
||||
|
||||
### Rules
|
||||
|
||||
> "Does this feature have multiple business rules that could be added incrementally (basic validation first, then complex policy)?"
|
||||
|
||||
If yes: split by rule complexity. Minimum viable rules first; complex policy in follow-ups.
|
||||
|
||||
## Workflow
|
||||
|
||||
When SPIDR triggers, the workflow:
|
||||
|
||||
1. Restates the user-supplied story.
|
||||
2. Asks "Which SPIDR axis fits best?" with the five options above.
|
||||
3. Walks through the chosen axis interactively (one focused question), produces a split proposal: "Phase N (this one): X. Phase N+1: Y. Phase N+2: Z."
|
||||
4. Confirms the split with the user.
|
||||
5. On accept: writes the FIRST phase's story to the current ROADMAP entry; defers creating new phases for the splits to a follow-up step (the workflow surfaces a list of `/gsd add-phase` invocations the user can run after `mvp-phase` completes — but does not run them automatically, to preserve user control over phase numbering).
|
||||
6. On reject: proceeds with the original story unchanged.
|
||||
|
||||
## Anti-patterns to reject
|
||||
|
||||
- **Splitting by technical layer.** "Phase 1: schema. Phase 2: API. Phase 3: UI." That's horizontal planning. Reject.
|
||||
- **Pre-splitting before the user even sees the original.** Always show the user-supplied story first; only offer split if it triggers a size signal.
|
||||
- **Splitting more than one axis at once.** SPIDR is one axis per split. If a story needs splitting on two axes (e.g., paths AND data), do paths first, then re-evaluate the resulting smaller stories.
|
||||
|
||||
## Reference
|
||||
|
||||
See [Mike Cohn — Five Simple But Powerful Ways to Split User Stories](https://www.mountaingoatsoftware.com/blog/five-simple-but-powerful-ways-to-split-user-stories).
|
||||
58
get-shit-done/references/user-story-template.md
Normal file
58
get-shit-done/references/user-story-template.md
Normal file
@@ -0,0 +1,58 @@
|
||||
# User Story Template (MVP Mode)
|
||||
|
||||
> Used by `mvp-phase` workflow and `gsd-planner` agent when `MVP_MODE=true`. Defines the canonical "As a / I want to / So that" format and the rules for converting it into the `**Goal:**` line in ROADMAP.md.
|
||||
|
||||
## Canonical format
|
||||
|
||||
```
|
||||
As a [user role], I want to [capability], so that [outcome].
|
||||
```
|
||||
|
||||
Three required components:
|
||||
|
||||
| Slot | Question | Examples |
|
||||
|---|---|---|
|
||||
| `[user role]` | Who is the actor? | "new user", "admin", "signed-in customer", "API consumer" |
|
||||
| `[capability]` | What can they do? | "register and log in", "upload a CSV", "see my dashboard" |
|
||||
| `[outcome]` | Why does it matter? | "I can access my account", "I can bulk-import contacts", "I can see at a glance what needs attention" |
|
||||
|
||||
All three must be present. Refuse to assemble a partial story.
|
||||
|
||||
## How it lands in ROADMAP.md
|
||||
|
||||
The full user story replaces the existing `**Goal:**` line in the phase section:
|
||||
|
||||
**Before:**
|
||||
```
|
||||
### Phase 1: User Auth MVP
|
||||
**Goal:** Users can register and log in
|
||||
```
|
||||
|
||||
**After:**
|
||||
```
|
||||
### Phase 1: User Auth MVP
|
||||
**Goal:** As a new user, I want to register and log in, so that I can access my dashboard.
|
||||
**Mode:** mvp
|
||||
```
|
||||
|
||||
Two structural rules:
|
||||
1. The `**Goal:**` line stays on a single line (no line breaks inside the story). If the story is longer than ~120 chars, it should be split into multiple phases via SPIDR (see `spidr-splitting.md`).
|
||||
2. The `**Mode:** mvp` line is added immediately below `**Goal:**`. If `**Mode:**` already exists, it is replaced (not duplicated).
|
||||
|
||||
## How it lands in PLAN.md
|
||||
|
||||
The `gsd-planner` agent (with MVP_MODE=true) emits the user story as the first content under the phase header in `PLAN.md`:
|
||||
|
||||
```markdown
|
||||
## Phase Goal
|
||||
|
||||
**As a** new user, **I want to** register and log in, **so that** I can access my dashboard.
|
||||
|
||||
## Acceptance Criteria
|
||||
- [ ] ...
|
||||
|
||||
## MVP Slice Tasks
|
||||
...
|
||||
```
|
||||
|
||||
Note the bold-keyword formatting (`**As a**`, `**I want to**`, `**so that**`) is for the PLAN.md emit only. The ROADMAP.md `**Goal:**` line uses prose form (the keywords are not bolded inside the goal line, since the goal is itself a single bolded label).
|
||||
85
get-shit-done/references/verify-mvp-mode.md
Normal file
85
get-shit-done/references/verify-mvp-mode.md
Normal file
@@ -0,0 +1,85 @@
|
||||
# Verify-Work — MVP Mode UAT Framing
|
||||
|
||||
> Loaded by `verify-work` workflow and `gsd-verifier` agent only when the phase under verification has `mode: mvp` in ROADMAP.md. Reframes UAT generation from technical checks to user-flow walk-throughs.
|
||||
|
||||
## Core rule
|
||||
|
||||
**Show expected, ask if reality matches** — same philosophy as standard verify-work (from `workflows/verify-work.md`). The MVP-mode change is WHAT gets shown:
|
||||
|
||||
- **Standard verify-work:** "The API endpoint at /users/register returns 201 with the new user's ID." → user confirms.
|
||||
- **MVP verify-work:** "Open the registration page. Fill in 'name', 'email', 'password'. Click Submit. You should see your dashboard with your name in the header." → user confirms.
|
||||
|
||||
The user-flow form mirrors what a real user does: open, fill, click, see. No HTTP verbs, no JSON shapes, no error codes.
|
||||
|
||||
## When this framing applies
|
||||
|
||||
The framing fires when:
|
||||
- The phase under verification has `**Mode:** mvp` in ROADMAP.md (parsed via `gsd-sdk query roadmap.get-phase --pick mode`).
|
||||
- AND the phase has a user-story-formatted goal (set by `/gsd mvp-phase` per Phase 2): "As a [user role], I want to [capability], so that [outcome]."
|
||||
|
||||
If the phase has `mode: mvp` but the goal is NOT in user-story format, the verifier surfaces this as a discrepancy and asks the user to run `/gsd mvp-phase` to reformat the goal — same pattern as the planner agent under MVP_MODE (per `references/planner-mvp-mode.md`).
|
||||
|
||||
## Generated UAT script structure under MVP mode
|
||||
|
||||
The UAT script generated by `verify-work` under MVP mode has THREE sections, in this exact order:
|
||||
|
||||
### 1. User-flow walk-through (always first, always required)
|
||||
|
||||
Derive ordered steps from the phase's user-story goal:
|
||||
|
||||
1. The first step opens the entry point ("Open the app", "Navigate to /register", "Run `gsd mvp-phase 1`").
|
||||
2. Each subsequent step is one user action: fill, click, type, observe.
|
||||
3. The final step asserts the user-visible outcome from the `[outcome]` clause of the user story.
|
||||
|
||||
Format each step as: "**Step N: [action]** — Expected: [what the user should see]". The user responds with one of:
|
||||
- `yes` / `y` / `next` / empty → step passes
|
||||
- Anything else → step is logged as an issue, and the script halts (do not proceed to step N+1 with a broken N).
|
||||
|
||||
If ALL user-flow steps pass, advance to section 2. If any step fails, the verdict is FAIL — do not run technical checks.
|
||||
|
||||
### 2. Technical checks (only if section 1 passes)
|
||||
|
||||
After the user flow passes, run the technical checks that would normally run in non-MVP mode:
|
||||
- API endpoint schema verification (if the phase shipped APIs)
|
||||
- Error state behavior (4xx, 5xx codes; invalid input handling)
|
||||
- Edge cases (empty data, large data, concurrent requests if applicable)
|
||||
- Cross-browser / cross-runtime checks (if applicable)
|
||||
|
||||
These are the same checks `verify-work` would run without MVP mode — just deferred until the user flow proves the slice actually works for a user.
|
||||
|
||||
### 3. Coverage check (always last, always required)
|
||||
|
||||
Verify that the user-story `[outcome]` clause is observably true in the codebase:
|
||||
- If the outcome is "I can access my dashboard", verify a dashboard route exists and renders for an authenticated user.
|
||||
- If the outcome is "I can bulk-import contacts", verify the import path produces persisted records.
|
||||
|
||||
Coverage is a goal-backward check: "did this phase deliver what its user story promised?" — sourced from the existing `gsd-verifier` agent's goal-backward methodology, narrowed to the user story.
|
||||
|
||||
## Anti-patterns to reject under MVP mode
|
||||
|
||||
- **Lead with technical checks.** "Step 1: GET /api/users/me returns 200." Reject. The user does not see API endpoints. Reorder so a user action comes first.
|
||||
- **Schema-as-feature.** "User has a `name` field on the User model." Reject. The user does not see database fields. Express the same check as a user-visible outcome ("the user's name appears in the dashboard header").
|
||||
- **Skip user flow because the test passed.** The unit test passing in CI is not evidence that the user flow works. The user-flow walk-through is mandatory under MVP mode even when all unit tests are green.
|
||||
|
||||
## Compatibility with existing verify-work philosophy
|
||||
|
||||
The "show expected, ask if reality matches" model is preserved. The user still types `yes` / `next` / empty to advance. The UAT.md state file format is unchanged. Only the WHAT changes — under MVP mode, the "expected" is a user-visible outcome rather than a technical assertion.
|
||||
|
||||
## Output: VERIFICATION.md changes under MVP mode
|
||||
|
||||
The `gsd-verifier` agent produces `VERIFICATION.md`. Under MVP mode, the report adds a top-level "User Flow Coverage" section that maps each step of the user story to evidence in the codebase:
|
||||
|
||||
```markdown
|
||||
## User Flow Coverage
|
||||
|
||||
User story: «As a new user, I want to register and log in, so that I can access my dashboard.»
|
||||
|
||||
| Step | Expected | Evidence | Status |
|
||||
|------|----------|----------|--------|
|
||||
| Register | Form at /register accepts name/email/password | src/app/register/page.tsx:12 (form component) | ✓ |
|
||||
| Submit | Persists user, redirects to /dashboard | src/api/register/route.ts:34 (db.insert + redirect) | ✓ |
|
||||
| See dashboard | Dashboard page renders, shows user's name | src/app/dashboard/page.tsx:8 (greeting line) | ✓ |
|
||||
| Outcome | "Access my dashboard" — user lands on a populated page | dashboard route + greeting both verified above | ✓ |
|
||||
```
|
||||
|
||||
Standard technical-check sections of VERIFICATION.md remain (API verification, error handling, etc.) but are appended below "User Flow Coverage", not above.
|
||||
@@ -138,6 +138,23 @@ if [[ ! "$ARGUMENTS" =~ --auto ]]; then
|
||||
gsd-sdk query config-set workflow._auto_chain_active false || true
|
||||
fi
|
||||
```
|
||||
|
||||
Resolve MVP_MODE (CLI flag → roadmap phase mode → config → false):
|
||||
```bash
|
||||
MVP_MODE_CFG=$(gsd-sdk query config-get workflow.mvp_mode 2>/dev/null || echo "false")
|
||||
PHASE_MODE=$(gsd-sdk query roadmap.get-phase "${PHASE_NUMBER}" --pick mode 2>/dev/null || echo "")
|
||||
MVP_MODE=false
|
||||
if [[ "$ARGUMENTS" =~ --mvp ]] || [ "$PHASE_MODE" = "mvp" ] || [ "$MVP_MODE_CFG" = "true" ]; then MVP_MODE=true; fi
|
||||
```
|
||||
|
||||
**MVP+TDD gate.** When `MVP_MODE=true` AND `TDD_MODE=true` AND `TASK_TDD=true`, require a failing-test commit before the implementation step. Doc-only / config-only tasks are exempt. See `execute-mvp-tdd.md` for full halt report format and "behavior-adding task" definition.
|
||||
```bash
|
||||
if [ "$MVP_MODE" = "true" ] && [ "$TDD_MODE" = "true" ] && [ "$TASK_TDD" = "true" ]; then
|
||||
RED_COMMIT=$(git log --oneline --grep="^test(${PHASE_NUMBER}-${PLAN_ID}):" -- "**/*.test.*" "**/*.spec.*" "tests/" | head -1)
|
||||
if [ -z "$RED_COMMIT" ]; then echo "MVP+TDD GATE TRIPPED: missing RED commit for ${PLAN_ID}/${TASK_ID}"; exit 1; fi
|
||||
fi
|
||||
```
|
||||
On trip, updates `STATE.md` with `last_gate_trip: {plan_id}/{task_id}`.
|
||||
</step>
|
||||
|
||||
<step name="check_blocking_antipatterns" priority="first">
|
||||
@@ -1053,6 +1070,14 @@ TDD_PLANS=$(grep -rl "^type: tdd" "${PHASE_DIR}"/*-PLAN.md 2>/dev/null | wc -l |
|
||||
| {id} | ✓ | ✗ | — | FAIL |
|
||||
```
|
||||
|
||||
**Escalation under MVP+TDD.** When `MVP_MODE=true` AND `TDD_MODE=true`, the review verdict escalates from advisory to **blocking**: missing RED or GREEN gate commits prevent marking the phase complete.
|
||||
```
|
||||
Phase blocked: {N} TDD plan(s) violate the RED→GREEN gate sequence under MVP+TDD.
|
||||
Resolve and re-run /gsd execute-phase, or override with
|
||||
/gsd execute-phase {phase} --force-mvp-gate to ship anyway.
|
||||
```
|
||||
`--force-mvp-gate` is the escape hatch (documented, not yet implemented). Without both modes active, the existing advisory behavior is preserved.
|
||||
|
||||
**Gate violations are advisory** — they do not block execution but are surfaced to the user for review. The verifier agent (step `verify_phase_goal`) will also check TDD discipline as part of its quality assessment.
|
||||
</step>
|
||||
|
||||
|
||||
195
get-shit-done/workflows/mvp-phase.md
Normal file
195
get-shit-done/workflows/mvp-phase.md
Normal file
@@ -0,0 +1,195 @@
|
||||
<purpose>
|
||||
Guide the user through MVP-mode planning for a phase. Prompts for an "As a / I want to / So that" user story, runs SPIDR splitting check on the story, writes the result to ROADMAP.md, and delegates to `/gsd plan-phase` (which auto-detects MVP via the roadmap mode field shipped in PRD Phase 1).
|
||||
</purpose>
|
||||
|
||||
<required_reading>
|
||||
@~/.claude/get-shit-done/references/user-story-template.md
|
||||
@~/.claude/get-shit-done/references/spidr-splitting.md
|
||||
@~/.claude/get-shit-done/references/planner-mvp-mode.md
|
||||
</required_reading>
|
||||
|
||||
<runtime_note>
|
||||
**Copilot (VS Code):** Use `vscode_askquestions` wherever this workflow calls `AskUserQuestion`. They are equivalent.
|
||||
|
||||
**TEXT_MODE fallback:** 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.
|
||||
</runtime_note>
|
||||
|
||||
<process>
|
||||
|
||||
## 1. Parse and validate phase argument
|
||||
|
||||
Extract the phase number from `$ARGUMENTS` (integer or decimal like `2.1`). Optional flag: `--force` (allow operating on `in_progress` / `completed` phases).
|
||||
|
||||
If no argument:
|
||||
```
|
||||
ERROR: Phase number required
|
||||
Usage: /gsd mvp-phase <phase-number>
|
||||
Example: /gsd mvp-phase 1
|
||||
Example: /gsd mvp-phase 2.1
|
||||
```
|
||||
Exit.
|
||||
|
||||
Normalize per `@~/.claude/get-shit-done/references/phase-argument-parsing.md` (zero-pad integer phases to two digits).
|
||||
|
||||
## 2. Validate phase exists and check status
|
||||
|
||||
```bash
|
||||
PHASE_INFO=$(gsd-sdk query roadmap.get-phase "${PHASE}")
|
||||
PHASE_FOUND=$(echo "$PHASE_INFO" | jq -r '.found')
|
||||
PHASE_NAME=$(echo "$PHASE_INFO" | jq -r '.phase_name')
|
||||
PHASE_GOAL=$(echo "$PHASE_INFO" | jq -r '.goal')
|
||||
PHASE_MODE=$(echo "$PHASE_INFO" | jq -r '.mode // ""')
|
||||
PHASE_COMPLETE=$(echo "$PHASE_INFO" | jq -r '.roadmap_complete // false')
|
||||
```
|
||||
|
||||
If `PHASE_FOUND` is `false`: error and exit. Suggest `/gsd add-phase` or `/gsd insert-phase` to create the phase first.
|
||||
|
||||
**Status guard.** If the phase is `in_progress` (has plans but not complete) or `completed`, refuse unless `--force` is in `$ARGUMENTS`:
|
||||
|
||||
```
|
||||
ERROR: Phase ${PHASE} is currently ${STATUS}.
|
||||
Converting an active or completed phase to MVP mode mid-flight will
|
||||
invalidate any existing plans and summaries.
|
||||
|
||||
To proceed anyway: /gsd mvp-phase ${PHASE} --force
|
||||
```
|
||||
|
||||
**Already-MVP guard.** If `PHASE_MODE` is already `mvp`, surface this and ask whether to re-prompt the user story or abort:
|
||||
|
||||
> "Phase ${PHASE} is already in MVP mode with goal: «${PHASE_GOAL}». Re-run user-story prompts and SPIDR check?"
|
||||
|
||||
Use `AskUserQuestion` with options [Re-prompt / Abort]. On Abort, exit cleanly. On Re-prompt, proceed.
|
||||
|
||||
## 3. User story prompts
|
||||
|
||||
Run three sequential `AskUserQuestion` calls. Each is free-text. After all three, assemble into the canonical sentence per `@~/.claude/get-shit-done/references/user-story-template.md`:
|
||||
|
||||
**Prompt 1 — As a:**
|
||||
> "As a [user role]?"
|
||||
> (Examples: "new user", "admin", "signed-in customer", "API consumer")
|
||||
|
||||
**Prompt 2 — I want to:**
|
||||
> "I want to [capability]?"
|
||||
> (Examples: "register and log in", "upload a CSV", "see my dashboard")
|
||||
|
||||
**Prompt 3 — So that:**
|
||||
> "So that [outcome]?"
|
||||
> (Examples: "I can access my account", "I can bulk-import contacts", "I can see at a glance what needs attention")
|
||||
|
||||
Assemble:
|
||||
|
||||
```
|
||||
USER_STORY="As a ${ROLE}, I want to ${CAPABILITY}, so that ${OUTCOME}."
|
||||
```
|
||||
|
||||
If any of the three answers is empty or whitespace-only, error and re-prompt that single field. Do NOT proceed with a partial story.
|
||||
|
||||
## 4. SPIDR splitting check
|
||||
|
||||
Run the SPIDR rules from `@~/.claude/get-shit-done/references/spidr-splitting.md`. Briefly:
|
||||
|
||||
**Trigger evaluation.** Check the assembled `USER_STORY` against the four size signals from the reference (compound capabilities, multi-actor, length > 120 chars, vague capability). If none fire, **skip SPIDR** entirely — go to step 5.
|
||||
|
||||
**If SPIDR triggers.**
|
||||
|
||||
a) Restate the story to the user:
|
||||
|
||||
> "Your story: «${USER_STORY}»
|
||||
>
|
||||
> This story has [signal description, e.g., 'two compound capabilities joined by and']. Splitting it into multiple phases will produce a cleaner Walking Skeleton and reduce the risk of mid-phase scope creep.
|
||||
>
|
||||
> Want to walk through SPIDR splitting?"
|
||||
|
||||
Use `AskUserQuestion` with options [Yes, walk through SPIDR / No, proceed with the story as-is].
|
||||
|
||||
If "No": skip SPIDR, go to step 5.
|
||||
|
||||
If "Yes": continue to (b).
|
||||
|
||||
b) Ask which SPIDR axis fits best:
|
||||
|
||||
> "Which axis best fits how to split this story?"
|
||||
|
||||
Use `AskUserQuestion` with the five options from `spidr-splitting.md` (Spike / Paths / Interfaces / Data / Rules). Each option includes its targeted question as the description so the user can pick by understanding what each axis means.
|
||||
|
||||
c) Walk through the chosen axis with **one** targeted question (not all five). For example, if the user picked "Paths":
|
||||
|
||||
> "Does this feature have a happy path and one or more error/edge paths?"
|
||||
|
||||
Free-text response. Workflow parses to identify the split.
|
||||
|
||||
d) Produce a split proposal. Example:
|
||||
|
||||
> "Proposed split (Paths axis):
|
||||
> - **Phase ${PHASE} (this one):** Happy path — ${HAPPY_STORY}
|
||||
> - **Phase ${PHASE+1} (new):** Edge case — ${EDGE_STORY}
|
||||
>
|
||||
> Accept this split?"
|
||||
|
||||
Use `AskUserQuestion` [Accept / Modify / Reject].
|
||||
|
||||
- **Accept**: `USER_STORY` becomes the first split's story (`${HAPPY_STORY}` in the example). Surface the remaining splits as a list of `/gsd add-phase` invocations the user can run after this command completes — do NOT auto-create the new phases (preserve user control over numbering).
|
||||
- **Modify**: re-prompt the splits one more time, then accept or reject.
|
||||
- **Reject**: revert `USER_STORY` to the original, proceed without splitting.
|
||||
|
||||
## 5. Update ROADMAP.md
|
||||
|
||||
Read `ROADMAP.md`. Find the section for `Phase ${PHASE}`. Apply two edits:
|
||||
|
||||
**Edit 1 — Update Goal line.**
|
||||
|
||||
Find: `**Goal:** ${OLD_GOAL_TEXT}`
|
||||
Replace with: `**Goal:** ${USER_STORY}`
|
||||
|
||||
**Edit 2 — Insert Mode line.**
|
||||
|
||||
If `**Mode:**` already exists in the section (replacing or re-running), update it to `**Mode:** mvp`.
|
||||
If `**Mode:**` does not exist, insert `**Mode:** mvp` on the line immediately after `**Goal:**`.
|
||||
|
||||
Show the user a unified diff (lines being changed) and ask:
|
||||
|
||||
> "Apply these changes to ROADMAP.md?"
|
||||
|
||||
Use `AskUserQuestion` [Apply / Cancel]. On Cancel, exit without writing.
|
||||
|
||||
On Apply, write the updated `ROADMAP.md` atomically (read-edit-write).
|
||||
|
||||
## 6. Verify the write
|
||||
|
||||
```bash
|
||||
NEW_MODE=$(gsd-sdk query roadmap.get-phase "${PHASE}" --pick mode)
|
||||
NEW_GOAL=$(gsd-sdk query roadmap.get-phase "${PHASE}" --pick goal)
|
||||
```
|
||||
|
||||
Assert:
|
||||
- `NEW_MODE` equals `mvp`
|
||||
- `NEW_GOAL` equals the assembled user story
|
||||
|
||||
If either assertion fails, surface the discrepancy to the user and exit. Do not proceed to plan-phase delegation with a half-applied write.
|
||||
|
||||
## 7. Delegate to /gsd plan-phase
|
||||
|
||||
Invoke `/gsd plan-phase ${PHASE}` (no flags). Phase 1's MVP_MODE resolution chain (CLI flag → roadmap mode → config → false) will detect the new `**Mode:** mvp` line and run plan-phase in vertical-slice mode automatically.
|
||||
|
||||
The Walking Skeleton gate (also from Phase 1) will fire automatically if `${PHASE} == "01"` and there are zero prior phase summaries.
|
||||
|
||||
## 8. Surface deferred phase splits (if any)
|
||||
|
||||
If SPIDR produced a split in step 4, append a final user-facing message:
|
||||
|
||||
> "**SPIDR split deferred phases.**
|
||||
>
|
||||
> Your original story was split. The first slice is now planned via plan-phase.
|
||||
> To create the remaining slice(s) as new phases, run:
|
||||
>
|
||||
> - `/gsd add-phase` — for the next slice: «${SPLIT_2_STORY}»
|
||||
> - `/gsd add-phase` — for the next slice: «${SPLIT_3_STORY}»
|
||||
>
|
||||
> Each will be added to the end of the current milestone. You can then run
|
||||
> `/gsd mvp-phase <new-phase-number>` on each to plan them as MVP slices."
|
||||
|
||||
## 9. Exit
|
||||
|
||||
Workflow ends. The phase is now in MVP mode with a planned PLAN.md, optionally with deferred follow-up phases surfaced for the user.
|
||||
|
||||
</process>
|
||||
@@ -1117,6 +1117,21 @@ If "adjust": Return to scoping.
|
||||
gsd-sdk query commit "docs: define v1 requirements" --files .planning/REQUIREMENTS.md
|
||||
```
|
||||
|
||||
## 7.5. Project Structure Mode
|
||||
|
||||
**If auto mode:** Set `PROJECT_MODE=mvp` and skip this prompt.
|
||||
|
||||
**Mode prompt: Vertical MVP vs Horizontal Layers.**
|
||||
|
||||
Ask the user how they want to structure the project. Use `AskUserQuestion` with two options:
|
||||
|
||||
- **Vertical MVP** — get a working app fast, add features slice by slice. Each phase delivers an end-to-end user capability. *(Recommended for new products and rapid-iteration MVPs.)*
|
||||
- **Horizontal Layers** — build complete technical layers (DB → API → UI → wiring) and assemble at the end. *(Better for infrastructure-heavy projects with multiple developers.)*
|
||||
|
||||
Set `PROJECT_MODE=mvp` if the user picks Vertical MVP, otherwise `PROJECT_MODE=standard`.
|
||||
|
||||
When `TEXT_MODE=true` (per the workflow's existing TEXT_MODE handling for non-Claude runtimes), present the same two options as a plain-text numbered list and ask the user to type their choice number.
|
||||
|
||||
## 8. Create Roadmap
|
||||
|
||||
Display stage banner:
|
||||
@@ -1129,6 +1144,23 @@ Display stage banner:
|
||||
◆ Spawning roadmapper...
|
||||
```
|
||||
|
||||
**ROADMAP.md template — mode-aware emit.** When generating the initial ROADMAP.md:
|
||||
|
||||
- If `PROJECT_MODE=mvp`: under each `### Phase N:` header, emit `**Mode:** mvp` on the line immediately following `**Goal:**`. This sets every initial phase to MVP mode (per Phase-4-Persistence decision: per-phase mode, not project-wide config).
|
||||
- If `PROJECT_MODE=standard`: emit the standard ROADMAP.md template with no `**Mode:**` lines (Horizontal Layers standard template — no behavioral change for users who pick Horizontal Layers).
|
||||
|
||||
Example MVP-mode emit for Phase 1:
|
||||
|
||||
```markdown
|
||||
### Phase 1: [Name]
|
||||
**Goal:** [Goal]
|
||||
**Mode:** mvp
|
||||
**Success Criteria**:
|
||||
1. [Criterion]
|
||||
```
|
||||
|
||||
Pass `PROJECT_MODE` to the roadmapper so it applies the correct template.
|
||||
|
||||
Spawn gsd-roadmapper agent with path references:
|
||||
|
||||
```
|
||||
|
||||
@@ -38,6 +38,7 @@ AGENT_SKILLS_PLANNER=$(gsd-sdk query agent-skills gsd-planner)
|
||||
AGENT_SKILLS_CHECKER=$(gsd-sdk query agent-skills gsd-plan-checker)
|
||||
CONTEXT_WINDOW=$(gsd-sdk query config-get context_window 2>/dev/null || echo "200000")
|
||||
TDD_MODE=$(gsd-sdk query config-get workflow.tdd_mode 2>/dev/null || echo "false")
|
||||
MVP_MODE_CFG=$(gsd-sdk query config-get workflow.mvp_mode 2>/dev/null || echo "false")
|
||||
```
|
||||
|
||||
When `TDD_MODE` is `true`, the planner agent is instructed to apply `type: tdd` to eligible tasks using heuristics from `references/tdd.md`. The planner's `<required_reading>` is extended to include `@~/.claude/get-shit-done/references/tdd.md` so gate enforcement rules are available during planning.
|
||||
@@ -54,10 +55,37 @@ Parse JSON for: `researcher_model`, `planner_model`, `checker_model`, `research_
|
||||
|
||||
## 2. Parse and Normalize Arguments
|
||||
|
||||
Extract from $ARGUMENTS: phase number (integer or decimal like `2.1`), flags (`--research`, `--skip-research`, `--gaps`, `--skip-verify`, `--skip-ui`, `--prd <filepath>`, `--reviews`, `--text`, `--bounce`, `--skip-bounce`, `--chunked`).
|
||||
Extract from $ARGUMENTS: phase number (integer or decimal like `2.1`), flags (`--research`, `--skip-research`, `--gaps`, `--skip-verify`, `--skip-ui`, `--prd <filepath>`, `--reviews`, `--text`, `--bounce`, `--skip-bounce`, `--chunked`, `--mvp`).
|
||||
|
||||
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 Claude Code remote sessions (`/rc` mode) where TUI menus don't work through the Claude App.
|
||||
|
||||
**MVP_MODE resolution.** Resolve `MVP_MODE` once and reuse for the rest of the workflow. Order (first hit wins):
|
||||
|
||||
1. **CLI flag.** If `$ARGUMENTS` contains `--mvp`, set `MVP_MODE=true`.
|
||||
2. **Roadmap phase mode.** Otherwise, query the phase's mode field:
|
||||
```bash
|
||||
PHASE_MODE=$(gsd-sdk query roadmap.get-phase "${PHASE}" --pick mode)
|
||||
if [ "$PHASE_MODE" = "mvp" ]; then MVP_MODE=true; fi
|
||||
```
|
||||
3. **Config default.** Otherwise, `MVP_MODE="$MVP_MODE_CFG"` (resolved in Step 1).
|
||||
4. **Fallback.** `MVP_MODE=false`.
|
||||
|
||||
The mode is **all-or-nothing per phase** (per PRD decision Q1). Do not allow `--mvp` to apply selectively to a subset of tasks within a phase.
|
||||
|
||||
**Walking Skeleton gate.** When `MVP_MODE=true` AND `phase_number == "01"` AND there are zero prior phase summaries (new project), the planner runs in **Walking Skeleton mode** (per PRD decision Q2 — new projects only). Detect with:
|
||||
|
||||
```bash
|
||||
WALKING_SKELETON=false
|
||||
if [ "$MVP_MODE" = "true" ] && [ "$padded_phase" = "01" ]; then
|
||||
PRIOR_SUMMARIES=$(gsd-sdk query phases.list --pick summaries_total 2>/dev/null || echo "0")
|
||||
if [ "$PRIOR_SUMMARIES" = "0" ]; then WALKING_SKELETON=true; fi
|
||||
fi
|
||||
```
|
||||
|
||||
When `WALKING_SKELETON=true`:
|
||||
- Planner is instructed to produce `SKELETON.md` in the phase directory alongside `PLAN.md`. The template lives at `@~/.claude/get-shit-done/references/skeleton-template.md`.
|
||||
- The plan must scaffold project + routing + one real DB read/write + one real UI interaction + dev deployment — the thinnest possible end-to-end working slice.
|
||||
|
||||
Extract `--prd <filepath>` from $ARGUMENTS. If present, set PRD_FILE to the filepath.
|
||||
|
||||
**If no phase number:** Detect next unplanned phase from roadmap.
|
||||
@@ -756,6 +784,15 @@ ${TDD_MODE === 'true' ? `
|
||||
Each TDD plan gets one feature with RED/GREEN/REFACTOR gate sequence.
|
||||
</tdd_mode_active>
|
||||
` : ''}
|
||||
|
||||
**MVP_MODE:** ${MVP_MODE} (when true, follow vertical-slice rules from `@~/.claude/get-shit-done/references/planner-mvp-mode.md`; when false, ignore MVP guidance entirely.)
|
||||
**WALKING_SKELETON:** ${WALKING_SKELETON} (when true, the first deliverable must be a Walking Skeleton — produce SKELETON.md alongside PLAN.md.)
|
||||
|
||||
${MVP_MODE === 'true' ? `
|
||||
<mvp_mode_active>
|
||||
**MVP Mode is ENABLED.** Follow vertical-slice planning rules from @~/.claude/get-shit-done/references/planner-mvp-mode.md. Each plan must deliver a complete vertical slice — thin end-to-end functionality rather than horizontal layers.
|
||||
</mvp_mode_active>
|
||||
` : ''}
|
||||
</planning_context>
|
||||
|
||||
<downstream_consumer>
|
||||
|
||||
@@ -135,6 +135,35 @@ CONTEXT: [✓ if has_context | - if not]
|
||||
|
||||
</step>
|
||||
|
||||
<step name="mvp_display">
|
||||
**MVP-mode display (when phase has `**Mode:** mvp` in ROADMAP.md).**
|
||||
|
||||
Resolve `MVP_MODE` per phase:
|
||||
|
||||
```bash
|
||||
PHASE_MODE=$(gsd-sdk query roadmap.get-phase "${PHASE_NUMBER}" --pick mode 2>/dev/null || echo "")
|
||||
MVP_MODE=false
|
||||
if [ "$PHASE_MODE" = "mvp" ]; then
|
||||
MVP_MODE=true
|
||||
fi
|
||||
```
|
||||
|
||||
When `MVP_MODE=true`, the per-phase progress block adds a **user-flow status** sub-block sourced from the phase's PLAN.md task names. Each task whose name reads like a user-visible capability (e.g., "Register flow", "Login flow", "Password reset") is rendered as a status line:
|
||||
|
||||
```
|
||||
Phase 1 — User Auth MVP
|
||||
✅ Walking Skeleton complete ← from SKELETON.md existence
|
||||
✅ Register flow working ← from PLAN.md task with summary
|
||||
✅ Login flow working ← from PLAN.md task with summary
|
||||
🔄 Password reset (in progress) ← from PLAN.md task without summary
|
||||
⬜ Email verification ← from PLAN.md task not yet started
|
||||
```
|
||||
|
||||
**User-flow filter:** Tasks whose names are technical-sounding ("Wire DB schema", "Create migration", "Bump deps") are NOT rendered as user-flow status lines. Heuristic: a task name is user-flow-shaped if it ends in "flow", "page", "screen", or starts with a verb the user would recognize ("Register", "Login", "Upload", "View"). Tasks that fail the heuristic still count toward the standard task progress total but don't appear in the user-flow sub-block.
|
||||
|
||||
When `MVP_MODE=false` (mode is null, absent, or the phase has no `**Mode:**` line), fall back to the standard display path — no behavioral change.
|
||||
</step>
|
||||
|
||||
<step name="route">
|
||||
**Determine next action based on verified counts.**
|
||||
|
||||
|
||||
@@ -51,6 +51,24 @@ X/Y plans complete (Z%)
|
||||
If no `.planning/` directory exists, inform the user to run `/gsd-new-project` first.
|
||||
</step>
|
||||
|
||||
<step name="mvp_summary">
|
||||
**MVP phase summary.** Read all phases via `gsd-sdk query roadmap.analyze` (Phase 1's `cmdRoadmapAnalyze` surfaces a `mode` field per phase). Count phases by mode:
|
||||
|
||||
```bash
|
||||
ANALYZE=$(gsd-sdk query roadmap.analyze)
|
||||
MVP_COUNT=$(echo "$ANALYZE" | jq '[.phases[] | select(.mode == "mvp")] | length')
|
||||
TOTAL_COUNT=$(echo "$ANALYZE" | jq '.phases | length')
|
||||
```
|
||||
|
||||
Emit a summary line in the stats output:
|
||||
|
||||
```
|
||||
Phases: ${TOTAL_COUNT} total | ${MVP_COUNT} MVP | $((TOTAL_COUNT - MVP_COUNT)) standard
|
||||
```
|
||||
|
||||
If `MVP_COUNT == 0`, the project has no MVP-mode phases — omit the line (no clutter for non-MVP projects).
|
||||
</step>
|
||||
|
||||
</process>
|
||||
|
||||
<success_criteria>
|
||||
|
||||
@@ -37,6 +37,15 @@ AGENT_SKILLS_CHECKER=$(gsd-sdk query agent-skills gsd-plan-checker)
|
||||
```
|
||||
|
||||
Parse JSON for: `planner_model`, `checker_model`, `commit_docs`, `phase_found`, `phase_dir`, `phase_number`, `phase_name`, `has_verification`, `uat_path`.
|
||||
|
||||
```bash
|
||||
# MVP mode detection — read the phase's mode field from ROADMAP.md
|
||||
PHASE_MODE=$(gsd-sdk query roadmap.get-phase "${phase_number}" --pick mode 2>/dev/null || echo "")
|
||||
MVP_MODE=false
|
||||
if [ "$PHASE_MODE" = "mvp" ]; then
|
||||
MVP_MODE=true
|
||||
fi
|
||||
```
|
||||
</step>
|
||||
|
||||
<step name="check_active_session">
|
||||
@@ -135,6 +144,21 @@ Read each SUMMARY.md to extract testable deliverables.
|
||||
</step>
|
||||
|
||||
<step name="extract_tests">
|
||||
**MVP-mode UAT framing.** When `MVP_MODE=true`, follow the rules in `@~/.claude/get-shit-done/references/verify-mvp-mode.md`. Briefly:
|
||||
|
||||
1. Generate the UAT script in three ordered sections: (a) user-flow walk-through derived from the phase's user-story goal, (b) technical checks (deferred — only run after user flow passes), (c) coverage check (goal-backward, narrowed to the user story's outcome clause).
|
||||
2. **User-flow steps run first.** Each step is one user action: open, fill, click, type, observe. No HTTP verbs, no JSON shapes, no error codes in user-flow steps.
|
||||
3. **Technical checks are deferred.** They run AFTER the user flow passes — same checks as non-MVP mode (endpoint schemas, error states, edge cases), just reordered.
|
||||
4. **If user-flow step N fails, do not advance.** The verdict is FAIL; technical checks do not run. The user can re-run after fixing the underlying flow.
|
||||
|
||||
When `MVP_MODE=false` (mode is null, absent, or the phase has no `**Mode:**` line in ROADMAP.md), fall back to the standard UAT generation path — no behavioral change.
|
||||
|
||||
**User-story format guard.** When `MVP_MODE=true`, also verify the phase's goal is in user-story format (matches `/^As a .+, I want to .+, so that .+\.$/`). If the goal is `mode: mvp` but NOT in user-story format, surface the discrepancy:
|
||||
|
||||
> "Phase ${PHASE} has `**Mode:** mvp` in ROADMAP.md but the **Goal:** is not in user-story format. Run `/gsd mvp-phase ${PHASE}` to set a user-story goal before verifying."
|
||||
|
||||
Halt UAT generation and exit cleanly. Do not attempt to derive user-flow steps from a non-user-story goal — that would produce a low-quality UAT.
|
||||
|
||||
**Extract testable deliverables from SUMMARY.md:**
|
||||
|
||||
Parse for:
|
||||
|
||||
7
package-lock.json
generated
7
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "get-shit-done-cc",
|
||||
"version": "1.39.0-rc.4",
|
||||
"version": "1.50.0-canary.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "get-shit-done-cc",
|
||||
"version": "1.39.0-rc.4",
|
||||
"version": "1.50.0-canary.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@anthropic-ai/claude-agent-sdk": "^0.2.84",
|
||||
@@ -14,7 +14,8 @@
|
||||
},
|
||||
"bin": {
|
||||
"get-shit-done-cc": "bin/install.js",
|
||||
"gsd-sdk": "bin/gsd-sdk.js"
|
||||
"gsd-sdk": "bin/gsd-sdk.js",
|
||||
"gsd-tools": "bin/gsd-sdk.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"c8": "^11.0.0"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "get-shit-done-cc",
|
||||
"version": "1.39.0-rc.4",
|
||||
"version": "1.50.0-canary.0",
|
||||
"description": "A meta-prompting, context engineering and spec-driven development system for Claude Code, OpenCode, Gemini and Codex by TÂCHES.",
|
||||
"bin": {
|
||||
"get-shit-done-cc": "bin/install.js",
|
||||
|
||||
4
sdk/package-lock.json
generated
4
sdk/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@gsd-build/sdk",
|
||||
"version": "0.1.0",
|
||||
"version": "1.50.0-canary.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@gsd-build/sdk",
|
||||
"version": "0.1.0",
|
||||
"version": "1.50.0-canary.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@anthropic-ai/claude-agent-sdk": "^0.2.84",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@gsd-build/sdk",
|
||||
"version": "1.39.0-rc.4",
|
||||
"version": "1.50.0-canary.0",
|
||||
"description": "GSD SDK — programmatic interface for running GSD plans via the Agent SDK",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
|
||||
85
tests/execute-mvp-tdd-gate.test.cjs
Normal file
85
tests/execute-mvp-tdd-gate.test.cjs
Normal file
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* execute-phase MVP+TDD gate — contract test
|
||||
* Verifies the workflow markdown documents the gate's resolution chain,
|
||||
* per-task firing condition, and end-of-phase review escalation.
|
||||
*/
|
||||
const { test, describe, beforeEach, afterEach } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const { runGsdTools, createTempProject, cleanup } = require('./helpers.cjs');
|
||||
|
||||
const WORKFLOW = path.join(__dirname, '..', 'get-shit-done', 'workflows', 'execute-phase.md');
|
||||
|
||||
describe('execute-phase — MVP+TDD gate', () => {
|
||||
const content = fs.readFileSync(WORKFLOW, 'utf-8');
|
||||
|
||||
test('Step 1 resolves MVP_MODE from roadmap mode field', () => {
|
||||
assert.match(content, /MVP_MODE/, 'workflow must declare MVP_MODE');
|
||||
assert.match(
|
||||
content,
|
||||
/roadmap[^\n]*mode|phase[^\n]*\.mode|\.mode\s*=/i,
|
||||
'must consult phase mode from roadmap'
|
||||
);
|
||||
});
|
||||
|
||||
test('gate fires when both MVP_MODE and TDD_MODE are true', () => {
|
||||
assert.match(
|
||||
content,
|
||||
/MVP_MODE[^\n]*TDD_MODE|TDD_MODE[^\n]*MVP_MODE/,
|
||||
'workflow must combine MVP_MODE and TDD_MODE for the gate'
|
||||
);
|
||||
});
|
||||
|
||||
test('per-task gate is documented before behavior-adding task execution', () => {
|
||||
assert.match(content, /MVP\+TDD\s*gate|mvp[\s-]?tdd[\s-]?gate/i, 'must label the gate');
|
||||
assert.match(content, /failing[\s-]?test\s*commit|test\(.+\):.*RED/i, 'must reference failing-test commit check');
|
||||
});
|
||||
|
||||
test('end-of-phase TDD review escalates to blocking under MVP+TDD', () => {
|
||||
assert.match(
|
||||
content,
|
||||
/blocking[^\n]*MVP|MVP[^\n]*blocking|escalat\w+\s*to\s*blocking/i,
|
||||
'must escalate end-of-phase review to blocking'
|
||||
);
|
||||
});
|
||||
|
||||
test('workflow references execute-mvp-tdd.md', () => {
|
||||
assert.match(content, /execute-mvp-tdd\.md/, 'must reference the gate semantics file');
|
||||
});
|
||||
});
|
||||
|
||||
describe('execute-phase MVP+TDD — resolution chain integration', () => {
|
||||
let tmpDir;
|
||||
beforeEach(() => { tmpDir = createTempProject(); });
|
||||
afterEach(() => { cleanup(tmpDir); });
|
||||
|
||||
test('roadmap.get-phase --pick mode returns mvp when **Mode:** mvp set', () => {
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, '.planning', 'ROADMAP.md'),
|
||||
`# Roadmap\n\n## v1.0.0\n\n### Phase 1: User Auth\n**Goal:** As a user, I want to log in, so that I can access.\n**Mode:** mvp\n`
|
||||
);
|
||||
const result = runGsdTools('roadmap get-phase 1 --pick mode', tmpDir);
|
||||
assert.ok(result.success);
|
||||
assert.strictEqual(result.output.trim(), 'mvp');
|
||||
});
|
||||
|
||||
test('roadmap.get-phase --pick mode returns null/empty when no Mode line', () => {
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, '.planning', 'ROADMAP.md'),
|
||||
`# Roadmap\n\n## v1.0.0\n\n### Phase 1: User Auth\n**Goal:** Users can log in.\n`
|
||||
);
|
||||
const result = runGsdTools('roadmap get-phase 1 --pick mode', tmpDir);
|
||||
if (result.success) {
|
||||
assert.ok(result.output.trim() === '' || result.output.trim() === 'null');
|
||||
}
|
||||
});
|
||||
|
||||
test('config-get workflow.mvp_mode default is unset in fresh project', () => {
|
||||
const result = runGsdTools('config-get workflow.mvp_mode', tmpDir);
|
||||
if (result.success) {
|
||||
assert.notStrictEqual(result.output.trim(), 'true');
|
||||
}
|
||||
});
|
||||
});
|
||||
33
tests/executor-mvp-tdd-section.test.cjs
Normal file
33
tests/executor-mvp-tdd-section.test.cjs
Normal file
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* gsd-executor agent — MVP+TDD gate section contract
|
||||
* Verifies the agent definition contains a section instructing the executor
|
||||
* to halt and report when the runtime gate trips.
|
||||
*/
|
||||
const { test, describe } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const AGENT = path.join(__dirname, '..', 'agents', 'gsd-executor.md');
|
||||
const REF = path.join(__dirname, '..', 'get-shit-done', 'references', 'execute-mvp-tdd.md');
|
||||
|
||||
describe('gsd-executor — MVP+TDD gate section', () => {
|
||||
const content = fs.readFileSync(AGENT, 'utf-8');
|
||||
|
||||
test('agent defines an MVP+TDD Gate section', () => {
|
||||
assert.match(content, /MVP\+TDD\s*Gate|MVP[\s-]?TDD[\s-]?gate/i, 'must label the gate');
|
||||
});
|
||||
|
||||
test('agent instructs halt-and-report when gate trips', () => {
|
||||
assert.match(content, /halt|stop[^\n]*gate|gate[^\n]*halt/i, 'must instruct halt');
|
||||
assert.match(content, /report|surface|emit/i, 'must instruct report');
|
||||
});
|
||||
|
||||
test('agent references execute-mvp-tdd.md', () => {
|
||||
assert.match(content, /execute-mvp-tdd\.md/, 'must reference the gate semantics file');
|
||||
});
|
||||
|
||||
test('referenced file exists on disk', () => {
|
||||
assert.ok(fs.existsSync(REF), `${REF} must exist`);
|
||||
});
|
||||
});
|
||||
39
tests/graphify-mvp-viz.test.cjs
Normal file
39
tests/graphify-mvp-viz.test.cjs
Normal file
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* graphify — MVP visual differentiation contract test
|
||||
* Per PRD Q5: distinct node color + 'MVP' label suffix.
|
||||
*/
|
||||
const { test, describe } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const CMD = path.join(__dirname, '..', 'commands', 'gsd', 'graphify.md');
|
||||
|
||||
describe('graphify — MVP visualization', () => {
|
||||
const content = fs.readFileSync(CMD, 'utf-8');
|
||||
|
||||
test('command documents distinct color for MVP-mode phases', () => {
|
||||
assert.match(content, /MVP/, 'must mention MVP in color rule');
|
||||
assert.match(
|
||||
content,
|
||||
/color|fill|hex|#[0-9a-f]{3,6}/i,
|
||||
'must reference a color/fill rule for MVP nodes'
|
||||
);
|
||||
});
|
||||
|
||||
test('command documents MVP label suffix on node text', () => {
|
||||
assert.match(
|
||||
content,
|
||||
/MVP[^\n]*label|label[^\n]*MVP|MVP\s*suffix|suffix[^\n]*MVP/i,
|
||||
'must add an MVP label/suffix to node text'
|
||||
);
|
||||
});
|
||||
|
||||
test('falls back to standard rendering when phase mode is null', () => {
|
||||
assert.match(
|
||||
content,
|
||||
/mode[^\n]*null|absent|not.*mvp|standard.*render/i,
|
||||
'must specify fallback when mode is not mvp'
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -22,6 +22,7 @@ const PROSE_ALLOWLIST = new Set([
|
||||
'intel',
|
||||
'into',
|
||||
'or',
|
||||
'init', // bare "init" appears in prose examples; real commands are init.<subcommand>
|
||||
'init.',
|
||||
]);
|
||||
|
||||
|
||||
39
tests/mvp-phase-command.test.cjs
Normal file
39
tests/mvp-phase-command.test.cjs
Normal file
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* /gsd mvp-phase command — frontmatter contract test
|
||||
* Verifies the command exists, has required frontmatter fields, and
|
||||
* points to the workflow file.
|
||||
*/
|
||||
const { test, describe } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const CMD = path.join(__dirname, '..', 'commands', 'gsd', 'mvp-phase.md');
|
||||
|
||||
describe('/gsd mvp-phase command frontmatter', () => {
|
||||
test('command file exists', () => {
|
||||
assert.ok(fs.existsSync(CMD), `${CMD} must exist`);
|
||||
});
|
||||
|
||||
test('frontmatter declares correct command name', () => {
|
||||
const content = fs.readFileSync(CMD, 'utf-8');
|
||||
assert.match(content, /^name:\s*gsd:mvp-phase\b/m);
|
||||
});
|
||||
|
||||
test('argument-hint mentions phase number', () => {
|
||||
const content = fs.readFileSync(CMD, 'utf-8');
|
||||
assert.match(content, /argument-hint:[^\n]*phase/i);
|
||||
});
|
||||
|
||||
test('allowed-tools includes Read, Write, Bash, Task, AskUserQuestion', () => {
|
||||
const content = fs.readFileSync(CMD, 'utf-8');
|
||||
for (const tool of ['Read', 'Write', 'Bash', 'Task', 'AskUserQuestion']) {
|
||||
assert.match(content, new RegExp(`-\\s*${tool}\\b`), `allowed-tools must include ${tool}`);
|
||||
}
|
||||
});
|
||||
|
||||
test('execution_context points to the workflow file', () => {
|
||||
const content = fs.readFileSync(CMD, 'utf-8');
|
||||
assert.match(content, /workflows\/mvp-phase\.md/);
|
||||
});
|
||||
});
|
||||
81
tests/mvp-phase-integration.test.cjs
Normal file
81
tests/mvp-phase-integration.test.cjs
Normal file
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* mvp-phase ROADMAP mutation — integration smoke test
|
||||
* Simulates the workflow's step 5 (Update ROADMAP.md) and verifies that
|
||||
* roadmap.get-phase returns the expected mode and user-story goal afterward.
|
||||
*/
|
||||
const { test, describe, beforeEach, afterEach } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { runGsdTools, createTempProject, cleanup } = require('./helpers.cjs');
|
||||
|
||||
const ROADMAP_BEFORE = `# Roadmap
|
||||
|
||||
## v1.0.0
|
||||
|
||||
### Phase 1: User Auth
|
||||
**Goal:** Users can register and log in
|
||||
**Success Criteria**:
|
||||
1. Registration works
|
||||
2. Login works
|
||||
`;
|
||||
|
||||
const ROADMAP_AFTER_MVP = `# Roadmap
|
||||
|
||||
## v1.0.0
|
||||
|
||||
### Phase 1: User Auth
|
||||
**Goal:** As a new user, I want to register and log in, so that I can access my dashboard.
|
||||
**Mode:** mvp
|
||||
**Success Criteria**:
|
||||
1. Registration works
|
||||
2. Login works
|
||||
`;
|
||||
|
||||
describe('mvp-phase — ROADMAP mutation result', () => {
|
||||
let tmpDir;
|
||||
beforeEach(() => { tmpDir = createTempProject(); });
|
||||
afterEach(() => { cleanup(tmpDir); });
|
||||
|
||||
test('after spec mutation, roadmap.get-phase reports mode=mvp', () => {
|
||||
fs.writeFileSync(path.join(tmpDir, '.planning', 'ROADMAP.md'), ROADMAP_AFTER_MVP);
|
||||
const result = runGsdTools('roadmap get-phase 1 --pick mode', tmpDir);
|
||||
assert.ok(result.success);
|
||||
assert.strictEqual(result.output.trim(), 'mvp');
|
||||
});
|
||||
|
||||
test('after spec mutation, roadmap.get-phase reports the full user story as goal', () => {
|
||||
fs.writeFileSync(path.join(tmpDir, '.planning', 'ROADMAP.md'), ROADMAP_AFTER_MVP);
|
||||
const result = runGsdTools('roadmap get-phase 1 --pick goal', tmpDir);
|
||||
assert.ok(result.success);
|
||||
assert.strictEqual(
|
||||
result.output.trim(),
|
||||
'As a new user, I want to register and log in, so that I can access my dashboard.'
|
||||
);
|
||||
});
|
||||
|
||||
test('before mutation, mode is null and goal is the original short text', () => {
|
||||
fs.writeFileSync(path.join(tmpDir, '.planning', 'ROADMAP.md'), ROADMAP_BEFORE);
|
||||
const modeResult = runGsdTools('roadmap get-phase 1 --pick mode', tmpDir);
|
||||
const goalResult = runGsdTools('roadmap get-phase 1 --pick goal', tmpDir);
|
||||
assert.ok(modeResult.success && goalResult.success);
|
||||
// mode field absent → empty/null per Phase 1 parser contract
|
||||
assert.ok(modeResult.output.trim() === '' || modeResult.output.trim() === 'null');
|
||||
assert.strictEqual(goalResult.output.trim(), 'Users can register and log in');
|
||||
});
|
||||
|
||||
test('user story longer than 120 chars (SPIDR trigger boundary)', () => {
|
||||
// This story is >120 chars — the workflow should have split it via SPIDR
|
||||
// before writing. This test confirms the parser still handles it correctly
|
||||
// if the user chose "Reject split" and proceeded with the long story.
|
||||
const longStory = 'As a registered customer with an active account and verified email, I want to reset my password and update my profile, so that I can recover access.';
|
||||
const variant = ROADMAP_AFTER_MVP.replace(
|
||||
'As a new user, I want to register and log in, so that I can access my dashboard.',
|
||||
longStory
|
||||
);
|
||||
fs.writeFileSync(path.join(tmpDir, '.planning', 'ROADMAP.md'), variant);
|
||||
const result = runGsdTools('roadmap get-phase 1 --pick goal', tmpDir);
|
||||
assert.ok(result.success);
|
||||
assert.strictEqual(result.output.trim(), longStory);
|
||||
});
|
||||
});
|
||||
57
tests/mvp-phase-spidr.test.cjs
Normal file
57
tests/mvp-phase-spidr.test.cjs
Normal file
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* mvp-phase workflow — contract test
|
||||
* Verifies the workflow markdown contains the four agreed gates:
|
||||
* 1. Phase existence + status guard (refuse in_progress/completed)
|
||||
* 2. User-story prompt (three AskUserQuestion calls, As a / I want to / So that)
|
||||
* 3. SPIDR splitting check
|
||||
* 4. ROADMAP write (Mode + Goal)
|
||||
* 5. Delegation to plan-phase
|
||||
*/
|
||||
const { test, describe } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const WORKFLOW = path.join(__dirname, '..', 'get-shit-done', 'workflows', 'mvp-phase.md');
|
||||
|
||||
describe('mvp-phase workflow', () => {
|
||||
const content = fs.readFileSync(WORKFLOW, 'utf-8');
|
||||
|
||||
test('declares phase status guard (refuse in_progress/completed unless --force)', () => {
|
||||
assert.match(content, /in_progress|completed/i, 'workflow must reference status guard');
|
||||
assert.match(content, /--force|status\s*guard/i, 'workflow must mention force override or status guard');
|
||||
});
|
||||
|
||||
test('runs three structured user-story prompts', () => {
|
||||
assert.match(content, /As a/i);
|
||||
assert.match(content, /I want to/i);
|
||||
assert.match(content, /[Ss]o that/);
|
||||
// The three prompts should each use AskUserQuestion (or vscode_askquestions for Copilot)
|
||||
const askCount = (content.match(/AskUserQuestion|vscode_askquestions/g) || []).length;
|
||||
assert.ok(askCount >= 3, `workflow must invoke AskUserQuestion at least 3 times for the story prompts (got ${askCount})`);
|
||||
});
|
||||
|
||||
test('runs SPIDR splitting check after user story', () => {
|
||||
assert.match(content, /SPIDR|spidr-splitting/i);
|
||||
assert.match(content, /spidr-splitting\.md/, 'workflow must reference the SPIDR rules file');
|
||||
});
|
||||
|
||||
test('writes Mode: mvp + Goal: line to ROADMAP.md', () => {
|
||||
assert.match(content, /\*\*Mode:\*\*\s*mvp/i, 'workflow must specify the **Mode:** mvp line');
|
||||
assert.match(content, /ROADMAP\.md/);
|
||||
assert.match(content, /\*\*Goal:\*\*/, 'workflow must update the **Goal:** line');
|
||||
});
|
||||
|
||||
test('delegates to /gsd plan-phase after ROADMAP write', () => {
|
||||
assert.match(content, /plan-phase/);
|
||||
// Order: SPIDR ... then plan-phase
|
||||
const spidrIdx = content.search(/SPIDR|spidr-splitting/i);
|
||||
const planPhaseIdx = content.search(/\/gsd[\s-]?plan-phase|plan-phase\s+(?:command|workflow|delegation)/i);
|
||||
assert.ok(spidrIdx > 0 && planPhaseIdx > 0, 'both SPIDR and plan-phase delegation must be present');
|
||||
assert.ok(planPhaseIdx > spidrIdx, 'plan-phase delegation must come AFTER SPIDR check');
|
||||
});
|
||||
|
||||
test('references user-story-template.md', () => {
|
||||
assert.match(content, /user-story-template\.md/);
|
||||
});
|
||||
});
|
||||
39
tests/new-project-mvp-prompt.test.cjs
Normal file
39
tests/new-project-mvp-prompt.test.cjs
Normal file
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* new-project workflow — MVP mode prompt contract test
|
||||
* Verifies the workflow markdown documents the Vertical MVP / Horizontal Layers
|
||||
* prompt and the ROADMAP.md template branch under MVP mode.
|
||||
*/
|
||||
const { test, describe } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const WORKFLOW = path.join(__dirname, '..', 'get-shit-done', 'workflows', 'new-project.md');
|
||||
|
||||
describe('new-project — MVP mode prompt', () => {
|
||||
const content = fs.readFileSync(WORKFLOW, 'utf-8');
|
||||
|
||||
test('workflow includes Vertical MVP option in mode prompt', () => {
|
||||
assert.match(content, /Vertical\s*MVP/i, 'must mention Vertical MVP option');
|
||||
});
|
||||
|
||||
test('workflow includes Horizontal Layers option in mode prompt', () => {
|
||||
assert.match(content, /Horizontal\s*Layers/i, 'must mention Horizontal Layers option');
|
||||
});
|
||||
|
||||
test('ROADMAP template emits **Mode:** mvp under Vertical MVP path', () => {
|
||||
assert.match(
|
||||
content,
|
||||
/\*\*Mode:\*\*\s*mvp/,
|
||||
'must emit **Mode:** mvp on initial roadmap phases under Vertical MVP'
|
||||
);
|
||||
});
|
||||
|
||||
test('workflow falls back to standard template when Horizontal Layers picked', () => {
|
||||
assert.match(
|
||||
content,
|
||||
/Horizontal[^\n]*standard|standard[^\n]*Horizontal|no.*Mode.*line/i,
|
||||
'must specify fallback to standard template'
|
||||
);
|
||||
});
|
||||
});
|
||||
75
tests/plan-phase-mvp-flag.test.cjs
Normal file
75
tests/plan-phase-mvp-flag.test.cjs
Normal file
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* plan-phase workflow — --mvp flag parsing and MVP_MODE resolution
|
||||
* Contract test: verifies the workflow markdown documents the agreed
|
||||
* resolution order (CLI flag → roadmap mode → config → default false).
|
||||
*/
|
||||
const { test, describe, beforeEach, afterEach } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const { runGsdTools, createTempProject, cleanup } = require('./helpers.cjs');
|
||||
|
||||
const WORKFLOW = path.join(__dirname, '..', 'get-shit-done', 'workflows', 'plan-phase.md');
|
||||
|
||||
describe('plan-phase workflow — --mvp flag', () => {
|
||||
const content = fs.readFileSync(WORKFLOW, 'utf-8');
|
||||
|
||||
test('argument list documents --mvp flag', () => {
|
||||
const argsLine = content.match(/Extract from \$ARGUMENTS:[^\n]*/);
|
||||
assert.ok(argsLine, 'Step 2 arg-extraction line not found');
|
||||
assert.match(argsLine[0], /--mvp/, 'argument list must mention --mvp');
|
||||
});
|
||||
|
||||
test('workflow defines MVP_MODE resolution block', () => {
|
||||
assert.match(content, /MVP_MODE/, 'workflow must declare MVP_MODE');
|
||||
assert.match(content, /workflow\.mvp_mode/, 'must read workflow.mvp_mode config');
|
||||
assert.match(
|
||||
content,
|
||||
/roadmap[^\n]*mode|phase[^\n]*\.mode/i,
|
||||
'must consult phase mode from roadmap'
|
||||
);
|
||||
});
|
||||
|
||||
test('Walking Skeleton gate references new-project + Phase 1', () => {
|
||||
assert.match(content, /SKELETON\.md/, 'workflow must mention SKELETON.md');
|
||||
assert.match(
|
||||
content,
|
||||
/Walking Skeleton|walking_skeleton/i,
|
||||
'workflow must label the gate as Walking Skeleton'
|
||||
);
|
||||
});
|
||||
|
||||
test('planner spawn passes MVP_MODE to gsd-planner', () => {
|
||||
assert.match(
|
||||
content,
|
||||
/MVP_MODE[^\n]*planner|planner[^\n]*MVP_MODE/i,
|
||||
'workflow must wire MVP_MODE into the planner subagent prompt'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('plan-phase --mvp — resolution chain integration', () => {
|
||||
let tmpDir;
|
||||
beforeEach(() => { tmpDir = createTempProject(); });
|
||||
afterEach(() => { cleanup(tmpDir); });
|
||||
|
||||
test('roadmap.get-phase reports mode=mvp when set in roadmap', () => {
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, '.planning', 'ROADMAP.md'),
|
||||
`# Roadmap\n\n## v1.0.0\n\n### Phase 1: Auth\n**Goal:** Users can log in\n**Mode:** mvp\n`
|
||||
);
|
||||
const result = runGsdTools('roadmap get-phase 1 --pick mode', tmpDir);
|
||||
assert.ok(result.success);
|
||||
assert.strictEqual(result.output.trim(), 'mvp');
|
||||
});
|
||||
|
||||
test('config-get workflow.mvp_mode default is empty/unset', () => {
|
||||
const result = runGsdTools('config-get workflow.mvp_mode', tmpDir);
|
||||
// Either success with empty output OR a non-zero exit; both are fine.
|
||||
// Real assertion: the key isn't accidentally set to "true" in tmp project.
|
||||
if (result.success) {
|
||||
assert.notStrictEqual(result.output.trim(), 'true');
|
||||
}
|
||||
});
|
||||
});
|
||||
66
tests/planner-mvp-mode.test.cjs
Normal file
66
tests/planner-mvp-mode.test.cjs
Normal file
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* gsd-planner agent — MVP-mode branch contract
|
||||
* Verifies the agent definition contains the MVP-mode planning section,
|
||||
* conditional reference loading, and Walking Skeleton handling.
|
||||
*/
|
||||
const { test, describe } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const AGENT = path.join(__dirname, '..', 'agents', 'gsd-planner.md');
|
||||
const REF_MVP = path.join(__dirname, '..', 'get-shit-done', 'references', 'planner-mvp-mode.md');
|
||||
const REF_SKEL = path.join(__dirname, '..', 'get-shit-done', 'references', 'skeleton-template.md');
|
||||
|
||||
describe('gsd-planner — MVP-mode branch', () => {
|
||||
const content = fs.readFileSync(AGENT, 'utf-8');
|
||||
|
||||
test('agent defines an MVP Mode Detection section', () => {
|
||||
assert.match(content, /MVP\s*Mode|MVP_MODE/i, 'must reference MVP mode');
|
||||
assert.match(content, /vertical[\s-]?slice/i, 'must use vertical-slice terminology');
|
||||
});
|
||||
|
||||
test('agent describes Walking Skeleton handling', () => {
|
||||
assert.match(content, /Walking\s*Skeleton/i, 'must mention Walking Skeleton');
|
||||
assert.match(content, /SKELETON\.md/, 'must mention SKELETON.md output');
|
||||
});
|
||||
|
||||
test('agent references planner-mvp-mode.md conditionally', () => {
|
||||
assert.match(
|
||||
content,
|
||||
/references\/planner-mvp-mode\.md/,
|
||||
'must reference the MVP-mode rules file'
|
||||
);
|
||||
});
|
||||
|
||||
test('referenced files exist on disk', () => {
|
||||
assert.ok(fs.existsSync(REF_MVP), `${REF_MVP} must exist`);
|
||||
assert.ok(fs.existsSync(REF_SKEL), `${REF_SKEL} must exist`);
|
||||
});
|
||||
|
||||
test('agent does not introduce horizontal/MVP mixing language', () => {
|
||||
// Q1: all-or-nothing per phase. Reject phrasing that would imply mixing.
|
||||
assert.doesNotMatch(
|
||||
content,
|
||||
/mix[a-z\s]*horizontal[a-z\s]*MVP|MVP[a-z\s]*and[a-z\s]*horizontal[a-z\s]*tasks/i,
|
||||
'agent must enforce all-or-nothing per phase'
|
||||
);
|
||||
});
|
||||
|
||||
test('agent requires PLAN.md to start with user-story header in MVP mode', () => {
|
||||
// The MVP Mode Detection section must instruct the planner to emit
|
||||
// a "## Phase Goal" section with **As a** / **I want to** / **so that**
|
||||
// bolded keywords as the first content under the phase header in PLAN.md.
|
||||
assert.match(content, /Phase\s*Goal/i, 'must mention "Phase Goal" header');
|
||||
assert.match(
|
||||
content,
|
||||
/\*\*As a\*\*[^\n]*\*\*I want to\*\*[^\n]*\*\*so that\*\*/i,
|
||||
'must specify the bolded user-story format for PLAN.md emit'
|
||||
);
|
||||
assert.match(
|
||||
content,
|
||||
/user-story-template\.md/,
|
||||
'must reference the user-story-template reference file'
|
||||
);
|
||||
});
|
||||
});
|
||||
43
tests/progress-mvp-display.test.cjs
Normal file
43
tests/progress-mvp-display.test.cjs
Normal file
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* progress workflow — MVP mode display contract test
|
||||
*/
|
||||
const { test, describe } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const WORKFLOW = path.join(__dirname, '..', 'get-shit-done', 'workflows', 'progress.md');
|
||||
|
||||
describe('progress — MVP mode display', () => {
|
||||
const content = fs.readFileSync(WORKFLOW, 'utf-8');
|
||||
|
||||
test('workflow declares MVP_MODE branch', () => {
|
||||
assert.match(content, /MVP_MODE/, 'must declare MVP_MODE');
|
||||
assert.match(
|
||||
content,
|
||||
/roadmap[^\n]*mode|phase[^\n]*\.mode/i,
|
||||
'must consult phase mode from roadmap'
|
||||
);
|
||||
});
|
||||
|
||||
test('MVP display sources user-flow status from PLAN.md task names', () => {
|
||||
assert.match(
|
||||
content,
|
||||
/PLAN\.md[^\n]*task|task[^\n]*PLAN\.md/i,
|
||||
'must source user-flow status from PLAN.md tasks'
|
||||
);
|
||||
assert.match(
|
||||
content,
|
||||
/user[\s-]?flow|user-visible/i,
|
||||
'must use user-flow framing'
|
||||
);
|
||||
});
|
||||
|
||||
test('falls back to standard display when mode null', () => {
|
||||
assert.match(
|
||||
content,
|
||||
/mode[^\n]*null|absent|not.*mvp|standard\s*display/i,
|
||||
'must specify fallback when mode is not mvp'
|
||||
);
|
||||
});
|
||||
});
|
||||
77
tests/roadmap-mode-field.test.cjs
Normal file
77
tests/roadmap-mode-field.test.cjs
Normal file
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* Roadmap parser — `**Mode:**` field extraction
|
||||
* Covers PRD: vertical-mvp-slice Phase 1 (Q1: all-or-nothing per phase).
|
||||
*/
|
||||
const { test, describe, beforeEach, afterEach } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { runGsdTools, createTempProject, cleanup } = require('./helpers.cjs');
|
||||
|
||||
const ROADMAP_WITH_MODE = `# Roadmap
|
||||
|
||||
## v1.0.0
|
||||
|
||||
### Phase 1: User Auth MVP
|
||||
**Goal:** A user can register and log in
|
||||
**Mode:** mvp
|
||||
**Success Criteria**:
|
||||
1. Registration works
|
||||
2. Login works
|
||||
|
||||
### Phase 2: Bulk Import
|
||||
**Goal:** Admin can upload CSV
|
||||
**Success Criteria**:
|
||||
1. CSV parses
|
||||
`;
|
||||
|
||||
describe('roadmap parser — mode field', () => {
|
||||
let tmpDir;
|
||||
beforeEach(() => { tmpDir = createTempProject(); });
|
||||
afterEach(() => { cleanup(tmpDir); });
|
||||
|
||||
test('roadmap.get-phase returns mode="mvp" when **Mode:** mvp present', () => {
|
||||
fs.writeFileSync(path.join(tmpDir, '.planning', 'ROADMAP.md'), ROADMAP_WITH_MODE);
|
||||
const result = runGsdTools('roadmap get-phase 1', tmpDir);
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
const out = JSON.parse(result.output);
|
||||
assert.strictEqual(out.found, true);
|
||||
assert.strictEqual(out.mode, 'mvp');
|
||||
});
|
||||
|
||||
test('roadmap.get-phase returns mode=null when **Mode:** absent', () => {
|
||||
fs.writeFileSync(path.join(tmpDir, '.planning', 'ROADMAP.md'), ROADMAP_WITH_MODE);
|
||||
const result = runGsdTools('roadmap get-phase 2', tmpDir);
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
const out = JSON.parse(result.output);
|
||||
assert.strictEqual(out.found, true);
|
||||
assert.strictEqual(out.mode, null);
|
||||
});
|
||||
|
||||
test('roadmap.analyze surfaces mode per phase', () => {
|
||||
fs.writeFileSync(path.join(tmpDir, '.planning', 'ROADMAP.md'), ROADMAP_WITH_MODE);
|
||||
const result = runGsdTools('roadmap analyze', tmpDir);
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
const out = JSON.parse(result.output);
|
||||
const p1 = out.phases.find(p => p.number === '1');
|
||||
const p2 = out.phases.find(p => p.number === '2');
|
||||
assert.strictEqual(p1.mode, 'mvp');
|
||||
assert.strictEqual(p2.mode, null);
|
||||
});
|
||||
|
||||
test('mode field is case-insensitive and trimmed', () => {
|
||||
const variant = ROADMAP_WITH_MODE.replace('**Mode:** mvp', '**mode**: MVP ');
|
||||
fs.writeFileSync(path.join(tmpDir, '.planning', 'ROADMAP.md'), variant);
|
||||
const result = runGsdTools('roadmap get-phase 1', tmpDir);
|
||||
const out = JSON.parse(result.output);
|
||||
assert.strictEqual(out.mode, 'mvp');
|
||||
});
|
||||
|
||||
test('unrecognized mode value is preserved verbatim (forward-compat)', () => {
|
||||
const variant = ROADMAP_WITH_MODE.replace('**Mode:** mvp', '**Mode:** experimental');
|
||||
fs.writeFileSync(path.join(tmpDir, '.planning', 'ROADMAP.md'), variant);
|
||||
const result = runGsdTools('roadmap get-phase 1', tmpDir);
|
||||
const out = JSON.parse(result.output);
|
||||
assert.strictEqual(out.mode, 'experimental');
|
||||
});
|
||||
});
|
||||
26
tests/stats-mvp-display.test.cjs
Normal file
26
tests/stats-mvp-display.test.cjs
Normal file
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* stats workflow — MVP mode summary contract test
|
||||
*/
|
||||
const { test, describe } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const WORKFLOW = path.join(__dirname, '..', 'get-shit-done', 'workflows', 'stats.md');
|
||||
|
||||
describe('stats — MVP mode summary', () => {
|
||||
const content = fs.readFileSync(WORKFLOW, 'utf-8');
|
||||
|
||||
test('workflow includes MVP phase count summary', () => {
|
||||
assert.match(content, /MVP/, 'must mention MVP in summary');
|
||||
assert.match(content, /mode/i, 'must reference mode field');
|
||||
});
|
||||
|
||||
test('uses roadmap.analyze to count MVP phases', () => {
|
||||
assert.match(
|
||||
content,
|
||||
/roadmap[^\n]*analyze|analyze[^\n]*mode/i,
|
||||
'must consult roadmap.analyze (which surfaces mode per phase from Phase 1)'
|
||||
);
|
||||
});
|
||||
});
|
||||
32
tests/verifier-mvp-section.test.cjs
Normal file
32
tests/verifier-mvp-section.test.cjs
Normal file
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* gsd-verifier agent — MVP Mode Verification section contract
|
||||
* Verifies the agent definition contains a section instructing the verifier
|
||||
* to emphasize user-visible outcomes under MVP mode.
|
||||
*/
|
||||
const { test, describe } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const AGENT = path.join(__dirname, '..', 'agents', 'gsd-verifier.md');
|
||||
const REF = path.join(__dirname, '..', 'get-shit-done', 'references', 'verify-mvp-mode.md');
|
||||
|
||||
describe('gsd-verifier — MVP Mode Verification section', () => {
|
||||
const content = fs.readFileSync(AGENT, 'utf-8');
|
||||
|
||||
test('agent defines an MVP Mode Verification section', () => {
|
||||
assert.match(content, /MVP\s*Mode\s*Verification|MVP[\s-]?mode[\s-]?verif/i);
|
||||
});
|
||||
|
||||
test('agent references verify-mvp-mode.md', () => {
|
||||
assert.match(content, /verify-mvp-mode\.md/);
|
||||
});
|
||||
|
||||
test('agent preserves goal-backward terminology', () => {
|
||||
assert.match(content, /goal[\s-]?backward/i);
|
||||
});
|
||||
|
||||
test('referenced file exists on disk', () => {
|
||||
assert.ok(fs.existsSync(REF));
|
||||
});
|
||||
});
|
||||
53
tests/verify-mvp-uat.test.cjs
Normal file
53
tests/verify-mvp-uat.test.cjs
Normal file
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* verify-work workflow — MVP mode UAT contract test
|
||||
* Verifies the workflow markdown documents MVP_MODE resolution,
|
||||
* conditional reference injection, user-flow-first UAT ordering,
|
||||
* and the deferred-technical-checks clause.
|
||||
*/
|
||||
const { test, describe } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const WORKFLOW = path.join(__dirname, '..', 'get-shit-done', 'workflows', 'verify-work.md');
|
||||
|
||||
describe('verify-work — MVP mode UAT framing', () => {
|
||||
const content = fs.readFileSync(WORKFLOW, 'utf-8');
|
||||
|
||||
test('Step 1 resolves MVP_MODE from phase mode field', () => {
|
||||
assert.match(content, /MVP_MODE/, 'workflow must declare MVP_MODE');
|
||||
assert.match(
|
||||
content,
|
||||
/roadmap[^\n]*mode|phase[^\n]*\.mode|\.mode\s*=/i,
|
||||
'must consult phase mode from roadmap'
|
||||
);
|
||||
});
|
||||
|
||||
test('workflow references verify-mvp-mode.md', () => {
|
||||
assert.match(content, /verify-mvp-mode\.md/, 'must reference the UAT framing file');
|
||||
});
|
||||
|
||||
test('UAT generation under MVP mode runs user-flow steps first', () => {
|
||||
assert.match(
|
||||
content,
|
||||
/user[\s-]?flow[^\n]{0,80}(first|before|precede)/i,
|
||||
'must specify user-flow-first ordering'
|
||||
);
|
||||
});
|
||||
|
||||
test('technical checks deferred under MVP mode', () => {
|
||||
assert.match(
|
||||
content,
|
||||
/technical[\s-]?checks[^\n]{0,80}(after|defer|second)/i,
|
||||
'must defer technical checks under MVP mode'
|
||||
);
|
||||
});
|
||||
|
||||
test('mode null falls back to standard UAT generation', () => {
|
||||
assert.match(
|
||||
content,
|
||||
/mode[^\n]*null|absent|not.*mvp|standard\s*UAT/i,
|
||||
'must specify fallback when mode is not mvp'
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user