mirror of
https://github.com/glittercowboy/get-shit-done
synced 2026-04-25 17:25:23 +02:00
Compare commits
20 Commits
fix/2544-c
...
hotfix/1.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
99aa2c1e54 | ||
|
|
900c36998b | ||
|
|
06d29af1cd | ||
|
|
a42d5db742 | ||
|
|
c86ca1b3eb | ||
|
|
337e052aa9 | ||
|
|
30433368a0 | ||
|
|
04fab926b5 | ||
|
|
f98ef1e460 | ||
|
|
d0565e95c1 | ||
|
|
4ef6275e86 | ||
|
|
6c50490766 | ||
|
|
4cbebfe78c | ||
|
|
9e87d43831 | ||
|
|
29ea90bc83 | ||
|
|
0c6172bfad | ||
|
|
e3bd06c9fd | ||
|
|
c69ecd975a | ||
|
|
06c4ded4ec | ||
|
|
341bb941c6 |
152
.github/workflows/install-smoke.yml
vendored
Normal file
152
.github/workflows/install-smoke.yml
vendored
Normal 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"
|
||||
37
.github/workflows/release.yml
vendored
37
.github/workflows/release.yml
vendored
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
150
bin/install.js
150
bin/install.js
@@ -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 },
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
4
package-lock.json
generated
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 },
|
||||
|
||||
54
tests/bug-2439-set-profile-gsd-sdk-preflight.test.cjs
Normal file
54
tests/bug-2439-set-profile-gsd-sdk-preflight.test.cjs
Normal 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`.'
|
||||
);
|
||||
});
|
||||
});
|
||||
168
tests/gsd-sdk-query-registry-integration.test.cjs
Normal file
168
tests/gsd-sdk-query-registry-integration.test.cjs
Normal 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);
|
||||
});
|
||||
});
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user