Compare commits

...

20 Commits

Author SHA1 Message Date
Lex Christopherson
99aa2c1e54 1.38.3 2026-04-21 09:20:20 -06:00
Lex Christopherson
900c36998b fix: spike workflow defaults to interactive UI demos, not stdout
Flips the bias in step 8b: build a simple HTML page/web UI by default,
fall back to stdout only for pure fact-checking (binary yes/no, benchmarks).
Mirrors upstream spike-idea skill constraint #3 update.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 09:20:11 -06:00
github-actions[bot]
06d29af1cd chore: bump version to 1.38.3 for hotfix 2026-04-21 15:19:54 +00:00
Lex Christopherson
a42d5db742 1.38.2 2026-04-21 09:14:52 -06:00
Lex Christopherson
c86ca1b3eb fix: sync spike/sketch workflows with upstream skill v2 improvements
Spike workflow:
- Add frontier mode (no-arg or "frontier" proposes integration + frontier spikes)
- Add depth-over-speed principle — follow surprising findings, test edge cases,
  document investigation trail not just verdict
- Add CONVENTIONS.md awareness — follow established patterns, update after session
- Add Requirements section in MANIFEST — track design decisions as they emerge
- Add re-ground step before each spike to prevent drift in long sessions
- Add Investigation Trail section to README template
- Restructured prior context loading with priority ordering
- Research step now runs per-spike with briefing and approach comparison table

Sketch workflow:
- Add frontier mode (no-arg or "frontier" proposes consistency + frontier sketches)
- Add spike context loading — ground mockups in real data shapes, requirements,
  and conventions from spike findings

Spike wrap-up workflow:
- Add CONVENTIONS.md generation step (recurring stack/structure/pattern choices)
- Reference files now use implementation blueprint format (Requirements, How to
  Build It, What to Avoid, Constraints)
- SKILL.md now includes requirements section from MANIFEST
- Next-steps route to /gsd-spike frontier mode instead of inline analysis

Sketch wrap-up workflow:
- Next-steps route to /gsd-sketch frontier mode

Commands updated with frontier mode in descriptions and argument hints.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 09:14:32 -06:00
github-actions[bot]
337e052aa9 chore: bump version to 1.38.2 for hotfix 2026-04-21 15:13:56 +00:00
Jeremy McSpadden
30433368a0 fix(install): template bare .claude hook paths for non-Claude runtimes 2026-04-19 18:42:30 -05:00
Jeremy McSpadden
04fab926b5 test: add --no-sdk to hook-deployment installer tests
Tests #1834, #1924, #2136 exercise hook/artifact deployment and don't
care about SDK install. Now that installSdkIfNeeded() failures are
fatal, these tests fail on any CI runner without gsd-sdk pre-built
because the sdk/ tsc build path runs and can fail in CI env.

Pass --no-sdk so each test focuses on its actual subject. SDK install
path has dedicated end-to-end coverage in install-smoke.yml.
2026-04-19 18:39:32 -05:00
Jeremy McSpadden
f98ef1e460 fix(install): fatal SDK install failures + CI smoke gate (#2439)
## Why
#2386 added `installSdkIfNeeded()` to build @gsd-build/sdk from bundled
source and `npm install -g .`, because the npm-published @gsd-build/sdk
is intentionally frozen and version-mismatched with get-shit-done-cc.

But every failure path in that function was warning-only — including
the final `which gsd-sdk` verification. When npm's global bin is off a
user's PATH (common on macOS), the installer printed a yellow warning
then exited 0. Users saw "install complete" and then every `/gsd-*`
command crashed with `command not found: gsd-sdk` (the #2439 symptom).

No CI job executed the install path, so this class of regression could
ship undetected — existing "install" tests only read bin/install.js as
a string.

## What changed

**bin/install.js — installSdkIfNeeded() is now transactional**
- All build/install failures exit non-zero (not just warn).
- Post-install `which gsd-sdk` check is fatal: if the binary landed
  globally but is off PATH, we exit 1 with a red banner showing the
  resolved npm bin dir, the user's shell, the target rc file, and the
  exact `export PATH=…` line to add.
- Escape hatch: `GSD_ALLOW_OFF_PATH=1` downgrades off-PATH to exit 2
  for users with intentionally restricted PATH who will wire up the
  binary manually.
- Resolver uses POSIX `command -v` via `sh -c` (replaces `which`) so
  behavior is consistent across sh/bash/zsh/fish.
- Factored `resolveGsdSdk()`, `detectShellRc()`, `emitSdkFatal()`.

**.github/workflows/install-smoke.yml (new)**
- Executes the real install path: `npm pack` → `npm install -g <tgz>`
  → run installer non-interactively → `command -v gsd-sdk` → run
  `gsd-sdk --version`.
- PRs: path-filtered to installer-adjacent files, ubuntu + Node 22 only.
- main/release branches: full matrix (ubuntu+macos × Node 22+24).
- Reusable via workflow_call with `ref` input for release gating.

**.github/workflows/release.yml — pre-publish gate**
- New `install-smoke-rc` and `install-smoke-finalize` jobs invoke the
  reusable workflow against the release branch. `rc` and `finalize`
  now `needs: [validate-version, install-smoke-*]`, so a broken SDK
  install blocks `npm publish`.

## Test plan
- Local full suite: 4154/4154 pass
- install-smoke.yml will self-validate on this PR (ubuntu+Node22 only)

Addresses root cause of #2439 (the per-command pre-flight in #2440 is
the complementary defensive layer).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 18:39:32 -05:00
Jeremy McSpadden
d0565e95c1 fix(set-profile): use hyphenated /gsd-set-profile in pre-flight message
Project convention (#1748) requires /gsd-<cmd> hyphen form everywhere
except designated test inputs. Fix the colon references in the
pre-flight error and its regression test to satisfy stale-colon-refs.
2026-04-19 18:39:32 -05:00
Jeremy McSpadden
4ef6275e86 fix(set-profile): guard gsd-sdk invocation with command -v pre-flight (#2439)
/gsd:set-profile crashed with `command not found: gsd-sdk` when gsd-sdk
was not on PATH. The command invoked `gsd-sdk query` directly in a `!`
backtick with no guard, so a missing binary produced an opaque shell
error with exit 127.

Add a `command -v gsd-sdk` pre-flight that prints the install/update
hint and exits 1 when absent, mirroring the #2334 fix on /gsd-quick.
The auto-install in #2386 still runs at install time; this guard is the
defensive layer for users whose npm global bin is off-PATH (install.js
warns but does not fail in that case).

Closes #2439
2026-04-19 18:39:32 -05:00
Jeremy McSpadden
6c50490766 fix(sdk): register init.ingest-docs handler and add registry drift guard (#2442)
The ingest-docs workflow called `gsd-sdk query init.ingest-docs` with a
fallback to `init.default` — neither was registered in createRegistry(),
so the workflow proceeded with `{}` and tried to parse project_exists,
planning_exists, has_git, and project_path from empty.

- Add initIngestDocs handler; register dotted + space aliases
- Simplify workflow call; drop broken fallback
- Repo-wide drift guard scans commands/, agents/, get-shit-done/,
  hooks/, bin/, scripts/, docs/ for `gsd-sdk query <cmd>` and fails
  on any reference with no registered handler (file:line citations)
- Unit tests for the new handler

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 18:39:20 -05:00
Jeremy McSpadden
4cbebfe78c docs(readme): add /gsd-ingest-docs to Brownfield commands
Surfaces the new ingest-docs command from the Unreleased changelog in
the README Commands section so users discover it without digging.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 18:39:20 -05:00
Jeremy McSpadden
9e87d43831 fix(build): include gsd-read-injection-scanner in hooks/dist (#2406)
The scanner was added in #2201 but never added to the HOOKS_TO_COPY
allowlist in scripts/build-hooks.js, so it never landed in hooks/dist/.
install.js reads from hooks/dist/, so every install on 1.37.0/1.37.1
emitted "Skipped read injection scanner hook — not found at target"
and the read-time prompt-injection scanner was silently disabled.

- Add gsd-read-injection-scanner.js to HOOKS_TO_COPY
- Add it to EXPECTED_ALL_HOOKS regression test in install-hooks-copy

Fixes #2406

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 18:39:20 -05:00
github-actions[bot]
29ea90bc83 chore: bump version to 1.38.1 for hotfix 2026-04-19 23:37:15 +00:00
github-actions[bot]
0c6172bfad chore: finalize v1.38.0 2026-04-18 03:45:59 +00:00
Jeremy McSpadden
e3bd06c9fd fix(release): make merge-back PR step non-fatal
Repos that disable "Allow GitHub Actions to create and approve pull
requests" (org-level policy or repo-level setting) cause the "Create PR
to merge release back to main" step to fail with a GraphQL 403. That
failure cascades: Tag and push, npm publish, GitHub Release creation
are all skipped, and the entire release aborts.

The merge-back PR is a convenience — it's re-openable manually after
the release. Making it non-fatal with continue-on-error lets the rest
of the release complete. The step now emits ::warning:: annotations
pointing at the manual-recovery command when it fails.

Shell pipelines also fall through with `|| echo "::warning::..."` so
transient gh CLI failures don't mask the underlying policy issue.

Covers the failure mode seen on run 24596079637 where dry-run publish
validation passed but the release halted at the PR-creation step.
2026-04-17 22:45:22 -05:00
github-actions[bot]
c69ecd975a chore: bump to 1.38.0-rc.1 2026-04-18 03:05:35 +00:00
Jeremy McSpadden
06c4ded4ec docs(changelog): promote Unreleased to [1.38.0] + add ultraplan entry 2026-04-17 22:03:26 -05:00
github-actions[bot]
341bb941c6 chore: bump version to 1.38.0 for release 2026-04-18 03:02:41 +00:00
25 changed files with 1103 additions and 224 deletions

152
.github/workflows/install-smoke.yml vendored Normal file
View File

@@ -0,0 +1,152 @@
name: Install Smoke
# Exercises the real install path: `npm pack` → `npm install -g <tarball>`
# → run `bin/install.js` → assert `gsd-sdk` is on PATH.
#
# Closes the CI gap that let #2439 ship: the rest of the suite only reads
# `bin/install.js` as a string and never executes it.
#
# - PRs: path-filtered, minimal runner (ubuntu + Node LTS) for fast signal.
# - Push to release branches / main: full matrix.
# - workflow_call: invoked from release.yml as a pre-publish gate.
on:
pull_request:
branches:
- main
paths:
- 'bin/install.js'
- 'sdk/**'
- 'package.json'
- 'package-lock.json'
- '.github/workflows/install-smoke.yml'
- '.github/workflows/release.yml'
push:
branches:
- main
- 'release/**'
- 'hotfix/**'
workflow_call:
inputs:
ref:
description: 'Git ref to check out (branch or SHA). Defaults to the triggering ref.'
required: false
type: string
default: ''
workflow_dispatch:
concurrency:
group: install-smoke-${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
smoke:
runs-on: ${{ matrix.os }}
timeout-minutes: 12
strategy:
fail-fast: false
matrix:
# PRs run the minimal path (ubuntu + LTS). Pushes / release branches
# and workflow_call add macOS + Node 24 coverage.
include:
- os: ubuntu-latest
node-version: 22
full_only: false
- os: ubuntu-latest
node-version: 24
full_only: true
- os: macos-latest
node-version: 24
full_only: true
steps:
- name: Skip full-only matrix entry on PR
id: skip
shell: bash
env:
EVENT: ${{ github.event_name }}
FULL_ONLY: ${{ matrix.full_only }}
run: |
if [ "$EVENT" = "pull_request" ] && [ "$FULL_ONLY" = "true" ]; then
echo "skip=true" >> "$GITHUB_OUTPUT"
else
echo "skip=false" >> "$GITHUB_OUTPUT"
fi
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
if: steps.skip.outputs.skip != 'true'
with:
ref: ${{ inputs.ref || github.ref }}
- name: Set up Node.js ${{ matrix.node-version }}
if: steps.skip.outputs.skip != 'true'
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- name: Install root deps
if: steps.skip.outputs.skip != 'true'
run: npm ci
- name: Pack root tarball
if: steps.skip.outputs.skip != 'true'
id: pack
shell: bash
run: |
set -euo pipefail
npm pack --silent
TARBALL=$(ls get-shit-done-cc-*.tgz | head -1)
echo "tarball=$TARBALL" >> "$GITHUB_OUTPUT"
echo "Packed: $TARBALL"
- name: Ensure npm global bin is on PATH (CI runner default may differ)
if: steps.skip.outputs.skip != 'true'
shell: bash
run: |
NPM_BIN="$(npm config get prefix)/bin"
echo "$NPM_BIN" >> "$GITHUB_PATH"
echo "npm global bin: $NPM_BIN"
- name: Install tarball globally (runs bin/install.js → installSdkIfNeeded)
if: steps.skip.outputs.skip != 'true'
shell: bash
env:
TARBALL: ${{ steps.pack.outputs.tarball }}
WORKSPACE: ${{ github.workspace }}
run: |
set -euo pipefail
TMPDIR_ROOT=$(mktemp -d)
cd "$TMPDIR_ROOT"
npm install -g "$WORKSPACE/$TARBALL"
command -v get-shit-done-cc
# `--claude --local` is the non-interactive code path (see
# install.js main block: when both a runtime and location are set,
# installAllRuntimes runs with isInteractive=false, no prompts).
# We tolerate non-zero here because the authoritative assertion is
# the next step: gsd-sdk must land on PATH. Some runtime targets
# may exit before the SDK step for unrelated reasons on CI.
get-shit-done-cc --claude --local || true
- name: Assert gsd-sdk resolves on PATH
if: steps.skip.outputs.skip != 'true'
shell: bash
run: |
set -euo pipefail
if ! command -v gsd-sdk >/dev/null 2>&1; then
echo "::error::gsd-sdk is not on PATH after install — installSdkIfNeeded() regression"
NPM_BIN="$(npm config get prefix)/bin"
echo "npm global bin: $NPM_BIN"
ls -la "$NPM_BIN" | grep -i gsd || true
exit 1
fi
echo "✓ gsd-sdk resolves at: $(command -v gsd-sdk)"
- name: Assert gsd-sdk is executable
if: steps.skip.outputs.skip != 'true'
shell: bash
run: |
set -euo pipefail
gsd-sdk --version || gsd-sdk --help
echo "✓ gsd-sdk is executable"

View File

@@ -113,9 +113,18 @@ jobs:
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "Next: run this workflow with \`rc\` action to publish a pre-release to \`next\`" >> "$GITHUB_STEP_SUMMARY"
rc:
install-smoke-rc:
needs: validate-version
if: inputs.action == 'rc'
permissions:
contents: read
uses: ./.github/workflows/install-smoke.yml
with:
ref: ${{ needs.validate-version.outputs.branch }}
rc:
needs: [validate-version, install-smoke-rc]
if: inputs.action == 'rc'
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
@@ -251,9 +260,18 @@ jobs:
echo "To publish another pre-release: run \`rc\` again" >> "$GITHUB_STEP_SUMMARY"
echo "To finalize: run \`finalize\` action" >> "$GITHUB_STEP_SUMMARY"
finalize:
install-smoke-finalize:
needs: validate-version
if: inputs.action == 'finalize'
permissions:
contents: read
uses: ./.github/workflows/install-smoke.yml
with:
ref: ${{ needs.validate-version.outputs.branch }}
finalize:
needs: [validate-version, install-smoke-finalize]
if: inputs.action == 'finalize'
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
@@ -298,23 +316,32 @@ jobs:
- name: Create PR to merge release back to main
if: ${{ !inputs.dry_run }}
continue-on-error: true
env:
GH_TOKEN: ${{ github.token }}
BRANCH: ${{ needs.validate-version.outputs.branch }}
VERSION: ${{ inputs.version }}
run: |
EXISTING_PR=$(gh pr list --base main --head "$BRANCH" --state open --json number --jq '.[0].number')
# Non-fatal: repos that disable "Allow GitHub Actions to create and
# approve pull requests" cause this step to fail with GraphQL 403.
# The release itself (tag + npm publish + GitHub Release) must still
# proceed. Open the merge-back PR manually afterwards with:
# gh pr create --base main --head release/${VERSION} \
# --title "chore: merge release v${VERSION} to main"
EXISTING_PR=$(gh pr list --base main --head "$BRANCH" --state open --json number --jq '.[0].number' 2>/dev/null || echo "")
if [ -n "$EXISTING_PR" ]; then
echo "PR #$EXISTING_PR already exists; updating"
gh pr edit "$EXISTING_PR" \
--title "chore: merge release v${VERSION} to main" \
--body "Merge release branch back to main after v${VERSION} stable release."
--body "Merge release branch back to main after v${VERSION} stable release." \
|| echo "::warning::Could not update merge-back PR (likely PR-creation policy disabled). Open it manually after release."
else
gh pr create \
--base main \
--head "$BRANCH" \
--title "chore: merge release v${VERSION} to main" \
--body "Merge release branch back to main after v${VERSION} stable release."
--body "Merge release branch back to main after v${VERSION} stable release." \
|| echo "::warning::Could not create merge-back PR (likely PR-creation policy disabled). Open it manually after release."
fi
- name: Tag and push

View File

@@ -6,11 +6,15 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
## [Unreleased]
## [1.38.0] - 2026-04-18
### Added
- **`/gsd-ingest-docs` command** — Scan a repo containing mixed ADRs, PRDs, SPECs, and DOCs and bootstrap or merge the full `.planning/` setup from them in a single pass. Parallel classification (`gsd-doc-classifier`), synthesis with precedence rules and cycle detection (`gsd-doc-synthesizer`), three-bucket conflicts report (`INGEST-CONFLICTS.md`: auto-resolved, competing-variants, unresolved-blockers), and hard-block on LOCKED-vs-LOCKED ADR contradictions in both new and merge modes. Supports directory-convention discovery and `--manifest <file>` YAML override with per-doc precedence. v1 caps at 50 docs per invocation; `--resolve interactive` is reserved. Extracts shared conflict-detection contract into `references/doc-conflict-engine.md` which `/gsd-import` now also consumes (#2387)
- **`/gsd-ultraplan-phase` command [BETA]** — Offload plan phase to Claude Code's ultraplan cloud. Drafts remotely while the terminal stays free; review in browser with inline comments; import the result back via `/gsd-import`. Claude Code only (#2378)
### Fixed
- **Installer now installs `@gsd-build/sdk` automatically** so `gsd-sdk` lands on PATH. Resolves `command not found: gsd-sdk` errors that affected every `/gsd-*` command after a fresh install or `/gsd-update` to 1.36+. Adds `--no-sdk` to opt out and `--sdk` to force reinstall. Implements the `--sdk` flag that was previously documented in README but never wired up (#2385)
- **Installer now builds `@gsd-build/sdk` from the in-repo `sdk/` source tree** so `gsd-sdk` lands on PATH with the query handlers that match the installed GSD version. Resolves `command not found: gsd-sdk` errors that affected every `/gsd-*` command after a fresh install or `/gsd-update` to 1.36+. Adds `--no-sdk` to opt out and `--sdk` to force rebuild. Implements the `--sdk` flag that was previously documented in README but never wired up. Replaces the initial PR #2386 design that installed the stale npm `@gsd-build/sdk` (published 2026-03-27) — the installer now runs `npm install && npm run build && npm install -g .` inside `sdk/` so users always get an SDK in lockstep with the rest of the repo. Root `package.json` `files` ships the sdk source tree for npm-registry installs (#2385)
- **`gsd-read-injection-scanner` hook now ships to users** — the scanner was added in 1.37.0 (#2201) but was never added to `scripts/build-hooks.js`' `HOOKS_TO_COPY` allowlist, so it never landed in `hooks/dist/` and `install.js` skipped it with "Skipped read injection scanner hook — gsd-read-injection-scanner.js not found at target". Effectively disabled the read-time prompt-injection scanner for every user on 1.37.0/1.37.1. Added to the build allowlist and regression test (#2406)
## [1.37.1] - 2026-04-17

View File

@@ -624,6 +624,7 @@ You're never locked in. The system adapts.
| Command | What it does |
|---------|--------------|
| `/gsd-map-codebase [area]` | Analyze existing codebase before new-project |
| `/gsd-ingest-docs [dir]` | Scan a repo of mixed ADRs, PRDs, SPECs, and DOCs and bootstrap or merge the full `.planning/` setup in one pass — parallel classification, synthesis with precedence rules, and a three-bucket conflicts report |
### Phase Management

View File

@@ -10,6 +10,8 @@ const crypto = require('crypto');
const cyan = '\x1b[36m';
const green = '\x1b[32m';
const yellow = '\x1b[33m';
const red = '\x1b[31m';
const bold = '\x1b[1m';
const dim = '\x1b[2m';
const reset = '\x1b[0m';
@@ -5825,6 +5827,7 @@ function install(isGlobal, runtime = 'claude') {
let content = fs.readFileSync(srcFile, 'utf8');
content = content.replace(/'\.claude'/g, configDirReplacement);
content = content.replace(/\/\.claude\//g, `/${getDirName(runtime)}/`);
content = content.replace(/\.claude\//g, `${getDirName(runtime)}/`);
if (isQwen) {
content = content.replace(/CLAUDE\.md/g, 'QWEN.md');
content = content.replace(/\bClaude Code\b/g, 'Qwen Code');
@@ -5950,6 +5953,7 @@ function install(isGlobal, runtime = 'claude') {
let content = fs.readFileSync(srcFile, 'utf8');
content = content.replace(/'\.claude'/g, configDirReplacement);
content = content.replace(/\/\.claude\//g, `/${getDirName(runtime)}/`);
content = content.replace(/\.claude\//g, `${getDirName(runtime)}/`);
content = content.replace(/\{\{GSD_VERSION\}\}/g, pkg.version);
fs.writeFileSync(destFile, content);
try { fs.chmodSync(destFile, 0o755); } catch (e) { /* Windows */ }
@@ -6643,8 +6647,94 @@ function promptLocation(runtimes) {
* every /gsd-* command that depends on newer query handlers.
*
* Skip if --no-sdk. Skip if already on PATH (unless --sdk was explicit).
* Failures are warnings, not fatal.
* Failures are FATAL — we exit non-zero so install does not complete with a
* silently broken SDK (issue #2439). Set GSD_ALLOW_OFF_PATH=1 to downgrade the
* post-install PATH verification to a warning (exit code 2) for users with an
* intentionally restricted PATH who will wire things up manually.
*/
/**
* Resolve `gsd-sdk` on PATH. Uses `command -v` via `sh -c` on POSIX (portable
* across sh/bash/zsh) and `where` on Windows. Returns trimmed path or null.
*/
function resolveGsdSdk() {
const { spawnSync } = require('child_process');
if (process.platform === 'win32') {
const r = spawnSync('where', ['gsd-sdk'], { encoding: 'utf-8' });
if (r.status === 0 && r.stdout && r.stdout.trim()) {
return r.stdout.trim().split('\n')[0].trim();
}
return null;
}
const r = spawnSync('sh', ['-c', 'command -v gsd-sdk'], { encoding: 'utf-8' });
if (r.status === 0 && r.stdout && r.stdout.trim()) {
return r.stdout.trim();
}
return null;
}
/**
* Best-effort detection of the user's shell rc file for PATH remediation hints.
*/
function detectShellRc() {
const path = require('path');
const shell = process.env.SHELL || '';
const home = process.env.HOME || '~';
if (/\/zsh$/.test(shell)) return { shell: 'zsh', rc: path.join(home, '.zshrc') };
if (/\/bash$/.test(shell)) return { shell: 'bash', rc: path.join(home, '.bashrc') };
if (/\/fish$/.test(shell)) return { shell: 'fish', rc: path.join(home, '.config', 'fish', 'config.fish') };
return { shell: 'sh', rc: path.join(home, '.profile') };
}
/**
* Emit a red fatal banner and exit. Prints actionable PATH remediation when
* the global install succeeded but the bin dir is not on PATH.
*
* If exitCode is 2, this is the "off-PATH" case and GSD_ALLOW_OFF_PATH respect
* is applied by the caller; we only print.
*/
function emitSdkFatal(reason, { globalBin, exitCode }) {
const { shell, rc } = detectShellRc();
const bar = '━'.repeat(72);
const redBold = `${red}${bold}`;
console.error('');
console.error(`${redBold}${bar}${reset}`);
console.error(`${redBold} ✗ GSD SDK install failed — /gsd-* commands will not work${reset}`);
console.error(`${redBold}${bar}${reset}`);
console.error(` ${red}Reason:${reset} ${reason}`);
if (globalBin) {
console.error('');
console.error(` ${yellow}gsd-sdk was installed to:${reset}`);
console.error(` ${cyan}${globalBin}${reset}`);
console.error('');
console.error(` ${yellow}Your shell's PATH does not include this directory.${reset}`);
console.error(` Add it by running:`);
if (shell === 'fish') {
console.error(` ${cyan}fish_add_path "${globalBin}"${reset}`);
console.error(` (or append to ${rc})`);
} else {
console.error(` ${cyan}echo 'export PATH="${globalBin}:$PATH"' >> ${rc}${reset}`);
console.error(` ${cyan}source ${rc}${reset}`);
}
console.error('');
console.error(` Then verify: ${cyan}command -v gsd-sdk${reset}`);
if (exitCode === 2) {
console.error('');
console.error(` ${dim}(GSD_ALLOW_OFF_PATH=1 set → exit ${exitCode} instead of hard failure)${reset}`);
}
} else {
console.error('');
console.error(` Build manually to retry:`);
console.error(` ${cyan}cd <install-dir>/sdk && npm install && npm run build && npm install -g .${reset}`);
}
console.error(`${redBold}${bar}${reset}`);
console.error('');
process.exit(exitCode);
}
function installSdkIfNeeded() {
if (hasNoSdk) {
console.log(`\n ${dim}Skipping GSD SDK install (--no-sdk)${reset}`);
@@ -6656,9 +6746,9 @@ function installSdkIfNeeded() {
const fs = require('fs');
if (!hasSdk) {
const probe = spawnSync(process.platform === 'win32' ? 'where' : 'which', ['gsd-sdk'], { stdio: 'ignore' });
if (probe.status === 0) {
console.log(` ${green}${reset} GSD SDK already installed (gsd-sdk on PATH)`);
const resolved = resolveGsdSdk();
if (resolved) {
console.log(` ${green}${reset} GSD SDK already installed (gsd-sdk on PATH at ${resolved})`);
return;
}
}
@@ -6671,17 +6761,8 @@ function installSdkIfNeeded() {
const sdkDir = path.resolve(__dirname, '..', 'sdk');
const sdkPackageJson = path.join(sdkDir, 'package.json');
const warnManual = (reason) => {
console.warn(` ${yellow}${reset} ${reason}`);
console.warn(` Build manually from the repo sdk/ directory:`);
console.warn(` ${cyan}cd ${sdkDir} && npm install && npm run build && npm install -g .${reset}`);
console.warn(` Then restart your shell so the updated PATH is picked up.`);
console.warn(` Without it, /gsd-* commands will fail with "command not found: gsd-sdk".`);
};
if (!fs.existsSync(sdkPackageJson)) {
warnManual(`SDK source tree not found at ${sdkDir}.`);
return;
emitSdkFatal(`SDK source tree not found at ${sdkDir}.`, { globalBin: null, exitCode: 1 });
}
console.log(`\n ${cyan}Building GSD SDK from source (${sdkDir})…${reset}`);
@@ -6690,36 +6771,43 @@ function installSdkIfNeeded() {
// 1. Install sdk build-time dependencies (tsc, etc.)
const installResult = spawnSync(npmCmd, ['install'], { cwd: sdkDir, stdio: 'inherit' });
if (installResult.status !== 0) {
warnManual('Failed to `npm install` in sdk/.');
return;
emitSdkFatal('Failed to `npm install` in sdk/.', { globalBin: null, exitCode: 1 });
}
// 2. Compile TypeScript → sdk/dist/
const buildResult = spawnSync(npmCmd, ['run', 'build'], { cwd: sdkDir, stdio: 'inherit' });
if (buildResult.status !== 0) {
warnManual('Failed to `npm run build` in sdk/.');
return;
emitSdkFatal('Failed to `npm run build` in sdk/.', { globalBin: null, exitCode: 1 });
}
// 3. Install the built package globally so `gsd-sdk` lands on PATH.
const globalResult = spawnSync(npmCmd, ['install', '-g', '.'], { cwd: sdkDir, stdio: 'inherit' });
if (globalResult.status !== 0) {
warnManual('Failed to `npm install -g .` from sdk/.');
emitSdkFatal('Failed to `npm install -g .` from sdk/.', { globalBin: null, exitCode: 1 });
}
// 4. Verify gsd-sdk is actually resolvable on PATH. npm's global bin dir is
// not always on the current shell's PATH (Homebrew prefixes, nvm setups,
// unconfigured npm prefix), so a zero exit status from `npm install -g`
// alone is not proof of a working binary (issue #2439 root cause).
const resolved = resolveGsdSdk();
if (resolved) {
console.log(` ${green}${reset} Built and installed GSD SDK from source (gsd-sdk resolved at ${resolved})`);
return;
}
// Verify gsd-sdk is actually resolvable on PATH. npm's global bin dir is
// not always on the current shell's PATH (Homebrew prefixes, nvm setups,
// unconfigured npm prefix), so a zero exit status from `npm install -g`
// alone is not proof of a working binary.
const resolverCmd = process.platform === 'win32' ? 'where' : 'which';
const verify = spawnSync(resolverCmd, ['gsd-sdk'], { encoding: 'utf-8' });
if (verify.status === 0 && verify.stdout && verify.stdout.trim()) {
console.log(` ${green}${reset} Built and installed GSD SDK from source (gsd-sdk resolved at ${verify.stdout.trim().split('\n')[0]})`);
} else {
warnManual('Built and installed GSD SDK from source but gsd-sdk is not on PATH — npm global bin may not be in your PATH.');
if (verify.stderr) console.warn(` resolver stderr: ${verify.stderr.trim()}`);
}
// Off-PATH: resolve npm global bin dir for actionable remediation.
const prefixResult = spawnSync(npmCmd, ['config', 'get', 'prefix'], { encoding: 'utf-8' });
const prefix = prefixResult.status === 0 ? (prefixResult.stdout || '').trim() : null;
const globalBin = prefix
? (process.platform === 'win32' ? prefix : path.join(prefix, 'bin'))
: null;
const allowOffPath = process.env.GSD_ALLOW_OFF_PATH === '1';
emitSdkFatal(
'Built and installed GSD SDK, but `gsd-sdk` is not on your PATH.',
{ globalBin, exitCode: allowOffPath ? 2 : 1 },
);
}
/**

View File

@@ -9,4 +9,4 @@ allowed-tools:
Show the following output to the user verbatim, with no extra commentary:
!`gsd-sdk query config-set-model-profile $ARGUMENTS --raw`
!`if ! command -v gsd-sdk >/dev/null 2>&1; then printf '⚠ gsd-sdk not found in PATH — /gsd-set-profile requires it.\n\nInstall the GSD SDK:\n npm install -g @gsd-build/sdk\n\nOr update GSD to get the latest packages:\n /gsd-update\n'; exit 1; fi; gsd-sdk query config-set-model-profile $ARGUMENTS --raw`

View File

@@ -1,7 +1,7 @@
---
name: gsd:sketch
description: Rapidly sketch UI/design ideas using throwaway HTML mockups with multi-variant exploration
argument-hint: "<design idea to explore> [--quick]"
description: Sketch UI/design ideas with throwaway HTML mockups, or propose what to sketch next (frontier mode)
argument-hint: "[design idea to explore] [--quick] [--text] or [frontier]"
allowed-tools:
- Read
- Write
@@ -10,11 +10,20 @@ allowed-tools:
- Grep
- Glob
- AskUserQuestion
- WebSearch
- WebFetch
- mcp__context7__resolve-library-id
- mcp__context7__query-docs
---
<objective>
Explore design directions through throwaway HTML mockups before committing to implementation.
Each sketch produces 2-3 variants for comparison. Sketches live in `.planning/sketches/` and
integrate with GSD commit patterns, state tracking, and handoff workflows.
integrate with GSD commit patterns, state tracking, and handoff workflows. Loads spike
findings to ground mockups in real data shapes and validated interaction patterns.
Two modes:
- **Idea mode** (default) — describe a design idea to sketch
- **Frontier mode** (no argument or "frontier") — analyzes existing sketch landscape and proposes consistency and frontier sketches
Does not require `/gsd-new-project` — auto-creates `.planning/sketches/` if needed.
</objective>
@@ -41,5 +50,5 @@ Design idea: $ARGUMENTS
<process>
Execute the sketch workflow from @~/.claude/get-shit-done/workflows/sketch.md end-to-end.
Preserve all workflow gates (intake, decomposition, variant evaluation, MANIFEST updates, commit patterns).
Preserve all workflow gates (intake, decomposition, target stack research, variant evaluation, MANIFEST updates, commit patterns).
</process>

View File

@@ -1,7 +1,7 @@
---
name: gsd:spike
description: Rapidly spike an idea with throwaway experiments to validate feasibility before planning
argument-hint: "<idea to validate> [--quick]"
description: Spike an idea through experiential exploration, or propose what to spike next (frontier mode)
argument-hint: "[idea to validate] [--quick] [--text] or [frontier]"
allowed-tools:
- Read
- Write
@@ -10,11 +10,20 @@ allowed-tools:
- Grep
- Glob
- AskUserQuestion
- WebSearch
- WebFetch
- mcp__context7__resolve-library-id
- mcp__context7__query-docs
---
<objective>
Rapid feasibility validation through focused, throwaway experiments. Each spike answers one
specific question with observable evidence. Spikes live in `.planning/spikes/` and integrate
with GSD commit patterns, state tracking, and handoff workflows.
Spike an idea through experiential exploration — build focused experiments to feel the pieces
of a future app, validate feasibility, and produce verified knowledge for the real build.
Spikes live in `.planning/spikes/` and integrate with GSD commit patterns, state tracking,
and handoff workflows.
Two modes:
- **Idea mode** (default) — describe an idea to spike
- **Frontier mode** (no argument or "frontier") — analyzes existing spike landscape and proposes integration and frontier spikes
Does not require `/gsd-new-project` — auto-creates `.planning/spikes/` if needed.
</objective>
@@ -33,9 +42,10 @@ Idea: $ARGUMENTS
**Available flags:**
- `--quick` — Skip decomposition/alignment, jump straight to building. Use when you already know what to spike.
- `--text` — Use plain-text numbered lists instead of AskUserQuestion (for non-Claude runtimes).
</context>
<process>
Execute the spike workflow from @~/.claude/get-shit-done/workflows/spike.md end-to-end.
Preserve all workflow gates (decomposition, risk ordering, verification, MANIFEST updates, commit patterns).
Preserve all workflow gates (prior spike check, decomposition, research, risk ordering, observability assessment, verification, MANIFEST updates, commit patterns).
</process>

View File

@@ -50,7 +50,7 @@ If `PATH_NOT_FOUND` or `MANIFEST_NOT_FOUND`: display error and exit.
Run the init query:
```bash
INIT=$(gsd-sdk query init.ingest-docs 2>/dev/null || gsd-sdk query init.default)
INIT=$(gsd-sdk query init.ingest-docs)
```
Parse `project_exists`, `planning_exists`, `has_git`, `project_path` from INIT.

View File

@@ -255,15 +255,16 @@ The sketch-findings skill will auto-load when building the UI.
## ▶ Next Up
**Start building** — implement the validated design
**Explore frontier sketches** — see what else is worth sketching based on what we've explored
`/gsd-plan-phase`
`/gsd-sketch` (run with no argument — its frontier mode analyzes the sketch landscape and proposes consistency and frontier sketches)
───────────────────────────────────────────────────────────────
**Also available:**
- `/gsd-plan-phase` — start building the real UI
- `/gsd-ui-phase` — generate a UI design contract for a frontend phase
- `/gsd-sketch` — sketch additional design areas
- `/gsd-sketch [idea]` — sketch a specific new design area
- `/gsd-explore` — continue exploring
───────────────────────────────────────────────────────────────
@@ -279,5 +280,6 @@ The sketch-findings skill will auto-load when building the UI.
- [ ] Reference files contain design decisions, CSS patterns, HTML structures, anti-patterns
- [ ] `.planning/sketches/WRAP-UP-SUMMARY.md` written for project history
- [ ] Project CLAUDE.md has auto-load routing line
- [ ] Summary presented with next-step routing
- [ ] Summary presented
- [ ] Next-step options presented (including frontier sketch exploration via `/gsd-sketch`)
</success_criteria>

View File

@@ -2,6 +2,10 @@
Explore design directions through throwaway HTML mockups before committing to implementation.
Each sketch produces 2-3 variants for comparison. Saves artifacts to `.planning/sketches/`.
Companion to `/gsd-sketch-wrap-up`.
Supports two modes:
- **Idea mode** (default) — user describes a design idea to sketch
- **Frontier mode** — no argument or "frontier" / "what should I sketch?" — analyzes existing sketch landscape and proposes consistency and frontier sketches
</purpose>
<required_reading>
@@ -25,9 +29,60 @@ Read all files referenced by the invoking prompt's execution_context before star
Parse `$ARGUMENTS` for:
- `--quick` flag → set `QUICK_MODE=true`
- `--text` flag → set `TEXT_MODE=true`
- `frontier` or empty → set `FRONTIER_MODE=true`
- Remaining text → the design idea to sketch
**Text mode (`workflow.text_mode: true` in config or `--text` flag):** Set `TEXT_MODE=true` if `--text` is present in `$ARGUMENTS` OR `text_mode` from init JSON is `true`. When TEXT_MODE is active, replace every `AskUserQuestion` call with a plain-text numbered list and ask the user to type their choice number. This is required for non-Claude runtimes (OpenAI Codex, Gemini CLI, etc.) where `AskUserQuestion` is not available.
**Text mode:** If TEXT_MODE is enabled, replace AskUserQuestion calls with plain-text numbered lists.
</step>
<step name="route">
## Routing
- **FRONTIER_MODE is true** → Jump to `frontier_mode`
- **Otherwise** → Continue to `setup_directory`
</step>
<step name="frontier_mode">
## Frontier Mode — Propose What to Sketch Next
### Load the Sketch Landscape
If no `.planning/sketches/` directory exists, tell the user there's nothing to analyze and offer to start fresh with an idea instead.
Otherwise, load in this order:
**a. MANIFEST.md** — the design direction, reference points, and sketch table with winners.
**b. Findings skills** — glob `./.claude/skills/sketch-findings-*/SKILL.md` and read any that exist, plus their `references/*.md`. These contain curated design decisions from prior wrap-ups.
**c. All sketch READMEs** — read `.planning/sketches/*/README.md` for design questions, winners, and tags.
### Analyze for Consistency Sketches
Review winning variants across all sketches. Look for:
- **Visual consistency gaps:** Two sketches made independent design choices that haven't been tested together.
- **State combinations:** Individual states validated but not seen in sequence.
- **Responsive gaps:** Validated at one viewport but the real app needs multiple.
- **Theme coherence:** Individual components look good but haven't been composed into a full-page view.
If consistency risks exist, present them as concrete proposed sketches with names and design questions. If no meaningful gaps, say so and skip.
### Analyze for Frontier Sketches
Think laterally about the design direction from MANIFEST.md and what's been explored:
- **Unsketched screens:** UI surfaces assumed but unexplored.
- **Interaction patterns:** Static layouts validated but transitions, loading, drag-and-drop need feeling.
- **Edge case UI:** 0 items, 1000 items, errors, slow connections.
- **Alternative directions:** Fresh takes on "fine but not great" sketches.
- **Polish passes:** Typography, spacing, micro-interactions, empty states.
Present frontier sketches as concrete proposals numbered from the highest existing sketch number.
### Get Alignment and Execute
Present all consistency and frontier candidates, then ask which to run. When the user picks sketches, update `.planning/sketches/MANIFEST.md` and proceed directly to building them starting at `build_sketches`.
</step>
<step name="setup_directory">
@@ -49,27 +104,45 @@ COMMIT_DOCS=$(gsd-sdk query config-get commit_docs 2>/dev/null || echo "true")
</step>
<step name="mood_intake">
**If `QUICK_MODE` is true:** Skip mood intake. Use whatever the user provided in `$ARGUMENTS` as the design direction. Jump to `decompose`.
**If `QUICK_MODE` is true:** Skip mood intake. Use whatever the user provided in `$ARGUMENTS` as the design direction. Jump to `load_spike_context`.
**Otherwise:**
**Text mode:** If TEXT_MODE is enabled (set in the banner step), replace AskUserQuestion calls with plain-text numbered lists — emit the options and ask the user to type the number of their choice.
Before sketching anything, explore the design intent through conversation. Ask one question at a time — using AskUserQuestion in normal mode, or a plain-text numbered list if TEXT_MODE is active — with a paragraph of context and reasoning for each.
Before sketching anything, explore the design intent through conversation. Ask one question at a time — using AskUserQuestion in normal mode, or a plain-text numbered list if TEXT_MODE is active.
**Questions to cover (adapt to what the user has already shared):**
1. **Feel:** "What should this feel like? Give me adjectives, emotions, or a vibe." (e.g., "clean and clinical", "warm and playful", "dense and powerful")
2. **References:** "What apps, sites, or products have a similar feel to what you're imagining?" (gives concrete visual anchors)
3. **Core action:** "What's the single most important thing a user does here?" (focuses the sketch on what matters)
1. **Feel:** "What should this feel like? Give me adjectives, emotions, or a vibe."
2. **References:** "What apps, sites, or products have a similar feel to what you're imagining?"
3. **Core action:** "What's the single most important thing a user does here?"
You may need more or fewer questions depending on how much the user shares upfront. After each answer, briefly reflect what you heard and how it shapes your thinking.
After each answer, briefly reflect what you heard and how it shapes your thinking.
When you have enough signal, ask: **"I think I have a good sense of the direction. Ready for me to sketch, or want to keep discussing?"**
Only proceed when the user says go.
</step>
<step name="load_spike_context">
## Load Spike Context
If spikes exist for this project, read them to ground the sketches in reality. Mockups are still pure HTML, but they should reflect what's actually been proven — real data shapes, real component names, real interaction patterns.
**a.** Glob for `./.claude/skills/spike-findings-*/SKILL.md` and read any that exist, plus their `references/*.md`. These contain validated patterns and requirements.
**b.** Read `.planning/spikes/MANIFEST.md` if it exists — check the Requirements section for non-negotiable design constraints (e.g., "must support streaming", "must render markdown"). These requirements should be visible in the mockup even though the mockup doesn't implement them for real.
**c.** Read `.planning/spikes/CONVENTIONS.md` if it exists — the established stack informs what's buildable and what interaction patterns are idiomatic.
**How spike context improves sketches:**
- Use real field names and data shapes from spike findings instead of generic placeholders
- Show realistic UI states that match what the spikes proved (e.g., if streaming was validated, show a streaming message state)
- Reference real component names and patterns from the target stack
- Include interaction states that reflect what the spikes discovered (loading, error, reconnection states)
**If no spikes exist**, skip this step.
</step>
<step name="decompose">
Break the idea into 2-5 design questions. Present as a table:
@@ -92,6 +165,28 @@ Bad sketches:
Present the table and get alignment before building.
</step>
<step name="research_stack">
## Research the Target Stack
Before sketching, ground the design in what's actually buildable. Sketches are HTML, but they should reflect real constraints of the target implementation.
**a. Identify the target stack.** Check for package.json, Cargo.toml, etc. If the user mentioned a framework (React, SwiftUI, Flutter, etc.), note it.
**b. Check component/pattern availability.** Use context7 (resolve-library-id → query-docs) or web search to answer:
- What layout primitives does the target framework provide?
- Are there existing component libraries in use? What components are available?
- What interaction patterns are idiomatic?
**c. Note constraints that affect design:**
- Platform conventions (iOS nav patterns, desktop menu bars, terminal grid constraints)
- Framework limitations (what's easy vs requires custom work)
- Existing design tokens or theme systems already in the project
**d. Let research inform variants.** At least one variant should follow the path of least resistance for the target stack.
**Skip when unnecessary.** Greenfield project with no stack, or user says "just explore visually." The point is grounding, not gatekeeping.
</step>
<step name="create_manifest">
Create or update `.planning/sketches/MANIFEST.md`:
@@ -124,26 +219,24 @@ Build each sketch in order.
### For Each Sketch:
**a.** Find next available number by checking existing `.planning/sketches/NNN-*/` directories.
Format: three-digit zero-padded + hyphenated descriptive name.
**a.** Find next available number. Format: three-digit zero-padded + hyphenated descriptive name.
**b.** Create the sketch directory: `.planning/sketches/NNN-descriptive-name/`
**c.** Build `index.html` with 2-3 variants:
**First round — dramatic differences:** Build 2-3 meaningfully different approaches to the design question. Different layouts, different visual structures, different interaction models.
**Subsequent rounds — refinements:** Once the user has picked a direction or cherry-picked elements, build subtler variations within that direction.
**First round — dramatic differences:** 2-3 meaningfully different approaches.
**Subsequent rounds — refinements:** Subtler variations within the chosen direction.
Each variant is a page/tab in the same HTML file. Include:
- Tab navigation to switch between variants (see `sketch-variant-patterns.md`)
- Clear labels: "Variant A: Sidebar Layout", "Variant B: Top Nav", etc.
- The sketch toolbar (see `sketch-tooling.md`)
- All interactive elements functional (see `sketch-interactivity.md`)
- Real-ish content, not lorem ipsum
- Real-ish content, not lorem ipsum (use real field names from spike context if available)
- Link to `../themes/default.css` for shared theme variables
**All sketches are plain HTML with inline CSS and JS.** No build step, no npm, no framework. Opens instantly in a browser.
**All sketches are plain HTML with inline CSS and JS.** No build step, no npm, no framework.
**d.** Write `README.md`:
@@ -190,16 +283,16 @@ Compare: {what to look for between variants}
──────────────────────────────────────────────────────────────
**f.** Handle feedback:
- **Pick a direction:** "I like variant B" → mark winner in README, move to next sketch
- **Cherry-pick elements:** "Rounded edges from A, color treatment from C" → build a synthesis as a new variant, show again
- **Want more exploration:** "None of these feel right, try X instead" → build new variants
- **Pick a direction:** mark winner, move to next sketch
- **Cherry-pick elements:** build synthesis as new variant, show again
- **Want more exploration:** build new variants
Iterate until the user is satisfied with a direction for this sketch.
Iterate until satisfied.
**g.** Finalize:
1. Mark the winning variant in the README frontmatter (`winner: "B"`)
2. Add ★ indicator to the winning tab in the HTML
3. Update `.planning/sketches/MANIFEST.md` with the sketch row
1. Mark winning variant in README frontmatter (`winner: "B"`)
2. Add ★ indicator to winning tab in HTML
3. Update `.planning/sketches/MANIFEST.md`
**h.** Commit (if `COMMIT_DOCS` is true):
```bash
@@ -215,7 +308,7 @@ gsd-sdk query commit "docs(sketch-NNN): [winning direction] — [key visual insi
</step>
<step name="report">
After all sketches complete, present the summary:
After all sketches complete:
```
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
@@ -243,8 +336,8 @@ After all sketches complete, present the summary:
───────────────────────────────────────────────────────────────
**Also available:**
- `/gsd-sketch` — sketch more (or run with no argument for frontier mode)
- `/gsd-plan-phase` — start building the real UI
- `/gsd-explore` — continue exploring the concept
- `/gsd-spike` — spike technical feasibility of a design pattern
───────────────────────────────────────────────────────────────
@@ -255,7 +348,9 @@ After all sketches complete, present the summary:
<success_criteria>
- [ ] `.planning/sketches/` created (auto-creates if needed, no project init required)
- [ ] Design direction explored conversationally before any code (unless --quick)
- [ ] Each sketch has 2-3 variants for comparison
- [ ] Spike context loaded — real data shapes, requirements, and conventions inform mockups
- [ ] Target stack researched — component availability, constraints, idioms (unless greenfield/skipped)
- [ ] Each sketch has 2-3 variants for comparison (at least one follows path of least resistance)
- [ ] User can open and interact with sketches in a browser
- [ ] Winning variant selected and marked for each sketch
- [ ] All variants preserved (winner marked, not others deleted)

View File

@@ -1,8 +1,8 @@
<purpose>
Curate spike experiment findings and package them into a persistent project skill for future
build conversations. Reads from `.planning/spikes/`, writes skill to `./.claude/skills/spike-findings-[project]/`
(project-local) and summary to `.planning/spikes/WRAP-UP-SUMMARY.md`.
Companion to `/gsd-spike`.
Package spike experiment findings into a persistent project skill — an implementation blueprint
for future build conversations. Reads from `.planning/spikes/`, writes skill to
`./.claude/skills/spike-findings-[project]/` (project-local) and summary to
`.planning/spikes/WRAP-UP-SUMMARY.md`. Companion to `/gsd-spike`.
</purpose>
<required_reading>
@@ -22,7 +22,7 @@ Read all files referenced by the invoking prompt's execution_context before star
<step name="gather">
## Gather Spike Inventory
1. Read `.planning/spikes/MANIFEST.md` for the overall idea context
1. Read `.planning/spikes/MANIFEST.md` for the overall idea context and requirements
2. Glob `.planning/spikes/*/README.md` and parse YAML frontmatter from each
3. Check if `./.claude/skills/spike-findings-*/SKILL.md` exists for this project
- If yes: read its `processed_spikes` list from the metadata section and filter those out
@@ -41,53 +41,28 @@ COMMIT_DOCS=$(gsd-sdk query config-get commit_docs 2>/dev/null || echo "true")
```
</step>
<step name="curate">
## Curate Spikes One-at-a-Time
<step name="auto_include">
## Auto-Include All Spikes
Present each unprocessed spike in ascending order. For each spike, show:
Include all unprocessed spikes automatically. Present a brief inventory showing what's being processed:
- **Spike number and name**
- **Validates:** the Given/When/Then from frontmatter
- **Verdict:** VALIDATED / INVALIDATED / PARTIAL
- **Tags:** from frontmatter
- **Key findings:** summarize the Results section from the README
- **Grey areas:** anything uncertain or partially proven
```
Processing N spikes:
001 — name (VALIDATED)
002 — name (PARTIAL)
003 — name (INVALIDATED)
```
Then ask the user:
╔══════════════════════════════════════════════════════════════╗
║ CHECKPOINT: Decision Required ║
╚══════════════════════════════════════════════════════════════╝
Spike {NNN}: {name} — {verdict}
{key findings summary}
──────────────────────────────────────────────────────────────
→ Include / Exclude / Partial / Help me UAT this
──────────────────────────────────────────────────────────────
**If "Help me UAT this":**
1. Read the spike's README "How to Run" and "What to Expect" sections
2. Present step-by-step instructions
3. Ask: "Does this match what you expected?"
4. After UAT, return to the include/exclude/partial decision
**If "Partial":**
Ask what specifically to include or exclude. Record their notes alongside the spike.
Every spike carries forward:
- **VALIDATED** spikes provide proven patterns
- **PARTIAL** spikes provide constrained patterns
- **INVALIDATED** spikes provide landmines and dead ends
</step>
<step name="group">
## Auto-Group by Feature Area
After all spikes are curated:
1. Read all included spikes' tags, names, `related` fields, and content
2. Propose feature-area groupings, e.g.:
- "**WebSocket Streaming** — spikes 001, 004, 007"
- "**Foo API Integration** — spikes 002, 003"
- "**PDF Parsing** — spike 005"
3. Present the grouping for approval — user may merge, split, rename, or rearrange
Group spikes by feature area based on tags, names, `related` fields, and content. Proceed directly into synthesis.
Each group becomes one reference file in the generated skill.
</step>
@@ -118,21 +93,29 @@ For each included spike:
<step name="synthesize">
## Synthesize Reference Files
For each feature-area group, write a reference file at `references/[feature-area-name].md`:
For each feature-area group, write a reference file at `references/[feature-area-name].md` as an **implementation blueprint** — it should read like a recipe, not a research paper. A future build session should be able to follow this and build the feature correctly without re-spiking anything.
```markdown
# [Feature Area Name]
## Validated Patterns
[For each validated finding: describe the approach that works, include key code snippets extracted from the spike source, explain why it works]
## Requirements
## Landmines
[Things that look right but aren't. Gotchas. Anti-patterns discovered during spiking.]
[Non-negotiable design decisions from MANIFEST.md Requirements section that apply to this feature area. These MUST be honored in the real build. E.g., "Must use streaming JSON output", "Must support reconnection".]
## How to Build It
[Step-by-step: what to install, how to configure, what code pattern to use. Include key code snippets extracted from the spike source. This is the proven approach — not theory, but tested and working code.]
## What to Avoid
[Things that look right but aren't. Gotchas. Anti-patterns discovered during spiking. Dead ends that were tried and failed.]
## Constraints
[Hard facts: rate limits, library limitations, version requirements, incompatibilities]
## Origin
Synthesized from spikes: NNN, NNN, NNN
Source files available in: sources/NNN-spike-name/, sources/NNN-spike-name/
```
@@ -146,7 +129,7 @@ Create (or update) the generated skill's SKILL.md:
```markdown
---
name: spike-findings-[project-dir-name]
description: Validated patterns, constraints, and implementation knowledge from spike experiments. Auto-loaded during implementation work on [project-dir-name].
description: Implementation blueprint from spike experiments. Requirements, proven patterns, and verified knowledge for building [project-dir-name]. Auto-loaded during implementation work.
---
<context>
@@ -157,6 +140,15 @@ description: Validated patterns, constraints, and implementation knowledge from
Spike sessions wrapped: [date(s)]
</context>
<requirements>
## Requirements
[Copied directly from MANIFEST.md Requirements section. These are non-negotiable design decisions that emerged from the user's choices during spiking. Every feature area reference must honor these.]
- [requirement 1]
- [requirement 2]
</requirements>
<findings_index>
## Feature Areas
@@ -193,13 +185,9 @@ Write `.planning/spikes/WRAP-UP-SUMMARY.md` for project history:
**Feature areas:** [list]
**Skill output:** `./.claude/skills/spike-findings-[project]/`
## Included Spikes
| # | Name | Verdict | Feature Area |
|---|------|---------|--------------|
## Excluded Spikes
| # | Name | Reason |
|---|------|--------|
## Processed Spikes
| # | Name | Type | Verdict | Feature Area |
|---|------|------|---------|--------------|
## Key Findings
[consolidated findings summary]
@@ -218,11 +206,47 @@ Add an auto-load routing line to the project's CLAUDE.md (create the file if it
If this routing line already exists (append mode), leave it as-is.
</step>
<step name="generate_conventions">
## Generate or Update CONVENTIONS.md
Analyze all processed spikes for recurring patterns and write `.planning/spikes/CONVENTIONS.md`. This file tells future spike sessions *how we spike* — the stack, structure, and patterns that have been established.
1. Read all spike source code and READMEs looking for:
- **Stack choices** — What language/framework/runtime appears across multiple spikes?
- **Structure patterns** — Common file layouts, port numbers, naming schemes
- **Recurring approaches** — How auth is handled, how styling is done, how data is served
- **Tools & libraries** — Packages that showed up repeatedly with versions that worked
2. Write or update `.planning/spikes/CONVENTIONS.md`:
```markdown
# Spike Conventions
Patterns and stack choices established across spike sessions. New spikes follow these unless the question requires otherwise.
## Stack
[What we use for frontend, backend, scripts, and why — derived from what repeated across spikes]
## Structure
[Common file layouts, port assignments, naming patterns]
## Patterns
[Recurring approaches: how we handle auth, how we style, how we serve, etc.]
## Tools & Libraries
[Preferred packages with versions that worked, and any to avoid]
```
3. Only include patterns that appeared in 2+ spikes or were explicitly chosen by the user.
4. If `CONVENTIONS.md` already exists (append mode), update sections with new patterns. Remove entries contradicted by newer spikes.
</step>
<step name="commit">
Commit all artifacts (if `COMMIT_DOCS` is true):
```bash
gsd-sdk query commit "docs(spike-wrap-up): package [N] spike findings into project skill" .planning/spikes/WRAP-UP-SUMMARY.md
gsd-sdk query commit "docs(spike-wrap-up): package [N] spike findings into project skill" .planning/spikes/WRAP-UP-SUMMARY.md .planning/spikes/CONVENTIONS.md
```
</step>
@@ -232,29 +256,37 @@ gsd-sdk query commit "docs(spike-wrap-up): package [N] spike findings into proje
GSD ► SPIKE WRAP-UP COMPLETE ✓
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
**Curated:** {N} spikes ({included} included, {excluded} excluded)
**Processed:** {N} spikes
**Feature areas:** {list}
**Skill:** `./.claude/skills/spike-findings-[project]/`
**Conventions:** `.planning/spikes/CONVENTIONS.md`
**Summary:** `.planning/spikes/WRAP-UP-SUMMARY.md`
**CLAUDE.md:** routing line added
The spike-findings skill will auto-load in future build conversations.
```
</step>
<step name="whats_next">
## What's Next
After the summary, present next-step options:
───────────────────────────────────────────────────────────────
## ▶ Next Up
**Start building** — plan the real implementation
**Explore frontier spikes** — see what else is worth spiking based on what we've learned
`/gsd-plan-phase`
`/gsd-spike` (run with no argument — its frontier mode analyzes the spike landscape and proposes integration and frontier spikes)
───────────────────────────────────────────────────────────────
**Also available:**
- `/gsd-add-phase`add a phase based on spike findings
- `/gsd-spike` — spike additional ideas
- `/gsd-plan-phase`start planning the real implementation
- `/gsd-spike [idea]` — spike a specific new idea
- `/gsd-explore` — continue exploring
- Other
───────────────────────────────────────────────────────────────
</step>
@@ -262,12 +294,13 @@ The spike-findings skill will auto-load in future build conversations.
</process>
<success_criteria>
- [ ] Every unprocessed spike presented for individual curation
- [ ] Feature-area grouping proposed and approved
- [ ] Spike-findings skill exists at `./.claude/skills/` with SKILL.md, references/, sources/
- [ ] Core source files from included spikes copied into sources/
- [ ] Reference files contain validated patterns, code snippets, landmines, constraints
- [ ] All unprocessed spikes auto-included and processed
- [ ] Spikes grouped by feature area
- [ ] Spike-findings skill exists at `./.claude/skills/` with SKILL.md (including requirements), references/, sources/
- [ ] Reference files are implementation blueprints with Requirements, How to Build It, What to Avoid, Constraints
- [ ] `.planning/spikes/CONVENTIONS.md` created or updated with recurring stack/structure/pattern choices
- [ ] `.planning/spikes/WRAP-UP-SUMMARY.md` written for project history
- [ ] Project CLAUDE.md has auto-load routing line
- [ ] Summary presented with next-step routing
- [ ] Summary presented
- [ ] Next-step options presented (including frontier spike exploration via `/gsd-spike`)
</success_criteria>

View File

@@ -1,7 +1,11 @@
<purpose>
Rapid feasibility validation through focused, throwaway experiments. Each spike answers one
specific question with observable evidence. Saves artifacts to `.planning/spikes/`.
Companion to `/gsd-spike-wrap-up`.
Spike an idea through experiential exploration — build focused experiments to feel the pieces
of a future app, validate feasibility, and produce verified knowledge for the real build.
Saves artifacts to `.planning/spikes/`. Companion to `/gsd-spike-wrap-up`.
Supports two modes:
- **Idea mode** (default) — user describes an idea to spike
- **Frontier mode** — no argument or "frontier" / "what should I spike?" — analyzes existing spike landscape and proposes integration and frontier spikes
</purpose>
<required_reading>
@@ -19,7 +23,63 @@ Read all files referenced by the invoking prompt's execution_context before star
Parse `$ARGUMENTS` for:
- `--quick` flag → set `QUICK_MODE=true`
- `--text` flag → set `TEXT_MODE=true`
- `frontier` or empty → set `FRONTIER_MODE=true`
- Remaining text → the idea to spike
**Text mode:** If TEXT_MODE is enabled, replace AskUserQuestion calls with plain-text numbered lists.
</step>
<step name="route">
## Routing
- **FRONTIER_MODE is true** → Jump to `frontier_mode`
- **Otherwise** → Continue to `setup_directory`
</step>
<step name="frontier_mode">
## Frontier Mode — Propose What to Spike Next
### Load the Spike Landscape
If no `.planning/spikes/` directory exists, tell the user there's nothing to analyze and offer to start fresh with an idea instead.
Otherwise, load in this order:
**a. MANIFEST.md** — the overall idea, requirements, and spike table with verdicts.
**b. Findings skills** — glob `./.claude/skills/spike-findings-*/SKILL.md` and read any that exist, plus their `references/*.md`. These contain curated knowledge from prior wrap-ups.
**c. CONVENTIONS.md** — read `.planning/spikes/CONVENTIONS.md` if it exists. Established stack and patterns.
**d. All spike READMEs** — read `.planning/spikes/*/README.md` for verdicts, results, investigation trails, and tags.
### Analyze for Integration Spikes
Review every pair and cluster of VALIDATED spikes. Look for:
- **Shared resources:** Two spikes that both touch the same API, database, state, or data format but were tested independently.
- **Data handoffs:** Spike A produces output that Spike B consumes. The formats were assumed compatible but never proven.
- **Timing/ordering:** Spikes that work in isolation but have sequencing dependencies in the real flow.
- **Resource contention:** Spikes that individually work but may compete for connections, memory, rate limits, or tokens when combined.
If integration risks exist, present them as concrete proposed spikes with names and Given/When/Then validation questions. If no meaningful integration risks exist, say so and skip this category.
### Analyze for Frontier Spikes
Think laterally about the overall idea from MANIFEST.md and what's been proven so far. Consider:
- **Gaps in the vision:** Capabilities assumed but unproven.
- **Discovered dependencies:** Findings that reveal new questions.
- **Alternative approaches:** Different angles for PARTIAL or INVALIDATED spikes.
- **Adjacent capabilities:** Things that would meaningfully improve the idea if feasible.
- **Comparison opportunities:** Approaches that worked but felt heavy.
Present frontier spikes as concrete proposals numbered from the highest existing spike number with Given/When/Then and risk ordering.
### Get Alignment and Execute
Present all integration and frontier candidates, then ask which to run. When the user picks spikes, write definitions into `.planning/spikes/MANIFEST.md` (appending to existing table) and proceed directly to building them starting at `research`.
</step>
<step name="setup_directory">
@@ -41,13 +101,16 @@ COMMIT_DOCS=$(gsd-sdk query config-get commit_docs 2>/dev/null || echo "true")
</step>
<step name="detect_stack">
Check for the project's tech stack to inform spike technology choices:
Check for the project's tech stack to inform spike technology choices.
**Check conventions first.** If `.planning/spikes/CONVENTIONS.md` exists, follow its stack and patterns — these represent validated choices the user expects to see continued.
**Then check the project stack:**
```bash
ls package.json pyproject.toml Cargo.toml go.mod 2>/dev/null
```
Use the project's language/framework by default. For greenfield projects with no existing stack, pick whatever gets to a runnable result fastest (Python, Node, Bash, single HTML file).
Use the project's language/framework by default. For greenfield projects with no conventions and no existing stack, pick whatever gets to a runnable result fastest.
Avoid unless the spike specifically requires it:
- Complex package management beyond `npm install` or `pip install`
@@ -56,40 +119,53 @@ Avoid unless the spike specifically requires it:
- Env files or config systems — hardcode everything
</step>
<step name="load_prior_context">
If `.planning/spikes/` has existing content, load context in this priority order:
**a. Conventions:** Read `.planning/spikes/CONVENTIONS.md` if it exists.
**b. Findings skills:** Glob for `./.claude/skills/spike-findings-*/SKILL.md` and read any that exist, plus their `references/*.md` files.
**c. Manifest:** Read `.planning/spikes/MANIFEST.md` for the index of all spikes.
**d. Related READMEs:** Based on the new idea, identify which prior spikes are related by matching tags, names, technologies, or domain overlap. Read only those `.planning/spikes/*/README.md` files. Skip unrelated ones.
Cross-reference against this full body of prior work:
- **Skip already-validated questions.** Note the prior spike number and move on.
- **Build on prior findings.** Don't repeat failed approaches. Use their Research and Results sections.
- **Reuse prior research.** Carry findings forward rather than re-researching.
- **Follow established conventions.** Mention any deviation.
- **Call out relevant prior art** when presenting the decomposition.
If no `.planning/spikes/` exists, skip this step.
</step>
<step name="decompose">
**If `QUICK_MODE` is true:** Skip decomposition and alignment. Take the user's idea as a single spike question. Assign it spike number `001` (or next available). Jump to `build_spikes`.
**If `QUICK_MODE` is true:** Skip decomposition and alignment. Take the user's idea as a single spike question. Assign it the next available number. Jump to `research`.
**Otherwise:**
Break the idea into 2-5 independent questions that each prove something specific. Frame each as an informal Given/When/Then. Present as a table:
Break the idea into 2-5 independent questions. Frame each as Given/When/Then. Present as a table:
```
| # | Spike | Validates (Given/When/Then) | Risk |
|---|-------|-----------------------------|------|
| 001 | websocket-streaming | Given a WS connection, when LLM streams tokens, then client receives chunks < 100ms | **High** |
| 002 | pdf-extraction | Given a multi-page PDF, when parsed with pdfjs, then structured text is extractable | Medium |
| # | Spike | Type | Validates (Given/When/Then) | Risk |
|---|-------|------|-----------------------------|------|
| 001 | websocket-streaming | standard | Given a WS connection, when LLM streams tokens, then client receives chunks < 100ms | **High** |
| 002a | pdf-parse-pdfjs | comparison | Given a multi-page PDF, when parsed with pdfjs, then structured text is extractable | Medium |
| 002b | pdf-parse-camelot | comparison | Given a multi-page PDF, when parsed with camelot, then structured text is extractable | Medium |
```
Good spikes answer one specific feasibility question:
- "Can we parse X format and extract Y?" — script that does it on a sample file
- "How fast is X approach?" — benchmark with real-ish data
- "Can we get X and Y to talk to each other?" — thinnest integration
- "What does X feel like as a UI?" — minimal interactive prototype
- "Does X API actually support Y?" — script that calls it and shows the response
**Spike types:**
- **standard** — one approach answering one question
- **comparison** — same question, different approaches. Shared number with letter suffix.
Bad spikes are too broad or don't produce observable output:
- "Set up the project" — not a question, just busywork
- "Design the architecture" — planning, not spiking
- "Build the backend" — too broad, no specific question
Good spikes: specific feasibility questions with observable output.
Bad spikes: too broad, no observable output, or just reading/planning.
Order by risk — the spike most likely to kill the idea runs first.
Order by risk — most likely to kill the idea runs first.
</step>
<step name="align">
**If `QUICK_MODE` is true:** Skip.
Present the ordered spike list and ask which to build:
╔══════════════════════════════════════════════════════════════╗
║ CHECKPOINT: Decision Required ║
╚══════════════════════════════════════════════════════════════╝
@@ -99,8 +175,33 @@ Present the ordered spike list and ask which to build:
──────────────────────────────────────────────────────────────
→ Build all in this order, or adjust the list?
──────────────────────────────────────────────────────────────
</step>
The user may reorder, merge, split, or skip spikes. Wait for alignment.
<step name="research">
## Research and Briefing Before Each Spike
This step runs **before each individual spike**, not once at the start.
**a. Present a spike briefing:**
> **Spike NNN: Descriptive Name**
> [2-3 sentences: what this spike is, why it matters, key risk or unknown.]
**b. Research the current state of the art.** Use context7 (resolve-library-id → query-docs) for libraries/frameworks. Use web search for APIs/services without a context7 entry. Read actual documentation.
**c. Surface competing approaches** as a table:
| Approach | Tool/Library | Pros | Cons | Status |
|----------|-------------|------|------|--------|
| ... | ... | ... | ... | ... |
**Chosen approach:** [which one and why]
If 2+ credible approaches exist, plan to build quick variants within the spike and compare them.
**d. Capture research findings** in a `## Research` section in the README.
**Skip when unnecessary** for pure logic with no external dependencies.
</step>
<step name="create_manifest">
@@ -112,33 +213,75 @@ Create or update `.planning/spikes/MANIFEST.md`:
## Idea
[One paragraph describing the overall idea being explored]
## Requirements
[Design decisions that emerged from the user's choices during spiking. Non-negotiable for the real build. Updated as spikes progress.]
- [e.g., "Must use streaming JSON output, not single-response"]
- [e.g., "Must support reconnection on network failure"]
## Spikes
| # | Name | Validates | Verdict | Tags |
|---|------|-----------|---------|------|
| # | Name | Type | Validates | Verdict | Tags |
|---|------|------|-----------|---------|------|
```
If MANIFEST.md already exists, append new spikes to the existing table.
**Track requirements as they emerge.** When the user expresses a preference during spiking, add it to the Requirements section immediately.
</step>
<step name="reground">
## Re-Ground Before Each Spike
Before starting each spike (not just the first), re-read `.planning/spikes/MANIFEST.md` and `.planning/spikes/CONVENTIONS.md` to prevent drift within long sessions. Check the Requirements section — make sure the spike doesn't contradict any established requirements.
</step>
<step name="build_spikes">
Build each spike sequentially, highest-risk first.
## Build Each Spike Sequentially
**Depth over speed.** The goal is genuine understanding, not a quick verdict. Never declare VALIDATED after a single happy-path test. Follow surprising findings. Test edge cases. Document the investigation trail, not just the conclusion.
**Comparison spikes** use shared number with letter suffix: `NNN-a-name` / `NNN-b-name`. Build back-to-back, then head-to-head comparison.
### For Each Spike:
**a.** Find next available number by checking existing `.planning/spikes/NNN-*/` directories.
Format: three-digit zero-padded + hyphenated descriptive name.
**a.** Create `.planning/spikes/NNN-descriptive-name/`
**b.** Create the spike directory: `.planning/spikes/NNN-descriptive-name/`
**b.** Default to giving the user something they can experience. The bias should be toward building a simple UI or interactive demo, not toward stdout that only Claude reads. The user wants to *feel* the spike working, not just be told it works.
**c.** Build the minimum code that answers the spike's question. Every line must serve the question — nothing incidental. If auth isn't the question, hardcode a token. If the database isn't the question, use a JSON file. Strip everything that doesn't directly answer "does X work?"
**The default is: build something the user can interact with.** This could be:
- A simple HTML page that shows the result visually
- A web UI with a button that triggers the action and shows the response
- A page that displays data flowing through a pipeline
- A minimal interface where the user can try different inputs and see outputs
**d.** Write `README.md` with YAML frontmatter:
**Only fall back to stdout/CLI verification when the spike is genuinely about a fact, not a feeling:**
- Pure data transformation where the answer is "yes it parses correctly"
- Binary yes/no questions (does this API authenticate? does this library exist?)
- Benchmark numbers (how fast is X? how much memory does Y use?)
When in doubt, build the UI. It takes a few extra minutes but produces a spike the user can actually demo and feel confident about.
**If the spike needs runtime observability,** build a forensic log layer:
1. Event log array with ISO timestamps and category tags
2. Export mechanism (server: GET endpoint, CLI: JSON file, browser: Export button)
3. Log summary (event counts, duration, errors, metadata)
4. Analysis helpers if volume warrants it
**c.** Build the code. Start with simplest version, then deepen.
**d.** Iterate when findings warrant it:
- **Surprising surface?** Write a follow-up test that isolates and explores it.
- **Answer feels shallow?** Probe edge cases — large inputs, concurrent requests, malformed data, network failures.
- **Assumption wrong?** Adjust. Note the pivot in the README.
Multiple files per spike are expected for complex questions (e.g., `test-basic.js`, `test-edge-cases.js`, `benchmark.js`).
**e.** Write `README.md` with YAML frontmatter:
```markdown
---
spike: NNN
name: descriptive-name
type: standard
validates: "Given [precondition], when [action], then [expected outcome]"
verdict: PENDING
related: []
@@ -148,30 +291,38 @@ tags: [tag1, tag2]
# Spike NNN: Descriptive Name
## What This Validates
[The specific feasibility question, framed as Given/When/Then]
[Given/When/Then]
## Research
[Docs checked, approach comparison table, chosen approach, gotchas. Omit if no external deps.]
## How to Run
[Single command or short sequence to run the spike]
[Command(s)]
## What to Expect
[Concrete observable outcomes: "When you click X, you should see Y within Z seconds"]
[Concrete observable outcomes]
## Observability
[If forensic log layer exists. Omit otherwise.]
## Investigation Trail
[Updated as spike progresses. Document each iteration: what tried, what revealed, what tried next.]
## Results
[Filled in after running — verdict, evidence, surprises]
[Verdict, evidence, surprises, log analysis findings.]
```
**e.** Auto-link related spikes: read existing spike READMEs and infer relationships from tags, names, and descriptions. Write the `related` field silently.
**f.** Auto-link related spikes silently.
**f.** Run and verify:
- If self-verifiable: run it, check output, update README verdict and Results section
- If needs human judgment: run it, present instructions using a checkpoint box:
**g.** Run and verify:
- Self-verifiable: run, iterate if findings warrant deeper investigation, update verdict
- Needs human judgment: present checkpoint box:
╔══════════════════════════════════════════════════════════════╗
║ CHECKPOINT: Verification Required ║
╚══════════════════════════════════════════════════════════════╝
**Spike {NNN}: {name}**
**How to run:** {command}
**What to expect:** {concrete outcomes}
@@ -179,43 +330,69 @@ tags: [tag1, tag2]
→ Does this match what you expected? Describe what you see.
──────────────────────────────────────────────────────────────
**g.** Update verdict to VALIDATED / INVALIDATED / PARTIAL. Update Results section with evidence.
**h.** Update `.planning/spikes/MANIFEST.md` with the spike's row.
**i.** Commit (if `COMMIT_DOCS` is true):
```bash
gsd-sdk query commit "docs(spike-NNN): [VERDICT] — [key finding in one sentence]" .planning/spikes/NNN-descriptive-name/ .planning/spikes/MANIFEST.md
gsd-sdk query commit "docs(spike-NNN): [VERDICT] — [key finding]" .planning/spikes/NNN-descriptive-name/ .planning/spikes/MANIFEST.md
```
**j.** Report before moving to next spike:
**j.** Report:
```
◆ Spike NNN: {name}
Verdict: {VALIDATED ✓ / INVALIDATED ✗ / PARTIAL ⚠}
Finding: {one sentence}
Impact: {effect on remaining spikes, if any}
Key findings: {not just verdict — investigation trail, surprises, edge cases explored}
Impact: {effect on remaining spikes}
```
**k.** If a spike invalidates a core assumption: stop and present:
Do not rush to a verdict. A spike that says "VALIDATED — it works" with no nuance is almost always incomplete.
**k.** If core assumption invalidated:
╔══════════════════════════════════════════════════════════════╗
║ CHECKPOINT: Decision Required ║
╚══════════════════════════════════════════════════════════════╝
Core assumption invalidated by Spike {NNN}.
{what was invalidated and why}
──────────────────────────────────────────────────────────────
→ Continue with remaining spikes / Pivot approach / Abandon
──────────────────────────────────────────────────────────────
</step>
Only proceed if the user says to.
<step name="update_conventions">
## Update Conventions
After all spikes in this session are built, update `.planning/spikes/CONVENTIONS.md` with patterns that emerged or solidified.
```markdown
# Spike Conventions
Patterns and stack choices established across spike sessions. New spikes follow these unless the question requires otherwise.
## Stack
[What we use for frontend, backend, scripts, and why]
## Structure
[Common file layouts, port assignments, naming patterns]
## Patterns
[Recurring approaches: how we handle auth, how we style, how we serve]
## Tools & Libraries
[Preferred packages with versions that worked, and any to avoid]
```
Only include patterns that repeated across 2+ spikes or were explicitly chosen by the user. If `CONVENTIONS.md` already exists, update sections with new patterns from this session.
Commit (if `COMMIT_DOCS` is true):
```bash
gsd-sdk query commit "docs(spikes): update conventions" .planning/spikes/CONVENTIONS.md
```
</step>
<step name="report">
After all spikes complete, present the consolidated report:
```
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
GSD ► SPIKE COMPLETE ✓
@@ -223,35 +400,35 @@ After all spikes complete, present the consolidated report:
## Verdicts
| # | Name | Verdict |
|---|------|---------|
| 001 | {name} | ✓ VALIDATED |
| 002 | {name} | ✗ INVALIDATED |
| # | Name | Type | Verdict |
|---|------|------|---------|
| 001 | {name} | standard | ✓ VALIDATED |
| 002a | {name} | comparison | ✓ WINNER |
## Key Discoveries
{surprises, gotchas, things that weren't expected}
{surprises, gotchas, investigation trail highlights}
## Feasibility Assessment
{overall, is the idea viable?}
{overall viability}
## Signal for the Build
{what the real implementation should use, avoid, or watch out for}
{what to use, avoid, watch out for}
```
───────────────────────────────────────────────────────────────
## ▶ Next Up
**Package findings** — wrap spike knowledge into a reusable skill
**Package findings** — wrap spike knowledge into an implementation blueprint
`/gsd-spike-wrap-up`
───────────────────────────────────────────────────────────────
**Also available:**
- `/gsd-spike` — spike more ideas (or run with no argument for frontier mode)
- `/gsd-plan-phase` — start planning the real implementation
- `/gsd-explore` — continue exploring the idea
- `/gsd-add-phase` — add a phase to the roadmap based on findings
───────────────────────────────────────────────────────────────
</step>
@@ -260,11 +437,16 @@ After all spikes complete, present the consolidated report:
<success_criteria>
- [ ] `.planning/spikes/` created (auto-creates if needed, no project init required)
- [ ] Each spike answers one specific question with observable evidence
- [ ] Each spike README has complete frontmatter, run instructions, and results
- [ ] User verified each spike (self-verified or human checkpoint)
- [ ] MANIFEST.md is current
- [ ] Prior spikes and findings skills consulted before building
- [ ] Conventions followed (or deviation documented)
- [ ] Research grounded each spike in current docs before coding
- [ ] Depth over speed — edge cases tested, surprising findings followed, investigation trail documented
- [ ] Comparison spikes built back-to-back with head-to-head verdict
- [ ] Spikes needing human interaction have forensic log layer
- [ ] Requirements tracked in MANIFEST.md as they emerge from user choices
- [ ] CONVENTIONS.md created or updated with patterns that emerged
- [ ] Each spike README has complete frontmatter, Investigation Trail, and Results
- [ ] MANIFEST.md is current (with Type column and Requirements section)
- [ ] Commits use `docs(spike-NNN): [VERDICT]` format
- [ ] Consolidated report presented with next-step routing
- [ ] If core assumption invalidated, execution stopped and user consulted
</success_criteria>

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "get-shit-done-cc",
"version": "1.37.1",
"version": "1.38.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "get-shit-done-cc",
"version": "1.37.1",
"version": "1.38.3",
"license": "MIT",
"bin": {
"get-shit-done-cc": "bin/install.js"

View File

@@ -1,6 +1,6 @@
{
"name": "get-shit-done-cc",
"version": "1.37.1",
"version": "1.38.3",
"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"

View File

@@ -20,6 +20,7 @@ const HOOKS_TO_COPY = [
'gsd-context-monitor.js',
'gsd-prompt-guard.js',
'gsd-read-guard.js',
'gsd-read-injection-scanner.js',
'gsd-statusline.js',
'gsd-workflow-guard.js',
// Community hooks (bash, opt-in via .planning/config.json hooks.community)

View File

@@ -44,6 +44,7 @@ import {
initExecutePhase, initPlanPhase, initNewMilestone, initQuick,
initResume, initVerifyWork, initPhaseOp, initTodos, initMilestoneOp,
initMapCodebase, initNewWorkspace, initListWorkspaces, initRemoveWorkspace,
initIngestDocs,
} from './init.js';
import { initNewProject, initProgress, initManager } from './init-complex.js';
import { agentSkills } from './skills.js';
@@ -338,6 +339,7 @@ export function createRegistry(eventStream?: GSDEventStream): QueryRegistry {
registry.register('init.new-workspace', initNewWorkspace);
registry.register('init.list-workspaces', initListWorkspaces);
registry.register('init.remove-workspace', initRemoveWorkspace);
registry.register('init.ingest-docs', initIngestDocs);
// Space-delimited aliases for CJS compatibility
registry.register('init execute-phase', initExecutePhase);
registry.register('init plan-phase', initPlanPhase);
@@ -352,6 +354,7 @@ export function createRegistry(eventStream?: GSDEventStream): QueryRegistry {
registry.register('init new-workspace', initNewWorkspace);
registry.register('init list-workspaces', initListWorkspaces);
registry.register('init remove-workspace', initRemoveWorkspace);
registry.register('init ingest-docs', initIngestDocs);
// Complex init handlers
registry.register('init.new-project', initNewProject);

View File

@@ -24,6 +24,7 @@ import {
initNewWorkspace,
initListWorkspaces,
initRemoveWorkspace,
initIngestDocs,
} from './init.js';
let tmpDir: string;
@@ -306,3 +307,24 @@ describe('initRemoveWorkspace', () => {
expect(data.error).toBeDefined();
});
});
describe('initIngestDocs', () => {
it('returns flat JSON with ingest-docs branching fields', async () => {
const result = await initIngestDocs([], tmpDir);
const data = result.data as Record<string, unknown>;
expect(data.project_exists).toBe(false);
expect(data.planning_exists).toBe(true);
expect(typeof data.has_git).toBe('boolean');
expect(data.project_path).toBe('.planning/PROJECT.md');
expect(data.commit_docs).toBeDefined();
expect(data.project_root).toBe(tmpDir);
});
it('reports project_exists true when PROJECT.md is present', async () => {
await writeFile(join(tmpDir, '.planning', 'PROJECT.md'), '# project');
const result = await initIngestDocs([], tmpDir);
const data = result.data as Record<string, unknown>;
expect(data.project_exists).toBe(true);
expect(data.planning_exists).toBe(true);
});
});

View File

@@ -945,6 +945,26 @@ export const initRemoveWorkspace: QueryHandler = async (args, _projectDir) => {
return { data: result };
};
// ─── initIngestDocs ───────────────────────────────────────────────────────
/**
* Init handler for ingest-docs workflow.
* Mirrors `initResume` shape but without current-agent-id lookup — the
* ingest-docs workflow reads `project_exists`, `planning_exists`, `has_git`,
* and `project_path` to branch between new-project vs merge-milestone modes.
*/
export const initIngestDocs: QueryHandler = async (_args, projectDir) => {
const config = await loadConfig(projectDir);
const result: Record<string, unknown> = {
project_exists: pathExists(projectDir, '.planning/PROJECT.md'),
planning_exists: pathExists(projectDir, '.planning'),
has_git: pathExists(projectDir, '.git'),
project_path: '.planning/PROJECT.md',
commit_docs: config.commit_docs,
};
return { data: withProjectRoot(projectDir, result, config as Record<string, unknown>) };
};
// ─── docsInit ────────────────────────────────────────────────────────────
export const docsInit: QueryHandler = async (_args, projectDir) => {

View File

@@ -58,7 +58,10 @@ function cleanup(dir) {
* Returns the path to the installed hooks directory.
*/
function runInstaller(configDir) {
execFileSync(process.execPath, [INSTALL_SCRIPT, '--claude', '--global', '--yes'], {
// --no-sdk: this test covers hook deployment only; skip SDK build to avoid
// flakiness and keep the test fast (SDK install path has dedicated coverage
// in install-smoke.yml).
execFileSync(process.execPath, [INSTALL_SCRIPT, '--claude', '--global', '--yes', '--no-sdk'], {
encoding: 'utf-8',
stdio: 'pipe',
env: {

View File

@@ -57,7 +57,9 @@ function cleanup(dir) {
function runInstaller(configDir) {
const env = { ...process.env, CLAUDE_CONFIG_DIR: configDir };
delete env.GSD_TEST_MODE;
execFileSync(process.execPath, [INSTALL_SCRIPT, '--claude', '--global', '--yes'], {
// --no-sdk: this test covers user-artifact preservation only; skip SDK
// build (covered by install-smoke.yml) to keep the test deterministic.
execFileSync(process.execPath, [INSTALL_SCRIPT, '--claude', '--global', '--yes', '--no-sdk'], {
encoding: 'utf-8',
stdio: 'pipe',
env,

View File

@@ -68,7 +68,9 @@ function cleanup(dir) {
}
function runInstaller(configDir) {
execFileSync(process.execPath, [INSTALL_SCRIPT, '--claude', '--global', '--yes'], {
// --no-sdk: this test covers .sh hook version stamping only; skip SDK
// build (covered by install-smoke.yml).
execFileSync(process.execPath, [INSTALL_SCRIPT, '--claude', '--global', '--yes', '--no-sdk'], {
encoding: 'utf-8',
stdio: 'pipe',
env: { ...process.env, CLAUDE_CONFIG_DIR: configDir },

View File

@@ -0,0 +1,54 @@
/**
* Regression test for bug #2439
*
* /gsd-set-profile crashed with `command not found: gsd-sdk` when the
* gsd-sdk binary was not installed or not in PATH. The command body
* invoked `gsd-sdk query config-set-model-profile` directly with no
* pre-flight check, so missing gsd-sdk produced an opaque shell error.
*
* Fix mirrors bug #2334: guard the invocation with `command -v gsd-sdk`
* and emit an install hint when absent.
*/
'use strict';
const { test, describe } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('fs');
const path = require('path');
const COMMAND_PATH = path.join(__dirname, '..', 'commands', 'gsd', 'set-profile.md');
describe('bug #2439: /gsd-set-profile gsd-sdk pre-flight check', () => {
const content = fs.readFileSync(COMMAND_PATH, 'utf-8');
test('command file exists', () => {
assert.ok(fs.existsSync(COMMAND_PATH), 'commands/gsd/set-profile.md should exist');
});
test('guards gsd-sdk invocation with command -v check', () => {
const sdkCall = content.indexOf('gsd-sdk query config-set-model-profile');
assert.ok(sdkCall !== -1, 'gsd-sdk query config-set-model-profile must be present');
const preamble = content.slice(0, sdkCall);
assert.ok(
preamble.includes('command -v gsd-sdk') || preamble.includes('which gsd-sdk'),
'set-profile must check for gsd-sdk in PATH before invoking it. ' +
'Without this guard the command crashes with exit 127 when gsd-sdk ' +
'is not installed (root cause of #2439).'
);
});
test('pre-flight error message references install/update path', () => {
const sdkCall = content.indexOf('gsd-sdk query config-set-model-profile');
const preamble = content.slice(0, sdkCall);
const hasInstallHint =
preamble.includes('@gsd-build/sdk') ||
preamble.includes('gsd-update') ||
preamble.includes('/gsd-update');
assert.ok(
hasInstallHint,
'Pre-flight error must point users at `npm install -g @gsd-build/sdk` or `/gsd-update`.'
);
});
});

View File

@@ -0,0 +1,168 @@
/**
* Drift guard: every `gsd-sdk query <cmd>` reference in the repo must
* resolve to a handler registered in sdk/src/query/index.ts.
*
* The set of commands workflows/agents/commands call must equal the set
* the SDK registry exposes. New references with no handler — or handlers
* with no in-repo callers — show up here so they can't diverge silently.
*/
const { describe, test } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('fs');
const path = require('path');
const REPO_ROOT = path.join(__dirname, '..');
const REGISTRY_FILE = path.join(REPO_ROOT, 'sdk', 'src', 'query', 'index.ts');
// Prose tokens that repeatedly appear after `gsd-sdk query` in English
// documentation but aren't real command names.
const PROSE_ALLOWLIST = new Set([
'commands',
'intel',
'into',
'or',
'init.',
]);
const SCAN_ROOTS = [
'commands',
'agents',
'get-shit-done',
'hooks',
'bin',
'scripts',
'docs',
];
const EXTRA_FILES = ['README.md', 'CHANGELOG.md'];
const EXTENSIONS = new Set(['.md', '.sh', '.cjs', '.js', '.ts']);
const SKIP_DIRS = new Set(['node_modules', '.git', 'dist', 'build']);
function collectRegisteredNames() {
const src = fs.readFileSync(REGISTRY_FILE, 'utf8');
const names = new Set();
const re = /registry\.register\(\s*['"]([^'"]+)['"]/g;
let m;
while ((m = re.exec(src)) !== null) names.add(m[1]);
return names;
}
function walk(dir, files) {
let entries;
try {
entries = fs.readdirSync(dir, { withFileTypes: true });
} catch {
return;
}
for (const entry of entries) {
if (SKIP_DIRS.has(entry.name)) continue;
const full = path.join(dir, entry.name);
if (entry.isDirectory()) {
walk(full, files);
} else if (entry.isFile() && EXTENSIONS.has(path.extname(entry.name))) {
files.push(full);
}
}
}
function collectReferences() {
const files = [];
for (const root of SCAN_ROOTS) walk(path.join(REPO_ROOT, root), files);
for (const rel of EXTRA_FILES) {
const full = path.join(REPO_ROOT, rel);
if (fs.existsSync(full)) files.push(full);
}
const refs = [];
const re = /gsd-sdk\s+query\s+([A-Za-z][-A-Za-z0-9._/]+)(?:\s+([A-Za-z][-A-Za-z0-9._]+))?/g;
for (const file of files) {
const content = fs.readFileSync(file, 'utf8');
const lines = content.split('\n');
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
let m;
re.lastIndex = 0;
while ((m = re.exec(line)) !== null) {
refs.push({
file: path.relative(REPO_ROOT, file),
line: i + 1,
tok1: m[1],
tok2: m[2] || null,
raw: line.trim(),
});
}
}
}
return refs;
}
function resolveReference(ref, registered) {
const { tok1, tok2 } = ref;
if (registered.has(tok1)) return true;
if (tok2) {
const dotted = tok1 + '.' + tok2;
const spaced = tok1 + ' ' + tok2;
if (registered.has(dotted) || registered.has(spaced)) return true;
}
if (PROSE_ALLOWLIST.has(tok1)) return true;
return false;
}
describe('gsd-sdk query registry integration', () => {
test('every referenced command resolves to a registered handler', () => {
const registered = collectRegisteredNames();
const refs = collectReferences();
assert.ok(registered.size > 0, 'expected to parse registered names');
assert.ok(refs.length > 0, 'expected to find gsd-sdk query references');
const offenders = [];
for (const ref of refs) {
if (!resolveReference(ref, registered)) {
const shown = ref.tok2 ? ref.tok1 + ' ' + ref.tok2 : ref.tok1;
offenders.push(ref.file + ':' + ref.line + ': "' + shown + '" — ' + ref.raw);
}
}
assert.strictEqual(
offenders.length, 0,
'Referenced `gsd-sdk query <cmd>` tokens with no handler in ' +
'sdk/src/query/index.ts. Either register the handler or remove ' +
'the reference.\n\n' + offenders.join('\n')
);
});
test('informational: handlers with no in-repo caller', () => {
const registered = collectRegisteredNames();
const refs = collectReferences();
const referencedNames = new Set();
for (const ref of refs) {
referencedNames.add(ref.tok1);
if (ref.tok2) {
referencedNames.add(ref.tok1 + '.' + ref.tok2);
referencedNames.add(ref.tok1 + ' ' + ref.tok2);
}
}
const unused = [];
for (const name of registered) {
if (referencedNames.has(name)) continue;
if (name.includes('.')) {
const spaced = name.replace('.', ' ');
if (referencedNames.has(spaced)) continue;
}
if (name.includes(' ')) {
const dotted = name.replace(' ', '.');
if (referencedNames.has(dotted)) continue;
}
unused.push(name);
}
if (unused.length > 0 && process.env.GSD_LOG_UNUSED_HANDLERS) {
console.log('[info] registered handlers with no in-repo caller:\n ' + unused.join('\n '));
}
assert.ok(true);
});
});

View File

@@ -35,6 +35,7 @@ const EXPECTED_ALL_HOOKS = [
'gsd-context-monitor.js',
'gsd-prompt-guard.js',
'gsd-read-guard.js',
'gsd-read-injection-scanner.js',
'gsd-statusline.js',
'gsd-workflow-guard.js',
...EXPECTED_SH_HOOKS,