mirror of
https://github.com/glittercowboy/get-shit-done
synced 2026-05-02 20:42:30 +02:00
Compare commits
71 Commits
dev
...
fix/2997-s
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cb98a88139 | ||
|
|
fb92d1e596 | ||
|
|
7424271aa0 | ||
|
|
7a416b10d4 | ||
|
|
ef43f5161f | ||
|
|
e9a66da1e7 | ||
|
|
b8d9bd69b2 | ||
|
|
0d25ef0c47 | ||
|
|
a346779213 | ||
|
|
0d6abb87ac | ||
|
|
c5dfdbe42e | ||
|
|
9d0d085a17 | ||
|
|
53cda93a01 | ||
|
|
ec07861228 | ||
|
|
3ba17e872e | ||
|
|
4d628b306a | ||
|
|
b328f3269f | ||
|
|
e2792536d9 | ||
|
|
7cc6358f91 | ||
|
|
8de8acee46 | ||
|
|
2cc8796265 | ||
|
|
faee0287a0 | ||
|
|
7e9477bb30 | ||
|
|
5abf46ac1c | ||
|
|
372d3453f5 | ||
|
|
c9d6306981 | ||
|
|
1168e9f59a | ||
|
|
3ed8980519 | ||
|
|
c3aef27aa6 | ||
|
|
ace61869d0 | ||
|
|
80f14cac1f | ||
|
|
2256e4c9a3 | ||
|
|
e5cd523e7b | ||
|
|
b5777572f7 | ||
|
|
861a7d972b | ||
|
|
bd0511988b | ||
|
|
4a5f36df5e | ||
|
|
840f2b349e | ||
|
|
140d334dab | ||
|
|
6e4fad7acc | ||
|
|
4e2f1105d9 | ||
|
|
4ce72cdee7 | ||
|
|
198022f58d | ||
|
|
ac100ae17b | ||
|
|
002db4dd2b | ||
|
|
0e0f6952c5 | ||
|
|
bdead2ee6a | ||
|
|
e107bb35d4 | ||
|
|
294564b951 | ||
|
|
9a13d2fc0b | ||
|
|
d29822c1da | ||
|
|
b126c0579a | ||
|
|
006cdafe8f | ||
|
|
8051bc4fd8 | ||
|
|
444db1714b | ||
|
|
6dce1de4a7 | ||
|
|
abb2cb63f6 | ||
|
|
8cbdbdd2de | ||
|
|
951d5bf7c0 | ||
|
|
ca88429bf8 | ||
|
|
5fdc950eb7 | ||
|
|
c72b893916 | ||
|
|
8fc1fa263c | ||
|
|
87917131f2 | ||
|
|
55298b2f70 | ||
|
|
4d394a249d | ||
|
|
73b9d1dac0 | ||
|
|
99af76b3ba | ||
|
|
ef08a89241 | ||
|
|
f2ada8500c | ||
|
|
f6a6e43226 |
26
.coderabbit.yaml
Normal file
26
.coderabbit.yaml
Normal file
@@ -0,0 +1,26 @@
|
||||
# CodeRabbit configuration — gsd-build/get-shit-done
|
||||
#
|
||||
# Schema: https://docs.coderabbit.ai/reference/yaml-template/
|
||||
#
|
||||
# Project context: GSD ships a CLI tool + an agent runtime, not a documented
|
||||
# public library. We carry rich JSDoc on internal helpers that warrant it
|
||||
# (see bin/install.js, get-shit-done/bin/lib/*.cjs) but we do not enforce a
|
||||
# blanket docstring coverage bar — see issue #2932 for rationale.
|
||||
|
||||
reviews:
|
||||
pre_merge_checks:
|
||||
# Disable docstring coverage check.
|
||||
#
|
||||
# The check produces false-positive warnings on PRs whose new code is
|
||||
# entirely test files: it counts test(...) / beforeEach / afterEach
|
||||
# arrow-function callbacks as functions and then reports 0% coverage
|
||||
# because nothing has JSDoc. There is no per-check path filter in CR's
|
||||
# documented schema that would let us exclude tests/** while keeping
|
||||
# the check active elsewhere, and the top-level path_filters approach
|
||||
# would silence ALL CR review on tests (security scans, out-of-scope
|
||||
# checks, line-level findings) which we want to keep.
|
||||
#
|
||||
# All other CR pre-merge checks (out-of-scope, security, title) remain
|
||||
# at their defaults.
|
||||
docstrings:
|
||||
mode: off
|
||||
6
.githooks/pre-commit
Executable file
6
.githooks/pre-commit
Executable file
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
if git diff --cached --name-only | grep -Eq "^sdk/src/query/command-manifest\.|^sdk/src/query/command-aliases\.generated\.ts$|^get-shit-done/bin/lib/command-aliases\.generated\.cjs$|^sdk/scripts/gen-command-aliases\.ts$"; then
|
||||
npm run check:alias-drift
|
||||
fi
|
||||
48
.githooks/pre-push
Executable file
48
.githooks/pre-push
Executable file
@@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
zero_sha='0000000000000000000000000000000000000000'
|
||||
blocked_regex="${GSD_BLOCKED_AUTHOR_REGEX:-}"
|
||||
|
||||
# Local-only guard: no-op unless the developer opts in via env var, e.g.
|
||||
# export GSD_BLOCKED_AUTHOR_REGEX='@example-corp\.com$'
|
||||
if [[ -z "$blocked_regex" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
violations=()
|
||||
|
||||
while read -r local_ref local_sha remote_ref remote_sha; do
|
||||
# branch/tag deletion
|
||||
if [[ "$local_sha" == "$zero_sha" ]]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
if [[ "$remote_sha" == "$zero_sha" ]]; then
|
||||
# New remote ref: inspect commits not already on any remote
|
||||
commit_list=$(git rev-list "$local_sha" --not --remotes)
|
||||
else
|
||||
commit_list=$(git rev-list "$remote_sha..$local_sha")
|
||||
fi
|
||||
|
||||
while read -r commit; do
|
||||
[[ -z "$commit" ]] && continue
|
||||
author_email=$(git show -s --format='%ae' "$commit")
|
||||
lower_email=$(printf '%s' "$author_email" | tr '[:upper:]' '[:lower:]')
|
||||
if printf '%s' "$lower_email" | grep -Eq "$blocked_regex"; then
|
||||
violations+=("$commit <$author_email>")
|
||||
fi
|
||||
done <<< "$commit_list"
|
||||
done
|
||||
|
||||
if [[ ${#violations[@]} -gt 0 ]]; then
|
||||
{
|
||||
echo "Push blocked: commit author email matched local blocked regex ($blocked_regex)."
|
||||
echo "Rewrite author info before pushing these commits:"
|
||||
for v in "${violations[@]}"; do
|
||||
echo " - $v"
|
||||
done
|
||||
echo "Suggested fix: git rebase -i <base> --exec \"git commit --amend --no-edit --author='Your Name <non-enterprise@email>'\""
|
||||
} >&2
|
||||
exit 1
|
||||
fi
|
||||
21
.github/workflows/canary.yml
vendored
21
.github/workflows/canary.yml
vendored
@@ -1,3 +1,12 @@
|
||||
# Release stream policy:
|
||||
# dev → @canary (this workflow — preview builds for the long-lived integration branch)
|
||||
# main → @next (RC train, see release.yml)
|
||||
# main → @latest (stable cuts, see release.yml)
|
||||
#
|
||||
# Streams do not mix. The publish/tag steps below gate on `refs/heads/dev` so a
|
||||
# workflow_dispatch run on any other branch (including main) completes the
|
||||
# build/test/dry-run validation but does not publish or tag.
|
||||
|
||||
name: Canary
|
||||
|
||||
on:
|
||||
@@ -80,7 +89,7 @@ jobs:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
- name: Tag and push
|
||||
if: ${{ github.ref == 'refs/heads/main' && !inputs.dry_run }}
|
||||
if: ${{ github.ref == 'refs/heads/dev' && !inputs.dry_run }}
|
||||
env:
|
||||
CANARY_VERSION: ${{ steps.canary.outputs.canary_version }}
|
||||
run: |
|
||||
@@ -88,19 +97,19 @@ jobs:
|
||||
git push origin "v${CANARY_VERSION}"
|
||||
|
||||
- name: Publish to npm (canary)
|
||||
if: ${{ github.ref == 'refs/heads/main' && !inputs.dry_run }}
|
||||
if: ${{ github.ref == 'refs/heads/dev' && !inputs.dry_run }}
|
||||
run: npm publish --provenance --access public --tag canary
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
- name: Publish SDK to npm (canary)
|
||||
if: ${{ github.ref == 'refs/heads/main' && !inputs.dry_run }}
|
||||
if: ${{ github.ref == 'refs/heads/dev' && !inputs.dry_run }}
|
||||
run: cd sdk && npm publish --provenance --access public --tag canary
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
- name: Verify publish
|
||||
if: ${{ github.ref == 'refs/heads/main' && !inputs.dry_run }}
|
||||
if: ${{ github.ref == 'refs/heads/dev' && !inputs.dry_run }}
|
||||
env:
|
||||
CANARY_VERSION: ${{ steps.canary.outputs.canary_version }}
|
||||
run: |
|
||||
@@ -132,10 +141,14 @@ jobs:
|
||||
env:
|
||||
CANARY_VERSION: ${{ steps.canary.outputs.canary_version }}
|
||||
DRY_RUN: ${{ inputs.dry_run }}
|
||||
PUBLISH_ELIGIBLE: ${{ github.ref == 'refs/heads/dev' && !inputs.dry_run }}
|
||||
BRANCH_REF: ${{ github.ref }}
|
||||
run: |
|
||||
echo "## Canary v${CANARY_VERSION}" >> "$GITHUB_STEP_SUMMARY"
|
||||
if [ "$DRY_RUN" = "true" ]; then
|
||||
echo "**DRY RUN** — npm publish, tagging, and push skipped" >> "$GITHUB_STEP_SUMMARY"
|
||||
elif [ "$PUBLISH_ELIGIBLE" != "true" ]; then
|
||||
echo "**VALIDATION ONLY** — publish/tag skipped for \`${BRANCH_REF}\`; canary publish is gated to \`refs/heads/dev\`." >> "$GITHUB_STEP_SUMMARY"
|
||||
else
|
||||
echo "- Published to npm as \`canary\`" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "- SDK also published: \`@gsd-build/sdk@${CANARY_VERSION}\` on \`canary\`" >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
366
.github/workflows/hotfix.yml
vendored
366
.github/workflows/hotfix.yml
vendored
@@ -1,5 +1,27 @@
|
||||
name: Hotfix Release
|
||||
|
||||
# Hotfix flow for X.YY.Z patch releases (Z > 0).
|
||||
#
|
||||
# create:
|
||||
# - Branches hotfix/X.YY.Z from the highest existing vX.YY.* tag (1.27.2 from
|
||||
# v1.27.1, 1.27.1 from v1.27.0). The base IS the cumulative-fix anchor for
|
||||
# the previous patch.
|
||||
# - Auto-cherry-picks every fix:/chore: commit on origin/main that isn't
|
||||
# already in the base, oldest-first. Patch-equivalents (already applied)
|
||||
# are skipped via `git cherry`. feat:/refactor: are NEVER auto-included.
|
||||
# - Conflicts fail the workflow with the offending SHA so the operator can
|
||||
# resolve manually on the branch and re-run finalize with auto_cherry_pick=false.
|
||||
# - Step summary lists every included SHA so the eventual vX.YY.Z tag
|
||||
# self-documents what shipped.
|
||||
#
|
||||
# finalize:
|
||||
# - install-smoke gate (cross-platform, parity with release.yml/release-sdk.yml)
|
||||
# - Bundles SDK as both loose tree (sdk/dist/cli.js) and recoverable tarball
|
||||
# (sdk-bundle/gsd-sdk.tgz) — parity with release-sdk.yml so a hotfix shipped
|
||||
# during the @gsd-build-token outage carries the same payload shape.
|
||||
# - Publishes to @latest, tags vX.YY.Z, re-points @next → vX.YY.Z, opens
|
||||
# merge-back PR.
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
@@ -14,6 +36,11 @@ on:
|
||||
description: 'Patch version (e.g., 1.27.1)'
|
||||
required: true
|
||||
type: string
|
||||
auto_cherry_pick:
|
||||
description: 'Auto-cherry-pick fix:/chore: commits from origin/main since base tag (create only)'
|
||||
required: false
|
||||
type: boolean
|
||||
default: true
|
||||
dry_run:
|
||||
description: 'Dry run (skip npm publish, tagging, and push)'
|
||||
required: false
|
||||
@@ -54,10 +81,13 @@ jobs:
|
||||
MAJOR_MINOR=$(echo "$VERSION" | cut -d. -f1-2)
|
||||
TARGET_TAG="v${VERSION}"
|
||||
BRANCH="hotfix/${VERSION}"
|
||||
BASE_TAG=$(git tag -l "v${MAJOR_MINOR}.*" \
|
||||
| grep -E "^v[0-9]+\.[0-9]+\.[0-9]+$" \
|
||||
# Append TARGET_TAG to the candidate list, then sort -V, then walk the
|
||||
# sorted list and print whatever immediately precedes TARGET_TAG. This
|
||||
# is semver-correct for multi-digit patches (v1.27.10 > v1.27.9) where
|
||||
# a plain `awk '$1 < target'` lexicographic compare would mis-order.
|
||||
BASE_TAG=$( ( git tag -l "v${MAJOR_MINOR}.*" | grep -E "^v[0-9]+\.[0-9]+\.[0-9]+$"; echo "$TARGET_TAG" ) \
|
||||
| sort -V \
|
||||
| awk -v target="$TARGET_TAG" '$1 < target { last=$1 } END { if (last != "") print last }')
|
||||
| awk -v target="$TARGET_TAG" '$1 == target { print prev; exit } { prev = $1 }')
|
||||
if [ -z "$BASE_TAG" ]; then
|
||||
echo "::error::No prior stable tag found for ${MAJOR_MINOR}.x before $TARGET_TAG"
|
||||
exit 1
|
||||
@@ -95,29 +125,160 @@ jobs:
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
- name: Create hotfix branch
|
||||
if: inputs.dry_run != 'true'
|
||||
- name: Create hotfix branch from base tag and push (skeleton)
|
||||
env:
|
||||
BRANCH: ${{ needs.validate-version.outputs.branch }}
|
||||
BASE_TAG: ${{ needs.validate-version.outputs.base_tag }}
|
||||
DRY_RUN: ${{ inputs.dry_run }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git checkout -b "$BRANCH" "$BASE_TAG"
|
||||
# Push the skeleton branch up-front so any subsequent cherry-pick
|
||||
# conflict leaves a remote artefact the operator can fetch, resolve,
|
||||
# and re-push. Skipped on dry-run — local checkout still exercises
|
||||
# the same cherry-pick + bump flow so conflicts are caught.
|
||||
if [ "$DRY_RUN" != "true" ]; then
|
||||
git push -u origin "$BRANCH"
|
||||
fi
|
||||
|
||||
- name: Cherry-pick fix/chore commits from origin/main since base tag
|
||||
if: ${{ inputs.auto_cherry_pick }}
|
||||
env:
|
||||
BRANCH: ${{ needs.validate-version.outputs.branch }}
|
||||
BASE_TAG: ${{ needs.validate-version.outputs.base_tag }}
|
||||
DRY_RUN: ${{ inputs.dry_run }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git fetch origin main:refs/remotes/origin/main
|
||||
|
||||
# `git cherry $BASE_TAG origin/main` lists every commit on main not
|
||||
# patch-equivalent in BASE_TAG. + means needs picking, - means
|
||||
# already applied (skipped silently).
|
||||
CANDIDATES=$(git cherry "$BASE_TAG" origin/main | awk '/^\+ / {print $2}')
|
||||
|
||||
if [ -z "$CANDIDATES" ]; then
|
||||
echo "No commits on origin/main beyond $BASE_TAG."
|
||||
echo "## Cherry-pick summary" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "Base: \`$BASE_TAG\` — no commits to consider." >> "$GITHUB_STEP_SUMMARY"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Re-order chronologically (oldest first) for predictable application.
|
||||
ORDERED=$(git log --reverse --format='%H' "$BASE_TAG..origin/main" \
|
||||
| grep -F -f <(echo "$CANDIDATES") || true)
|
||||
|
||||
INCLUDED=""
|
||||
SKIPPED=""
|
||||
while IFS= read -r SHA; do
|
||||
[ -z "$SHA" ] && continue
|
||||
SUBJECT=$(git log -1 --format='%s' "$SHA")
|
||||
# fix: or chore:, optional scope, optional ! breaking marker
|
||||
if echo "$SUBJECT" | grep -qE '^(fix|chore)(\([^)]+\))?!?: '; then
|
||||
echo "→ cherry-picking $SHA $SUBJECT"
|
||||
if ! git cherry-pick -x "$SHA"; then
|
||||
# Abort restores HEAD to the last successful pick. On real
|
||||
# runs, push that state so the operator can fetch, resolve
|
||||
# $SHA manually, and finalize with auto_cherry_pick=false.
|
||||
git cherry-pick --abort || true
|
||||
if [ "$DRY_RUN" != "true" ]; then
|
||||
git push --force-with-lease origin "$BRANCH" || git push origin "$BRANCH" || true
|
||||
fi
|
||||
{
|
||||
echo "## Cherry-pick conflict"
|
||||
echo ""
|
||||
echo "Failed at: \`${SHA}\` — \`${SUBJECT}\`"
|
||||
echo ""
|
||||
if [ "$DRY_RUN" = "true" ]; then
|
||||
echo "**Dry run:** branch was not pushed, so the picks below were discarded with the runner."
|
||||
if [ -n "$INCLUDED" ]; then
|
||||
echo ""
|
||||
echo "Already-applied picks (lost — must be re-applied before resolving \`${SHA}\`):"
|
||||
echo ""
|
||||
echo "$INCLUDED"
|
||||
fi
|
||||
echo ""
|
||||
echo "**To resolve:** re-run \`create\` with \`auto_cherry_pick=true\` (real, not dry-run) to materialize the partial branch on origin, then resolve \`${SHA}\` manually. Re-running with \`auto_cherry_pick=false\` would recreate the branch from \`${BASE_TAG}\` and lose every pick listed above."
|
||||
else
|
||||
echo "Branch \`${BRANCH}\` was pushed with picks applied up to (but not including) the conflicting commit."
|
||||
echo ""
|
||||
echo "**To resolve:** \`git fetch origin && git checkout ${BRANCH} && git cherry-pick -x ${SHA}\`, fix the conflict, push, then re-run \`finalize\` with \`auto_cherry_pick=false\`."
|
||||
fi
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "::error::Cherry-pick of $SHA failed. See summary."
|
||||
exit 1
|
||||
fi
|
||||
INCLUDED="${INCLUDED}- \`${SHA}\` ${SUBJECT}"$'\n'
|
||||
else
|
||||
echo " skip $SHA $SUBJECT (not fix/chore)"
|
||||
SKIPPED="${SKIPPED}- \`${SHA}\` ${SUBJECT}"$'\n'
|
||||
fi
|
||||
done <<< "$ORDERED"
|
||||
|
||||
{
|
||||
echo "## Cherry-pick summary"
|
||||
echo ""
|
||||
echo "Base: \`$BASE_TAG\`"
|
||||
echo ""
|
||||
if [ -n "$INCLUDED" ]; then
|
||||
echo "### Included (fix/chore)"
|
||||
echo ""
|
||||
echo "$INCLUDED"
|
||||
else
|
||||
echo "_No fix/chore commits to include._"
|
||||
echo ""
|
||||
fi
|
||||
if [ -n "$SKIPPED" ]; then
|
||||
echo "### Skipped (feat/refactor/etc — not auto-included)"
|
||||
echo ""
|
||||
echo "$SKIPPED"
|
||||
fi
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
- name: Bump version and push
|
||||
env:
|
||||
BRANCH: ${{ needs.validate-version.outputs.branch }}
|
||||
BASE_TAG: ${{ needs.validate-version.outputs.base_tag }}
|
||||
VERSION: ${{ inputs.version }}
|
||||
DRY_RUN: ${{ inputs.dry_run }}
|
||||
run: |
|
||||
git checkout -b "$BRANCH" "$BASE_TAG"
|
||||
# Bump version in package.json
|
||||
set -euo pipefail
|
||||
npm version "$VERSION" --no-git-tag-version
|
||||
git add package.json package-lock.json
|
||||
# Keep sdk/package.json in lockstep (parity with release-sdk.yml).
|
||||
if [ -f sdk/package.json ]; then
|
||||
(cd sdk && npm version "$VERSION" --no-git-tag-version)
|
||||
git add sdk/package.json
|
||||
[ -f sdk/package-lock.json ] && git add sdk/package-lock.json
|
||||
fi
|
||||
git commit -m "chore: bump version to $VERSION for hotfix"
|
||||
git push origin "$BRANCH"
|
||||
echo "## Hotfix branch created" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "- Branch: \`$BRANCH\`" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "- Based on: \`$BASE_TAG\`" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "- Apply your fix, push, then run this workflow again with \`finalize\`" >> "$GITHUB_STEP_SUMMARY"
|
||||
if [ "$DRY_RUN" != "true" ]; then
|
||||
git push origin "$BRANCH"
|
||||
else
|
||||
echo "DRY RUN — branch not pushed. Local checkout exercised the cherry-pick and bump flow."
|
||||
fi
|
||||
{
|
||||
echo "## Hotfix branch created"
|
||||
echo ""
|
||||
echo "- Branch: \`$BRANCH\`"
|
||||
echo "- Based on: \`$BASE_TAG\`"
|
||||
echo "- Apply additional manual fixes if needed, then run \`finalize\`."
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
finalize:
|
||||
install-smoke:
|
||||
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]
|
||||
if: inputs.action == 'finalize'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
timeout-minutes: 15
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
@@ -140,31 +301,83 @@ jobs:
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
- name: Detect prior publish (reconciliation mode)
|
||||
id: prior_publish
|
||||
env:
|
||||
VERSION: ${{ inputs.version }}
|
||||
run: |
|
||||
EXISTING=$(npm view get-shit-done-cc@"$VERSION" version 2>/dev/null || true)
|
||||
if [ -n "$EXISTING" ]; then
|
||||
echo "::warning::get-shit-done-cc@${VERSION} is already on the registry — entering reconciliation mode (skip publish, continue with tag/release/PR/dist-tag)."
|
||||
echo "skip_publish=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "skip_publish=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Install and test
|
||||
run: |
|
||||
npm ci
|
||||
npm run test:coverage
|
||||
|
||||
- name: Create PR to merge hotfix back to main
|
||||
if: ${{ !inputs.dry_run }}
|
||||
- name: Build SDK dist for tarball
|
||||
run: npm run build:sdk
|
||||
|
||||
- name: Verify CC tarball ships sdk/dist/cli.js (bug #2647 guard)
|
||||
run: bash scripts/verify-tarball-sdk-dist.sh
|
||||
|
||||
- name: Pack SDK as tarball and bundle into CC source tree
|
||||
env:
|
||||
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')
|
||||
if [ -n "$EXISTING_PR" ]; then
|
||||
echo "PR #$EXISTING_PR already exists; updating"
|
||||
gh pr edit "$EXISTING_PR" \
|
||||
--title "chore: merge hotfix v${VERSION} back to main" \
|
||||
--body "Merge hotfix changes back to main after v${VERSION} release."
|
||||
else
|
||||
gh pr create \
|
||||
--base main \
|
||||
--head "$BRANCH" \
|
||||
--title "chore: merge hotfix v${VERSION} back to main" \
|
||||
--body "Merge hotfix changes back to main after v${VERSION} release."
|
||||
set -e
|
||||
cd sdk
|
||||
npm pack
|
||||
TARBALL="gsd-build-sdk-${VERSION}.tgz"
|
||||
if [ ! -f "$TARBALL" ]; then
|
||||
echo "::error::Expected $TARBALL but npm pack did not produce it."
|
||||
ls -la
|
||||
exit 1
|
||||
fi
|
||||
mkdir -p ../sdk-bundle
|
||||
mv "$TARBALL" ../sdk-bundle/gsd-sdk.tgz
|
||||
cd ..
|
||||
ls -la sdk-bundle/
|
||||
|
||||
- name: Add sdk-bundle to CC files whitelist (in-tree, not committed)
|
||||
run: |
|
||||
node <<'NODE'
|
||||
const fs = require('fs');
|
||||
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
|
||||
if (!Array.isArray(pkg.files)) {
|
||||
console.error('::error::package.json files is not an array');
|
||||
process.exit(1);
|
||||
}
|
||||
if (!pkg.files.includes('sdk-bundle')) {
|
||||
pkg.files.push('sdk-bundle');
|
||||
fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2) + '\n');
|
||||
console.log('Added sdk-bundle/ to package.json files whitelist');
|
||||
}
|
||||
NODE
|
||||
|
||||
- name: Verify CC tarball will contain sdk-bundle/gsd-sdk.tgz
|
||||
run: |
|
||||
set -e
|
||||
TARBALL=$(npm pack --ignore-scripts 2>/dev/null | tail -1)
|
||||
if [ -z "$TARBALL" ] || [ ! -f "$TARBALL" ]; then
|
||||
echo "::error::npm pack produced no tarball"
|
||||
exit 1
|
||||
fi
|
||||
if ! tar -tzf "$TARBALL" | grep -q "package/sdk-bundle/gsd-sdk.tgz"; then
|
||||
echo "::error::CC tarball is missing package/sdk-bundle/gsd-sdk.tgz"
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ CC tarball contains sdk-bundle/gsd-sdk.tgz"
|
||||
rm -f "$TARBALL"
|
||||
|
||||
- name: Dry-run publish validation
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
run: npm publish --dry-run --tag latest
|
||||
|
||||
- name: Tag and push
|
||||
if: ${{ !inputs.dry_run }}
|
||||
@@ -185,55 +398,98 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Publish to npm (latest)
|
||||
if: ${{ !inputs.dry_run }}
|
||||
run: npm publish --provenance --access public
|
||||
if: ${{ !inputs.dry_run && steps.prior_publish.outputs.skip_publish != 'true' }}
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
run: npm publish --provenance --access public --tag latest
|
||||
|
||||
- name: Create GitHub Release
|
||||
- name: Re-point next dist-tag at this hotfix
|
||||
if: ${{ !inputs.dry_run }}
|
||||
env:
|
||||
VERSION: ${{ inputs.version }}
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
run: |
|
||||
npm dist-tag add "get-shit-done-cc@${VERSION}" next
|
||||
echo "✅ next dist-tag re-pointed to v${VERSION} (matches latest)"
|
||||
|
||||
- name: Create GitHub Release (idempotent)
|
||||
if: ${{ !inputs.dry_run }}
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
VERSION: ${{ inputs.version }}
|
||||
run: |
|
||||
gh release create "v${VERSION}" \
|
||||
--title "v${VERSION} (hotfix)" \
|
||||
--generate-notes
|
||||
if gh release view "v${VERSION}" >/dev/null 2>&1; then
|
||||
echo "GitHub Release v${VERSION} already exists; ensuring --latest flag is set"
|
||||
gh release edit "v${VERSION}" --latest || true
|
||||
else
|
||||
gh release create "v${VERSION}" \
|
||||
--title "v${VERSION} (hotfix)" \
|
||||
--generate-notes \
|
||||
--latest
|
||||
fi
|
||||
|
||||
- name: Clean up next dist-tag
|
||||
- name: Create PR to merge hotfix back to main
|
||||
if: ${{ !inputs.dry_run }}
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
BRANCH: ${{ needs.validate-version.outputs.branch }}
|
||||
VERSION: ${{ inputs.version }}
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
run: |
|
||||
# Point next to the stable release so @next never returns something
|
||||
# older than @latest. This prevents stale pre-release installs.
|
||||
npm dist-tag add "get-shit-done-cc@${VERSION}" next 2>/dev/null || true
|
||||
echo "✓ next dist-tag updated to v${VERSION}"
|
||||
EXISTING_PR=$(gh pr list --base main --head "$BRANCH" --state open --json number --jq '.[0].number')
|
||||
if [ -n "$EXISTING_PR" ]; then
|
||||
gh pr edit "$EXISTING_PR" \
|
||||
--title "chore: merge hotfix v${VERSION} back to main" \
|
||||
--body "Merge hotfix changes back to main after v${VERSION} release."
|
||||
else
|
||||
gh pr create \
|
||||
--base main \
|
||||
--head "$BRANCH" \
|
||||
--title "chore: merge hotfix v${VERSION} back to main" \
|
||||
--body "Merge hotfix changes back to main after v${VERSION} release."
|
||||
fi
|
||||
|
||||
- name: Verify publish
|
||||
- name: Verify publish landed on registry
|
||||
if: ${{ !inputs.dry_run }}
|
||||
env:
|
||||
VERSION: ${{ inputs.version }}
|
||||
run: |
|
||||
sleep 10
|
||||
PUBLISHED=$(npm view get-shit-done-cc@"$VERSION" version 2>/dev/null || echo "NOT_FOUND")
|
||||
PUBLISHED="NOT_FOUND"
|
||||
for delay in 5 10 20 30 45; do
|
||||
PUBLISHED=$(npm view get-shit-done-cc@"$VERSION" version 2>/dev/null || echo "NOT_FOUND")
|
||||
if [ "$PUBLISHED" = "$VERSION" ]; then
|
||||
break
|
||||
fi
|
||||
echo "Waiting ${delay}s for registry to catch up (saw: $PUBLISHED)..."
|
||||
sleep "$delay"
|
||||
done
|
||||
if [ "$PUBLISHED" != "$VERSION" ]; then
|
||||
echo "::error::Published version verification failed. Expected $VERSION, got $PUBLISHED"
|
||||
echo "::error::Version $VERSION did not appear on the registry within timeout"
|
||||
exit 1
|
||||
fi
|
||||
echo "✓ Verified: get-shit-done-cc@$VERSION is live on npm"
|
||||
LATEST_VER=$(npm view get-shit-done-cc dist-tags.latest 2>/dev/null || echo "NOT_FOUND")
|
||||
if [ "$LATEST_VER" != "$VERSION" ]; then
|
||||
echo "::error::dist-tag 'latest' resolves to '$LATEST_VER', expected '$VERSION'"
|
||||
exit 1
|
||||
fi
|
||||
echo "✓ Verified: get-shit-done-cc@$VERSION is live on @latest"
|
||||
|
||||
- name: Summary
|
||||
env:
|
||||
VERSION: ${{ inputs.version }}
|
||||
BASE_TAG: ${{ needs.validate-version.outputs.base_tag }}
|
||||
DRY_RUN: ${{ inputs.dry_run }}
|
||||
run: |
|
||||
echo "## Hotfix v${VERSION}" >> "$GITHUB_STEP_SUMMARY"
|
||||
if [ "$DRY_RUN" = "true" ]; then
|
||||
echo "**DRY RUN** — npm publish, tagging, and push skipped" >> "$GITHUB_STEP_SUMMARY"
|
||||
else
|
||||
echo "- Published to npm as \`latest\`" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "- Tagged \`v${VERSION}\`" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "- PR created to merge back to main" >> "$GITHUB_STEP_SUMMARY"
|
||||
fi
|
||||
{
|
||||
echo "## Hotfix v${VERSION}"
|
||||
echo ""
|
||||
echo "- Base (cumulative-fix anchor): \`${BASE_TAG}\`"
|
||||
if [ "$DRY_RUN" = "true" ]; then
|
||||
echo "- **DRY RUN** — npm publish, tagging, and push skipped"
|
||||
else
|
||||
echo "- Published to npm as \`latest\`"
|
||||
echo "- \`next\` dist-tag re-pointed to v${VERSION}"
|
||||
echo "- Tagged \`v${VERSION}\` (anchor for the next hotfix's cherry-pick base)"
|
||||
echo "- SDK bundled at \`sdk-bundle/gsd-sdk.tgz\` inside CC tarball"
|
||||
echo "- Merge-back PR opened against main"
|
||||
fi
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
790
.github/workflows/release-sdk.yml
vendored
Normal file
790
.github/workflows/release-sdk.yml
vendored
Normal file
@@ -0,0 +1,790 @@
|
||||
# Release SDK Bundle
|
||||
#
|
||||
# Stopgap workflow_dispatch publish path: builds get-shit-done-cc with the
|
||||
# compiled SDK and the SDK .tgz bundled inside the CC tarball, then
|
||||
# publishes the CC package to ONE chosen dist-tag (dev | next | latest)
|
||||
# per run.
|
||||
#
|
||||
# Why this exists: @gsd-build/sdk publishes from canary.yml and release.yml
|
||||
# fail because the @gsd-build npm token is currently unavailable. CC users
|
||||
# do not consume @gsd-build/sdk directly — bin/gsd-sdk.js resolves
|
||||
# sdk/dist/cli.js from inside the installed CC package, so the bundled
|
||||
# copy is sufficient for full functionality. This workflow ships CC alone
|
||||
# (no separate @gsd-build/sdk publish attempt) and additionally bakes a
|
||||
# bundled gsd-sdk-<version>.tgz at sdk-bundle/gsd-sdk.tgz inside the CC
|
||||
# tarball as a recoverable npm-installable artifact.
|
||||
#
|
||||
# Existing canary.yml and release.yml are intentionally untouched. They
|
||||
# remain the canonical two-package publish path; restore them to primary
|
||||
# use once @gsd-build/sdk ownership is recovered.
|
||||
#
|
||||
# Tracking issues: #2925 (initial workflow), #2929 (CI-gate parity with release.yml)
|
||||
|
||||
name: Release SDK Bundle
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
action:
|
||||
description: 'publish = normal dev/next/latest publish; hotfix = create hotfix/X.YY.Z branch from latest vX.YY.* tag, cherry-pick fix:/chore: from main, publish to @latest'
|
||||
required: true
|
||||
type: choice
|
||||
default: publish
|
||||
options:
|
||||
- publish
|
||||
- hotfix
|
||||
tag:
|
||||
description: 'npm dist-tag (publish action only; hotfix forces latest)'
|
||||
required: false
|
||||
type: choice
|
||||
default: latest
|
||||
options:
|
||||
- dev
|
||||
- next
|
||||
- latest
|
||||
version:
|
||||
description: 'Version. publish: explicit (e.g. 1.50.0-dev.3) or empty to derive. hotfix: REQUIRED patch (e.g. 1.27.1, Z>0).'
|
||||
required: false
|
||||
type: string
|
||||
ref:
|
||||
description: 'Branch or ref to build from. Ignored for hotfix (workflow uses hotfix/X.YY.Z).'
|
||||
required: false
|
||||
type: string
|
||||
auto_cherry_pick:
|
||||
description: 'Hotfix only: auto-cherry-pick fix:/chore: commits from origin/main since base tag.'
|
||||
required: false
|
||||
type: boolean
|
||||
default: true
|
||||
dry_run:
|
||||
description: 'Dry run (skip npm publish, git tag, and push). Hotfix branch creation/push also skipped.'
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
# Per stream (dist-tag for publish, version for hotfix) — no concurrent publishes for the same stream.
|
||||
concurrency:
|
||||
group: release-sdk-${{ inputs.action == 'hotfix' && format('hotfix-{0}', inputs.version) || inputs.tag }}
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
NODE_VERSION: 24
|
||||
|
||||
jobs:
|
||||
# Resolves the effective git ref for this run.
|
||||
#
|
||||
# action=publish → outputs inputs.ref verbatim (may be empty = workflow ref)
|
||||
# action=hotfix → branches hotfix/X.YY.Z from highest existing vX.YY.* tag,
|
||||
# auto-cherry-picks fix:/chore: from origin/main, pushes,
|
||||
# and outputs the new branch as ref. Idempotent: if branch
|
||||
# already exists (operator pre-prepared it via hotfix.yml),
|
||||
# we just check it out and re-run the cherry-pick step
|
||||
# no-ops since `git cherry` will report nothing new.
|
||||
prepare:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
contents: write
|
||||
outputs:
|
||||
ref: ${{ steps.out.outputs.ref }}
|
||||
base_tag: ${{ steps.hotfix.outputs.base_tag }}
|
||||
steps:
|
||||
- name: Validate hotfix inputs
|
||||
if: inputs.action == 'hotfix'
|
||||
env:
|
||||
VERSION: ${{ inputs.version }}
|
||||
run: |
|
||||
if [ -z "$VERSION" ]; then
|
||||
echo "::error::action=hotfix requires the 'version' input (e.g. 1.27.1)"
|
||||
exit 1
|
||||
fi
|
||||
if ! echo "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[1-9][0-9]*$'; then
|
||||
echo "::error::Hotfix version must match X.YY.Z with Z>0 (got: $VERSION)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
if: inputs.action == 'hotfix'
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Configure git identity
|
||||
if: inputs.action == 'hotfix'
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
- name: Prepare hotfix branch
|
||||
id: hotfix
|
||||
if: inputs.action == 'hotfix'
|
||||
env:
|
||||
VERSION: ${{ inputs.version }}
|
||||
AUTO_CHERRY_PICK: ${{ inputs.auto_cherry_pick }}
|
||||
DRY_RUN: ${{ inputs.dry_run }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# Stash the shipped-paths classifier from the dispatched ref's
|
||||
# working tree BEFORE `git checkout -b ... "$BASE_TAG"` below
|
||||
# overwrites it. Base tags predating #2980 don't have the
|
||||
# classifier in their tree, so the loop must reference a
|
||||
# location that survives the working-tree swap. Bug #2983.
|
||||
CLASSIFIER_SRC="scripts/diff-touches-shipped-paths.cjs"
|
||||
if [ ! -f "$CLASSIFIER_SRC" ]; then
|
||||
echo "::error::shipped-paths classifier not found at $CLASSIFIER_SRC in dispatched ref — refusing to run"
|
||||
exit 1
|
||||
fi
|
||||
CLASSIFIER="${RUNNER_TEMP}/diff-touches-shipped-paths.cjs"
|
||||
cp "$CLASSIFIER_SRC" "$CLASSIFIER"
|
||||
if [ ! -f "$CLASSIFIER" ]; then
|
||||
echo "::error::failed to stage classifier at $CLASSIFIER"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
MAJOR_MINOR=$(echo "$VERSION" | cut -d. -f1-2)
|
||||
TARGET_TAG="v${VERSION}"
|
||||
BRANCH="hotfix/${VERSION}"
|
||||
# Semver-correct selection: append TARGET_TAG, sort -V, take preceding entry.
|
||||
# Plain lexicographic compare mis-orders multi-digit patches (v1.27.10 vs v1.27.9).
|
||||
BASE_TAG=$( ( git tag -l "v${MAJOR_MINOR}.*" | grep -E "^v[0-9]+\.[0-9]+\.[0-9]+$"; echo "$TARGET_TAG" ) \
|
||||
| sort -V \
|
||||
| awk -v target="$TARGET_TAG" '$1 == target { print prev; exit } { prev = $1 }')
|
||||
if [ -z "$BASE_TAG" ]; then
|
||||
echo "::error::No prior stable tag found for ${MAJOR_MINOR}.x before $TARGET_TAG"
|
||||
exit 1
|
||||
fi
|
||||
echo "base_tag=$BASE_TAG" >> "$GITHUB_OUTPUT"
|
||||
echo "branch=$BRANCH" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# Idempotent branch creation — operator may have pre-prepared via hotfix.yml.
|
||||
git fetch origin main:refs/remotes/origin/main
|
||||
if git ls-remote --exit-code origin "refs/heads/$BRANCH" >/dev/null 2>&1; then
|
||||
echo "Branch $BRANCH already exists on origin; checking out"
|
||||
git fetch origin "$BRANCH"
|
||||
git checkout "$BRANCH"
|
||||
BRANCH_PRE_EXISTED=1
|
||||
else
|
||||
git checkout -b "$BRANCH" "$BASE_TAG"
|
||||
BRANCH_PRE_EXISTED=0
|
||||
# Push the skeleton up-front (real runs only) so cherry-pick conflicts
|
||||
# leave a remote artefact the operator can resolve. Dry-run keeps
|
||||
# everything local — no orphan branch created on origin.
|
||||
if [ "$DRY_RUN" != "true" ]; then
|
||||
git push -u origin "$BRANCH"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$AUTO_CHERRY_PICK" = "true" ]; then
|
||||
CANDIDATES=$(git cherry HEAD origin/main | awk '/^\+ / {print $2}')
|
||||
if [ -n "$CANDIDATES" ]; then
|
||||
ORDERED=$(git log --reverse --format='%H' "${BASE_TAG}..origin/main" \
|
||||
| grep -F -f <(echo "$CANDIDATES") || true)
|
||||
INCLUDED=""
|
||||
# POLICY_SKIPPED — commits intentionally not picked because they
|
||||
# don't match the fix/chore filter (feat/refactor/docs/etc).
|
||||
# CONFLICT_SKIPPED — fix/chore commits whose cherry-pick failed
|
||||
# and were skipped per the full-automation policy (#2968).
|
||||
# NON_SHIPPED_SKIPPED — fix/chore commits whose diff doesn't
|
||||
# touch any path in the npm tarball's `files` whitelist
|
||||
# (CI / test / docs / planning-only changes). They can't
|
||||
# affect the published package's behavior, so picking them
|
||||
# into a hotfix is meaningless — and picking workflow-file
|
||||
# changes specifically would also fail the push step because
|
||||
# the default GITHUB_TOKEN lacks the `workflow` scope. The
|
||||
# shipped-paths filter is the precise root cause: bug #2980.
|
||||
# Operators reviewing the run summary need these distinct so
|
||||
# the manual-review queue (CONFLICT_SKIPPED) isn't buried in
|
||||
# the noise from the other two buckets.
|
||||
POLICY_SKIPPED=""
|
||||
CONFLICT_SKIPPED=""
|
||||
NON_SHIPPED_SKIPPED=""
|
||||
while IFS= read -r SHA; do
|
||||
[ -z "$SHA" ] && continue
|
||||
SUBJECT=$(git log -1 --format='%s' "$SHA")
|
||||
if echo "$SUBJECT" | grep -qE '^(fix|chore)(\([^)]+\))?!?: '; then
|
||||
# Merge commits with fix:/chore: titles can't be cherry-picked
|
||||
# without `-m <parent>` and we can't pick the parent
|
||||
# automatically. They fail BEFORE entering cherry-pick state
|
||||
# (no CHERRY_PICK_HEAD), so an unconditional `--skip` would
|
||||
# then fail and brick the loop. Skip them upfront with a
|
||||
# distinct reason. Bug #2968 / CodeRabbit on PR #2970.
|
||||
PARENT_COUNT=$(git rev-list --parents -n 1 "$SHA" | awk '{print NF - 1}')
|
||||
if [ "$PARENT_COUNT" -gt 1 ]; then
|
||||
REASON="merge commit — manual -m parent selection required"
|
||||
echo "↷ skipping $SHA — $REASON"
|
||||
CONFLICT_SKIPPED="${CONFLICT_SKIPPED}- \`${SHA}\` ${SUBJECT} ($REASON)"$'\n'
|
||||
continue
|
||||
fi
|
||||
# Pre-pick guard: a hotfix release can only be affected
|
||||
# by commits whose diff intersects the npm tarball's
|
||||
# shipped paths (package.json `files` whitelist plus
|
||||
# package.json itself, which `npm pack` always
|
||||
# includes). Commits that touch only CI workflows,
|
||||
# tests, docs, or planning artifacts cannot change what
|
||||
# ships, so picking them into a hotfix is meaningless.
|
||||
# As a side benefit, this excludes
|
||||
# `.github/workflows/*` changes whose push would
|
||||
# otherwise be rejected by GitHub because the default
|
||||
# GITHUB_TOKEN lacks the `workflow` scope. The filter
|
||||
# is implemented in
|
||||
# scripts/diff-touches-shipped-paths.cjs rather than
|
||||
# inline so the rules (read package.json `files`,
|
||||
# treat entries as file-OR-directory prefix, the
|
||||
# `package.json`-always-shipped rule) are
|
||||
# unit-testable. Bug #2980.
|
||||
#
|
||||
# Use $CLASSIFIER (staged at workflow-start, before
|
||||
# `git checkout -b ... "$BASE_TAG"` swapped the working
|
||||
# tree) rather than `scripts/...` directly — base tags
|
||||
# older than #2980 don't have the classifier in their
|
||||
# tree. Capture the exit code via PIPESTATUS and
|
||||
# dispatch on it: 0 = shipped, 1 = not shipped, 2+ =
|
||||
# classifier error → fail-fast (don't silently treat
|
||||
# tooling errors as informational skips). Bug #2983.
|
||||
#
|
||||
# PIPESTATUS capture must happen IMMEDIATELY after the
|
||||
# pipeline — the previous form (`pipeline || true; RC=
|
||||
# ${PIPESTATUS[1]}`) had a subtle bug: when the
|
||||
# pipeline fails (exit 1 or 2 — exactly the cases we
|
||||
# care about), `|| true` runs `true` as a one-command
|
||||
# pipeline, overwriting PIPESTATUS to (0). The fix is
|
||||
# to wrap the pipeline in `set +e`/`set -e` and snapshot
|
||||
# PIPESTATUS into a local array on the very next line.
|
||||
# CodeRabbit on PR #2984.
|
||||
set +e
|
||||
git diff-tree --no-commit-id --name-only -r "$SHA" \
|
||||
| node "$CLASSIFIER"
|
||||
PIPE_RC=("${PIPESTATUS[@]}")
|
||||
set -e
|
||||
DIFFTREE_RC="${PIPE_RC[0]}"
|
||||
CLASSIFIER_RC="${PIPE_RC[1]}"
|
||||
if [ "$DIFFTREE_RC" -ne 0 ]; then
|
||||
echo "::error::git diff-tree failed for $SHA (exit $DIFFTREE_RC) — refusing to classify on incomplete input."
|
||||
exit "$DIFFTREE_RC"
|
||||
fi
|
||||
case "$CLASSIFIER_RC" in
|
||||
0) ;;
|
||||
1)
|
||||
REASON="touches no shipped paths (CI / test / docs / planning only)"
|
||||
echo "↷ skipping $SHA — $REASON"
|
||||
NON_SHIPPED_SKIPPED="${NON_SHIPPED_SKIPPED}- \`${SHA}\` ${SUBJECT}"$'\n'
|
||||
continue
|
||||
;;
|
||||
*)
|
||||
echo "::error::shipped-paths classifier failed for $SHA (exit $CLASSIFIER_RC). Refusing to silently skip — bug #2983."
|
||||
exit "$CLASSIFIER_RC"
|
||||
;;
|
||||
esac
|
||||
echo "→ cherry-picking $SHA $SUBJECT"
|
||||
# Pin merge.conflictStyle=merge on the cherry-pick so the
|
||||
# awk classifier below sees deterministic marker shapes —
|
||||
# diff3/zdiff3 would inject `||||||| ancestor` lines into
|
||||
# the HEAD section and cause context-missing conflicts to
|
||||
# misclassify as real. Bug #2966.
|
||||
if ! git -c merge.conflictStyle=merge cherry-pick -x --allow-empty --keep-redundant-commits "$SHA"; then
|
||||
# Full automation policy (bug #2968): any conflict the
|
||||
# cherry-pick can't auto-resolve is skipped, not aborted.
|
||||
# The hotfix run completes with whatever applies cleanly;
|
||||
# the CONFLICT_SKIPPED list below becomes the operator's
|
||||
# review queue (see "Cherry-pick summary" in the run
|
||||
# summary).
|
||||
#
|
||||
# Classify the conflict for the skip reason (operator-
|
||||
# facing diagnostic — doesn't change control flow):
|
||||
# - context absent at base: HEAD section in every
|
||||
# conflict marker is empty (the picked commit modifies
|
||||
# code that doesn't exist at the base). Bug #2966.
|
||||
# - merge conflict: HEAD section has content (both base
|
||||
# and patch want different content for the same
|
||||
# region). Typical when the base tag was cut from a
|
||||
# branch that has diverged from main. Bug #2968.
|
||||
UNMERGED=$(git diff --name-only --diff-filter=U)
|
||||
REASON="merge conflict — manual review"
|
||||
if [ -n "$UNMERGED" ]; then
|
||||
ALL_EMPTY_HEAD=true
|
||||
while IFS= read -r CONFLICTED; do
|
||||
[ -z "$CONFLICTED" ] && continue
|
||||
# Guard the classifier against degenerate cases that
|
||||
# would otherwise skew toward "context absent" (the
|
||||
# auto-skip path) when they're actually unsafe to skip:
|
||||
# - file missing or unreadable: don't pretend the
|
||||
# conflict is benign; treat as real.
|
||||
# - file listed as unmerged but no conflict markers
|
||||
# present: anomalous git state; treat as real so
|
||||
# the pick goes to the manual-review queue.
|
||||
# CodeRabbit on PR #2970.
|
||||
if [ ! -r "$CONFLICTED" ] || ! grep -q '^<<<<<<< ' "$CONFLICTED" 2>/dev/null; then
|
||||
ALL_EMPTY_HEAD=false
|
||||
break
|
||||
fi
|
||||
REAL=$(awk '
|
||||
/^<<<<<<< / { in_head=1; head=""; next }
|
||||
/^=======$/ && in_head { in_head=0; next }
|
||||
/^>>>>>>> / {
|
||||
if (head ~ /[^[:space:]]/) { print "real"; exit }
|
||||
head=""
|
||||
next
|
||||
}
|
||||
in_head { head = head $0 "\n" }
|
||||
' "$CONFLICTED" 2>/dev/null || echo "real")
|
||||
if [ "$REAL" = "real" ]; then
|
||||
ALL_EMPTY_HEAD=false
|
||||
break
|
||||
fi
|
||||
done <<< "$UNMERGED"
|
||||
if [ "$ALL_EMPTY_HEAD" = "true" ]; then
|
||||
REASON="context absent at base"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "↷ skipping $SHA — $REASON"
|
||||
# Guard `--skip`: cherry-pick can fail before entering the
|
||||
# conflict state (e.g. unreadable commit, empty-without-
|
||||
# --allow-empty edge cases the flag misses). Calling
|
||||
# `--skip` outside an in-progress cherry-pick exits non-
|
||||
# zero and would brick the loop. CodeRabbit on PR #2970.
|
||||
if git rev-parse -q --verify CHERRY_PICK_HEAD >/dev/null 2>&1; then
|
||||
git cherry-pick --skip
|
||||
fi
|
||||
CONFLICT_SKIPPED="${CONFLICT_SKIPPED}- \`${SHA}\` ${SUBJECT} ($REASON)"$'\n'
|
||||
continue
|
||||
fi
|
||||
INCLUDED="${INCLUDED}- \`${SHA}\` ${SUBJECT}"$'\n'
|
||||
else
|
||||
POLICY_SKIPPED="${POLICY_SKIPPED}- \`${SHA}\` ${SUBJECT}"$'\n'
|
||||
fi
|
||||
done <<< "$ORDERED"
|
||||
{
|
||||
echo "## Cherry-pick summary"
|
||||
echo ""
|
||||
echo "Base: \`$BASE_TAG\` → Branch: \`$BRANCH\`$([ "$DRY_RUN" = "true" ] && echo " (DRY RUN — local only)")"
|
||||
echo ""
|
||||
if [ -n "$INCLUDED" ]; then
|
||||
echo "### Included (fix/chore)"
|
||||
echo ""
|
||||
echo "$INCLUDED"
|
||||
else
|
||||
echo "_No fix/chore commits to include._"
|
||||
fi
|
||||
if [ -n "$NON_SHIPPED_SKIPPED" ]; then
|
||||
echo "### Skipped — touches no shipped paths (informational)"
|
||||
echo ""
|
||||
echo "These fix/chore commits don't touch any path in the npm tarball's \`files\` whitelist (or \`package.json\`), so they cannot change the published package's behavior. CI / test / docs / planning-only changes belong on \`main\`, not in a hotfix. No action needed."
|
||||
echo ""
|
||||
echo "$NON_SHIPPED_SKIPPED"
|
||||
fi
|
||||
if [ -n "$CONFLICT_SKIPPED" ]; then
|
||||
echo "### Skipped — cherry-pick conflict (manual review)"
|
||||
echo ""
|
||||
echo "$CONFLICT_SKIPPED"
|
||||
fi
|
||||
if [ -n "$POLICY_SKIPPED" ]; then
|
||||
echo "### Not auto-included (feat/refactor/docs/etc)"
|
||||
echo ""
|
||||
echo "$POLICY_SKIPPED"
|
||||
fi
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Bump version on the branch (committed) so downstream install-smoke +
|
||||
# release jobs build the correct version. The release job's own in-tree
|
||||
# bump becomes a no-op when the file already has the right version.
|
||||
CURRENT=$(node -p "require('./package.json').version")
|
||||
if [ "$CURRENT" != "$VERSION" ]; then
|
||||
npm version "$VERSION" --no-git-tag-version
|
||||
git add package.json package-lock.json
|
||||
if [ -f sdk/package.json ]; then
|
||||
(cd sdk && npm version "$VERSION" --no-git-tag-version)
|
||||
git add sdk/package.json
|
||||
[ -f sdk/package-lock.json ] && git add sdk/package-lock.json
|
||||
fi
|
||||
git commit -m "chore: bump version to $VERSION for hotfix"
|
||||
fi
|
||||
if [ "$DRY_RUN" != "true" ]; then
|
||||
git push origin "$BRANCH"
|
||||
else
|
||||
echo "DRY RUN — cherry-picks applied locally; branch not pushed. Downstream install-smoke will run against \`$BASE_TAG\` (the cherry-pick verification above is the dry-run signal)."
|
||||
fi
|
||||
|
||||
- name: Determine effective ref
|
||||
id: out
|
||||
env:
|
||||
ACTION: ${{ inputs.action }}
|
||||
INPUT_REF: ${{ inputs.ref }}
|
||||
DRY_RUN: ${{ inputs.dry_run }}
|
||||
BASE_TAG: ${{ steps.hotfix.outputs.base_tag }}
|
||||
BRANCH: ${{ steps.hotfix.outputs.branch }}
|
||||
run: |
|
||||
if [ "$ACTION" = "hotfix" ]; then
|
||||
if [ "$DRY_RUN" = "true" ]; then
|
||||
echo "ref=$BASE_TAG" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "ref=$BRANCH" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
else
|
||||
echo "ref=$INPUT_REF" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
# Cross-platform install validation gate (parity with release.yml).
|
||||
install-smoke:
|
||||
needs: prepare
|
||||
permissions:
|
||||
contents: read
|
||||
uses: ./.github/workflows/install-smoke.yml
|
||||
with:
|
||||
ref: ${{ needs.prepare.outputs.ref }}
|
||||
|
||||
release:
|
||||
needs: [prepare, install-smoke]
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
permissions:
|
||||
contents: write # tag + push + GitHub Release
|
||||
id-token: write # provenance
|
||||
# The merge-back PR step (and the pull-request scope it required)
|
||||
# was removed in #2983 — auto-cherry-pick hotfix flow only picks
|
||||
# commits already on main, so there's nothing to merge back.
|
||||
environment: npm-publish
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ needs.prepare.outputs.ref }}
|
||||
|
||||
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Determine version
|
||||
id: ver
|
||||
env:
|
||||
ACTION: ${{ inputs.action }}
|
||||
INPUT_TAG: ${{ inputs.tag }}
|
||||
INPUT_OVERRIDE: ${{ inputs.version }}
|
||||
run: |
|
||||
set -e
|
||||
# Hotfix forces version=inputs.version and dist-tag=latest.
|
||||
if [ "$ACTION" = "hotfix" ]; then
|
||||
if [ -z "$INPUT_OVERRIDE" ]; then
|
||||
echo "::error::action=hotfix requires the 'version' input"
|
||||
exit 1
|
||||
fi
|
||||
VERSION="$INPUT_OVERRIDE"
|
||||
EFFECTIVE_TAG="latest"
|
||||
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
||||
echo "tag=$EFFECTIVE_TAG" >> "$GITHUB_OUTPUT"
|
||||
echo "→ Hotfix: will publish v${VERSION} to dist-tag '${EFFECTIVE_TAG}'"
|
||||
exit 0
|
||||
fi
|
||||
RAW=$(node -p "require('./package.json').version")
|
||||
BASE=$(echo "$RAW" | sed 's/-.*//')
|
||||
if [ -n "$INPUT_OVERRIDE" ]; then
|
||||
VERSION="$INPUT_OVERRIDE"
|
||||
else
|
||||
case "$INPUT_TAG" in
|
||||
dev)
|
||||
N=1
|
||||
while git tag -l "v${BASE}-dev.${N}" | grep -q .; do
|
||||
N=$((N + 1))
|
||||
done
|
||||
VERSION="${BASE}-dev.${N}"
|
||||
;;
|
||||
next)
|
||||
N=1
|
||||
while git tag -l "v${BASE}-rc.${N}" | grep -q .; do
|
||||
N=$((N + 1))
|
||||
done
|
||||
VERSION="${BASE}-rc.${N}"
|
||||
;;
|
||||
latest)
|
||||
VERSION="$BASE"
|
||||
;;
|
||||
*)
|
||||
echo "::error::Unknown tag '$INPUT_TAG' (expected dev|next|latest)"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
||||
echo "tag=$INPUT_TAG" >> "$GITHUB_OUTPUT"
|
||||
echo "→ Will publish v${VERSION} to dist-tag '${INPUT_TAG}'"
|
||||
|
||||
# Reconciliation mode: if version is already on npm (a prior run
|
||||
# published successfully but a downstream step failed), don't hard-fail.
|
||||
# Set a flag and skip the publish step below; tag/release/PR/dist-tag
|
||||
# steps still execute so the rerun can finish reconciling state.
|
||||
- name: Detect prior publish (reconciliation mode)
|
||||
id: prior_publish
|
||||
env:
|
||||
VERSION: ${{ steps.ver.outputs.version }}
|
||||
run: |
|
||||
EXISTING=$(npm view get-shit-done-cc@"$VERSION" version 2>/dev/null || true)
|
||||
if [ -n "$EXISTING" ]; then
|
||||
echo "::warning::get-shit-done-cc@${VERSION} is already on the registry — entering reconciliation mode (skip publish, continue with tag/release/PR/dist-tag)."
|
||||
echo "skip_publish=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "skip_publish=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
# Tolerant tag-existence check (matches release.yml pattern). An
|
||||
# operator re-running after a mid-flight publish-step failure should
|
||||
# not be blocked just because the tag step succeeded last time. Only
|
||||
# error if the existing tag points at a different commit than HEAD.
|
||||
- name: Check git tag (skip if matches HEAD, error if mismatched)
|
||||
env:
|
||||
VERSION: ${{ steps.ver.outputs.version }}
|
||||
run: |
|
||||
if git rev-parse -q --verify "refs/tags/v${VERSION}" >/dev/null; then
|
||||
EXISTING_SHA=$(git rev-parse "refs/tags/v${VERSION}")
|
||||
HEAD_SHA=$(git rev-parse HEAD)
|
||||
if [ "$EXISTING_SHA" != "$HEAD_SHA" ]; then
|
||||
echo "::error::git tag v${VERSION} already exists pointing at ${EXISTING_SHA}, but HEAD is ${HEAD_SHA}"
|
||||
exit 1
|
||||
fi
|
||||
echo "::notice::tag v${VERSION} already exists at HEAD; tag step will skip"
|
||||
fi
|
||||
|
||||
- name: Configure git identity
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
- name: Bump in-tree version (not committed)
|
||||
env:
|
||||
VERSION: ${{ steps.ver.outputs.version }}
|
||||
run: |
|
||||
# --allow-same-version: prepare may have already committed this bump
|
||||
# on the hotfix branch (release checks out BRANCH in real runs,
|
||||
# BASE_TAG in dry-runs — only the latter has the older version).
|
||||
npm version "$VERSION" --no-git-tag-version --allow-same-version
|
||||
cd sdk && npm version "$VERSION" --no-git-tag-version --allow-same-version
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Run full test suite with coverage (parity with release.yml)
|
||||
run: npm run test:coverage
|
||||
|
||||
- name: Build SDK dist for tarball
|
||||
run: npm run build:sdk
|
||||
|
||||
- name: Verify CC tarball ships sdk/dist/cli.js (bug #2647 guard)
|
||||
run: bash scripts/verify-tarball-sdk-dist.sh
|
||||
|
||||
- name: Pack SDK as tarball and bundle into CC source tree
|
||||
env:
|
||||
VERSION: ${{ steps.ver.outputs.version }}
|
||||
run: |
|
||||
set -e
|
||||
cd sdk
|
||||
npm pack
|
||||
# npm pack emits gsd-build-sdk-<version>.tgz in the cwd
|
||||
TARBALL="gsd-build-sdk-${VERSION}.tgz"
|
||||
if [ ! -f "$TARBALL" ]; then
|
||||
echo "::error::Expected $TARBALL but npm pack did not produce it. Listing sdk/:"
|
||||
ls -la
|
||||
exit 1
|
||||
fi
|
||||
mkdir -p ../sdk-bundle
|
||||
mv "$TARBALL" ../sdk-bundle/gsd-sdk.tgz
|
||||
cd ..
|
||||
ls -la sdk-bundle/
|
||||
|
||||
- name: Add sdk-bundle to CC files whitelist (in-tree, not committed)
|
||||
run: |
|
||||
node <<'NODE'
|
||||
const fs = require('fs');
|
||||
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
|
||||
if (!Array.isArray(pkg.files)) {
|
||||
console.error('::error::package.json files is not an array');
|
||||
process.exit(1);
|
||||
}
|
||||
if (!pkg.files.includes('sdk-bundle')) {
|
||||
pkg.files.push('sdk-bundle');
|
||||
fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2) + '\n');
|
||||
console.log('Added sdk-bundle/ to package.json files whitelist');
|
||||
} else {
|
||||
console.log('sdk-bundle/ already in files whitelist');
|
||||
}
|
||||
NODE
|
||||
|
||||
- name: Verify CC tarball will contain sdk-bundle/gsd-sdk.tgz
|
||||
run: |
|
||||
set -e
|
||||
TARBALL=$(npm pack --ignore-scripts 2>/dev/null | tail -1)
|
||||
if [ -z "$TARBALL" ] || [ ! -f "$TARBALL" ]; then
|
||||
echo "::error::npm pack produced no tarball"
|
||||
exit 1
|
||||
fi
|
||||
echo "Inspecting $TARBALL for sdk-bundle/gsd-sdk.tgz:"
|
||||
if ! tar -tzf "$TARBALL" | grep -q "package/sdk-bundle/gsd-sdk.tgz"; then
|
||||
echo "::error::CC tarball is missing package/sdk-bundle/gsd-sdk.tgz"
|
||||
tar -tzf "$TARBALL" | grep -E "sdk-bundle|sdk/dist" | head -20
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ CC tarball contains sdk-bundle/gsd-sdk.tgz"
|
||||
rm -f "$TARBALL"
|
||||
|
||||
- name: Dry-run publish validation
|
||||
# Skip the rehearsal when the version is already on npm
|
||||
# (reconciliation mode). `npm publish --dry-run` contacts the
|
||||
# registry and fails with "You cannot publish over the
|
||||
# previously published versions" if the version exists, even
|
||||
# though no actual publish would be attempted. The real publish
|
||||
# step (further down) is gated on the same condition; gate the
|
||||
# rehearsal too so re-runs of an already-published hotfix don't
|
||||
# fail here on a check that doesn't apply. Bug #2987.
|
||||
if: ${{ steps.prior_publish.outputs.skip_publish != 'true' }}
|
||||
env:
|
||||
TAG: ${{ steps.ver.outputs.tag }}
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
run: npm publish --dry-run --tag "$TAG"
|
||||
|
||||
- name: Tag and push
|
||||
if: ${{ !inputs.dry_run }}
|
||||
env:
|
||||
VERSION: ${{ steps.ver.outputs.version }}
|
||||
run: |
|
||||
if git rev-parse -q --verify "refs/tags/v${VERSION}" >/dev/null; then
|
||||
echo "Tag v${VERSION} already exists at HEAD (per pre-flight check); skipping git tag step"
|
||||
else
|
||||
git tag "v${VERSION}"
|
||||
fi
|
||||
git push origin "v${VERSION}"
|
||||
|
||||
- name: Publish to npm (CC bundle, SDK included as both loose tree and .tgz)
|
||||
if: ${{ !inputs.dry_run && steps.prior_publish.outputs.skip_publish != 'true' }}
|
||||
env:
|
||||
TAG: ${{ steps.ver.outputs.tag }}
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
run: npm publish --provenance --access public --tag "$TAG"
|
||||
|
||||
# Keep `next` from going stale relative to `latest`. When publishing a
|
||||
# stable release, also point `next` at it so users on `@next` don't
|
||||
# get stuck on an older pre-release than what's now stable. Parity
|
||||
# with release.yml#finalize "Clean up next dist-tag" step.
|
||||
- name: Re-point next dist-tag at the new latest (only when tag=latest)
|
||||
if: ${{ !inputs.dry_run && steps.ver.outputs.tag == 'latest' }}
|
||||
env:
|
||||
VERSION: ${{ steps.ver.outputs.version }}
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
run: |
|
||||
npm dist-tag add "get-shit-done-cc@${VERSION}" next
|
||||
echo "✅ next dist-tag re-pointed to v${VERSION} (matches latest)"
|
||||
|
||||
- name: Create GitHub Release (idempotent)
|
||||
if: ${{ !inputs.dry_run }}
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
VERSION: ${{ steps.ver.outputs.version }}
|
||||
TAG: ${{ steps.ver.outputs.tag }}
|
||||
run: |
|
||||
# Per-tag release flags:
|
||||
# dev, next → --prerelease (won't be highlighted as the latest release on the repo page)
|
||||
# latest → --latest (becomes the highlighted release)
|
||||
# Idempotent: if release already exists (rerun after a transient
|
||||
# downstream failure), edit the latest flag instead of failing.
|
||||
if gh release view "v${VERSION}" >/dev/null 2>&1; then
|
||||
echo "GitHub Release v${VERSION} already exists; reconciling --latest flag"
|
||||
if [ "$TAG" = "latest" ]; then
|
||||
gh release edit "v${VERSION}" --latest || true
|
||||
fi
|
||||
elif [ "$TAG" = "latest" ]; then
|
||||
gh release create "v${VERSION}" \
|
||||
--title "v${VERSION}" \
|
||||
--generate-notes \
|
||||
--latest
|
||||
else
|
||||
gh release create "v${VERSION}" \
|
||||
--title "v${VERSION}" \
|
||||
--generate-notes \
|
||||
--prerelease
|
||||
fi
|
||||
echo "✅ GitHub Release v${VERSION} ready"
|
||||
|
||||
# Merge-back PR step removed — bug #2983.
|
||||
#
|
||||
# The auto-cherry-pick hotfix flow only picks commits already on
|
||||
# main (`git cherry HEAD origin/main` outputs unmerged commits;
|
||||
# we filter to fix:/chore: from main). By construction every code
|
||||
# commit on the hotfix branch is already on main. The only
|
||||
# hotfix-branch-only commit is `chore: bump version to X.Y.Z for
|
||||
# hotfix`, which would either no-op against main (already past
|
||||
# X.Y.Z) or rewind main's in-progress version — strictly
|
||||
# counterproductive in either case.
|
||||
#
|
||||
# The original merge-back step also failed in production with
|
||||
# `GitHub Actions is not permitted to create or approve pull
|
||||
# requests (createPullRequest)` (org policy), but even if the
|
||||
# policy were lifted the PR would have nothing useful to merge.
|
||||
# Run 25232968975 was the trigger for removal.
|
||||
|
||||
- name: Verify publish landed on registry
|
||||
if: ${{ !inputs.dry_run }}
|
||||
env:
|
||||
VERSION: ${{ steps.ver.outputs.version }}
|
||||
TAG: ${{ steps.ver.outputs.tag }}
|
||||
run: |
|
||||
PUBLISHED="NOT_FOUND"
|
||||
for delay in 5 10 20 30 45; do
|
||||
PUBLISHED=$(npm view get-shit-done-cc@"$VERSION" version 2>/dev/null || echo "NOT_FOUND")
|
||||
if [ "$PUBLISHED" = "$VERSION" ]; then
|
||||
break
|
||||
fi
|
||||
echo "Waiting ${delay}s for registry to catch up (saw: $PUBLISHED)..."
|
||||
sleep "$delay"
|
||||
done
|
||||
if [ "$PUBLISHED" != "$VERSION" ]; then
|
||||
echo "::error::Version $VERSION did not appear on the registry within timeout"
|
||||
exit 1
|
||||
fi
|
||||
TAG_VERSION=$(npm view get-shit-done-cc dist-tags."$TAG" 2>/dev/null || echo "NOT_FOUND")
|
||||
if [ "$TAG_VERSION" != "$VERSION" ]; then
|
||||
echo "::error::dist-tag '$TAG' resolves to '$TAG_VERSION', expected '$VERSION'"
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ get-shit-done-cc@${VERSION} live on dist-tag '${TAG}'"
|
||||
|
||||
- name: Summary
|
||||
env:
|
||||
ACTION: ${{ inputs.action }}
|
||||
VERSION: ${{ steps.ver.outputs.version }}
|
||||
TAG: ${{ steps.ver.outputs.tag }}
|
||||
BASE_TAG: ${{ needs.prepare.outputs.base_tag }}
|
||||
BRANCH: ${{ needs.prepare.outputs.ref }}
|
||||
DRY_RUN: ${{ inputs.dry_run }}
|
||||
run: |
|
||||
{
|
||||
if [ "$ACTION" = "hotfix" ]; then
|
||||
echo "## Release SDK Bundle (hotfix): v${VERSION} → @${TAG}"
|
||||
echo ""
|
||||
echo "- Base (cumulative-fix anchor): \`${BASE_TAG}\`"
|
||||
echo "- Branch: \`${BRANCH}\`"
|
||||
else
|
||||
echo "## Release SDK Bundle: v${VERSION} → @${TAG}"
|
||||
fi
|
||||
echo ""
|
||||
if [ "$DRY_RUN" = "true" ]; then
|
||||
echo "**DRY RUN** — npm publish, git tag, push, and GitHub Release were skipped."
|
||||
else
|
||||
echo "- Published \`get-shit-done-cc@${VERSION}\` to dist-tag \`${TAG}\`"
|
||||
echo "- SDK bundled inside the CC tarball at:"
|
||||
echo " - \`sdk/dist/cli.js\` (loose tree, consumed by \`bin/gsd-sdk.js\` shim)"
|
||||
echo " - \`sdk-bundle/gsd-sdk.tgz\` (npm-installable artifact)"
|
||||
echo "- Git tag \`v${VERSION}\` pushed"
|
||||
echo "- GitHub Release \`v${VERSION}\` created"
|
||||
if [ "$TAG" = "latest" ]; then
|
||||
echo "- \`next\` dist-tag re-pointed at \`v${VERSION}\` (kept current with \`latest\`)"
|
||||
fi
|
||||
if [ "$ACTION" = "hotfix" ]; then
|
||||
# Auto-cherry-pick hotfixes only pick commits already on
|
||||
# main, so there's nothing to merge back. The merge-back
|
||||
# PR step was removed in #2983; this line surfaces the
|
||||
# explicit non-action so operators don't expect a PR
|
||||
# that was never opened.
|
||||
echo "- No merge-back PR (auto-picked commits are already on main)"
|
||||
fi
|
||||
echo "- Install: \`npm install -g get-shit-done-cc@${TAG}\`"
|
||||
fi
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
17
.github/workflows/require-issue-link.yml
vendored
17
.github/workflows/require-issue-link.yml
vendored
@@ -24,19 +24,20 @@ jobs:
|
||||
echo "found=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Comment and fail if no issue link
|
||||
- name: Comment, close, and fail if no issue link
|
||||
if: steps.check.outputs.found == 'false'
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
# Uses GitHub API SDK — no shell string interpolation of untrusted input
|
||||
script: |
|
||||
const repoUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}`;
|
||||
const prNumber = context.payload.pull_request.number;
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.payload.pull_request.number,
|
||||
issue_number: prNumber,
|
||||
body: [
|
||||
'## Missing issue link',
|
||||
'## Missing issue link — PR auto-closed',
|
||||
'',
|
||||
'This PR does not reference an issue. **All PRs must link to an open issue** using a closing keyword in the PR body:',
|
||||
'',
|
||||
@@ -46,7 +47,13 @@ jobs:
|
||||
'',
|
||||
`If no issue exists for this change, [open one first](${repoUrl}/issues/new/choose), then update this PR body with the reference.`,
|
||||
'',
|
||||
'This PR will remain blocked until a valid `Closes #NNN`, `Fixes #NNN`, or `Resolves #NNN` line is present in the description.',
|
||||
'To resume work after fixing the body: edit the PR description to add a valid `Closes #NNN`, `Fixes #NNN`, or `Resolves #NNN` line, then click **Reopen pull request**. The workflow will re-evaluate on reopen.',
|
||||
].join('\n')
|
||||
});
|
||||
core.setFailed('PR body must contain a closing issue reference (e.g. "Closes #123")');
|
||||
await github.rest.pulls.update({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: prNumber,
|
||||
state: 'closed',
|
||||
});
|
||||
core.setFailed('PR body must contain a closing issue reference (e.g. "Closes #123") — PR closed.');
|
||||
|
||||
12
.github/workflows/test.yml
vendored
12
.github/workflows/test.yml
vendored
@@ -88,6 +88,18 @@ jobs:
|
||||
- name: Build SDK dist (required by installer)
|
||||
run: npm run build:sdk
|
||||
|
||||
# Seam contract gate: keep manifest -> generated aliases -> registry/CJS adapters aligned.
|
||||
# Run once per workflow on the primary Linux node to avoid redundant matrix cost.
|
||||
- name: SDK seam coverage tests
|
||||
if: matrix.os == 'ubuntu-latest' && matrix.node-version == 24
|
||||
shell: bash
|
||||
run: cd sdk && npx vitest run src/query/command-seam-coverage.test.ts
|
||||
|
||||
- name: SDK generated alias artifact drift check
|
||||
if: matrix.os == 'ubuntu-latest' && matrix.node-version == 24
|
||||
shell: bash
|
||||
run: node sdk/scripts/check-command-aliases-fresh.mjs
|
||||
|
||||
- name: Run tests with coverage
|
||||
shell: bash
|
||||
run: npm run test:coverage
|
||||
|
||||
101
CHANGELOG.md
101
CHANGELOG.md
@@ -6,6 +6,78 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
||||
|
||||
## [Unreleased](https://github.com/gsd-build/get-shit-done/compare/v1.38.5...HEAD)
|
||||
|
||||
### Fixed
|
||||
|
||||
- **`release-sdk` hotfix re-run no longer fails at `Dry-run publish validation` when the version is already on npm** — the `Detect prior publish (reconciliation mode)` step sets `skip_publish=true` when the package version is already on the registry, and the actual publish step honors that gate. The `Dry-run publish validation` step was missing the same guard, so any operator re-run of an already-published hotfix (the typical recovery path when later steps fail mid-flight) hit `npm publish --dry-run` first and got `npm error You cannot publish over the previously published versions: X.Y.Z` — `npm publish --dry-run` contacts the registry and rejects existing-version targets even though it doesn't actually publish. The dry-run validation step is now gated on the same `steps.prior_publish.outputs.skip_publish != 'true'` condition as the publish step. The rehearsal still runs on first publishes (where it has value); it skips only in the specific reconciliation case where the publish itself would be skipped. Trigger run: [25233855236](https://github.com/gsd-build/get-shit-done/actions/runs/25233855236/job/73995605643). Regression covered by `tests/bug-2987-dry-run-validation-skip-on-reconciliation.test.cjs`. (#2987)
|
||||
- **`release-sdk` hotfix flow hardened against silent classifier failures, missing-classifier-at-base-tag, and a vestigial merge-back PR step** — three issues surfaced by CodeRabbit's post-merge review of #2981 plus a production failure on the v1.39.1 release run. **(1)** `scripts/diff-touches-shipped-paths.cjs` reused exit code `1` for both the legitimate "no shipped paths" classifier result and Node's default uncaught-throw exit, so any tooling failure was indistinguishable from a normal skip. The script now uses `0` (shipped), `1` (not shipped), `2` (classifier error) with `try`/`catch` + `uncaughtException`/`unhandledRejection` handlers routing all failure paths to exit `2`. **(2)** The workflow's `git checkout -b "$BRANCH" "$BASE_TAG"` overwrote the working tree with the base tag's contents *before* the cherry-pick loop ran the classifier — but base tags predating the classifier's introduction (notably v1.39.0) don't have the file in their tree, so `node scripts/diff-touches-shipped-paths.cjs` would exit non-zero and silently drop every commit, producing an empty hotfix release. The classifier is now staged into `$RUNNER_TEMP` at the top of `Prepare hotfix branch` (before any working-tree-mutating git command), and the loop references that staged copy. The cherry-pick loop snapshots `$PIPESTATUS` into a local array (`PIPE_RC=("${PIPESTATUS[@]}")`) immediately after the classifier pipeline — under bracketed `set +e`/`set -e` — and dispatches via explicit `case`: `0` proceeds, `1` skips into `NON_SHIPPED_SKIPPED`, anything else emits `::error::shipped-paths classifier failed for $SHA (exit N)` and fails the workflow. CodeRabbit on PR #2984 caught a subtler bug in the first iteration: `pipeline \|\| true; RC=${PIPESTATUS[1]}` is broken because `\|\| true` runs `true` as its own one-command pipeline on the failure paths, overwriting `PIPESTATUS` to `(0)` and leaving `${PIPESTATUS[1]}` unset. The array-snapshot form is invariant against this. The same hardening also surfaces `git diff-tree`'s exit code (via `PIPE_RC[0]`); a non-zero diff-tree result now also fails the workflow rather than feeding partial input to the classifier. **(3)** Removed the `Open merge-back PR (hotfix only)` step. The auto-cherry-pick hotfix flow only picks commits already on main (`git cherry HEAD origin/main` outputs the unmerged ones), so by construction every code commit on the hotfix branch is already on main. The only hotfix-branch-only commit is the version-bump chore, which would either no-op against main or rewind main's in-progress version. The step also failed in production with `GitHub Actions is not permitted to create or approve pull requests (createPullRequest)` (org policy) on run [25232968975](https://github.com/gsd-build/get-shit-done/actions/runs/25232968975). The `pull-requests: write` permission previously granted to the release job has been dropped in line with least-privilege. The run-summary line that previously echoed `Merge-back PR opened against main` has been replaced with `No merge-back PR (auto-picked commits are already on main)` so operators reading the summary see an accurate non-action statement (CodeRabbit on PR #2984). Regression covered by `tests/bug-2983-classifier-exit-codes-and-base-tag-staging.test.cjs` (15 assertions across exit-code semantics, classifier staging, error dispatch, PIPESTATUS-snapshot hardening, diff-tree fail-fast, merge-back removal, and run-summary accuracy). (#2983)
|
||||
- **`release-sdk` hotfix only cherry-picks commits that change what actually ships** — the `fix:`/`chore:` filter in `Prepare hotfix branch` was too broad: it picked any commit with that conventional-commit type regardless of whether the diff could affect the published npm package. CI-only fixes (release-sdk.yml itself, hotfix tooling, test-only commits) were getting cherry-picked into hotfix branches even though they cannot change the tarball — and the subset touching `.github/workflows/*` then caused the prepare job's `git push` to be rejected by GitHub because the default `GITHUB_TOKEN` lacks the `workflow` scope, aborting the run. v1.39.1 hit this on PR #2977 (run [25232010071](https://github.com/gsd-build/get-shit-done/actions/runs/25232010071)). The loop now pre-skips any candidate commit whose `git diff-tree` output doesn't intersect the npm tarball's shipped paths (entries in `package.json` `files`, plus `package.json` itself, which `npm pack` always includes). Skipped commits land in a new `NON_SHIPPED_SKIPPED` summary bucket framed as informational — non-shipping commits cannot affect the package, so the skip needs no operator action. The shipped-paths classifier lives in `scripts/diff-touches-shipped-paths.cjs` so its rules (file-OR-directory prefix matching `npm pack` semantics, the always-shipped rule for `package.json`, the lockfile-not-shipped rule) are unit-testable. Regression covered by `tests/bug-2980-hotfix-only-picks-shipping-changes.test.cjs`. (#2980)
|
||||
- **`release-sdk` hotfix workflow fails on real run with `npm error Version not changed`** — the `release` job's `Bump in-tree version (not committed)` step ran `npm version "$VERSION"` without `--allow-same-version`, so it errored on real (non-dry-run) hotfix runs because `prepare` had already committed the bump on the hotfix branch. The release job's checkout `ref` is asymmetric — `BRANCH` (already bumped) on real runs vs `BASE_TAG` (older version) on dry-runs — which is why dry-run never caught the bug. Both `npm version` calls in that step now pass `--allow-same-version`, matching the existing pattern in `release.yml:326`. (#2976)
|
||||
- **`gsd-sdk query agent-skills` emits raw `<agent_skills>` block instead of JSON-wrapped string** — workflows that embed via `$(gsd-sdk query agent-skills <agent>)` were receiving a JSON-quoted string literal mid-prompt (e.g. `"<agent_skills>\n…"`), silently breaking all `<agent_skills>` injection into spawned subagents. The CLI dispatcher now honors an opt-in `format: 'text'` field on `QueryResult` and writes such results raw via `process.stdout.write`; `--pick` always returns JSON regardless. (#2917)
|
||||
- **`sketch --wrap-up` now dispatches correctly** — `/gsd-sketch --wrap-up` was silently no-oping because the flag dispatch wiring was omitted when the micro-skill entry point was absorbed in #2790. (#2949)
|
||||
- **`help.md` no longer advertises eight slash commands removed by the #2824 consolidation** — `/gsd-do`, `/gsd-note`, `/gsd-check-todos`, `/gsd-plant-seed`, `/gsd-research-phase`, `/gsd-list-phase-assumptions`, `/gsd-plan-milestone-gaps`, and `/gsd-join-discord` were removed when 86 skills were folded into 59. `help.md` was not updated alongside, so users typing the documented commands hit *Unknown command*. Each entry is now either rewritten to the surviving flag-based dispatcher (e.g., `/gsd-do …` → `/gsd-progress --do "…"`, `/gsd-note` → `/gsd-capture --note`, `/gsd-plant-seed` → `/gsd-capture --seed`, `/gsd-check-todos` → `/gsd-capture --list`) or removed for skills with no replacement. A regression test now asserts every `/gsd-*` reference in `help.md` has a matching `commands/gsd/*.md` stub. (#2954)
|
||||
- **`--sdk` install on Windows now writes a callable `gsd-sdk` shim** — `npx get-shit-done-cc@latest --claude --global --sdk` on Windows previously left `gsd-sdk` off PATH because `trySelfLinkGsdSdk` returned `null` unconditionally on `win32` (a missed gap from #2775's POSIX self-link, not an intentional deferral). The function now dispatches to a Windows counterpart that writes the standard npm shim triple (`gsd-sdk.cmd`, `gsd-sdk.ps1`, and a Bash wrapper) to npm's global bin, so `gsd-sdk` resolves in a fresh shell across cmd.exe, PowerShell, and Cygwin/MSYS/Git-Bash. A new regression guard in `tests/no-unconditional-win32-skip.test.cjs` blocks any future `if (process.platform === 'win32') return null;` skip-only branches in `bin/install.js`. (#2962)
|
||||
- **`/gsd-reapply-patches` Step 5 gate is now deterministic — no more silent content drops** — the prior gate parsed a Claude-generated *Hunk Verification Table* whose `verified: yes` rows were filled in without actually checking content presence, leading to merged files that lost user-added blocks (e.g., a `<visual_companion>` section, an `--execute-only` flag block) while the workflow reported success. The gate now invokes a Node script (`scripts/verify-reapply-patches.cjs`) that diffs each backup against the pristine baseline, computes the user-added significant lines, and asserts each one is present in the merged file. Exits non-zero with a per-file diagnostic on any miss; the workflow halts and surfaces the JSON output to the user. The verifier ignores low-signal lines (too short, pure whitespace, decorative comments) so trivial differences don't trigger false failures. Out of scope here: the manifest-baseline tightening described in #2969 Failure 1 — that's separate work. (#2969)
|
||||
|
||||
### Added — 1.40.0-rc.1
|
||||
- **Six namespace meta-skills with keyword-tag descriptions** — replace the flat 86-skill
|
||||
listing with two-stage hierarchical routing. Model sees 6 namespace routers
|
||||
(`gsd:workflow`, `gsd:project`, `gsd:review`, `gsd:context`, `gsd:manage`,
|
||||
`gsd:ideate`) instead of 86 flat entries; selects a namespace, then routes to the
|
||||
sub-skill. Descriptions use pipe-separated keyword tags (≤ 60 chars). Cuts cold-start
|
||||
system-prompt overhead from ~2,150 tokens to ~120. Existing sub-skills are unchanged
|
||||
and still invocable directly. (#2792)
|
||||
- **`/gsd-health --context` utilization guard** — context-window quality guard with two
|
||||
thresholds: 60 % warns ("consider `/gsd-thread`"), 70 % is critical ("reasoning
|
||||
quality may degrade"). Exposed via `/gsd-health --context` and as a structured
|
||||
`gsd-tools validate context` command. (#2792)
|
||||
- **Phase-lifecycle status-line — read-side** — `parseStateMd()` now reads four new
|
||||
STATE.md frontmatter fields: `active_phase`, `next_action`, `next_phases`, and
|
||||
`progress` (nested completed/total/percent). `formatGsdState()` gains scenes for
|
||||
in-flight, idle, and progress display. All fields default to undefined so existing
|
||||
STATE.md files keep rendering. Write-side and status-line wiring follow in a later
|
||||
RC. (#2833)
|
||||
|
||||
### Changed — 1.40.0-rc.1
|
||||
- **Hotfix release flow now auto-incorporates fixes from `main` and bundles the SDK** — `hotfix.yml create` auto-cherry-picks every `fix:`/`chore:` commit on `origin/main` not yet shipped (oldest-first; patch-equivalents skipped via `git cherry`; `feat:`/`refactor:` excluded; conflicts halt with the offending SHA; run summary lists every included SHA). `hotfix.yml finalize` adds the `install-smoke` cross-platform gate, bundles `sdk-bundle/gsd-sdk.tgz` inside the CC tarball (parity with `release-sdk.yml`), tightens the `next` dist-tag re-point, and marks the GitHub Release `--latest`. `release-sdk.yml` gains `action: publish | hotfix` plus an `auto_cherry_pick` toggle, with a new `prepare` job that branches `hotfix/X.YY.Z` from the highest existing `vX.YY.*` tag and runs the same cherry-pick logic — idempotent if the branch was pre-prepared via `hotfix.yml`. Hotfix `vX.YY.Z` is now defined as everything in `vX.YY.{Z-1}` plus every `fix:`/`chore:` since that base, so each tag is the cumulative-fix anchor for the next. (#2955)
|
||||
- **Planning workspace seam extracted from `core.cjs` into `planning-workspace.cjs`** — path/workstream/lock behavior now lives in a dedicated module (`planningDir`, `planningPaths`, `planningRoot`, active-workstream routing, `withPlanningLock`). `core.cjs` keeps compatibility re-exports while call-sites migrate to direct imports, improving locality and reducing coupling. (#2900)
|
||||
- **Skill surface consolidated 86 → 59 `commands/gsd/*.md` entries** — four new
|
||||
grouped skills (`capture`, `phase`, `config`, `workspace`) replace clusters of
|
||||
micro-skills. Six existing parents absorb wrap-up and sub-operations as flags:
|
||||
`update --sync/--reapply`, `sketch --wrap-up`, `spike --wrap-up`,
|
||||
`map-codebase --fast/--query`, `code-review --fix`, `progress --do/--next`. Zero
|
||||
functional loss; 31 micro-skills deleted. `autonomous.md` corrected to call
|
||||
`gsd:code-review --fix` (was invoking deleted `gsd:code-review-fix`). (#2790)
|
||||
- **PRs missing `Closes #NNN` are auto-closed** — the `Issue link required` workflow
|
||||
now auto-closes PRs opened without a closing keyword that links a tracking issue,
|
||||
posting a comment that points to the contribution guide. (#2872)
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Stale deleted command references updated across workflow files** — `help.md`, `do.md`, `settings.md`, `discuss-phase.md`, `new-project.md`, `plan-phase.md`, `spike.md`, and `sketch.md` referenced command names removed in #2790; updated to new consolidated equivalents. (#2950)
|
||||
|
||||
### Fixed — 1.40.0-rc.1
|
||||
- **`spike --wrap-up` now dispatches correctly** — `/gsd-spike --wrap-up` was silently no-oping because the flag dispatch wiring was omitted when the micro-skill entry point was absorbed in #2790. (#2948)
|
||||
- **`config-get context_window` returns `200000` when key absent** — querying an unset `context_window` previously exited 1 with "Key not found", surfacing a confusing error in planning logs even though the workflow fallback worked correctly. `cmdConfigGet` now consults a `SCHEMA_DEFAULTS` map and returns the documented default (`200000`, exit 0) for absent schema-defaulted keys; unknown absent keys still error as before. (#2943)
|
||||
- **`gap-analysis` now parses non-`REQ-` requirement IDs and ignores traceability table headers** — `parseRequirements()` no longer hard-codes the `REQ-` prefix and now accepts uppercase prefixed IDs such as `TST-01`, `BACK-07`, and `INSP-04`; markdown table header rows (for example `| REQ-ID | ... |`) are excluded so header tokens are not reported as phantom uncovered requirements. Added regression coverage for mixed-prefix REQUIREMENTS files with traceability tables. (#2897)
|
||||
- **Gemini slash commands namespaced as `/gsd:<cmd>` instead of `/gsd-<cmd>`** —
|
||||
Gemini CLI namespaces commands under `gsd:`, so `/gsd-plan-phase` was unexecutable.
|
||||
Body-text references in commands, agents, banners, and patch-reapply hints are now
|
||||
converted via a roster-checked regex (boundary lookbehind + extension-aware
|
||||
lookahead + roster lookup, defense-in-depth). The roster fail-loud guard prevents
|
||||
silent no-op'ing if `commands/gsd/` is ever missing. (#2768, #2783)
|
||||
- **`SKILL.md` description quoted for Copilot / Antigravity / Trae / CodeBuddy** —
|
||||
descriptions starting with a YAML 1.2 flow indicator (`[BETA]`, `{`, `*`, `&`, `!`,
|
||||
`|`, `>`, `%`, `@`, backtick) crashed gh-copilot's strict YAML loader. Six emission
|
||||
sites now wrap descriptions in `yamlQuote(...)` (= `JSON.stringify`, a valid YAML
|
||||
1.2 double-quoted scalar). (#2876)
|
||||
- **`gsd-tools` invocations use the absolute installed path** — bare `gsd-tools …`
|
||||
calls inside skill bodies relied on PATH resolution that is not guaranteed in every
|
||||
runtime; replaced with the absolute path emitted at install time. (#2851)
|
||||
- **Codex installer preserves trailing newline when stripping legacy hooks** — the
|
||||
legacy-hook strip in the Codex installer ran against files with no terminating
|
||||
newline at EOF and emitted a config that lost the newline, breaking downstream
|
||||
parsers. (#2866)
|
||||
|
||||
### Added
|
||||
- `--minimal` install flag (alias `--core-only`) writes only the main-loop core skills
|
||||
(`new-project`, `discuss-phase`, `plan-phase`, `execute-phase`, `help`, `update`) and
|
||||
@@ -36,6 +108,12 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
||||
optional `dry_run` boolean and the same publish-verification gate as `release.yml`. (#2828)
|
||||
|
||||
### Changed
|
||||
- **Canary release workflow now publishes from `dev` branch only** — `.github/workflows/canary.yml`
|
||||
swaps its four publish-step guards from `refs/heads/main` to `refs/heads/dev`. Aligns the
|
||||
workflow with the new branch→dist-tag policy (`dev` → `@canary`, `main` → `@next`/`@latest`).
|
||||
Added a header comment documenting the policy. `workflow_dispatch` runs on `main` (or any
|
||||
other branch) now complete build/test/dry-run validation but skip publish + tag, instead
|
||||
of the previous behaviour where `main` published and `dev` silently no-op'd. (#2868)
|
||||
- **Skill descriptions trimmed to ≤ 100 chars across all `commands/gsd/*.md`** — three
|
||||
anti-patterns eliminated: flag documentation already present in `argument-hint:` (e.g.
|
||||
`discuss-phase` was 380 chars, now 76), `Triggers:` keyword-stuffing lists, and
|
||||
@@ -44,7 +122,30 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
||||
`commands/gsd/*.md` description exceeds 100 chars. Run via `npm run lint:descriptions`.
|
||||
(#2789)
|
||||
|
||||
### Changed
|
||||
- **Skill surface consolidated from 86 → 59 `commands/gsd/*.md` entries** — four new
|
||||
grouped skills replace clusters of micro-skills: `capture` (add-todo, note, add-backlog,
|
||||
plant-seed, check-todos), `phase` (add-phase, insert-phase, remove-phase, edit-phase),
|
||||
`config` (settings-advanced, settings-integrations, set-profile), `workspace`
|
||||
(new-workspace, list-workspaces, remove-workspace). Six parent skills absorb wrap-up
|
||||
and sub-operations as flags: `update --sync/--reapply`, `sketch --wrap-up`,
|
||||
`spike --wrap-up`, `map-codebase --fast/--query`, `code-review --fix`,
|
||||
`progress --do/--next`. Zero functional loss. (#2790)
|
||||
- **`autonomous.md` corrected** — was invoking deleted `gsd:code-review-fix`; now calls
|
||||
`gsd:code-review --fix`. (#2790)
|
||||
|
||||
### Removed
|
||||
- **31 micro-skills deleted** — absorbed into consolidated parents or removed outright:
|
||||
add-todo, note, add-backlog, plant-seed, check-todos, add-phase, insert-phase,
|
||||
remove-phase, edit-phase, settings-advanced, settings-integrations, set-profile,
|
||||
new-workspace, list-workspaces, remove-workspace, sync-skills, reapply-patches,
|
||||
sketch-wrap-up, spike-wrap-up, scan, intel, code-review-fix, next, do,
|
||||
join-discord, research-phase, session-report, from-gsd2, analyze-dependencies,
|
||||
list-phase-assumptions, plan-milestone-gaps. All functionality preserved via flags on
|
||||
consolidated skills. (#2790)
|
||||
|
||||
### Fixed
|
||||
- **GSD slash command namespace drift cleaned up across docs, workflows, and autocomplete** — remaining active `/gsd:<cmd>` references now use canonical `/gsd-<cmd>`, escaped workflow `Skill(skill=\"gsd:...\")` prompts now use hyphenated skill names, `scripts/fix-slash-commands.cjs` rewrites retired colon syntax to hyphen syntax, and the extract-learnings command file now uses `extract-learnings.md` so generated Claude/Qwen skill autocomplete exposes `gsd-extract-learnings` instead of `gsd-extract_learnings`. (#2855)
|
||||
- **`extractCurrentMilestone` no longer truncates ROADMAP.md at heading-like lines inside fenced code blocks** — the milestone-end search now scans line-by-line while tracking ` ``` ` / `~~~` fence state, so a line like `# Ops runbook (v1.0 compat)` inside a code block no longer acts as a milestone boundary. Previously, any phase defined after such a block was invisible to `roadmap analyze`, `roadmap get-phase`, `/gsd-autonomous`, and all phase-number commands. (#2787)
|
||||
- **Codex install no longer corrupts existing `~/.codex/config.toml`** — the installer
|
||||
now defensively strips legacy `[agents]` (single-bracket) and `[[agents]]` (sequence)
|
||||
|
||||
130
CONTRIBUTING.md
130
CONTRIBUTING.md
@@ -281,6 +281,7 @@ Some tests legitimately read source files. There are six recognized categories:
|
||||
| `docs-parity` | A reference doc must stay in sync with source-defined constants (e.g., `CONFIG_DEFAULTS`). The source is the canonical list; there is no runtime API to enumerate it. |
|
||||
| `integration-test-input` | A source file is used as a real fixture input to a transformation function under test — the file is not inspected for strings but passed as data. |
|
||||
| `structural-implementation-guard` | A feature's interception or wiring point is not reachable end-to-end via `runGsdTools`. Used temporarily until a behavioral path exists. |
|
||||
| `pending-migration-to-typed-ir` | **Tracked for correction, not exempted.** Test was identified by the lint as carrying a raw-text-matching pattern that contradicts the rule above. Each annotated file MUST cite the open migration issue (e.g. `// allow-test-rule: pending-migration-to-typed-ir [#NNNN]`) so the tracking is auditable. New tests cannot use this category — they must refactor production to expose typed IR. The annotation is removed when the test is corrected. |
|
||||
|
||||
Annotate with a standalone `//` comment before the file's opening block comment:
|
||||
|
||||
@@ -296,6 +297,68 @@ Annotate with a standalone `//` comment before the file's opening block comment:
|
||||
|
||||
The annotation **must** be a standalone `// allow-test-rule:` line, not inside a `/** */` block comment — the CI linter scans for the pattern `// allow-test-rule:`.
|
||||
|
||||
### Prohibited: Raw Text Matching on Test Outputs (file content, stdout, stderr)
|
||||
|
||||
**Source-grep is not just `readFileSync` of a `.cjs` file.** The same anti-pattern shows up wherever a test pattern-matches against text that a system-under-test produced, regardless of whether that text came from a source file, a rendered shim, a child process's stdout, or a free-form `reason` string. **All forms are forbidden.**
|
||||
|
||||
The following are all violations of the same rule:
|
||||
|
||||
```javascript
|
||||
// BAD — substring match on text written by the code under test
|
||||
const cmdContent = fs.readFileSync(path.join(tmpDir, 'gsd-sdk.cmd'), 'utf8');
|
||||
assert.ok(cmdContent.includes(`@node ${jsonQuoted} %*`), '.cmd embeds shim path');
|
||||
|
||||
// BAD — regex match on a child process's human-readable stdout formatter
|
||||
const r = cp.spawnSync(SCRIPT, ['--patches-dir', dir]);
|
||||
assert.match(r.stdout, /Failures: 1/);
|
||||
assert.match(r.stdout, /not a regular file/);
|
||||
|
||||
// BAD — "structured parser" that hides string ops behind a function wrapper
|
||||
function parseCmdShim(content) {
|
||||
const lines = content.split('\r\n').filter((l) => l.length > 0);
|
||||
return { header: lines[0], usesCRLF: content.includes('\r\n') };
|
||||
}
|
||||
|
||||
// BAD — assert.match on a free-form `reason` string from a JSON report
|
||||
assert.ok(/not a regular file/.test(report.results[0].reason));
|
||||
```
|
||||
|
||||
Each of these passes on accidental near-matches (a comment containing `@node` somewhere, a stack trace that happens to say `Failures: 1`, a mis-typed reason that still contains the substring you're matching) and fails on harmless reformatting (changing `Failures: 1` to `1 failure`, swapping CRLF rendering style, rewording the error prose).
|
||||
|
||||
#### The rule
|
||||
|
||||
> **Tests assert on typed structured values. If the code under test produces text, the code under test must also expose a structured intermediate representation, and the test must assert on that IR — never on the rendered text.**
|
||||
|
||||
Concretely: for any system-under-test that produces text output (a file renderer, a CLI formatter, an error-message builder), the production code MUST expose a typed alternative that the test consumes:
|
||||
|
||||
| Output kind | Required structured surface | What the test asserts on |
|
||||
|---|---|---|
|
||||
| Rendered file (shim, template, generated code) | A pure builder function returning the IR (`{ invocation, eol, fileNames, render }`) | `triple.invocation.target === expected`, `triple.eol.cmd === '\r\n'` |
|
||||
| CLI human-formatter output | A `--json` mode that emits the same data structurally | `report.results[0].reason === REASON.FAIL_INSTALLED_NOT_REGULAR_FILE` |
|
||||
| Error / status / reason | A frozen enum (`Object.freeze({ FAIL_X: 'fail_x', ... })`) | `assert.equal(result.reason, REASON.FAIL_X)` |
|
||||
| File presence after a write | `fs.statSync().isFile()`, `.size > 0`, `.mtimeMs` advances | Filesystem facts; never read the file content back |
|
||||
|
||||
#### Concrete examples from this repo
|
||||
|
||||
`buildWindowsShimTriple(shimSrc)` in `bin/install.js` is the canonical IR pattern: pure function, no I/O, returns `{ invocation, eol, fileNames, render }`. `trySelfLinkGsdSdkWindows` calls it and writes `triple.render[kind]()` to disk. Tests assert on `triple.invocation.target`, `triple.eol.cmd`, `Object.keys(triple).sort()` — never on the rendered text. Filesystem-level tests assert `fs.statSync(target).size === Buffer.byteLength(triple.render.cmd())` to prove the writer writes what the renderer produces, **without comparing content**.
|
||||
|
||||
`scripts/verify-reapply-patches.cjs` exposes a frozen `REASON` enum and emits it through `--json`. Tests assert `report.results[0].reason === REASON.FAIL_USER_LINES_MISSING`. The human formatter exists for operator console output only — tests must not depend on its prose. Adding a new reason code requires updating the `REASON` enum, the `--json` output, AND the test that locks `Object.keys(REASON).sort()` — three coordinated changes that prevent the code surface from drifting from the test surface.
|
||||
|
||||
#### Hiding grep behind a function is still grep
|
||||
|
||||
`parseCmdShim`, `parsePs1Invocation`, etc. that internally do `content.split(...)`, `lines[1].trim()`, `content.includes(...)` are still string manipulation. The fact that the entry point looks like a parser doesn't change what's happening underneath — the test is still asserting on the lexical shape of rendered text. The fix is not "wrap the grep in a function with a typed-looking return value." The fix is to **eliminate the rendered text from the test path entirely** by surfacing the IR.
|
||||
|
||||
#### When you cannot eliminate text matching
|
||||
|
||||
There are exactly two cases where text content is the legitimate object of a test, both already covered by the existing exemption matrix:
|
||||
|
||||
1. `source-text-is-the-product` — workflow `.md` / agent `.md` / command `.md` files where the deployed text IS what the runtime loads.
|
||||
2. `docs-parity` — a reference doc must mirror source-defined constants and there is no runtime enumeration API.
|
||||
|
||||
For everything else, if a test reaches for `.includes()` / `.startsWith()` / `assert.match(text, /…/)`, the production code is missing a typed surface. **Add the typed surface; do not work around it.**
|
||||
|
||||
**CI enforcement:** `scripts/lint-no-source-grep.cjs` is being extended (see issue tracker for the latest scope) to flag `String#includes`/`String#startsWith`/`String#endsWith`/`assert.match` on `readFileSync` results and on `cp.spawnSync` stdout/stderr in test files, with the same `// allow-test-rule:` exemption mechanism.
|
||||
|
||||
### Node.js Version Compatibility
|
||||
|
||||
**Node 22 is the minimum supported version.** Node 24 is the primary CI target. All tests must pass on both.
|
||||
@@ -345,6 +408,73 @@ node --test tests/core.test.cjs
|
||||
npm run test:coverage
|
||||
```
|
||||
|
||||
### Pre-PR Seam Checks (Manifest/Alias Routing)
|
||||
|
||||
If you touched any of the command-manifest or generated alias files, run:
|
||||
|
||||
```bash
|
||||
npm run check:alias-drift
|
||||
```
|
||||
|
||||
This verifies generated alias artifacts are in sync with manifest source-of-truth.
|
||||
|
||||
Optional local pre-commit hook entry (Git-native):
|
||||
|
||||
```bash
|
||||
# one-time setup
|
||||
mkdir -p .githooks
|
||||
cat > .githooks/pre-commit <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
if git diff --cached --name-only | grep -Eq "^sdk/src/query/command-manifest\.|^sdk/src/query/command-aliases\.generated\.ts$|^get-shit-done/bin/lib/command-aliases\.generated\.cjs$|^sdk/scripts/gen-command-aliases\.ts$"; then
|
||||
npm run check:alias-drift
|
||||
fi
|
||||
EOF
|
||||
chmod +x .githooks/pre-commit
|
||||
git config core.hooksPath .githooks
|
||||
```
|
||||
|
||||
Optional local pre-push hook to block a private author-email pattern:
|
||||
|
||||
```bash
|
||||
# set locally in your shell profile (example)
|
||||
export GSD_BLOCKED_AUTHOR_REGEX='@example-corp\\.com$'
|
||||
|
||||
cat > .githooks/pre-push <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
zero_sha='0000000000000000000000000000000000000000'
|
||||
blocked_regex="${GSD_BLOCKED_AUTHOR_REGEX:-}"
|
||||
[[ -z "$blocked_regex" ]] && exit 0
|
||||
violations=()
|
||||
|
||||
while read -r local_ref local_sha remote_ref remote_sha; do
|
||||
[[ "$local_sha" == "$zero_sha" ]] && continue
|
||||
if [[ "$remote_sha" == "$zero_sha" ]]; then
|
||||
commits=$(git rev-list "$local_sha" --not --remotes)
|
||||
else
|
||||
commits=$(git rev-list "$remote_sha..$local_sha")
|
||||
fi
|
||||
while read -r commit; do
|
||||
[[ -z "$commit" ]] && continue
|
||||
email=$(git show -s --format='%ae' "$commit" | tr '[:upper:]' '[:lower:]')
|
||||
if printf '%s' "$email" | grep -Eq "$blocked_regex"; then
|
||||
violations+=("$commit <$email>")
|
||||
fi
|
||||
done <<< "$commits"
|
||||
done
|
||||
|
||||
if [[ ${#violations[@]} -gt 0 ]]; then
|
||||
echo "Push blocked: commit author email matched local blocked regex ($blocked_regex)." >&2
|
||||
printf ' - %s\n' "${violations[@]}" >&2
|
||||
exit 1
|
||||
fi
|
||||
EOF
|
||||
chmod +x .githooks/pre-push
|
||||
```
|
||||
|
||||
### CI Test Quality Checks
|
||||
|
||||
The following checks run on every PR in addition to the test suite:
|
||||
|
||||
@@ -75,15 +75,17 @@ GSDはそれを解決します。Claude Codeを信頼性の高いものにする
|
||||
|
||||
ビルトインの品質ゲートが本当の問題を検出します:スキーマドリフト検出はマイグレーション漏れのORM変更をフラグし、セキュリティ強制は検証を脅威モデルに紐付け、スコープ削減検出はプランナーが要件を暗黙的に落とすのを防止します。
|
||||
|
||||
### v1.32.0 ハイライト
|
||||
### v1.39.0 ハイライト
|
||||
|
||||
- **STATE.md整合性ゲート** — `state validate`がSTATE.mdとファイルシステムの差分を検出、`state sync`が実際のプロジェクト状態から再構築
|
||||
- **`--to N`フラグ** — 自律実行を特定のフェーズ完了後に停止
|
||||
- **リサーチゲート** — RESEARCH.mdに未解決の質問がある場合、計画をブロック
|
||||
- **検証マイルストーンスコープフィルタリング** — 後のフェーズで対処されるギャップは「ギャップ」ではなく「延期」としてマーク
|
||||
- **読み取り後編集ガード** — 非Claudeランタイムでの無限リトライループを防止するアドバイザリーフック
|
||||
- **コンテキスト削減** — Markdownのトランケーションとキャッシュフレンドリーなプロンプト順序でトークン使用量を削減
|
||||
- **4つの新ランタイム** — Trae、Kilo、Augment、Cline(合計12ランタイム)
|
||||
完全なリストは [v1.39.0 リリースノート](https://github.com/gsd-build/get-shit-done/releases/tag/v1.39.0) を参照してください。
|
||||
|
||||
- **`--minimal` インストールプロファイル** — エイリアス `--core-only`。メインループの6スキル(`new-project`、`discuss-phase`、`plan-phase`、`execute-phase`、`help`、`update`)のみをインストールし、`gsd-*` サブエージェントはゼロ。コールドスタート時のシステムプロンプトのオーバーヘッドを ~12kトークンから ~700トークンへ削減(≥94%減)。32K〜128Kコンテキストのローカル LLM やトークン課金 API に有効。
|
||||
- **`/gsd-edit-phase`** — `ROADMAP.md` 上の既存フェーズの任意フィールドをその場で編集(番号や位置は変更されない)。`--force` で確認 diff をスキップ、`depends_on` の参照を検証し、書き込み時に `STATE.md` も更新。
|
||||
- **マージ後ビルド & テストゲート** — `execute-phase` のステップ 5.6 が `workflow.build_command` の設定を自動検出し、無ければ Xcode(`.xcodeproj`)、Makefile、Justfile、Cargo、Go、Python、npm の順にフォールバック。Xcode/iOS プロジェクトでは `xcodebuild build` と `xcodebuild test` を自動実行。並列・直列両モードで動作。
|
||||
- **ランタイム別レビューモデル選択** — `review.models.<cli>` で各外部レビュー CLI(codex、gemini など)が使うモデルをプランナー/実行プロファイルとは独立に指定可能。
|
||||
- **ワークストリーム設定の継承** — `GSD_WORKSTREAM` が設定されている場合、ルートの `.planning/config.json` を先に読み込み、ワークストリーム設定をディープマージ(衝突時はワークストリーム側が優先)。ワークストリーム設定で明示的に `null` を指定するとルート値を上書き可能。
|
||||
- **手動カナリアリリースワークフロー** — `.github/workflows/canary.yml` が `workflow_dispatch` 経由で `dev` ブランチから `{base}-canary.{N}` ビルドを `@canary` dist-tag に手動公開(`get-shit-done-cc` と `@gsd-build/sdk`)。
|
||||
- **スキルの統合:86 → 59** — 4つの新しいグループ化スキル(`capture`、`phase`、`config`、`workspace`)が31のマイクロスキルを吸収。既存の親スキル6つはラップアップやサブ操作をフラグ化:`update --sync/--reapply`、`sketch --wrap-up`、`spike --wrap-up`、`map-codebase --fast/--query`、`code-review --fix`、`progress --do/--next`。機能の欠損なし。
|
||||
|
||||
---
|
||||
|
||||
@@ -597,6 +599,7 @@ lmn012o feat(08-02): create registration endpoint
|
||||
|---------|--------------|
|
||||
| `/gsd-add-phase` | ロードマップにフェーズを追加 |
|
||||
| `/gsd-insert-phase [N]` | フェーズ間に緊急作業を挿入 |
|
||||
| `/gsd-edit-phase [N] [--force]` | 既存フェーズの任意フィールドをその場で編集 — 番号と位置は変更されない |
|
||||
| `/gsd-remove-phase [N]` | 将来のフェーズを削除し番号を振り直し |
|
||||
| `/gsd-list-phase-assumptions [N]` | 計画前にClaudeの意図するアプローチを確認 |
|
||||
| `/gsd-plan-milestone-gaps` | 監査で見つかったギャップを埋めるフェーズを作成 |
|
||||
|
||||
@@ -75,15 +75,17 @@ GSD가 그걸 고칩니다. Claude Code를 신뢰할 수 있게 만드는 컨텍
|
||||
|
||||
내장 품질 게이트가 실제 문제를 잡아냅니다: 스키마 드리프트 감지는 마이그레이션 누락된 ORM 변경을 플래그하고, 보안 강제는 검증을 위협 모델에 고정시키고, 스코프 축소 감지는 플래너가 요구사항을 몰래 빠뜨리는 걸 방지합니다.
|
||||
|
||||
### v1.32.0 하이라이트
|
||||
### v1.39.0 하이라이트
|
||||
|
||||
- **STATE.md 일관성 게이트** — `state validate`가 STATE.md와 파일시스템 간 드리프트를 감지, `state sync`가 실제 프로젝트 상태에서 재구성
|
||||
- **`--to N` 플래그** — 자율 실행을 특정 단계 완료 후 중지
|
||||
- **리서치 게이트** — RESEARCH.md에 미해결 질문이 있으면 기획을 차단
|
||||
- **검증 마일스톤 스코프 필터링** — 이후 단계에서 처리될 격차는 "격차"가 아닌 "지연됨"으로 표시
|
||||
- **읽기-후-편집 가드** — 비Claude 런타임에서 무한 재시도 루프를 방지하는 어드바이저리 훅
|
||||
- **컨텍스트 축소** — 마크다운 잘라내기 및 캐시 친화적 프롬프트 순서로 토큰 사용량 절감
|
||||
- **4개의 새 런타임** — Trae, Kilo, Augment, Cline (총 12개 런타임)
|
||||
전체 목록은 [v1.39.0 릴리스 노트](https://github.com/gsd-build/get-shit-done/releases/tag/v1.39.0)를 참고하세요.
|
||||
|
||||
- **`--minimal` 설치 프로파일** — 별칭 `--core-only`. 메인 루프 6개 스킬(`new-project`, `discuss-phase`, `plan-phase`, `execute-phase`, `help`, `update`)만 설치하고 `gsd-*` 서브에이전트는 설치하지 않음. 콜드 스타트 시스템 프롬프트 오버헤드를 ~12k 토큰에서 ~700 토큰으로 축소(≥94% 감소). 32K–128K 컨텍스트의 로컬 LLM이나 토큰 과금 API에 유용.
|
||||
- **`/gsd-edit-phase`** — `ROADMAP.md`에 있는 기존 단계의 임의 필드를 그 자리에서 수정(번호와 위치는 변경되지 않음). `--force`는 확인 diff를 건너뛰고, `depends_on` 참조를 검증하며 쓰기 시 `STATE.md`도 갱신.
|
||||
- **머지 후 빌드 & 테스트 게이트** — `execute-phase` 5.6 단계가 `workflow.build_command` 설정을 우선 자동 감지하고, 없으면 Xcode(`.xcodeproj`), Makefile, Justfile, Cargo, Go, Python, npm 순으로 폴백. Xcode/iOS 프로젝트는 `xcodebuild build` 및 `xcodebuild test`를 자동 실행. 병렬·직렬 모드 모두에서 동작.
|
||||
- **런타임별 리뷰 모델 선택** — `review.models.<cli>`로 각 외부 리뷰 CLI(codex, gemini 등)가 플래너/실행 프로파일과 독립적으로 자체 모델을 선택할 수 있음.
|
||||
- **워크스트림 설정 상속** — `GSD_WORKSTREAM`이 설정되면 루트 `.planning/config.json`을 먼저 로드한 뒤 워크스트림 설정을 딥 머지(충돌 시 워크스트림 우선). 워크스트림 설정에서 명시적 `null`은 루트 값을 덮어씀.
|
||||
- **수동 카나리 릴리스 워크플로** — `.github/workflows/canary.yml`이 `workflow_dispatch`로 `dev` 브랜치에서 `{base}-canary.{N}` 빌드를 `@canary` dist-tag로 수동 게시(`get-shit-done-cc`와 `@gsd-build/sdk`).
|
||||
- **스킬 통합: 86 → 59** — 4개의 새로운 그룹 스킬(`capture`, `phase`, `config`, `workspace`)이 31개의 마이크로 스킬을 흡수. 기존 6개의 부모 스킬은 래퍼업/하위 동작을 플래그로 흡수: `update --sync/--reapply`, `sketch --wrap-up`, `spike --wrap-up`, `map-codebase --fast/--query`, `code-review --fix`, `progress --do/--next`. 기능 손실 없음.
|
||||
|
||||
---
|
||||
|
||||
@@ -594,6 +596,7 @@ lmn012o feat(08-02): create registration endpoint
|
||||
|---------|------------|
|
||||
| `/gsd-add-phase` | 로드맵에 단계 추가 |
|
||||
| `/gsd-insert-phase [N]` | 단계 사이에 긴급 작업 삽입 |
|
||||
| `/gsd-edit-phase [N] [--force]` | 기존 단계의 임의 필드를 그 자리에서 수정 — 번호와 위치는 그대로 |
|
||||
| `/gsd-remove-phase [N]` | 미래 단계 제거, 번호 재정렬 |
|
||||
| `/gsd-list-phase-assumptions [N]` | 기획 전 Claude의 의도된 접근 방식 확인 |
|
||||
| `/gsd-plan-milestone-gaps` | 감사에서 발견된 갭을 해소하기 위한 단계 생성 |
|
||||
|
||||
34
README.md
34
README.md
@@ -4,7 +4,7 @@
|
||||
|
||||
**English** · [Português](README.pt-BR.md) · [简体中文](README.zh-CN.md) · [日本語](README.ja-JP.md) · [한국어](README.ko-KR.md)
|
||||
|
||||
**A light-weight and powerful meta-prompting, context engineering and spec-driven development system for Claude Code, OpenCode, Gemini CLI, Kilo, Codex, Copilot, Cursor, Windsurf, Antigravity, Augment, Trae, Qwen Code, Cline, and CodeBuddy.**
|
||||
**A light-weight and powerful meta-prompting, context engineering and spec-driven development system for Claude Code, OpenCode, Gemini CLI, Kilo, Codex, Copilot, Cursor, Windsurf, Antigravity, Augment, Trae, Qwen Code, Hermes Agent, Cline, and CodeBuddy.**
|
||||
|
||||
**Solves context rot — the quality degradation that happens as Claude fills its context window.**
|
||||
|
||||
@@ -89,11 +89,17 @@ People who want to describe what they want and have it built correctly — witho
|
||||
|
||||
Built-in quality gates catch real problems: schema drift detection flags ORM changes missing migrations, security enforcement anchors verification to threat models, and scope reduction detection prevents the planner from silently dropping your requirements.
|
||||
|
||||
### v1.37.0 Highlights
|
||||
### v1.39.0 Highlights
|
||||
|
||||
- **Spiking & sketching** — `/gsd-spike` runs 2–5 focused experiments with Given/When/Then verdicts; `/gsd-sketch` produces 2–3 interactive HTML mockup variants per design question — both store artifacts in `.planning/` and pair with wrap-up commands to package findings into project-local skills
|
||||
- **Agent size-budget enforcement** — Tiered line-count limits (XL: 1 600, Large: 1 000, Default: 500) keep agent prompts lean; violations surface in CI
|
||||
- **Shared boilerplate extraction** — Mandatory-initial-read and project-skills-discovery logic extracted to reference files, reducing duplication across a dozen agents
|
||||
See the [v1.39.0 release notes](https://github.com/gsd-build/get-shit-done/releases/tag/v1.39.0) for the full list.
|
||||
|
||||
- **`--minimal` install profile** — alias `--core-only`, writes only the six main-loop skills (`new-project`, `discuss-phase`, `plan-phase`, `execute-phase`, `help`, `update`) and zero `gsd-*` subagents. Cuts cold-start system-prompt overhead from ~12k tokens to ~700 (≥94% reduction). Useful for local LLMs with 32K–128K context and token-billed APIs.
|
||||
- **`/gsd-edit-phase`** — modify any field of an existing phase in `ROADMAP.md` in place, without changing its number or position. `--force` skips the confirmation diff; `depends_on` references are validated and `STATE.md` is updated on write.
|
||||
- **Post-merge build & test gate** — `execute-phase` step 5.6 now auto-detects the build command from `workflow.build_command`, then falls back to Xcode (`.xcodeproj`), Makefile, Justfile, Cargo, Go, Python, or npm. Xcode/iOS projects get `xcodebuild build` + `xcodebuild test` automatically. Runs in both parallel and serial mode.
|
||||
- **Per-runtime review-model selection** — `review.models.<cli>` lets each external review CLI (codex, gemini, etc.) pick its own model independently of the planner/executor profile.
|
||||
- **Workstream config inheritance** — when `GSD_WORKSTREAM` is set, the root `.planning/config.json` is loaded first and deep-merged with the workstream config (workstream wins on conflict). Explicit `null` in a workstream config now correctly overrides a root value.
|
||||
- **Manual canary release workflow** — `.github/workflows/canary.yml` publishes `{base}-canary.{N}` builds of `get-shit-done-cc` and `@gsd-build/sdk` to the `@canary` dist-tag from `dev` on demand via `workflow_dispatch`.
|
||||
- **Skill consolidation: 86 → 59** — four new grouped skills (`capture`, `phase`, `config`, `workspace`) absorb 31 micro-skills. Six existing parents absorb wrap-up and sub-operations as flags: `update --sync/--reapply`, `sketch --wrap-up`, `spike --wrap-up`, `map-codebase --fast/--query`, `code-review --fix`, `progress --do/--next`. Zero functional loss.
|
||||
|
||||
---
|
||||
|
||||
@@ -104,11 +110,11 @@ npx get-shit-done-cc@latest
|
||||
```
|
||||
|
||||
The installer prompts you to choose:
|
||||
1. **Runtime** — Claude Code, OpenCode, Gemini, Kilo, Codex, Copilot, Cursor, Windsurf, Antigravity, Augment, Trae, Qwen Code, CodeBuddy, Cline, or all (interactive multi-select — pick multiple runtimes in a single install session)
|
||||
1. **Runtime** — Claude Code, OpenCode, Gemini, Kilo, Codex, Copilot, Cursor, Windsurf, Antigravity, Augment, Trae, Qwen Code, Hermes Agent, CodeBuddy, Cline, or all (interactive multi-select — pick multiple runtimes in a single install session)
|
||||
2. **Location** — Global (all projects) or local (current project only)
|
||||
|
||||
Verify with:
|
||||
- Claude Code / Gemini / Copilot / Antigravity / Qwen Code: `/gsd-help`
|
||||
- Claude Code / Gemini / Copilot / Antigravity / Qwen Code / Hermes Agent: `/gsd-help`
|
||||
- OpenCode / Kilo / Augment / Trae / CodeBuddy: `/gsd-help`
|
||||
- Codex: `$gsd-help`
|
||||
- Cline: GSD installs via `.clinerules` — verify by checking `.clinerules` exists
|
||||
@@ -179,6 +185,10 @@ npx get-shit-done-cc --trae --local # Install to ./.trae/
|
||||
npx get-shit-done-cc --qwen --global # Install to ~/.qwen/
|
||||
npx get-shit-done-cc --qwen --local # Install to ./.qwen/
|
||||
|
||||
# Hermes Agent
|
||||
npx get-shit-done-cc --hermes --global # Install to ~/.hermes/ (honors $HERMES_HOME)
|
||||
npx get-shit-done-cc --hermes --local # Install to ./.hermes/
|
||||
|
||||
# CodeBuddy
|
||||
npx get-shit-done-cc --codebuddy --global # Install to ~/.codebuddy/
|
||||
npx get-shit-done-cc --codebuddy --local # Install to ./.codebuddy/
|
||||
@@ -192,7 +202,7 @@ npx get-shit-done-cc --all --global # Install to all directories
|
||||
```
|
||||
|
||||
Use `--global` (`-g`) or `--local` (`-l`) to skip the location prompt.
|
||||
Use `--claude`, `--opencode`, `--gemini`, `--kilo`, `--codex`, `--copilot`, `--cursor`, `--windsurf`, `--antigravity`, `--augment`, `--trae`, `--qwen`, `--codebuddy`, `--cline`, or `--all` to skip the runtime prompt.
|
||||
Use `--claude`, `--opencode`, `--gemini`, `--kilo`, `--codex`, `--copilot`, `--cursor`, `--windsurf`, `--antigravity`, `--augment`, `--trae`, `--qwen`, `--hermes`, `--codebuddy`, `--cline`, or `--all` to skip the runtime prompt.
|
||||
The GSD SDK CLI (`gsd-sdk`) is installed automatically (required by `/gsd-*` commands). Pass `--no-sdk` to skip the SDK install, or `--sdk` to force a reinstall.
|
||||
|
||||
</details>
|
||||
@@ -685,6 +695,7 @@ You're never locked in. The system adapts.
|
||||
|---------|--------------|
|
||||
| `/gsd-add-phase` | Append phase to roadmap |
|
||||
| `/gsd-insert-phase [N]` | Insert urgent work between phases |
|
||||
| `/gsd-edit-phase [N] [--force]` | Modify any field of an existing phase in place — number and position unchanged |
|
||||
| `/gsd-remove-phase [N]` | Remove future phase, renumber |
|
||||
| `/gsd-list-phase-assumptions [N]` | See Claude's intended approach before planning |
|
||||
| `/gsd-plan-milestone-gaps` | Create phases to close gaps from audit |
|
||||
@@ -746,6 +757,8 @@ You're never locked in. The system adapts.
|
||||
|
||||
GSD stores project settings in `.planning/config.json`. Configure during `/gsd-new-project` or update later with `/gsd-settings`. For the full config schema, workflow toggles, git branching options, and per-agent model breakdown, see the [User Guide](docs/USER-GUIDE.md#configuration-reference).
|
||||
|
||||
When `GSD_WORKSTREAM` is set, GSD loads the root `.planning/config.json` first and deep-merges the workstream's `config.json` on top — workstream values win on conflict, and an explicit `null` in a workstream config overrides a root value.
|
||||
|
||||
### Core Settings
|
||||
|
||||
| Setting | Options | Default | What it controls |
|
||||
@@ -774,6 +787,8 @@ Use `inherit` when using non-Anthropic providers (OpenRouter, local models) or t
|
||||
|
||||
Or configure via `/gsd-settings`.
|
||||
|
||||
Per-runtime review-model overrides live under `review.models.<cli>` (e.g. `review.models.codex`, `review.models.gemini`) and let each external review CLI pick its own model independently of the planner/executor profile.
|
||||
|
||||
### Workflow Agents
|
||||
|
||||
These spawn additional agents during planning/execution. They improve quality but add tokens and time.
|
||||
@@ -789,6 +804,7 @@ These spawn additional agents during planning/execution. They improve quality bu
|
||||
| `workflow.skip_discuss` | `false` | Skip discuss-phase in autonomous mode |
|
||||
| `workflow.text_mode` | `false` | Text-only mode for remote sessions (no TUI menus) |
|
||||
| `workflow.use_worktrees` | `true` | Toggle worktree isolation for execution |
|
||||
| `workflow.build_command` | _(auto-detect)_ | Override the post-merge build gate command. Falls back to Xcode (`.xcodeproj`), Makefile, Justfile, Cargo, Go, Python, or npm; Xcode/iOS projects also run `xcodebuild test`. |
|
||||
|
||||
Use `/gsd-settings` to toggle these, or override per-invocation:
|
||||
- `/gsd-plan-phase --skip-research`
|
||||
@@ -919,6 +935,7 @@ npx get-shit-done-cc --antigravity --global --uninstall
|
||||
npx get-shit-done-cc --augment --global --uninstall
|
||||
npx get-shit-done-cc --trae --global --uninstall
|
||||
npx get-shit-done-cc --qwen --global --uninstall
|
||||
npx get-shit-done-cc --hermes --global --uninstall
|
||||
npx get-shit-done-cc --codebuddy --global --uninstall
|
||||
npx get-shit-done-cc --cline --global --uninstall
|
||||
|
||||
@@ -935,6 +952,7 @@ npx get-shit-done-cc --antigravity --local --uninstall
|
||||
npx get-shit-done-cc --augment --local --uninstall
|
||||
npx get-shit-done-cc --trae --local --uninstall
|
||||
npx get-shit-done-cc --qwen --local --uninstall
|
||||
npx get-shit-done-cc --hermes --local --uninstall
|
||||
npx get-shit-done-cc --codebuddy --local --uninstall
|
||||
npx get-shit-done-cc --cline --local --uninstall
|
||||
```
|
||||
|
||||
@@ -73,15 +73,17 @@ Para quem quer descrever o que precisa e receber isso construído do jeito certo
|
||||
|
||||
Quality gates embutidos capturam problemas reais: detecção de schema drift sinaliza mudanças ORM sem migrations, segurança ancora verificação a modelos de ameaça, e detecção de redução de escopo impede o planner de descartar requisitos silenciosamente.
|
||||
|
||||
### Destaques v1.32.0
|
||||
### Destaques v1.39.0
|
||||
|
||||
- **Gates de consistência STATE.md** — `state validate` detecta divergência entre STATE.md e o filesystem; `state sync` reconstrói a partir do estado real do projeto
|
||||
- **Flag `--to N`** — Para a execução autônoma após completar uma fase específica
|
||||
- **Research gate** — Bloqueia planejamento quando RESEARCH.md tem perguntas abertas não resolvidas
|
||||
- **Filtro de escopo do verificador** — Lacunas abordadas em fases posteriores são marcadas como "adiadas", não como lacunas
|
||||
- **Guard de leitura antes de edição** — Hook consultivo previne loops de retry infinitos em runtimes não-Claude
|
||||
- **Redução de contexto** — Truncamento de Markdown e ordenação de prompts cache-friendly para menor uso de tokens
|
||||
- **4 novos runtimes** — Trae, Kilo, Augment e Cline (12 runtimes no total)
|
||||
Lista completa nas [notas de release v1.39.0](https://github.com/gsd-build/get-shit-done/releases/tag/v1.39.0).
|
||||
|
||||
- **Perfil de instalação `--minimal`** — alias `--core-only`. Instala apenas os 6 skills do loop principal (`new-project`, `discuss-phase`, `plan-phase`, `execute-phase`, `help`, `update`) e nenhum subagente `gsd-*`. Reduz o overhead do system prompt no cold-start de ~12k para ~700 tokens (≥94% de redução). Útil para LLMs locais com contexto de 32K–128K e APIs cobradas por token.
|
||||
- **`/gsd-edit-phase`** — edita qualquer campo de uma fase existente em `ROADMAP.md` no lugar, sem alterar o número ou a posição. `--force` pula o diff de confirmação; referências em `depends_on` são validadas e o `STATE.md` é atualizado na escrita.
|
||||
- **Build & test gate pós-merge** — o passo 5.6 de `execute-phase` agora detecta automaticamente o comando de build em `workflow.build_command`, com fallback para Xcode (`.xcodeproj`), Makefile, Justfile, Cargo, Go, Python ou npm. Projetos Xcode/iOS rodam `xcodebuild build` e `xcodebuild test` automaticamente. Funciona em modo paralelo e serial.
|
||||
- **Modelo de review por runtime** — `review.models.<cli>` permite que cada CLI externa de review (codex, gemini, etc.) escolha seu próprio modelo, independente do perfil de planner/executor.
|
||||
- **Herança de configuração de workstream** — quando `GSD_WORKSTREAM` está definido, o `.planning/config.json` raiz é carregado primeiro e merge-deep com o config da workstream (workstream vence em conflito). Um `null` explícito no config da workstream sobrescreve corretamente o valor raiz.
|
||||
- **Workflow manual de canary release** — `.github/workflows/canary.yml` publica builds `{base}-canary.{N}` de `get-shit-done-cc` e `@gsd-build/sdk` na dist-tag `@canary` a partir de `dev`, sob demanda via `workflow_dispatch`.
|
||||
- **Consolidação de skills: 86 → 59** — 4 novos skills agrupados (`capture`, `phase`, `config`, `workspace`) absorvem 31 micro-skills. 6 skills pais existentes absorvem wrap-up e sub-operações como flags: `update --sync/--reapply`, `sketch --wrap-up`, `spike --wrap-up`, `map-codebase --fast/--query`, `code-review --fix`, `progress --do/--next`. Sem perda funcional.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -73,15 +73,17 @@ GSD 解决的就是这个问题。它是让 Claude Code 变得可靠的上下文
|
||||
|
||||
适合那些想把自己的需求说明白,然后让系统正确构建出来的人,而不是假装自己在运营一个 50 人工程组织的人。
|
||||
|
||||
### v1.32.0 亮点
|
||||
### v1.39.0 亮点
|
||||
|
||||
- **STATE.md 一致性检查** — `state validate` 检测 STATE.md 与文件系统之间的偏差;`state sync` 从实际项目状态重建
|
||||
- **`--to N` 标志** — 在完成特定阶段后停止自主执行
|
||||
- **研究门控** — 当 RESEARCH.md 有未解决的开放问题时阻止规划
|
||||
- **验证里程碑范围过滤** — 后续阶段将处理的差距标记为"延迟"而非差距
|
||||
- **读取后编辑保护** — 咨询性 hook 防止非 Claude 运行时的无限重试循环
|
||||
- **上下文缩减** — Markdown 截断和缓存友好的 prompt 排序,降低 token 使用量
|
||||
- **4 个新运行时** — Trae、Kilo、Augment 和 Cline(共 12 个运行时)
|
||||
完整列表请参阅 [v1.39.0 发行说明](https://github.com/gsd-build/get-shit-done/releases/tag/v1.39.0)。
|
||||
|
||||
- **`--minimal` 安装档** — 别名 `--core-only`。仅安装主循环的 6 个核心技能(`new-project`、`discuss-phase`、`plan-phase`、`execute-phase`、`help`、`update`),不安装任何 `gsd-*` 子代理。将冷启动系统提示开销从 ~12k token 降至 ~700 token(≥94% 减少)。适合 32K–128K 上下文的本地 LLM 和按 token 计费的 API。
|
||||
- **`/gsd-edit-phase`** — 就地修改 `ROADMAP.md` 中已有阶段的任意字段,不改变其编号或位置。`--force` 跳过确认 diff,验证 `depends_on` 引用,并在写入时更新 `STATE.md`。
|
||||
- **合并后构建与测试门** — `execute-phase` 步骤 5.6 优先自动检测 `workflow.build_command` 配置,否则按 Xcode(`.xcodeproj`)、Makefile、Justfile、Cargo、Go、Python、npm 顺序回退。Xcode/iOS 项目自动运行 `xcodebuild build` 和 `xcodebuild test`。在并行与串行模式下均生效。
|
||||
- **每运行时评审模型选择** — `review.models.<cli>` 让每个外部评审 CLI(codex、gemini 等)独立于规划/执行档选择自己的模型。
|
||||
- **工作流设置继承** — 设置 `GSD_WORKSTREAM` 后,先加载根 `.planning/config.json`,再与该工作流的配置进行深合并(冲突时工作流优先)。工作流配置中显式 `null` 会覆盖根值。
|
||||
- **手动 canary 发布工作流** — `.github/workflows/canary.yml` 通过 `workflow_dispatch` 从 `dev` 分支按需将 `{base}-canary.{N}` 构建(`get-shit-done-cc` 与 `@gsd-build/sdk`)发布到 `@canary` dist-tag。
|
||||
- **技能整合:86 → 59** — 4 个新分组技能(`capture`、`phase`、`config`、`workspace`)吸收了 31 个微技能。6 个已有父技能将收尾与子操作合并为标志:`update --sync/--reapply`、`sketch --wrap-up`、`spike --wrap-up`、`map-codebase --fast/--query`、`code-review --fix`、`progress --do/--next`。功能无损失。
|
||||
|
||||
---
|
||||
|
||||
@@ -589,6 +591,7 @@ lmn012o feat(08-02): create registration endpoint
|
||||
|------|------|
|
||||
| `/gsd-add-phase` | 在路线图末尾追加 phase |
|
||||
| `/gsd-insert-phase [N]` | 在 phase 之间插入紧急工作 |
|
||||
| `/gsd-edit-phase [N] [--force]` | 就地修改已有 phase 的任意字段 — 编号与位置保持不变 |
|
||||
| `/gsd-remove-phase [N]` | 删除未来 phase,并重编号 |
|
||||
| `/gsd-list-phase-assumptions [N]` | 在规划前查看 Claude 打算采用的方案 |
|
||||
| `/gsd-plan-milestone-gaps` | 为 audit 发现的缺口创建 phase |
|
||||
|
||||
@@ -67,15 +67,38 @@ main ← stable, always deployable
|
||||
|
||||
### Patch Release (Hotfix)
|
||||
|
||||
For critical bugs that can't wait for the next minor release.
|
||||
For fixes that need to ship without waiting for the next minor.
|
||||
|
||||
1. Trigger `hotfix.yml` with version (e.g., `1.27.1`)
|
||||
2. Workflow creates `hotfix/1.27.1` branch from the latest patch tag for that minor version (e.g., `v1.27.0` or `v1.27.1`)
|
||||
3. Cherry-pick or apply fix on the hotfix branch
|
||||
4. Push — CI runs tests automatically
|
||||
5. Trigger `hotfix.yml` finalize action
|
||||
6. Workflow runs full test suite, bumps version, tags, publishes to `latest`
|
||||
7. Merge hotfix branch back to main
|
||||
A hotfix `vX.YY.Z` cumulatively includes everything in `vX.YY.{Z-1}` plus every `fix:`/`chore:` commit landed on `main` since that base. The base tag is the anchor — `git cherry $BASE_TAG main` reveals exactly which commits are still unshipped, and the new `vX.YY.Z` tag becomes the next hotfix's base, so the cycle is self-documenting.
|
||||
|
||||
#### Two paths
|
||||
|
||||
**Path A — `hotfix.yml` (canonical, two-step):**
|
||||
|
||||
1. Trigger `hotfix.yml` with `action=create`, `version=1.27.1`, `auto_cherry_pick=true` (default).
|
||||
- Workflow detects `BASE_TAG` = highest `v1.27.*` < `v1.27.1` (so `1.27.1` branches from `v1.27.0`; `1.27.2` would branch from `v1.27.1`).
|
||||
- Branches `hotfix/1.27.1` from `BASE_TAG`.
|
||||
- Auto-cherry-picks every `fix:`/`chore:` commit on `origin/main` not already in the base, oldest-first. Patch-equivalents are skipped via `git cherry`. `feat:`/`refactor:` are **never** auto-included.
|
||||
- On conflict the workflow halts with the offending SHA. Resolve manually on the branch, then re-run finalize with `auto_cherry_pick=false`.
|
||||
- Bumps `package.json` (and `sdk/package.json`), pushes the branch, and lists every included SHA in the run summary.
|
||||
2. (Optional) push additional manual commits to `hotfix/1.27.1`.
|
||||
3. Trigger `hotfix.yml` with `action=finalize`. The workflow:
|
||||
- Runs `install-smoke` cross-platform gate.
|
||||
- Runs full test suite + coverage.
|
||||
- Builds SDK, bundles `sdk-bundle/gsd-sdk.tgz` inside the CC tarball (parity with `release-sdk.yml`).
|
||||
- Tags `v1.27.1`, publishes to `@latest`, re-points `@next → v1.27.1`.
|
||||
- Opens merge-back PR against `main`.
|
||||
|
||||
**Path B — `release-sdk.yml` (stopgap, one-shot):**
|
||||
|
||||
Active while the `@gsd-build/sdk` npm token is unavailable; bundles the SDK inside the CC tarball.
|
||||
|
||||
1. Trigger `release-sdk.yml` with `action=hotfix`, `version=1.27.1`, `auto_cherry_pick=true`.
|
||||
- The `prepare` job creates the branch and cherry-picks (same logic as Path A).
|
||||
- `install-smoke` runs against the new branch.
|
||||
- The `release` job tags, publishes to `@latest`, re-points `@next`, opens merge-back PR.
|
||||
- Idempotent: if `hotfix/1.27.1` already exists (e.g. you ran `hotfix.yml create` first), the prepare job checks it out and re-runs cherry-pick as a no-op.
|
||||
2. `dry_run=true` exercises the full pipeline without pushing the branch or publishing.
|
||||
|
||||
### Minor Release (Standard Cycle)
|
||||
|
||||
|
||||
@@ -358,6 +358,30 @@ If RED or GREEN gate commits are missing, add a warning to SUMMARY.md under a `#
|
||||
<task_commit_protocol>
|
||||
After each task completes (verification passed, done criteria met), commit immediately.
|
||||
|
||||
**0. Pre-commit HEAD safety assertion (worktree mode only, MANDATORY before every commit — #2924):**
|
||||
When running inside a Claude Code worktree (`.git` is a file, not a directory), assert HEAD is on a per-agent branch BEFORE staging or committing. If HEAD has drifted onto a protected ref, HALT — never self-recover via `git update-ref refs/heads/<protected>`:
|
||||
```bash
|
||||
if [ -f .git ]; then # worktree
|
||||
HEAD_REF=$(git symbolic-ref --quiet HEAD || echo "DETACHED")
|
||||
ACTUAL_BRANCH=$(git rev-parse --abbrev-ref HEAD)
|
||||
# Deny-list: never commit on a protected ref.
|
||||
if [ "$HEAD_REF" = "DETACHED" ] || \
|
||||
echo "$ACTUAL_BRANCH" | grep -Eq '^(main|master|develop|trunk|release/.*)$'; then
|
||||
echo "FATAL: refusing to commit — worktree HEAD is on '$ACTUAL_BRANCH' (expected per-agent branch)." >&2
|
||||
echo "DO NOT use 'git update-ref' to rewind the protected branch — surface as blocker (#2924)." >&2
|
||||
exit 1
|
||||
fi
|
||||
# Positive allow-list: HEAD must be on the canonical Claude Code worktree-agent
|
||||
# branch namespace (`worktree-agent-<id>`). This catches feature/* and any other
|
||||
# arbitrary branch that the deny-list would silently allow (#2924).
|
||||
if ! echo "$ACTUAL_BRANCH" | grep -Eq '^worktree-agent-[A-Za-z0-9._/-]+$'; then
|
||||
echo "FATAL: refusing to commit — worktree HEAD '$ACTUAL_BRANCH' is not in the worktree-agent-* namespace." >&2
|
||||
echo "Agent commits must live on per-agent branches; surface as blocker (#2924)." >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
```
|
||||
|
||||
**1. Check modified files:** `git status --short`
|
||||
|
||||
**2. Stage task-related files individually** (NEVER `git add .` or `git add -A`):
|
||||
@@ -426,6 +450,15 @@ back, those deletions appear on the main branch, destroying prior-wave work (#20
|
||||
- `git rm` on files not explicitly created by the current task
|
||||
- `git checkout -- .` or `git restore .` (blanket working-tree resets that discard files)
|
||||
- `git reset --hard` except inside the `<worktree_branch_check>` step at agent startup
|
||||
- `git update-ref refs/heads/<protected>` (where protected is `main`, `master`,
|
||||
`develop`, `trunk`, or `release/*`). This is an absolute prohibition (#2924).
|
||||
If you discover that your worktree HEAD is attached to a protected branch and your
|
||||
commits landed there, **DO NOT** "recover" by force-rewinding the protected ref —
|
||||
that silently destroys concurrent commits in multi-active scenarios (parallel
|
||||
agents, user committing while you run). HALT and surface a blocker. The setup-time
|
||||
`<worktree_branch_check>` and per-commit `<pre_commit_head_assertion>` are the
|
||||
correct prevention; if either fails, the workflow MUST stop, not self-heal.
|
||||
- `git push --force` / `git push -f` to any branch you did not create.
|
||||
|
||||
If you need to discard changes to a specific file you modified during this task, use:
|
||||
```bash
|
||||
|
||||
777
bin/install.js
777
bin/install.js
File diff suppressed because one or more lines are too long
@@ -1,79 +0,0 @@
|
||||
---
|
||||
name: gsd:add-backlog
|
||||
description: Add an idea to the backlog parking lot (999.x numbering)
|
||||
argument-hint: <description>
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Write
|
||||
- Bash
|
||||
---
|
||||
|
||||
<objective>
|
||||
Add a backlog item to the roadmap using 999.x numbering. Backlog items are
|
||||
unsequenced ideas that aren't ready for active planning — they live outside
|
||||
the normal phase sequence and accumulate context over time.
|
||||
</objective>
|
||||
|
||||
<process>
|
||||
|
||||
1. **Read ROADMAP.md** to find existing backlog entries:
|
||||
```bash
|
||||
cat .planning/ROADMAP.md
|
||||
```
|
||||
|
||||
2. **Find next backlog number:**
|
||||
```bash
|
||||
NEXT=$(gsd-sdk query phase.next-decimal 999 --raw)
|
||||
```
|
||||
If no 999.x phases exist, start at 999.1.
|
||||
|
||||
3. **Add to ROADMAP.md** under a `## Backlog` section. If the section doesn't exist, create it at the end.
|
||||
Write the ROADMAP entry BEFORE creating the directory — this ensures directory existence is always
|
||||
a reliable indicator that the phase is already registered, which prevents false duplicate detection
|
||||
in any hook that checks for existing 999.x directories (#2280):
|
||||
|
||||
```markdown
|
||||
## Backlog
|
||||
|
||||
### Phase {NEXT}: {description} (BACKLOG)
|
||||
|
||||
**Goal:** [Captured for future planning]
|
||||
**Requirements:** TBD
|
||||
**Plans:** 0 plans
|
||||
|
||||
Plans:
|
||||
- [ ] TBD (promote with /gsd-review-backlog when ready)
|
||||
```
|
||||
|
||||
4. **Create the phase directory:**
|
||||
```bash
|
||||
SLUG=$(gsd-sdk query generate-slug "$ARGUMENTS" --raw)
|
||||
mkdir -p ".planning/phases/${NEXT}-${SLUG}"
|
||||
touch ".planning/phases/${NEXT}-${SLUG}/.gitkeep"
|
||||
```
|
||||
|
||||
5. **Commit:**
|
||||
```bash
|
||||
gsd-sdk query commit "docs: add backlog item ${NEXT} — ${ARGUMENTS}" --files .planning/ROADMAP.md ".planning/phases/${NEXT}-${SLUG}/.gitkeep"
|
||||
```
|
||||
|
||||
6. **Report:**
|
||||
```
|
||||
## 📋 Backlog Item Added
|
||||
|
||||
Phase {NEXT}: {description}
|
||||
Directory: .planning/phases/{NEXT}-{slug}/
|
||||
|
||||
This item lives in the backlog parking lot.
|
||||
Use /gsd-discuss-phase {NEXT} to explore it further.
|
||||
Use /gsd-review-backlog to promote items to active milestone.
|
||||
```
|
||||
|
||||
</process>
|
||||
|
||||
<notes>
|
||||
- 999.x numbering keeps backlog items out of the active phase sequence
|
||||
- Phase directories are created immediately, so /gsd-discuss-phase and /gsd-plan-phase work on them
|
||||
- No `Depends on:` field — backlog items are unsequenced by definition
|
||||
- Sparse numbering is fine (999.1, 999.3) — always uses next-decimal
|
||||
</notes>
|
||||
@@ -1,43 +0,0 @@
|
||||
---
|
||||
name: gsd:add-phase
|
||||
description: Add phase to end of current milestone in roadmap
|
||||
argument-hint: <description>
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Write
|
||||
- Bash
|
||||
---
|
||||
|
||||
<objective>
|
||||
Add a new integer phase to the end of the current milestone in the roadmap.
|
||||
|
||||
Routes to the add-phase workflow which handles:
|
||||
- Phase number calculation (next sequential integer)
|
||||
- Directory creation with slug generation
|
||||
- Roadmap structure updates
|
||||
- STATE.md roadmap evolution tracking
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@~/.claude/get-shit-done/workflows/add-phase.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
Arguments: $ARGUMENTS (phase description)
|
||||
|
||||
Roadmap and state are resolved in-workflow via `init phase-op` and targeted tool calls.
|
||||
</context>
|
||||
|
||||
<process>
|
||||
**Follow the add-phase workflow** from `@~/.claude/get-shit-done/workflows/add-phase.md`.
|
||||
|
||||
The workflow handles all logic including:
|
||||
1. Argument parsing and validation
|
||||
2. Roadmap existence checking
|
||||
3. Current milestone identification
|
||||
4. Next phase number calculation (ignoring decimals)
|
||||
5. Slug generation from description
|
||||
6. Phase directory creation
|
||||
7. Roadmap entry insertion
|
||||
8. STATE.md updates
|
||||
</process>
|
||||
@@ -1,47 +0,0 @@
|
||||
---
|
||||
name: gsd:add-todo
|
||||
description: Capture idea or task as todo from current conversation context
|
||||
argument-hint: [optional description]
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Write
|
||||
- Bash
|
||||
- AskUserQuestion
|
||||
---
|
||||
|
||||
<objective>
|
||||
Capture an idea, task, or issue that surfaces during a GSD session as a structured todo for later work.
|
||||
|
||||
Routes to the add-todo workflow which handles:
|
||||
- Directory structure creation
|
||||
- Content extraction from arguments or conversation
|
||||
- Area inference from file paths
|
||||
- Duplicate detection and resolution
|
||||
- Todo file creation with frontmatter
|
||||
- STATE.md updates
|
||||
- Git commits
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@~/.claude/get-shit-done/workflows/add-todo.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
Arguments: $ARGUMENTS (optional todo description)
|
||||
|
||||
State is resolved in-workflow via `init todos` and targeted reads.
|
||||
</context>
|
||||
|
||||
<process>
|
||||
**Follow the add-todo workflow** from `@~/.claude/get-shit-done/workflows/add-todo.md`.
|
||||
|
||||
The workflow handles all logic including:
|
||||
1. Directory ensuring
|
||||
2. Existing area checking
|
||||
3. Content extraction (arguments or conversation)
|
||||
4. Area inference
|
||||
5. Duplicate checking
|
||||
6. File creation with slug generation
|
||||
7. STATE.md updates
|
||||
8. Git commits
|
||||
</process>
|
||||
@@ -1,34 +0,0 @@
|
||||
---
|
||||
name: gsd:analyze-dependencies
|
||||
description: Analyze phase dependencies and suggest Depends on entries for ROADMAP.md
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Write
|
||||
- Bash
|
||||
- Glob
|
||||
- Grep
|
||||
- AskUserQuestion
|
||||
---
|
||||
<objective>
|
||||
Analyze the phase dependency graph for the current milestone. For each phase pair, determine if there is a dependency relationship based on:
|
||||
- File overlap (phases that modify the same files must be ordered)
|
||||
- Semantic dependencies (a phase that uses an API built by another phase)
|
||||
- Data flow (a phase that consumes output from another phase)
|
||||
|
||||
Then suggest `Depends on` updates to ROADMAP.md.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@~/.claude/get-shit-done/workflows/analyze-dependencies.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
No arguments required. Requires an active milestone with ROADMAP.md.
|
||||
|
||||
Run this command BEFORE `/gsd-manager` to fill in missing `Depends on` fields and prevent merge conflicts from unordered parallel execution.
|
||||
</context>
|
||||
|
||||
<process>
|
||||
Execute the analyze-dependencies workflow from @~/.claude/get-shit-done/workflows/analyze-dependencies.md end-to-end.
|
||||
Present dependency suggestions clearly and apply confirmed updates to ROADMAP.md.
|
||||
</process>
|
||||
62
commands/gsd/capture.md
Normal file
62
commands/gsd/capture.md
Normal file
@@ -0,0 +1,62 @@
|
||||
---
|
||||
name: gsd:capture
|
||||
description: Capture ideas, tasks, notes, and seeds to their destination
|
||||
argument-hint: "[--note | --backlog | --seed | --list] [text]"
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Write
|
||||
- Edit
|
||||
- Bash
|
||||
- Glob
|
||||
- Grep
|
||||
- AskUserQuestion
|
||||
---
|
||||
|
||||
<objective>
|
||||
Capture ideas, tasks, notes, and seeds to their appropriate destination in the GSD system.
|
||||
|
||||
Mode routing:
|
||||
- **default** (no flag): Capture as a structured todo for later work → add-todo workflow
|
||||
- **--note**: Zero-friction idea capture (append/list/promote) → note workflow
|
||||
- **--backlog**: Add an idea to the backlog parking lot (999.x numbering) → add-backlog workflow
|
||||
- **--seed**: Capture a forward-looking idea with trigger conditions → plant-seed workflow
|
||||
- **--list**: List pending todos and select one to work on → check-todos workflow
|
||||
</objective>
|
||||
|
||||
<routing>
|
||||
|
||||
| Flag | Destination | Workflow |
|
||||
|------|-------------|----------|
|
||||
| (none) | Structured todo in .planning/todos/ | add-todo |
|
||||
| --note | Timestamped note file, list, or promote | note |
|
||||
| --backlog | ROADMAP.md backlog section (999.x) | add-backlog |
|
||||
| --seed | .planning/seeds/SEED-NNN-slug.md | plant-seed |
|
||||
| --list | Interactive todo browser + action router | check-todos |
|
||||
|
||||
</routing>
|
||||
|
||||
<execution_context>
|
||||
@~/.claude/get-shit-done/workflows/add-todo.md
|
||||
@~/.claude/get-shit-done/workflows/note.md
|
||||
@~/.claude/get-shit-done/workflows/add-backlog.md
|
||||
@~/.claude/get-shit-done/workflows/plant-seed.md
|
||||
@~/.claude/get-shit-done/workflows/check-todos.md
|
||||
@~/.claude/get-shit-done/references/ui-brand.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
Arguments: $ARGUMENTS
|
||||
|
||||
Parse the first token of $ARGUMENTS:
|
||||
- If it is `--note`: strip the flag, pass remainder to note workflow
|
||||
- If it is `--backlog`: strip the flag, pass remainder to add-backlog workflow
|
||||
- If it is `--seed`: strip the flag, pass remainder to plant-seed workflow
|
||||
- If it is `--list`: pass remainder (optional area filter) to check-todos workflow
|
||||
- Otherwise: pass all of $ARGUMENTS to add-todo workflow
|
||||
</context>
|
||||
|
||||
<process>
|
||||
1. Parse the leading flag (if any) from $ARGUMENTS.
|
||||
2. Load and execute the appropriate workflow end-to-end based on the routing table above.
|
||||
3. Preserve all workflow gates from the target workflow (directory structure, duplicate detection, commits, etc.).
|
||||
</process>
|
||||
@@ -1,45 +0,0 @@
|
||||
---
|
||||
name: gsd:check-todos
|
||||
description: List pending todos and select one to work on
|
||||
argument-hint: [area filter]
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Write
|
||||
- Bash
|
||||
- AskUserQuestion
|
||||
---
|
||||
|
||||
<objective>
|
||||
List all pending todos, allow selection, load full context for the selected todo, and route to appropriate action.
|
||||
|
||||
Routes to the check-todos workflow which handles:
|
||||
- Todo counting and listing with area filtering
|
||||
- Interactive selection with full context loading
|
||||
- Roadmap correlation checking
|
||||
- Action routing (work now, add to phase, brainstorm, create phase)
|
||||
- STATE.md updates and git commits
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@~/.claude/get-shit-done/workflows/check-todos.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
Arguments: $ARGUMENTS (optional area filter)
|
||||
|
||||
Todo state and roadmap correlation are loaded in-workflow using `init todos` and targeted reads.
|
||||
</context>
|
||||
|
||||
<process>
|
||||
**Follow the check-todos workflow** from `@~/.claude/get-shit-done/workflows/check-todos.md`.
|
||||
|
||||
The workflow handles all logic including:
|
||||
1. Todo existence checking
|
||||
2. Area filtering
|
||||
3. Interactive listing and selection
|
||||
4. Full context loading with file summaries
|
||||
5. Roadmap correlation checking
|
||||
6. Action offering and execution
|
||||
7. STATE.md updates
|
||||
8. Git commits
|
||||
</process>
|
||||
@@ -1,52 +0,0 @@
|
||||
---
|
||||
name: gsd:code-review-fix
|
||||
description: Auto-fix issues found by code review in REVIEW.md; commits each fix atomically.
|
||||
argument-hint: "<phase-number> [--all] [--auto]"
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Bash
|
||||
- Glob
|
||||
- Grep
|
||||
- Write
|
||||
- Edit
|
||||
- Task
|
||||
---
|
||||
<objective>
|
||||
Auto-fix issues found by code review. Reads REVIEW.md from the specified phase, spawns gsd-code-fixer agent to apply fixes, and produces REVIEW-FIX.md summary.
|
||||
|
||||
Arguments:
|
||||
- Phase number (required) — which phase's REVIEW.md to fix (e.g., "2" or "02")
|
||||
- `--all` (optional) — include Info findings in fix scope (default: Critical + Warning only)
|
||||
- `--auto` (optional) — enable fix + re-review iteration loop, capped at 3 iterations
|
||||
|
||||
Output: {padded_phase}-REVIEW-FIX.md in phase directory + inline summary of fixes applied
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@~/.claude/get-shit-done/workflows/code-review-fix.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
Phase: $ARGUMENTS (first positional argument is phase number)
|
||||
|
||||
Optional flags parsed from $ARGUMENTS:
|
||||
- `--all` — Include Info findings in fix scope. Default behavior fixes Critical + Warning only.
|
||||
- `--auto` — Enable fix + re-review iteration loop. After applying fixes, re-run code-review at same depth. If new issues found, iterate. Cap at 3 iterations total. Without this flag, single fix pass only.
|
||||
|
||||
Context files (CLAUDE.md, REVIEW.md, phase state) are resolved inside the workflow via `gsd-sdk query init.phase-op` and delegated to agent via config blocks.
|
||||
</context>
|
||||
|
||||
<process>
|
||||
This command is a thin dispatch layer. It parses arguments and delegates to the workflow.
|
||||
|
||||
Execute the code-review-fix workflow from @~/.claude/get-shit-done/workflows/code-review-fix.md end-to-end.
|
||||
|
||||
The workflow (not this command) enforces these gates:
|
||||
- Phase validation (before config gate)
|
||||
- Config gate check (workflow.code_review)
|
||||
- REVIEW.md existence check (error if missing)
|
||||
- REVIEW.md status check (skip if clean/skipped)
|
||||
- Agent spawning (gsd-code-fixer)
|
||||
- Iteration loop (if --auto, capped at 3 iterations)
|
||||
- Result presentation (inline summary + next steps)
|
||||
</process>
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: gsd:code-review
|
||||
description: Review source files changed during a phase for bugs, security issues, and code quality problems
|
||||
argument-hint: "<phase-number> [--depth=quick|standard|deep] [--files file1,file2,...]"
|
||||
argument-hint: "<phase-number> [--depth=quick|standard|deep] [--files file1,file2,...] [--fix [--all] [--auto]]"
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Bash
|
||||
@@ -22,6 +22,9 @@ Arguments:
|
||||
- standard: Per-file analysis with language-specific checks (~5-15 min, default)
|
||||
- deep: Cross-file analysis including import graphs and call chains (~15-30 min)
|
||||
- `--files file1,file2,...` (optional) — explicit comma-separated file list, skips SUMMARY/git scoping (highest precedence for scoping)
|
||||
- `--fix` (optional) — after review completes (or if REVIEW.md already exists), auto-apply fixes found. Spawns gsd-code-fixer agent. Accepts sub-flags:
|
||||
- `--all` — include Info findings in fix scope (default: Critical + Warning only)
|
||||
- `--auto` — enable fix + re-review iteration loop, capped at 3 iterations
|
||||
|
||||
Output: {padded_phase}-REVIEW.md in phase directory + inline summary of findings
|
||||
</objective>
|
||||
|
||||
57
commands/gsd/config.md
Normal file
57
commands/gsd/config.md
Normal file
@@ -0,0 +1,57 @@
|
||||
---
|
||||
name: gsd:config
|
||||
description: Configure GSD settings — workflow toggles, advanced knobs, integrations, and model profile
|
||||
argument-hint: "[--advanced | --integrations | --profile <name>]"
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Write
|
||||
- Bash
|
||||
- AskUserQuestion
|
||||
---
|
||||
|
||||
<objective>
|
||||
Configure GSD settings interactively with a single consolidated command.
|
||||
|
||||
Mode routing:
|
||||
- **default** (no flag): Common-case toggles (model, research, plan_check, verifier, branching) → settings workflow
|
||||
- **--advanced**: Power-user knobs (planning tuning, timeouts, branch templates, cross-AI execution) → settings-advanced workflow
|
||||
- **--integrations**: Third-party API keys, code-review CLI routing, agent-skill injection → settings-integrations workflow
|
||||
- **--profile <name>**: Switch model profile (quality|balanced|budget|inherit) → set-profile (inline)
|
||||
</objective>
|
||||
|
||||
<routing>
|
||||
|
||||
| Flag | Action | Workflow |
|
||||
|------|--------|----------|
|
||||
| (none) | Interactive 5-question common-case config prompt | settings |
|
||||
| --advanced | Power-user knobs: planning, execution, discussion, cross-AI, git, runtime | settings-advanced |
|
||||
| --integrations | API keys (Brave/Firecrawl/Exa), review CLI routing, agent skills | settings-integrations |
|
||||
| --profile <name> | Switch model profile without interactive prompt | gsd-sdk config-set-model-profile |
|
||||
|
||||
</routing>
|
||||
|
||||
<execution_context>
|
||||
@~/.claude/get-shit-done/workflows/settings.md
|
||||
@~/.claude/get-shit-done/workflows/settings-advanced.md
|
||||
@~/.claude/get-shit-done/workflows/settings-integrations.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
Arguments: $ARGUMENTS
|
||||
|
||||
Parse the first token of $ARGUMENTS:
|
||||
- If it is `--advanced`: strip the flag, execute settings-advanced workflow
|
||||
- If it is `--integrations`: strip the flag, execute settings-integrations workflow
|
||||
- If it starts with `--profile`: extract the profile name (remainder after `--profile`), then:
|
||||
1. **Pre-flight check (#2439):** verify `gsd-sdk` is on PATH via `command -v gsd-sdk`.
|
||||
If absent, emit the install hint `Install GSD via 'npm i -g get-shit-done'` and stop —
|
||||
do NOT invoke `gsd-sdk` directly (avoids the opaque `command not found: gsd-sdk` failure).
|
||||
2. Run: `gsd-sdk query config-set-model-profile <profile-name> --raw` and display the output verbatim.
|
||||
- Otherwise: execute settings workflow (no argument needed)
|
||||
</context>
|
||||
|
||||
<process>
|
||||
1. Parse the leading flag (if any) from $ARGUMENTS.
|
||||
2. Load and execute the appropriate workflow end-to-end, or run the inline SDK command for --profile.
|
||||
3. Preserve all workflow gates from the target workflow.
|
||||
</process>
|
||||
@@ -1,30 +0,0 @@
|
||||
---
|
||||
name: gsd:do
|
||||
description: Route freeform text to the right GSD command automatically
|
||||
argument-hint: "<description of what you want to do>"
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Bash
|
||||
- AskUserQuestion
|
||||
---
|
||||
<objective>
|
||||
Analyze freeform natural language input and dispatch to the most appropriate GSD command.
|
||||
|
||||
Acts as a smart dispatcher — never does the work itself. Matches intent to the best GSD command using routing rules, confirms the match, then hands off.
|
||||
|
||||
Use when you know what you want but don't know which `/gsd-*` command to run.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@~/.claude/get-shit-done/workflows/do.md
|
||||
@~/.claude/get-shit-done/references/ui-brand.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
$ARGUMENTS
|
||||
</context>
|
||||
|
||||
<process>
|
||||
Execute the do workflow from @~/.claude/get-shit-done/workflows/do.md end-to-end.
|
||||
Route user intent to the best GSD command and invoke it.
|
||||
</process>
|
||||
@@ -1,35 +0,0 @@
|
||||
---
|
||||
name: gsd:edit-phase
|
||||
description: Edit any field of an existing roadmap phase in place, preserving number and position
|
||||
argument-hint: <phase-number> [--force]
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Write
|
||||
- Bash
|
||||
---
|
||||
|
||||
<objective>
|
||||
Modify any field of an existing phase in ROADMAP.md in place.
|
||||
|
||||
Supports:
|
||||
- Editing individual fields (title, description/goal, requirements, success criteria, depends_on)
|
||||
- Full regeneration of all fields from a clarified intent
|
||||
- Guarded edits: refuses in_progress/completed phases unless --force is passed
|
||||
- Depends-on validation: blocks invalid references with a clear error
|
||||
- Diff + confirmation before writing
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@~/.claude/get-shit-done/workflows/edit-phase.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
Arguments: $ARGUMENTS (format: <phase-number> [--force])
|
||||
|
||||
Roadmap and state are resolved in-workflow via `init phase-op` and targeted reads.
|
||||
</context>
|
||||
|
||||
<process>
|
||||
Execute the edit-phase workflow from @~/.claude/get-shit-done/workflows/edit-phase.md end-to-end.
|
||||
Preserve all validation gates (phase existence, status guard, depends_on validation, diff + confirmation).
|
||||
</process>
|
||||
@@ -1,47 +0,0 @@
|
||||
---
|
||||
name: gsd:from-gsd2
|
||||
description: Import a GSD-2 (.gsd/) project back to GSD v1 (.planning/) format
|
||||
argument-hint: "[--path <dir>] [--force]"
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Write
|
||||
- Bash
|
||||
type: prompt
|
||||
---
|
||||
|
||||
<objective>
|
||||
Reverse-migrate a GSD-2 project (`.gsd/` directory) back to GSD v1 (`.planning/`) format.
|
||||
|
||||
Maps the GSD-2 hierarchy (Milestone → Slice → Task) to the GSD v1 hierarchy (Milestone sections in ROADMAP.md → Phase → Plan), preserving completion state, research files, and summaries.
|
||||
|
||||
**CJS-only:** `from-gsd2` is not on the `gsd-sdk query` registry; call `gsd-tools.cjs` as shown below (see `docs/CLI-TOOLS.md`).
|
||||
</objective>
|
||||
|
||||
<process>
|
||||
|
||||
1. **Locate the .gsd/ directory** — check the current working directory (or `--path` argument):
|
||||
```bash
|
||||
node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" from-gsd2 --dry-run
|
||||
```
|
||||
If no `.gsd/` is found, report the error and stop.
|
||||
|
||||
2. **Show the dry-run preview** — present the full file list and migration statistics to the user. Ask for confirmation before writing anything.
|
||||
|
||||
3. **Run the migration** after confirmation:
|
||||
```bash
|
||||
node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" from-gsd2
|
||||
```
|
||||
Use `--force` if `.planning/` already exists and the user has confirmed overwrite.
|
||||
|
||||
4. **Report the result** — show the `filesWritten` count, `planningDir` path, and the preview summary.
|
||||
|
||||
</process>
|
||||
|
||||
<notes>
|
||||
- The migration is non-destructive: `.gsd/` is never modified or removed.
|
||||
- Pass `--path <dir>` to migrate a project at a different path than the current directory.
|
||||
- Slices are numbered sequentially across all milestones (M001/S01 → phase 01, M001/S02 → phase 02, M002/S01 → phase 03, etc.).
|
||||
- Tasks within each slice become plans (T01 → plan 01, T02 → plan 02, etc.).
|
||||
- Completed slices and tasks carry their done state into ROADMAP.md checkboxes and SUMMARY.md files.
|
||||
- GSD-2 cost/token ledger, database state, and VS Code extension state cannot be migrated.
|
||||
</notes>
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: gsd:health
|
||||
description: Diagnose planning directory health and optionally repair issues
|
||||
argument-hint: [--repair]
|
||||
argument-hint: "[--repair] [--context]"
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Bash
|
||||
@@ -10,6 +10,14 @@ allowed-tools:
|
||||
---
|
||||
<objective>
|
||||
Validate `.planning/` directory integrity and report actionable issues. Checks for missing files, invalid configurations, inconsistent state, and orphaned plans.
|
||||
|
||||
`--context` runs an orthogonal check: the running session's context utilization. The workflow asks for the model's tokensUsed + contextWindow, calls `gsd-sdk query validate.context`, and renders one of three states:
|
||||
|
||||
| Utilization | State | Action |
|
||||
|-------------|----------|-------------------------------------------------------|
|
||||
| < 60% | healthy | no action — context is comfortable |
|
||||
| 60% – 70% | warning | recommend `/gsd-thread` to start fresh |
|
||||
| ≥ 70% | critical | reasoning quality may degrade past the fracture point |
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@@ -18,5 +26,5 @@ Validate `.planning/` directory integrity and report actionable issues. Checks f
|
||||
|
||||
<process>
|
||||
Execute the health workflow from @~/.claude/get-shit-done/workflows/health.md end-to-end.
|
||||
Parse --repair flag from arguments and pass to workflow.
|
||||
Parse `--repair` and `--context` flags from arguments and pass to workflow.
|
||||
</process>
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
---
|
||||
name: gsd:insert-phase
|
||||
description: Insert urgent work as decimal phase (e.g., 72.1) between existing phases
|
||||
argument-hint: <after> <description>
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Bash
|
||||
---
|
||||
|
||||
<objective>
|
||||
Insert a decimal phase for urgent work discovered mid-milestone that must be completed between existing integer phases.
|
||||
|
||||
Uses decimal numbering (72.1, 72.2, etc.) to preserve the logical sequence of planned phases while accommodating urgent insertions.
|
||||
|
||||
Purpose: Handle urgent work discovered during execution without renumbering entire roadmap.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@~/.claude/get-shit-done/workflows/insert-phase.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
Arguments: $ARGUMENTS (format: <after-phase-number> <description>)
|
||||
|
||||
Roadmap and state are resolved in-workflow via `init phase-op` and targeted tool calls.
|
||||
</context>
|
||||
|
||||
<process>
|
||||
Execute the insert-phase workflow from @~/.claude/get-shit-done/workflows/insert-phase.md end-to-end.
|
||||
Preserve all validation gates (argument parsing, phase verification, decimal calculation, roadmap updates).
|
||||
</process>
|
||||
@@ -1,179 +0,0 @@
|
||||
---
|
||||
name: gsd:intel
|
||||
description: "Query, inspect, or refresh codebase intelligence files in .planning/intel/"
|
||||
argument-hint: "[query <term>|status|diff|refresh]"
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Bash
|
||||
- Task
|
||||
---
|
||||
|
||||
**STOP -- DO NOT READ THIS FILE. You are already reading it. This prompt was injected into your context by Claude Code's command system. Using the Read tool on this file wastes tokens. Begin executing Step 0 immediately.**
|
||||
|
||||
## Step 0 -- Banner
|
||||
|
||||
**Before ANY tool calls**, display this banner:
|
||||
|
||||
```
|
||||
GSD > INTEL
|
||||
```
|
||||
|
||||
Then proceed to Step 1.
|
||||
|
||||
## Step 1 -- Config Gate
|
||||
|
||||
Check if intel is enabled by reading `.planning/config.json` directly using the Read tool.
|
||||
|
||||
**DO NOT use the gsd-tools config get-value command** -- it hard-exits on missing keys.
|
||||
|
||||
1. Read `.planning/config.json` using the Read tool
|
||||
2. If the file does not exist: display the disabled message below and **STOP**
|
||||
3. Parse the JSON content. Check if `config.intel && config.intel.enabled === true`
|
||||
4. If `intel.enabled` is NOT explicitly `true`: display the disabled message below and **STOP**
|
||||
5. If `intel.enabled` is `true`: proceed to Step 2
|
||||
|
||||
**Disabled message:**
|
||||
|
||||
```
|
||||
GSD > INTEL
|
||||
|
||||
Intel system is disabled. To activate:
|
||||
|
||||
gsd-sdk query config-set intel.enabled true
|
||||
|
||||
Then run /gsd-intel refresh to build the initial index.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 2 -- Parse Argument
|
||||
|
||||
Parse `$ARGUMENTS` to determine the operation mode:
|
||||
|
||||
| Argument | Action |
|
||||
|----------|--------|
|
||||
| `query <term>` | Run inline query (Step 2a) |
|
||||
| `status` | Run inline status check (Step 2b) |
|
||||
| `diff` | Run inline diff check (Step 2c) |
|
||||
| `refresh` | Spawn intel-updater agent (Step 3) |
|
||||
| No argument or unknown | Show usage message |
|
||||
|
||||
**Usage message** (shown when no argument or unrecognized argument):
|
||||
|
||||
```
|
||||
GSD > INTEL
|
||||
|
||||
Usage: /gsd-intel <mode>
|
||||
|
||||
Modes:
|
||||
query <term> Search intel files for a term
|
||||
status Show intel file freshness and staleness
|
||||
diff Show changes since last snapshot
|
||||
refresh Rebuild all intel files from codebase analysis
|
||||
```
|
||||
|
||||
### Step 2a -- Query
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
gsd-sdk query intel.query <term>
|
||||
```
|
||||
|
||||
Parse the JSON output and display results:
|
||||
- If the output contains `"disabled": true`, display the disabled message from Step 1 and **STOP**
|
||||
- If no matches found, display: `No intel matches for '<term>'. Try /gsd-intel refresh to build the index.`
|
||||
- Otherwise, display matching entries grouped by intel file
|
||||
|
||||
**STOP** after displaying results. Do not spawn an agent.
|
||||
|
||||
### Step 2b -- Status
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
gsd-sdk query intel.status
|
||||
```
|
||||
|
||||
Parse the JSON output and display each intel file with:
|
||||
- File name
|
||||
- Last `updated_at` timestamp
|
||||
- STALE or FRESH status (stale if older than 24 hours or missing)
|
||||
|
||||
**STOP** after displaying status. Do not spawn an agent.
|
||||
|
||||
### Step 2c -- Diff
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
gsd-sdk query intel.diff
|
||||
```
|
||||
|
||||
Parse the JSON output and display:
|
||||
- Added entries since last snapshot
|
||||
- Removed entries since last snapshot
|
||||
- Changed entries since last snapshot
|
||||
|
||||
If no snapshot exists, suggest running `refresh` first.
|
||||
|
||||
**STOP** after displaying diff. Do not spawn an agent.
|
||||
|
||||
---
|
||||
|
||||
## Step 3 -- Refresh (Agent Spawn)
|
||||
|
||||
Display before spawning:
|
||||
|
||||
```
|
||||
GSD > Spawning intel-updater agent to analyze codebase...
|
||||
```
|
||||
|
||||
Spawn a Task:
|
||||
|
||||
```
|
||||
Task(
|
||||
description="Refresh codebase intelligence files",
|
||||
prompt="You are the gsd-intel-updater agent. Your job is to analyze this codebase and write/update intelligence files in .planning/intel/.
|
||||
|
||||
Project root: ${CWD}
|
||||
Prefer: gsd-sdk query <subcommand> (installed gsd-sdk on PATH). Legacy: node $HOME/.claude/get-shit-done/bin/gsd-tools.cjs
|
||||
|
||||
Instructions:
|
||||
1. Analyze the codebase structure, dependencies, APIs, and architecture
|
||||
2. Write JSON intel files to .planning/intel/ (stack.json, api-map.json, dependency-graph.json, file-roles.json, arch-decisions.json)
|
||||
3. Each file must have a _meta object with updated_at timestamp
|
||||
4. Use `gsd-sdk query intel.extract-exports <file>` to analyze source files
|
||||
5. Use `gsd-sdk query intel.patch-meta <file>` to update timestamps after writing
|
||||
6. Use `gsd-sdk query intel.validate` to check your output
|
||||
|
||||
When complete, output: ## INTEL UPDATE COMPLETE
|
||||
If something fails, output: ## INTEL UPDATE FAILED with details."
|
||||
)
|
||||
```
|
||||
|
||||
Wait for the agent to complete.
|
||||
|
||||
---
|
||||
|
||||
## Step 4 -- Post-Refresh Summary
|
||||
|
||||
After the agent completes, run:
|
||||
|
||||
```bash
|
||||
gsd-sdk query intel.status
|
||||
```
|
||||
|
||||
Display a summary showing:
|
||||
- Which intel files were written or updated
|
||||
- Last update timestamps
|
||||
- Overall health of the intel index
|
||||
|
||||
---
|
||||
|
||||
## Anti-Patterns
|
||||
|
||||
1. DO NOT spawn an agent for query/status/diff operations -- these are inline CLI calls
|
||||
2. DO NOT modify intel files directly -- the agent handles writes during refresh
|
||||
3. DO NOT skip the config gate check
|
||||
4. DO NOT use the gsd-tools config get-value CLI for the config gate -- it exits on missing keys
|
||||
@@ -1,19 +0,0 @@
|
||||
---
|
||||
name: gsd:join-discord
|
||||
description: Join the GSD Discord community
|
||||
allowed-tools: []
|
||||
---
|
||||
|
||||
<objective>
|
||||
Display the Discord invite link for the GSD community server.
|
||||
</objective>
|
||||
|
||||
<output>
|
||||
# Join the GSD Discord
|
||||
|
||||
Connect with other GSD users, get help, share what you're building, and stay updated.
|
||||
|
||||
**Invite link:** https://discord.gg/mYgfVNfA2r
|
||||
|
||||
Click the link or paste it into your browser to join.
|
||||
</output>
|
||||
@@ -1,46 +0,0 @@
|
||||
---
|
||||
name: gsd:list-phase-assumptions
|
||||
description: Surface Claude's assumptions about a phase approach before planning
|
||||
argument-hint: "[phase]"
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Bash
|
||||
- Grep
|
||||
- Glob
|
||||
---
|
||||
|
||||
<objective>
|
||||
Analyze a phase and present Claude's assumptions about technical approach, implementation order, scope boundaries, risk areas, and dependencies.
|
||||
|
||||
Purpose: Help users see what Claude thinks BEFORE planning begins - enabling course correction early when assumptions are wrong.
|
||||
Output: Conversational output only (no file creation) - ends with "What do you think?" prompt
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@~/.claude/get-shit-done/workflows/list-phase-assumptions.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
Phase number: $ARGUMENTS (required)
|
||||
|
||||
Project state and roadmap are loaded in-workflow using targeted reads.
|
||||
</context>
|
||||
|
||||
<process>
|
||||
1. Validate phase number argument (error if missing or invalid)
|
||||
2. Check if phase exists in roadmap
|
||||
3. Follow list-phase-assumptions.md workflow:
|
||||
- Analyze roadmap description
|
||||
- Surface assumptions about: technical approach, implementation order, scope, risks, dependencies
|
||||
- Present assumptions clearly
|
||||
- Prompt "What do you think?"
|
||||
4. Gather feedback and offer next steps
|
||||
</process>
|
||||
|
||||
<success_criteria>
|
||||
|
||||
- Phase validated against roadmap
|
||||
- Assumptions surfaced across five areas
|
||||
- User prompted for feedback
|
||||
- User knows next steps (discuss context, plan phase, or correct assumptions)
|
||||
</success_criteria>
|
||||
@@ -1,19 +0,0 @@
|
||||
---
|
||||
name: gsd:list-workspaces
|
||||
description: List active GSD workspaces and their status
|
||||
allowed-tools:
|
||||
- Bash
|
||||
- Read
|
||||
---
|
||||
<objective>
|
||||
Scan `~/gsd-workspaces/` for workspace directories containing `WORKSPACE.md` manifests. Display a summary table with name, path, repo count, strategy, and GSD project status.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@~/.claude/get-shit-done/workflows/list-workspaces.md
|
||||
@~/.claude/get-shit-done/references/ui-brand.md
|
||||
</execution_context>
|
||||
|
||||
<process>
|
||||
Execute the list-workspaces workflow from @~/.claude/get-shit-done/workflows/list-workspaces.md end-to-end.
|
||||
</process>
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: gsd:map-codebase
|
||||
description: Analyze codebase with parallel mapper agents to produce .planning/codebase/ documents
|
||||
argument-hint: "[optional: specific area to map, e.g., 'api' or 'auth']"
|
||||
argument-hint: "[--fast [--focus tech|arch|quality|concerns]] [--query <term>|status|diff|refresh] [area]"
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Bash
|
||||
@@ -23,8 +23,19 @@ Output: .planning/codebase/ folder with 7 structured documents about the codebas
|
||||
@~/.claude/get-shit-done/workflows/map-codebase.md
|
||||
</execution_context>
|
||||
|
||||
<flags>
|
||||
- **--fast**: Lightweight scan mode — spawns one mapper agent instead of four. Accepts an optional `--focus` value: `tech`, `arch`, `quality`, `concerns`, or `tech+arch` (default). Faster and lower-context than the full map.
|
||||
- **--query**: Codebase intelligence query mode. Sub-commands: `query <term>`, `status`, `diff`, `refresh`. Requires intel to be enabled in config (`intel.enabled: true`). Runs inline for query/status/diff; spawns an agent for refresh.
|
||||
- **(no flag)**: Full parallel map — spawns 4 mapper agents to produce all 7 codebase documents.
|
||||
</flags>
|
||||
|
||||
<context>
|
||||
Focus area: $ARGUMENTS (optional - if provided, tells agents to focus on specific subsystem)
|
||||
Arguments: $ARGUMENTS
|
||||
|
||||
Parse the first token of $ARGUMENTS:
|
||||
- If it is `--fast`: strip the flag, run the scan workflow (passing remaining args including optional --focus).
|
||||
- If it is `--query`: strip the flag, run the intel workflow (passing remaining args as the subcommand).
|
||||
- Otherwise: pass all of $ARGUMENTS as focus area to the map-codebase workflow.
|
||||
|
||||
**Load project state if exists:**
|
||||
Check for .planning/STATE.md - loads context if project already initialized
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
---
|
||||
name: gsd:new-workspace
|
||||
description: Create an isolated workspace with repo copies and independent .planning/
|
||||
argument-hint: "--name <name> [--repos repo1,repo2] [--path /target] [--strategy worktree|clone] [--branch name] [--auto]"
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Bash
|
||||
- Write
|
||||
- AskUserQuestion
|
||||
---
|
||||
<context>
|
||||
**Flags:**
|
||||
- `--name` (required) — Workspace name
|
||||
- `--repos` — Comma-separated repo paths or names. If omitted, interactive selection from child git repos in cwd
|
||||
- `--path` — Target directory. Defaults to `~/gsd-workspaces/<name>`
|
||||
- `--strategy` — `worktree` (default, lightweight) or `clone` (fully independent)
|
||||
- `--branch` — Branch to checkout. Defaults to `workspace/<name>`
|
||||
- `--auto` — Skip interactive questions, use defaults
|
||||
</context>
|
||||
|
||||
<objective>
|
||||
Create a physical workspace directory containing copies of specified git repos (as worktrees or clones) with an independent `.planning/` directory for isolated GSD sessions.
|
||||
|
||||
**Use cases:**
|
||||
- Multi-repo orchestration: work on a subset of repos in parallel with isolated GSD state
|
||||
- Feature branch isolation: create a worktree of the current repo with its own `.planning/`
|
||||
|
||||
**Creates:**
|
||||
- `<path>/WORKSPACE.md` — workspace manifest
|
||||
- `<path>/.planning/` — independent planning directory
|
||||
- `<path>/<repo>/` — git worktree or clone for each specified repo
|
||||
|
||||
**After this command:** `cd` into the workspace and run `/gsd-new-project` to initialize GSD.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@~/.claude/get-shit-done/workflows/new-workspace.md
|
||||
@~/.claude/get-shit-done/references/ui-brand.md
|
||||
</execution_context>
|
||||
|
||||
<process>
|
||||
Execute the new-workspace workflow from @~/.claude/get-shit-done/workflows/new-workspace.md end-to-end.
|
||||
Preserve all workflow gates (validation, approvals, commits, routing).
|
||||
</process>
|
||||
@@ -1,28 +0,0 @@
|
||||
---
|
||||
name: gsd:next
|
||||
description: Automatically advance to the next logical step in the GSD workflow
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Bash
|
||||
- Grep
|
||||
- Glob
|
||||
- SlashCommand
|
||||
---
|
||||
<objective>
|
||||
Detect the current project state and automatically invoke the next logical GSD workflow step.
|
||||
No arguments needed — reads STATE.md, ROADMAP.md, and phase directories to determine what comes next.
|
||||
|
||||
Designed for rapid multi-project workflows where remembering which phase/step you're on is overhead.
|
||||
|
||||
Supports `--force` flag to bypass safety gates (checkpoint, error state, verification failures, and prior-phase completeness scan).
|
||||
|
||||
Before routing to the next step, scans all prior phases for incomplete work: plans that ran without producing summaries, verification failures without overrides, and phases where discussion happened but planning never ran. When incomplete work is found, shows a structured report and offers three options: defer the gaps to the backlog and continue, stop and resolve manually, or force advance without recording. When prior phases are clean, routes silently with no interruption.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@~/.claude/get-shit-done/workflows/next.md
|
||||
</execution_context>
|
||||
|
||||
<process>
|
||||
Execute the next workflow from @~/.claude/get-shit-done/workflows/next.md end-to-end.
|
||||
</process>
|
||||
@@ -1,34 +0,0 @@
|
||||
---
|
||||
name: gsd:note
|
||||
description: Zero-friction idea capture. Append, list, or promote notes to todos.
|
||||
argument-hint: "<text> | list | promote <N> [--global]"
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Write
|
||||
- Glob
|
||||
- Grep
|
||||
---
|
||||
<objective>
|
||||
Zero-friction idea capture — one Write call, one confirmation line.
|
||||
|
||||
Three subcommands:
|
||||
- **append** (default): Save a timestamped note file. No questions, no formatting.
|
||||
- **list**: Show all notes from project and global scopes.
|
||||
- **promote**: Convert a note into a structured todo.
|
||||
|
||||
Runs inline — no Task, no AskUserQuestion, no Bash.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@~/.claude/get-shit-done/workflows/note.md
|
||||
@~/.claude/get-shit-done/references/ui-brand.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
$ARGUMENTS
|
||||
</context>
|
||||
|
||||
<process>
|
||||
Execute the note workflow from @~/.claude/get-shit-done/workflows/note.md end-to-end.
|
||||
Capture the note, list notes, or promote to todo — depending on arguments.
|
||||
</process>
|
||||
22
commands/gsd/ns-context.md
Normal file
22
commands/gsd/ns-context.md
Normal file
@@ -0,0 +1,22 @@
|
||||
---
|
||||
name: gsd-context
|
||||
description: "codebase intelligence | map graphify docs learnings"
|
||||
argument-hint: ""
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Skill
|
||||
---
|
||||
|
||||
Route to the appropriate codebase-intelligence skill based on the user's intent.
|
||||
`gsd-scan` and `gsd-intel` were folded into `gsd-map-codebase` flags by #2790.
|
||||
|
||||
| User wants | Invoke |
|
||||
|---|---|
|
||||
| Map the full codebase structure | gsd-map-codebase |
|
||||
| Quick lightweight codebase scan | gsd-map-codebase --fast |
|
||||
| Query mapped intelligence files | gsd-map-codebase --query |
|
||||
| Generate a knowledge graph | gsd-graphify |
|
||||
| Update project documentation | gsd-docs-update |
|
||||
| Extract learnings from a completed phase | gsd-extract-learnings |
|
||||
|
||||
Invoke the matched skill directly using the Skill tool.
|
||||
23
commands/gsd/ns-ideate.md
Normal file
23
commands/gsd/ns-ideate.md
Normal file
@@ -0,0 +1,23 @@
|
||||
---
|
||||
name: gsd-ideate
|
||||
description: "exploration capture | explore sketch spike spec capture"
|
||||
argument-hint: ""
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Skill
|
||||
---
|
||||
|
||||
Route to the appropriate exploration / capture skill based on the user's intent.
|
||||
`gsd-note`, `gsd-add-todo`, `gsd-add-backlog`, and `gsd-plant-seed` were folded
|
||||
into `gsd-capture` (with `--note`, default, `--backlog`, `--seed` modes) by
|
||||
#2790. The capture target lists pending todos via `--list`.
|
||||
|
||||
| User wants | Invoke |
|
||||
|---|---|
|
||||
| Explore an idea or opportunity | gsd-explore |
|
||||
| Sketch out a rough design or plan | gsd-sketch |
|
||||
| Time-boxed technical spike | gsd-spike |
|
||||
| Write a spec for a phase | gsd-spec-phase |
|
||||
| Capture a thought (todo / note / backlog / seed) | gsd-capture |
|
||||
|
||||
Invoke the matched skill directly using the Skill tool.
|
||||
28
commands/gsd/ns-manage.md
Normal file
28
commands/gsd/ns-manage.md
Normal file
@@ -0,0 +1,28 @@
|
||||
---
|
||||
name: gsd-manage
|
||||
description: "config workspace | workstreams thread update ship inbox"
|
||||
argument-hint: ""
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Skill
|
||||
---
|
||||
|
||||
Route to the appropriate management skill based on the user's intent.
|
||||
`gsd-config` (settings + advanced + integrations + profile) and `gsd-workspace`
|
||||
(new + list + remove) are post-#2790 consolidated entries.
|
||||
|
||||
| User wants | Invoke |
|
||||
|---|---|
|
||||
| Configure GSD settings (basic / advanced / integrations / profile) | gsd-config |
|
||||
| Manage workspaces (create / list / remove) | gsd-workspace |
|
||||
| Manage parallel workstreams | gsd-workstreams |
|
||||
| Continue work in a fresh context thread | gsd-thread |
|
||||
| Pause current work | gsd-pause-work |
|
||||
| Resume paused work | gsd-resume-work |
|
||||
| Update the GSD installation | gsd-update |
|
||||
| Ship completed work | gsd-ship |
|
||||
| Process inbox items | gsd-inbox |
|
||||
| Create a clean PR branch | gsd-pr-branch |
|
||||
| Undo the last GSD action | gsd-undo |
|
||||
|
||||
Invoke the matched skill directly using the Skill tool.
|
||||
22
commands/gsd/ns-project.md
Normal file
22
commands/gsd/ns-project.md
Normal file
@@ -0,0 +1,22 @@
|
||||
---
|
||||
name: gsd-project
|
||||
description: "project lifecycle | milestones audits summary"
|
||||
argument-hint: ""
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Skill
|
||||
---
|
||||
|
||||
Route to the appropriate project / milestone skill based on the user's intent.
|
||||
`gsd-plan-milestone-gaps` was deleted by #2790 — gap planning now happens
|
||||
inline as part of `gsd-audit-milestone`'s output.
|
||||
|
||||
| User wants | Invoke |
|
||||
|---|---|
|
||||
| Start a new project | gsd-new-project |
|
||||
| Create a new milestone | gsd-new-milestone |
|
||||
| Complete the current milestone | gsd-complete-milestone |
|
||||
| Audit a milestone for issues | gsd-audit-milestone |
|
||||
| Summarize milestone status | gsd-milestone-summary |
|
||||
|
||||
Invoke the matched skill directly using the Skill tool.
|
||||
25
commands/gsd/ns-review.md
Normal file
25
commands/gsd/ns-review.md
Normal file
@@ -0,0 +1,25 @@
|
||||
---
|
||||
name: gsd-review
|
||||
description: "quality gates | code review debug audit security eval ui"
|
||||
argument-hint: ""
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Skill
|
||||
---
|
||||
|
||||
Route to the appropriate quality / review skill based on the user's intent.
|
||||
`gsd-code-review-fix` was absorbed by `gsd-code-review --fix` in #2790.
|
||||
|
||||
| User wants | Invoke |
|
||||
|---|---|
|
||||
| Review code for quality and correctness | gsd-code-review |
|
||||
| Auto-fix code review findings | gsd-code-review --fix |
|
||||
| Audit UAT / acceptance testing | gsd-audit-uat |
|
||||
| Security review of a phase | gsd-secure-phase |
|
||||
| Evaluate AI response quality | gsd-eval-review |
|
||||
| Review UI for design and accessibility | gsd-ui-review |
|
||||
| Validate phase outputs | gsd-validate-phase |
|
||||
| Debug a failing feature or error | gsd-debug |
|
||||
| Forensic investigation of a broken system | gsd-forensics |
|
||||
|
||||
Invoke the matched skill directly using the Skill tool.
|
||||
27
commands/gsd/ns-workflow.md
Normal file
27
commands/gsd/ns-workflow.md
Normal file
@@ -0,0 +1,27 @@
|
||||
---
|
||||
name: gsd-workflow
|
||||
description: "workflow | discuss plan execute verify phase progress"
|
||||
argument-hint: ""
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Skill
|
||||
---
|
||||
|
||||
Route to the appropriate phase-pipeline skill based on the user's intent.
|
||||
Sub-skill names below are post-#2790 consolidated targets — `gsd-phase`
|
||||
absorbs the former add/insert/remove/edit-phase commands and `gsd-progress`
|
||||
absorbs the former next/do commands.
|
||||
|
||||
| User wants | Invoke |
|
||||
|---|---|
|
||||
| Gather context before planning | gsd-discuss-phase |
|
||||
| Clarify what a phase delivers | gsd-spec-phase |
|
||||
| Create a PLAN.md | gsd-plan-phase |
|
||||
| Execute plans in a phase | gsd-execute-phase |
|
||||
| Verify built features through UAT | gsd-verify-work |
|
||||
| Add / insert / remove / edit a phase | gsd-phase |
|
||||
| Advance to the next logical step | gsd-progress |
|
||||
| Offload planning to the ultraplan cloud | gsd-ultraplan-phase |
|
||||
| Cross-AI plan review convergence loop | gsd-plan-review-convergence |
|
||||
|
||||
Invoke the matched skill directly using the Skill tool.
|
||||
56
commands/gsd/phase.md
Normal file
56
commands/gsd/phase.md
Normal file
@@ -0,0 +1,56 @@
|
||||
---
|
||||
name: gsd:phase
|
||||
description: CRUD for phases in ROADMAP.md — add, insert, remove, or edit phases
|
||||
argument-hint: "[--insert | --remove | --edit] <phase-name-or-number>"
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Write
|
||||
- Bash
|
||||
- Glob
|
||||
---
|
||||
|
||||
<objective>
|
||||
Manage phases in ROADMAP.md with a single consolidated command.
|
||||
|
||||
Mode routing:
|
||||
- **default** (no flag): Add a new integer phase to the end of the current milestone → add-phase workflow
|
||||
- **--insert**: Insert urgent work as a decimal phase (e.g., 72.1) between existing phases → insert-phase workflow
|
||||
- **--remove**: Remove a future phase and renumber subsequent phases → remove-phase workflow
|
||||
- **--edit**: Edit any field of an existing phase in place → edit-phase workflow
|
||||
</objective>
|
||||
|
||||
<routing>
|
||||
|
||||
| Flag | Action | Workflow |
|
||||
|------|--------|----------|
|
||||
| (none) | Add new integer phase at end of milestone | add-phase |
|
||||
| --insert | Insert decimal phase (e.g., 72.1) after specified phase | insert-phase |
|
||||
| --remove | Remove future phase, renumber subsequent | remove-phase |
|
||||
| --edit | Edit fields of existing phase in place | edit-phase |
|
||||
|
||||
</routing>
|
||||
|
||||
<execution_context>
|
||||
@~/.claude/get-shit-done/workflows/add-phase.md
|
||||
@~/.claude/get-shit-done/workflows/insert-phase.md
|
||||
@~/.claude/get-shit-done/workflows/remove-phase.md
|
||||
@~/.claude/get-shit-done/workflows/edit-phase.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
Arguments: $ARGUMENTS
|
||||
|
||||
Parse the first token of $ARGUMENTS:
|
||||
- If it is `--insert`: strip the flag, pass remainder (format: <after-phase-number> <description>) to insert-phase workflow
|
||||
- If it is `--remove`: strip the flag, pass remainder (phase number) to remove-phase workflow
|
||||
- If it is `--edit`: strip the flag, pass remainder (phase-number [--force]) to edit-phase workflow
|
||||
- Otherwise: pass all of $ARGUMENTS (phase description) to add-phase workflow
|
||||
|
||||
Roadmap and state are resolved in-workflow via `init phase-op` and targeted reads.
|
||||
</context>
|
||||
|
||||
<process>
|
||||
1. Parse the leading flag (if any) from $ARGUMENTS.
|
||||
2. Load and execute the appropriate workflow end-to-end based on the routing table above.
|
||||
3. Preserve all validation gates from the target workflow.
|
||||
</process>
|
||||
@@ -1,34 +0,0 @@
|
||||
---
|
||||
name: gsd:plan-milestone-gaps
|
||||
description: Create phases to close all gaps identified by milestone audit
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Write
|
||||
- Bash
|
||||
- Glob
|
||||
- Grep
|
||||
- AskUserQuestion
|
||||
---
|
||||
<objective>
|
||||
Create all phases necessary to close gaps identified by `/gsd-audit-milestone`.
|
||||
|
||||
Reads MILESTONE-AUDIT.md, groups gaps into logical phases, creates phase entries in ROADMAP.md, and offers to plan each phase.
|
||||
|
||||
One command creates all fix phases — no manual `/gsd-add-phase` per gap.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@~/.claude/get-shit-done/workflows/plan-milestone-gaps.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
**Audit results:**
|
||||
Glob: .planning/v*-MILESTONE-AUDIT.md (use most recent)
|
||||
|
||||
Original intent and current planning state are loaded on demand inside the workflow.
|
||||
</context>
|
||||
|
||||
<process>
|
||||
Execute the plan-milestone-gaps workflow from @~/.claude/get-shit-done/workflows/plan-milestone-gaps.md end-to-end.
|
||||
Preserve all workflow gates (audit loading, prioritization, phase grouping, user confirmation, roadmap updates).
|
||||
</process>
|
||||
@@ -1,28 +0,0 @@
|
||||
---
|
||||
name: gsd:plant-seed
|
||||
description: Capture a forward-looking idea that surfaces automatically at the right milestone.
|
||||
argument-hint: "[idea summary]"
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Write
|
||||
- Edit
|
||||
- Bash
|
||||
- AskUserQuestion
|
||||
---
|
||||
|
||||
<objective>
|
||||
Capture an idea that's too big for now but should surface automatically when the right
|
||||
milestone arrives. Seeds solve context rot: instead of a one-liner in Deferred that nobody
|
||||
reads, a seed preserves the full WHY, WHEN to surface, and breadcrumbs to details.
|
||||
|
||||
Creates: .planning/seeds/SEED-NNN-slug.md
|
||||
Consumed by: /gsd-new-milestone (scans seeds and presents matches)
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@~/.claude/get-shit-done/workflows/plant-seed.md
|
||||
</execution_context>
|
||||
|
||||
<process>
|
||||
Execute the plant-seed workflow from @~/.claude/get-shit-done/workflows/plant-seed.md end-to-end.
|
||||
</process>
|
||||
@@ -1,25 +1,44 @@
|
||||
---
|
||||
name: gsd:progress
|
||||
description: Check project progress, show context, and route to the next action (execute or plan).
|
||||
argument-hint: "[--forensic]"
|
||||
description: Check progress, advance workflow, or dispatch freeform intent — the unified GSD situational command
|
||||
argument-hint: "[--forensic | --next | --do \"task description\"]"
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Bash
|
||||
- Grep
|
||||
- Glob
|
||||
- SlashCommand
|
||||
- AskUserQuestion
|
||||
---
|
||||
<objective>
|
||||
Check project progress, summarize recent work and what's ahead, then intelligently route to the next action - either executing an existing plan or creating the next one.
|
||||
Check project progress, summarize recent work and what's ahead, then intelligently route to the next action.
|
||||
|
||||
Provides situational awareness before continuing work.
|
||||
Three modes:
|
||||
- **default**: Show progress report + intelligently route to the next action (execute or plan). Provides situational awareness before continuing work.
|
||||
- **--next**: Automatically advance to the next logical step without manual route selection. Reads STATE.md, ROADMAP.md, and phase directories. Supports `--force` to bypass safety gates.
|
||||
- **--do "task description"**: Analyze freeform natural language and dispatch to the most appropriate GSD command. Never does the work itself — matches intent, confirms, hands off.
|
||||
- **--forensic**: Append a 6-check integrity audit after the standard progress report.
|
||||
</objective>
|
||||
|
||||
<flags>
|
||||
- **--next**: Detect current project state and automatically invoke the next logical GSD workflow step. Scans all prior phases for incomplete work before routing. `--next --force` bypasses safety gates.
|
||||
- **--do "..."**: Smart dispatcher — match freeform intent to the best GSD command using routing rules, confirm the match, then hand off.
|
||||
- **--forensic**: Run 6-check integrity audit after the standard progress report.
|
||||
- **(no flag)**: Standard progress check + intelligent routing (Routes A through F).
|
||||
</flags>
|
||||
|
||||
<execution_context>
|
||||
@~/.claude/get-shit-done/workflows/progress.md
|
||||
@~/.claude/get-shit-done/workflows/next.md
|
||||
@~/.claude/get-shit-done/workflows/do.md
|
||||
@~/.claude/get-shit-done/references/ui-brand.md
|
||||
</execution_context>
|
||||
|
||||
<process>
|
||||
Execute the progress workflow from @~/.claude/get-shit-done/workflows/progress.md end-to-end.
|
||||
Preserve all routing logic (Routes A through F) and edge case handling.
|
||||
Parse the first token of $ARGUMENTS:
|
||||
- If it is `--next`: strip the flag, execute the next workflow (passing remaining args e.g. --force).
|
||||
- If it is `--do`: strip the flag, pass remainder as freeform intent to the do workflow.
|
||||
- Otherwise: execute the progress workflow end-to-end (pass --forensic through if present).
|
||||
|
||||
Preserve all routing logic from the target workflow.
|
||||
</process>
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
---
|
||||
name: gsd:remove-phase
|
||||
description: Remove a future phase from roadmap and renumber subsequent phases
|
||||
argument-hint: <phase-number>
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Write
|
||||
- Bash
|
||||
- Glob
|
||||
---
|
||||
<objective>
|
||||
Remove an unstarted future phase from the roadmap and renumber all subsequent phases to maintain a clean, linear sequence.
|
||||
|
||||
Purpose: Clean removal of work you've decided not to do, without polluting context with cancelled/deferred markers.
|
||||
Output: Phase deleted, all subsequent phases renumbered, git commit as historical record.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@~/.claude/get-shit-done/workflows/remove-phase.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
Phase: $ARGUMENTS
|
||||
|
||||
Roadmap and state are resolved in-workflow via `init phase-op` and targeted reads.
|
||||
</context>
|
||||
|
||||
<process>
|
||||
Execute the remove-phase workflow from @~/.claude/get-shit-done/workflows/remove-phase.md end-to-end.
|
||||
Preserve all validation gates (future phase check, work check), renumbering logic, and commit.
|
||||
</process>
|
||||
@@ -1,26 +0,0 @@
|
||||
---
|
||||
name: gsd:remove-workspace
|
||||
description: Remove a GSD workspace and clean up worktrees
|
||||
argument-hint: "<workspace-name>"
|
||||
allowed-tools:
|
||||
- Bash
|
||||
- Read
|
||||
- AskUserQuestion
|
||||
---
|
||||
<context>
|
||||
**Arguments:**
|
||||
- `<workspace-name>` (required) — Name of the workspace to remove
|
||||
</context>
|
||||
|
||||
<objective>
|
||||
Remove a workspace directory after confirmation. For worktree strategy, runs `git worktree remove` for each member repo first. Refuses if any repo has uncommitted changes.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@~/.claude/get-shit-done/workflows/remove-workspace.md
|
||||
@~/.claude/get-shit-done/references/ui-brand.md
|
||||
</execution_context>
|
||||
|
||||
<process>
|
||||
Execute the remove-workspace workflow from @~/.claude/get-shit-done/workflows/remove-workspace.md end-to-end.
|
||||
</process>
|
||||
@@ -1,195 +0,0 @@
|
||||
---
|
||||
name: gsd:research-phase
|
||||
description: Research how to implement a phase (standalone - usually use /gsd-plan-phase instead)
|
||||
argument-hint: "[phase]"
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Bash
|
||||
- Task
|
||||
---
|
||||
|
||||
<objective>
|
||||
Research how to implement a phase. Spawns gsd-phase-researcher agent with phase context.
|
||||
|
||||
**Note:** This is a standalone research command. For most workflows, use `/gsd-plan-phase` which integrates research automatically.
|
||||
|
||||
**Use this command when:**
|
||||
- You want to research without planning yet
|
||||
- You want to re-research after planning is complete
|
||||
- You need to investigate before deciding if a phase is feasible
|
||||
|
||||
**Orchestrator role:** Parse phase, validate against roadmap, check existing research, gather context, spawn researcher agent, present results.
|
||||
|
||||
**Why subagent:** Research burns context fast (WebSearch, Context7 queries, source verification). Fresh 200k context for investigation. Main context stays lean for user interaction.
|
||||
</objective>
|
||||
|
||||
<available_agent_types>
|
||||
Valid GSD subagent types (use exact names — do not fall back to 'general-purpose'):
|
||||
- gsd-phase-researcher — Researches technical approaches for a phase
|
||||
</available_agent_types>
|
||||
|
||||
<context>
|
||||
Phase number: $ARGUMENTS (required)
|
||||
|
||||
Normalize phase input in step 1 before any directory lookups.
|
||||
</context>
|
||||
|
||||
<process>
|
||||
|
||||
## 0. Initialize Context
|
||||
|
||||
```bash
|
||||
INIT=$(gsd-sdk query init.phase-op "$ARGUMENTS")
|
||||
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
||||
```
|
||||
|
||||
Extract from init JSON: `phase_dir`, `phase_number`, `phase_name`, `phase_found`, `commit_docs`, `has_research`, `state_path`, `requirements_path`, `context_path`, `research_path`.
|
||||
|
||||
Resolve researcher model:
|
||||
```bash
|
||||
RESEARCHER_MODEL=$(gsd-sdk query resolve-model gsd-phase-researcher --raw)
|
||||
```
|
||||
|
||||
## 1. Validate Phase
|
||||
|
||||
```bash
|
||||
PHASE_INFO=$(gsd-sdk query roadmap.get-phase "${phase_number}")
|
||||
```
|
||||
|
||||
**If `found` is false:** Error and exit. **If `found` is true:** Extract `phase_number`, `phase_name`, `goal` from JSON.
|
||||
|
||||
## 2. Check Existing Research
|
||||
|
||||
```bash
|
||||
ls .planning/phases/${PHASE}-*/RESEARCH.md 2>/dev/null
|
||||
```
|
||||
|
||||
**If exists:** Offer: 1) Update research, 2) View existing, 3) Skip. Wait for response.
|
||||
|
||||
**If doesn't exist:** Continue.
|
||||
|
||||
## 3. Gather Phase Context
|
||||
|
||||
Use paths from INIT (do not inline file contents in orchestrator context):
|
||||
- `requirements_path`
|
||||
- `context_path`
|
||||
- `state_path`
|
||||
|
||||
Present summary with phase description and what files the researcher will load.
|
||||
|
||||
## 4. Spawn gsd-phase-researcher Agent
|
||||
|
||||
Research modes: ecosystem (default), feasibility, implementation, comparison.
|
||||
|
||||
```markdown
|
||||
<research_type>
|
||||
Phase Research — investigating HOW to implement a specific phase well.
|
||||
</research_type>
|
||||
|
||||
<key_insight>
|
||||
The question is NOT "which library should I use?"
|
||||
|
||||
The question is: "What do I not know that I don't know?"
|
||||
|
||||
For this phase, discover:
|
||||
- What's the established architecture pattern?
|
||||
- What libraries form the standard stack?
|
||||
- What problems do people commonly hit?
|
||||
- What's SOTA vs what Claude's training thinks is SOTA?
|
||||
- What should NOT be hand-rolled?
|
||||
</key_insight>
|
||||
|
||||
<objective>
|
||||
Research implementation approach for Phase {phase_number}: {phase_name}
|
||||
Mode: ecosystem
|
||||
</objective>
|
||||
|
||||
<files_to_read>
|
||||
- {requirements_path} (Requirements)
|
||||
- {context_path} (Phase context from discuss-phase, if exists)
|
||||
- {state_path} (Prior project decisions and blockers)
|
||||
</files_to_read>
|
||||
|
||||
<additional_context>
|
||||
**Phase description:** {phase_description}
|
||||
</additional_context>
|
||||
|
||||
<downstream_consumer>
|
||||
Your RESEARCH.md will be loaded by `/gsd-plan-phase` which uses specific sections:
|
||||
- `## Standard Stack` → Plans use these libraries
|
||||
- `## Architecture Patterns` → Task structure follows these
|
||||
- `## Don't Hand-Roll` → Tasks NEVER build custom solutions for listed problems
|
||||
- `## Common Pitfalls` → Verification steps check for these
|
||||
- `## Code Examples` → Task actions reference these patterns
|
||||
|
||||
Be prescriptive, not exploratory. "Use X" not "Consider X or Y."
|
||||
</downstream_consumer>
|
||||
|
||||
<quality_gate>
|
||||
Before declaring complete, verify:
|
||||
- [ ] All domains investigated (not just some)
|
||||
- [ ] Negative claims verified with official docs
|
||||
- [ ] Multiple sources for critical claims
|
||||
- [ ] Confidence levels assigned honestly
|
||||
- [ ] Section names match what plan-phase expects
|
||||
</quality_gate>
|
||||
|
||||
<output>
|
||||
Write to: .planning/phases/${PHASE}-{slug}/${PHASE}-RESEARCH.md
|
||||
</output>
|
||||
```
|
||||
|
||||
```
|
||||
Task(
|
||||
prompt=filled_prompt,
|
||||
subagent_type="gsd-phase-researcher",
|
||||
model="{researcher_model}",
|
||||
description="Research Phase {phase}"
|
||||
)
|
||||
```
|
||||
|
||||
## 5. Handle Agent Return
|
||||
|
||||
**`## RESEARCH COMPLETE`:** Display summary, offer: Plan phase, Dig deeper, Review full, Done.
|
||||
|
||||
**`## CHECKPOINT REACHED`:** Present to user, get response, spawn continuation.
|
||||
|
||||
**`## RESEARCH INCONCLUSIVE`:** Show what was attempted, offer: Add context, Try different mode, Manual.
|
||||
|
||||
## 6. Spawn Continuation Agent
|
||||
|
||||
```markdown
|
||||
<objective>
|
||||
Continue research for Phase {phase_number}: {phase_name}
|
||||
</objective>
|
||||
|
||||
<prior_state>
|
||||
<files_to_read>
|
||||
- .planning/phases/${PHASE}-{slug}/${PHASE}-RESEARCH.md (Existing research)
|
||||
</files_to_read>
|
||||
</prior_state>
|
||||
|
||||
<checkpoint_response>
|
||||
**Type:** {checkpoint_type}
|
||||
**Response:** {user_response}
|
||||
</checkpoint_response>
|
||||
```
|
||||
|
||||
```
|
||||
Task(
|
||||
prompt=continuation_prompt,
|
||||
subagent_type="gsd-phase-researcher",
|
||||
model="{researcher_model}",
|
||||
description="Continue research Phase {phase}"
|
||||
)
|
||||
```
|
||||
|
||||
</process>
|
||||
|
||||
<success_criteria>
|
||||
- [ ] Phase validated against roadmap
|
||||
- [ ] Existing research checked
|
||||
- [ ] gsd-phase-researcher spawned with context
|
||||
- [ ] Checkpoints handled correctly
|
||||
- [ ] User knows next steps
|
||||
</success_criteria>
|
||||
@@ -1,26 +0,0 @@
|
||||
---
|
||||
name: gsd:scan
|
||||
description: Rapid codebase assessment — lightweight alternative to /gsd-map-codebase
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Write
|
||||
- Bash
|
||||
- Grep
|
||||
- Glob
|
||||
- Agent
|
||||
- AskUserQuestion
|
||||
---
|
||||
<objective>
|
||||
Run a focused codebase scan for a single area, producing targeted documents in `.planning/codebase/`.
|
||||
Accepts an optional `--focus` flag: `tech`, `arch`, `quality`, `concerns`, or `tech+arch` (default).
|
||||
|
||||
Lightweight alternative to `/gsd-map-codebase` — spawns one mapper agent instead of four parallel ones.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@~/.claude/get-shit-done/workflows/scan.md
|
||||
</execution_context>
|
||||
|
||||
<process>
|
||||
Execute the scan workflow from @~/.claude/get-shit-done/workflows/scan.md end-to-end.
|
||||
</process>
|
||||
@@ -1,19 +0,0 @@
|
||||
---
|
||||
name: gsd:session-report
|
||||
description: Generate a session report with token usage estimates, work summary, and outcomes
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Bash
|
||||
- Write
|
||||
---
|
||||
<objective>
|
||||
Generate a structured SESSION_REPORT.md document capturing session outcomes, work performed, and estimated resource usage. Provides a shareable artifact for post-session review.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@~/.claude/get-shit-done/workflows/session-report.md
|
||||
</execution_context>
|
||||
|
||||
<process>
|
||||
Execute the session-report workflow from @~/.claude/get-shit-done/workflows/session-report.md end-to-end.
|
||||
</process>
|
||||
@@ -1,12 +0,0 @@
|
||||
---
|
||||
name: gsd:set-profile
|
||||
description: Switch model profile for GSD agents (quality/balanced/budget/inherit)
|
||||
argument-hint: <profile (quality|balanced|budget|inherit)>
|
||||
model: haiku
|
||||
allowed-tools:
|
||||
- Bash
|
||||
---
|
||||
|
||||
Show the following output to the user verbatim, with no extra commentary:
|
||||
|
||||
!`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,39 +0,0 @@
|
||||
---
|
||||
name: gsd:settings-advanced
|
||||
description: Power-user configuration for plan bounce, timeouts, branch templates, and cross-AI execution.
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Write
|
||||
- Bash
|
||||
- AskUserQuestion
|
||||
---
|
||||
|
||||
<objective>
|
||||
Interactive configuration of GSD power-user knobs that don't belong in the common-case `/gsd-settings` prompt.
|
||||
|
||||
Routes to the settings-advanced workflow which handles:
|
||||
- Config existence ensuring (workstream-aware path resolution)
|
||||
- Current settings reading and parsing
|
||||
- Sectioned prompts: Planning Tuning, Execution Tuning, Discussion Tuning, Cross-AI Execution, Git Customization, Runtime / Output
|
||||
- Config merging that preserves every unrelated key
|
||||
- Confirmation table display
|
||||
|
||||
Use `/gsd-settings` for the common-case toggles (model profile, research/plan_check/verifier, branching strategy, context warnings). Use `/gsd-settings-advanced` once those are set and you want to tune the internals.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@~/.claude/get-shit-done/workflows/settings-advanced.md
|
||||
</execution_context>
|
||||
|
||||
<process>
|
||||
**Follow the settings-advanced workflow** from `@~/.claude/get-shit-done/workflows/settings-advanced.md`.
|
||||
|
||||
The workflow handles all logic including:
|
||||
1. Config file creation with defaults if missing (via `gsd-sdk query config-ensure-section`)
|
||||
2. Current config reading
|
||||
3. Six sectioned AskUserQuestion batches with current values pre-selected
|
||||
4. Numeric-input validation (non-numeric rejected, empty input keeps current)
|
||||
5. Answer parsing and config merging (preserves unrelated keys)
|
||||
6. File writing (atomic)
|
||||
7. Confirmation table display
|
||||
</process>
|
||||
@@ -1,44 +0,0 @@
|
||||
---
|
||||
name: gsd:settings-integrations
|
||||
description: Configure third-party API keys, code-review CLI routing, and agent-skill injection
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Write
|
||||
- Bash
|
||||
- AskUserQuestion
|
||||
---
|
||||
|
||||
<objective>
|
||||
Interactive configuration of GSD's third-party integration surface:
|
||||
- Search API keys: `brave_search`, `firecrawl`, `exa_search`, and
|
||||
the `search_gitignored` toggle
|
||||
- Code-review CLI routing: `review.models.{claude,codex,gemini,opencode}`
|
||||
- Agent-skill injection: `agent_skills.<agent-type>`
|
||||
|
||||
API keys are stored plaintext in `.planning/config.json` but are masked
|
||||
(`****<last-4>`) in every piece of interactive output. The workflow never
|
||||
echoes plaintext to stdout, stderr, or any log.
|
||||
|
||||
This command is deliberately distinct from `/gsd-settings` (workflow toggles)
|
||||
and any `/gsd-settings-advanced` tuning surface. It handles *connectivity*,
|
||||
not pipeline shape.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@~/.claude/get-shit-done/workflows/settings-integrations.md
|
||||
</execution_context>
|
||||
|
||||
<process>
|
||||
**Follow the settings-integrations workflow** from
|
||||
`@~/.claude/get-shit-done/workflows/settings-integrations.md`.
|
||||
|
||||
The workflow handles:
|
||||
1. Resolving `$GSD_CONFIG_PATH` (flat vs workstream)
|
||||
2. Reading current integration values (masked for display)
|
||||
3. Section 1 — Search Integrations: Brave / Firecrawl / Exa / search_gitignored
|
||||
4. Section 2 — Review CLI Routing: review.models.{claude,codex,gemini,opencode}
|
||||
5. Section 3 — Agent Skills Injection: agent_skills.<agent-type>
|
||||
6. Writing values via `gsd-sdk query config-set` (which merges, preserving
|
||||
unrelated keys)
|
||||
7. Masked confirmation display
|
||||
</process>
|
||||
@@ -1,31 +0,0 @@
|
||||
---
|
||||
name: gsd:sketch-wrap-up
|
||||
description: Package sketch design findings into a persistent project skill for future build conversations
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Write
|
||||
- Edit
|
||||
- Bash
|
||||
- Grep
|
||||
- Glob
|
||||
- AskUserQuestion
|
||||
---
|
||||
<objective>
|
||||
Curate sketch design findings and package them into a persistent project skill that Claude
|
||||
auto-loads when building the real UI. Also writes a summary to `.planning/sketches/` for
|
||||
project history. Output skill goes to `./.claude/skills/sketch-findings-[project]/` (project-local).
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@~/.claude/get-shit-done/workflows/sketch-wrap-up.md
|
||||
@~/.claude/get-shit-done/references/ui-brand.md
|
||||
</execution_context>
|
||||
|
||||
<runtime_note>
|
||||
**Copilot (VS Code):** Use `vscode_askquestions` wherever this workflow calls `AskUserQuestion`.
|
||||
</runtime_note>
|
||||
|
||||
<process>
|
||||
Execute the sketch-wrap-up workflow from @~/.claude/get-shit-done/workflows/sketch-wrap-up.md end-to-end.
|
||||
Preserve all curation gates (per-sketch review, grouping approval, CLAUDE.md routing line).
|
||||
</process>
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: gsd:sketch
|
||||
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]"
|
||||
argument-hint: "[design idea to explore] [--quick] [--text] [--wrap-up] or [frontier]"
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Write
|
||||
@@ -30,6 +30,7 @@ Does not require `/gsd-new-project` — auto-creates `.planning/sketches/` if ne
|
||||
|
||||
<execution_context>
|
||||
@~/.claude/get-shit-done/workflows/sketch.md
|
||||
@~/.claude/get-shit-done/workflows/sketch-wrap-up.md
|
||||
@~/.claude/get-shit-done/references/ui-brand.md
|
||||
@~/.claude/get-shit-done/references/sketch-theme-system.md
|
||||
@~/.claude/get-shit-done/references/sketch-interactivity.md
|
||||
@@ -46,9 +47,13 @@ Design idea: $ARGUMENTS
|
||||
|
||||
**Available flags:**
|
||||
- `--quick` — Skip mood/direction intake, jump straight to decomposition and building. Use when the design direction is already clear.
|
||||
- `--wrap-up` — Package sketch design findings into a persistent project skill for future build conversations. Runs the sketch-wrap-up workflow.
|
||||
</context>
|
||||
|
||||
<process>
|
||||
Execute the sketch workflow from @~/.claude/get-shit-done/workflows/sketch.md end-to-end.
|
||||
Parse the first token of $ARGUMENTS:
|
||||
- If it is `--wrap-up`: strip the flag, execute the sketch-wrap-up workflow from @~/.claude/get-shit-done/workflows/sketch-wrap-up.md end-to-end.
|
||||
- Otherwise: execute the sketch workflow from @~/.claude/get-shit-done/workflows/sketch.md end-to-end.
|
||||
|
||||
Preserve all workflow gates (intake, decomposition, target stack research, variant evaluation, MANIFEST updates, commit patterns).
|
||||
</process>
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
---
|
||||
name: gsd:spike-wrap-up
|
||||
description: Package spike findings into a persistent project skill for future build conversations
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Write
|
||||
- Edit
|
||||
- Bash
|
||||
- Grep
|
||||
- Glob
|
||||
- AskUserQuestion
|
||||
---
|
||||
<objective>
|
||||
Curate spike experiment findings and package them into a persistent project skill that Claude
|
||||
auto-loads in future build conversations. Also writes a summary to `.planning/spikes/` for
|
||||
project history. Output skill goes to `./.claude/skills/spike-findings-[project]/` (project-local).
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@~/.claude/get-shit-done/workflows/spike-wrap-up.md
|
||||
@~/.claude/get-shit-done/references/ui-brand.md
|
||||
</execution_context>
|
||||
|
||||
<runtime_note>
|
||||
**Copilot (VS Code):** Use `vscode_askquestions` wherever this workflow calls `AskUserQuestion`.
|
||||
</runtime_note>
|
||||
|
||||
<process>
|
||||
Execute the spike-wrap-up workflow from @~/.claude/get-shit-done/workflows/spike-wrap-up.md end-to-end.
|
||||
Preserve all workflow gates (auto-include, feature-area grouping, skill synthesis, CLAUDE.md routing line, intelligent next-step routing).
|
||||
</process>
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: gsd:spike
|
||||
description: Spike an idea through experiential exploration, or propose what to spike next (frontier mode)
|
||||
argument-hint: "[idea to validate] [--quick] [--text] or [frontier]"
|
||||
argument-hint: "[idea to validate] [--quick] [--text] [--wrap-up] or [frontier]"
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Write
|
||||
@@ -30,6 +30,7 @@ Does not require `/gsd-new-project` — auto-creates `.planning/spikes/` if need
|
||||
|
||||
<execution_context>
|
||||
@~/.claude/get-shit-done/workflows/spike.md
|
||||
@~/.claude/get-shit-done/workflows/spike-wrap-up.md
|
||||
@~/.claude/get-shit-done/references/ui-brand.md
|
||||
</execution_context>
|
||||
|
||||
@@ -43,9 +44,13 @@ 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).
|
||||
- `--wrap-up` — Package spike findings into a persistent project skill for future build conversations. Runs the spike-wrap-up workflow.
|
||||
</context>
|
||||
|
||||
<process>
|
||||
Execute the spike workflow from @~/.claude/get-shit-done/workflows/spike.md end-to-end.
|
||||
Parse the first token of $ARGUMENTS:
|
||||
- If it is `--wrap-up`: strip the flag, execute the spike-wrap-up workflow from @~/.claude/get-shit-done/workflows/spike-wrap-up.md.
|
||||
- Otherwise: pass all of $ARGUMENTS as the idea to the spike workflow from @~/.claude/get-shit-done/workflows/spike.md end-to-end.
|
||||
|
||||
Preserve all workflow gates (prior spike check, decomposition, research, risk ordering, observability assessment, verification, MANIFEST updates, commit patterns).
|
||||
</process>
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
---
|
||||
name: gsd:sync-skills
|
||||
description: Sync managed GSD skills across runtime roots so multi-runtime users stay aligned after an update
|
||||
allowed-tools:
|
||||
- Bash
|
||||
- AskUserQuestion
|
||||
---
|
||||
|
||||
<objective>
|
||||
Sync managed `gsd-*` skill directories from one canonical runtime's skills root to one or more destination runtime skills roots.
|
||||
|
||||
Routes to the sync-skills workflow which handles:
|
||||
- Argument parsing (--from, --to, --dry-run, --apply)
|
||||
- Runtime skills root resolution via install.js --skills-root
|
||||
- Diff computation (CREATE / UPDATE / REMOVE per destination)
|
||||
- Dry-run reporting (default — no writes)
|
||||
- Apply execution (copy and remove with idempotency)
|
||||
- Non-GSD skill preservation (only gsd-* dirs are touched)
|
||||
</objective>
|
||||
@@ -1,8 +1,14 @@
|
||||
---
|
||||
name: gsd:update
|
||||
description: Update GSD to latest version with changelog display
|
||||
argument-hint: "[--sync | --reapply]"
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Write
|
||||
- Edit
|
||||
- Bash
|
||||
- Glob
|
||||
- Grep
|
||||
- AskUserQuestion
|
||||
---
|
||||
|
||||
@@ -22,10 +28,19 @@ Routes to the update workflow which handles:
|
||||
@~/.claude/get-shit-done/workflows/update.md
|
||||
</execution_context>
|
||||
|
||||
<process>
|
||||
**Follow the update workflow** from `@~/.claude/get-shit-done/workflows/update.md`.
|
||||
<flags>
|
||||
- **--sync**: Sync managed GSD skills across runtime roots so multi-runtime users stay aligned after an update. Runs the sync-skills workflow (--from, --to, --dry-run, --apply flags supported).
|
||||
- **--reapply**: Reapply local modifications after a GSD update. Uses three-way comparison (pristine baseline, user-modified backup, newly installed version) to merge user customizations back. Runs the reapply-patches workflow.
|
||||
- **(no flag)**: Standard update — check for new version, show changelog, install.
|
||||
</flags>
|
||||
|
||||
The workflow handles all logic including:
|
||||
<process>
|
||||
Parse the first token of $ARGUMENTS:
|
||||
- If it is `--sync`: strip the flag, execute the sync-skills workflow (passing remaining args for --from/--to/--dry-run/--apply).
|
||||
- If it is `--reapply`: strip the flag, execute the reapply-patches workflow.
|
||||
- Otherwise: **Follow the update workflow** from `@~/.claude/get-shit-done/workflows/update.md`.
|
||||
|
||||
The update workflow handles all logic including:
|
||||
1. Installed version detection (local/global)
|
||||
2. Latest version checking via npm
|
||||
3. Version comparison
|
||||
@@ -35,3 +50,8 @@ The workflow handles all logic including:
|
||||
7. Update execution
|
||||
8. Cache clearing
|
||||
</process>
|
||||
|
||||
<execution_context_extended>
|
||||
@~/.claude/get-shit-done/workflows/sync-skills.md
|
||||
@~/.claude/get-shit-done/workflows/reapply-patches.md
|
||||
</execution_context_extended>
|
||||
|
||||
52
commands/gsd/workspace.md
Normal file
52
commands/gsd/workspace.md
Normal file
@@ -0,0 +1,52 @@
|
||||
---
|
||||
name: gsd:workspace
|
||||
description: Manage GSD workspaces — create, list, or remove isolated workspace environments
|
||||
argument-hint: "[--new | --list | --remove] [name]"
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Write
|
||||
- Bash
|
||||
- AskUserQuestion
|
||||
---
|
||||
|
||||
<objective>
|
||||
Manage GSD workspaces with a single consolidated command.
|
||||
|
||||
Mode routing:
|
||||
- **--new**: Create an isolated workspace with repo copies and independent .planning/ → new-workspace workflow
|
||||
- **--list**: List active GSD workspaces and their status → list-workspaces workflow
|
||||
- **--remove**: Remove a GSD workspace and clean up worktrees → remove-workspace workflow
|
||||
</objective>
|
||||
|
||||
<routing>
|
||||
|
||||
| Flag | Action | Workflow |
|
||||
|------|--------|----------|
|
||||
| --new | Create workspace with worktree/clone strategy | new-workspace |
|
||||
| --list | Scan ~/gsd-workspaces/, show summary table | list-workspaces |
|
||||
| --remove | Confirm and remove workspace directory | remove-workspace |
|
||||
|
||||
</routing>
|
||||
|
||||
<execution_context>
|
||||
@~/.claude/get-shit-done/workflows/new-workspace.md
|
||||
@~/.claude/get-shit-done/workflows/list-workspaces.md
|
||||
@~/.claude/get-shit-done/workflows/remove-workspace.md
|
||||
@~/.claude/get-shit-done/references/ui-brand.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
Arguments: $ARGUMENTS
|
||||
|
||||
Parse the first token of $ARGUMENTS:
|
||||
- If it is `--new`: strip the flag, pass remainder (--name, --repos, --path, --strategy, --branch, --auto flags) to new-workspace workflow
|
||||
- If it is `--list`: execute list-workspaces workflow (no argument needed)
|
||||
- If it is `--remove`: strip the flag, pass remainder (workspace-name) to remove-workspace workflow
|
||||
- Otherwise (no flag): show usage — one of --new, --list, or --remove is required
|
||||
</context>
|
||||
|
||||
<process>
|
||||
1. Parse the leading flag from $ARGUMENTS.
|
||||
2. Load and execute the appropriate workflow end-to-end based on the routing table above.
|
||||
3. Preserve all workflow gates from the target workflow (validation, approvals, commits, routing).
|
||||
</process>
|
||||
@@ -343,7 +343,7 @@ GSD uses a multi-agent architecture where thin orchestrators (workflow files) sp
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| **Spawned by** | `/gsd-map-codebase`, post-execute drift gate in `/gsd:execute-phase` |
|
||||
| **Spawned by** | `/gsd-map-codebase`, post-execute drift gate in `/gsd-execute-phase` |
|
||||
| **Parallelism** | 4 instances (tech, architecture, quality, concerns) |
|
||||
| **Tools** | Read, Bash, Grep, Glob, Write |
|
||||
| **Model (balanced)** | Haiku |
|
||||
|
||||
@@ -134,7 +134,7 @@ Orchestration logic that commands reference. Contains the step-by-step process i
|
||||
#### Progressive disclosure for workflows
|
||||
|
||||
Workflow files are loaded verbatim into Claude's context every time the
|
||||
corresponding `/gsd:*` command is invoked. To keep that cost bounded, the
|
||||
corresponding `/gsd-*` command is invoked. To keep that cost bounded, the
|
||||
workflow size budget enforced by `tests/workflow-size-budget.test.cjs`
|
||||
mirrors the agent budget from #2361:
|
||||
|
||||
@@ -257,12 +257,13 @@ See [`docs/INVENTORY.md`](INVENTORY.md#hooks-11-shipped) for the authoritative 1
|
||||
|
||||
### CLI Tools (`get-shit-done/bin/`)
|
||||
|
||||
Node.js CLI utility (`gsd-tools.cjs`) with domain modules split across `get-shit-done/bin/lib/` (see [`docs/INVENTORY.md`](INVENTORY.md#cli-modules-24-shipped) for the authoritative roster):
|
||||
Node.js CLI utility (`gsd-tools.cjs`) with domain modules split across `get-shit-done/bin/lib/` (see [`docs/INVENTORY.md`](INVENTORY.md#cli-modules-33-shipped) for the authoritative roster):
|
||||
|
||||
|
||||
| Module | Responsibility |
|
||||
| ---------------------- | --------------------------------------------------------------------------------------------------- |
|
||||
| `core.cjs` | Error handling, output formatting, shared utilities |
|
||||
| `core.cjs` | Error handling, output formatting, shared utilities; compatibility re-exports for planning helpers |
|
||||
| `planning-workspace.cjs` | Planning seam (`planningDir`, `planningPaths`, active workstream routing, `.planning/.lock`) |
|
||||
| `state.cjs` | STATE.md parsing, updating, progression, metrics |
|
||||
| `phase.cjs` | Phase directory operations, decimal numbering, plan indexing |
|
||||
| `roadmap.cjs` | ROADMAP.md parsing, phase extraction, plan progress |
|
||||
@@ -534,7 +535,7 @@ Equivalent paths for other runtimes:
|
||||
|
||||
### Post-Execute Codebase Drift Gate (#2003)
|
||||
|
||||
After the last wave of `/gsd:execute-phase` commits, the workflow runs a
|
||||
After the last wave of `/gsd-execute-phase` commits, the workflow runs a
|
||||
non-blocking `codebase_drift_gate` step (between `schema_drift_gate` and
|
||||
`verify_phase_goal`). It compares the diff `last_mapped_commit..HEAD`
|
||||
against `.planning/codebase/STRUCTURE.md` and counts four kinds of
|
||||
@@ -546,7 +547,7 @@ structural elements:
|
||||
4. New route modules under `routes/` or `api/`
|
||||
|
||||
If the count meets `workflow.drift_threshold` (default 3), the gate either
|
||||
**warns** (default) with the suggested `/gsd:map-codebase --paths …` command,
|
||||
**warns** (default) with the suggested `/gsd-map-codebase --paths …` command,
|
||||
or **auto-remaps** (`workflow.drift_action = auto-remap`) by spawning
|
||||
`gsd-codebase-mapper` scoped to the affected paths. Any error in detection
|
||||
or remap is logged and the phase continues — drift detection cannot fail
|
||||
@@ -675,4 +676,4 @@ GSD supports multiple AI coding runtimes through a unified command/workflow arch
|
||||
4. **Path conventions** — Each runtime stores config in different directories
|
||||
5. **Model references** — `inherit` profile lets GSD defer to runtime's model selection
|
||||
|
||||
The installer handles all translation at install time. Workflows and agents are written in Claude Code's native format and transformed during deployment.
|
||||
The installer handles all translation at install time. Workflows and agents are written in Claude Code's native format and transformed during deployment.
|
||||
|
||||
@@ -452,9 +452,10 @@ User-facing entry point: `/gsd-graphify` (see [Command Reference](COMMANDS.md#gs
|
||||
|
||||
| Module | File | Exports |
|
||||
|--------|------|---------|
|
||||
| Core | `lib/core.cjs` | `error()`, `output()`, `parseArgs()`, shared utilities |
|
||||
| Core | `lib/core.cjs` | `error()`, `output()`, `parseArgs()`, shared utilities, compatibility re-exports |
|
||||
| State | `lib/state.cjs` | All `state` subcommands, `state-snapshot` |
|
||||
| Phase | `lib/phase.cjs` | Phase CRUD, `find-phase`, `phase-plan-index`, `phases list` |
|
||||
| Planning Workspace | `lib/planning-workspace.cjs` | Planning seam: `planningDir`, `planningPaths`, active workstream routing, `.planning/.lock` |
|
||||
| Roadmap | `lib/roadmap.cjs` | Roadmap parsing, phase extraction, progress updates |
|
||||
| Config | `lib/config.cjs` | Config read/write, section initialization |
|
||||
| Verify | `lib/verify.cjs` | All verification and validation commands |
|
||||
@@ -498,4 +499,4 @@ API keys configured via `/gsd-settings-integrations` (`brave_search`, `firecrawl
|
||||
|
||||
- [sdk/src/query/QUERY-HANDLERS.md](../sdk/src/query/QUERY-HANDLERS.md) — registry matrix, routing, golden parity, intentional CJS differences
|
||||
- [Architecture](ARCHITECTURE.md) — where `gsd-sdk query` fits in orchestration
|
||||
- [Command Reference](COMMANDS.md) — user-facing `/gsd:` commands
|
||||
- [Command Reference](COMMANDS.md) — user-facing `/gsd-` commands
|
||||
|
||||
546
docs/COMMANDS.md
546
docs/COMMANDS.md
@@ -32,14 +32,17 @@ Initialize a new project with deep context gathering.
|
||||
|
||||
---
|
||||
|
||||
### `/gsd-new-workspace`
|
||||
### `/gsd-workspace`
|
||||
|
||||
Create an isolated workspace with repo copies and independent `.planning/` directory.
|
||||
Manage GSD workspaces — create, list, or remove isolated workspace environments with repo copies and independent `.planning/` directories.
|
||||
|
||||
| Flag | Description |
|
||||
|------|-------------|
|
||||
| `--name <name>` | Workspace name (required) |
|
||||
| `--repos repo1,repo2` | Comma-separated repo paths or names |
|
||||
| `--new` | Create a new workspace (use with `--name`, `--repos`, etc.) |
|
||||
| `--list` | List active GSD workspaces and their status |
|
||||
| `--remove <name>` | Remove a workspace and clean up git worktrees |
|
||||
| `--name <name>` | Workspace name (used with `--new`) |
|
||||
| `--repos repo1,repo2` | Comma-separated repo paths or names (used with `--new`) |
|
||||
| `--path /target` | Target directory (default: `~/gsd-workspaces/<name>`) |
|
||||
| `--strategy worktree\|clone` | Copy strategy (default: `worktree`) |
|
||||
| `--branch <name>` | Branch to checkout (default: `workspace/<name>`) |
|
||||
@@ -52,38 +55,10 @@ Create an isolated workspace with repo copies and independent `.planning/` direc
|
||||
**Produces:** `WORKSPACE.md`, `.planning/`, repo copies (worktrees or clones)
|
||||
|
||||
```bash
|
||||
/gsd-new-workspace --name feature-b --repos hr-ui,ZeymoAPI
|
||||
/gsd-new-workspace --name feature-b --repos . --strategy worktree # Same-repo isolation
|
||||
/gsd-new-workspace --name spike --repos api,web --strategy clone # Full clones
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `/gsd-list-workspaces`
|
||||
|
||||
List active GSD workspaces and their status.
|
||||
|
||||
**Scans:** `~/gsd-workspaces/` for `WORKSPACE.md` manifests
|
||||
**Shows:** Name, repo count, strategy, GSD project status
|
||||
|
||||
```bash
|
||||
/gsd-list-workspaces
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `/gsd-remove-workspace`
|
||||
|
||||
Remove a workspace and clean up git worktrees.
|
||||
|
||||
| Argument | Required | Description |
|
||||
|----------|----------|-------------|
|
||||
| `<name>` | Yes | Workspace name to remove |
|
||||
|
||||
**Safety:** Refuses removal if any repo has uncommitted changes. Requires name confirmation.
|
||||
|
||||
```bash
|
||||
/gsd-remove-workspace feature-b
|
||||
/gsd-workspace --new --name feature-b --repos hr-ui,ZeymoAPI
|
||||
/gsd-workspace --new --name feature-b --repos . --strategy worktree # Same-repo isolation
|
||||
/gsd-workspace --list
|
||||
/gsd-workspace --remove feature-b
|
||||
```
|
||||
|
||||
---
|
||||
@@ -247,43 +222,6 @@ User acceptance testing with auto-diagnosis.
|
||||
|
||||
---
|
||||
|
||||
### `/gsd-next`
|
||||
|
||||
Automatically advance to the next logical workflow step. Reads project state and runs the appropriate command.
|
||||
|
||||
**Prerequisites:** `.planning/` directory exists
|
||||
**Behavior:**
|
||||
- No project → suggests `/gsd-new-project`
|
||||
- Phase needs discussion → runs `/gsd-discuss-phase`
|
||||
- Phase needs planning → runs `/gsd-plan-phase`
|
||||
- Phase needs execution → runs `/gsd-execute-phase`
|
||||
- Phase needs verification → runs `/gsd-verify-work`
|
||||
- All phases complete → suggests `/gsd-complete-milestone`
|
||||
|
||||
```bash
|
||||
/gsd-next # Auto-detect and run next step
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `/gsd-session-report`
|
||||
|
||||
Generate a session report with work summary, outcomes, and estimated resource usage.
|
||||
|
||||
**Prerequisites:** Active project with recent work
|
||||
**Produces:** `.planning/reports/SESSION_REPORT.md`
|
||||
|
||||
```bash
|
||||
/gsd-session-report # Generate post-session summary
|
||||
```
|
||||
|
||||
**Report includes:**
|
||||
- Work performed (commits, plans executed, phases progressed)
|
||||
- Outcomes and deliverables
|
||||
- Blockers and decisions made
|
||||
- Estimated token/cost usage
|
||||
- Next steps recommendation
|
||||
|
||||
---
|
||||
|
||||
### `/gsd-ship`
|
||||
@@ -417,112 +355,31 @@ Start next version cycle.
|
||||
|
||||
## Phase Management Commands
|
||||
|
||||
### `/gsd-add-phase`
|
||||
### `/gsd-phase`
|
||||
|
||||
Append new phase to roadmap.
|
||||
|
||||
```bash
|
||||
/gsd-add-phase # Interactive — describe the phase
|
||||
```
|
||||
|
||||
### `/gsd-edit-phase`
|
||||
|
||||
Edit any field of an existing roadmap phase in place.
|
||||
|
||||
| Argument | Required | Description |
|
||||
|----------|----------|-------------|
|
||||
| `N` | Yes | Phase number to edit |
|
||||
CRUD for phases in ROADMAP.md — add, insert, remove, or edit phases with a single consolidated command.
|
||||
|
||||
| Flag | Description |
|
||||
|------|-------------|
|
||||
| `--force` | Allow editing in-progress or completed phases |
|
||||
|
||||
**Prerequisites:** `.planning/ROADMAP.md` exists, phase N must exist
|
||||
**Produces:** Updated phase section in ROADMAP.md (in place, number and position preserved)
|
||||
|
||||
```bash
|
||||
/gsd-edit-phase 5 # Edit any field of phase 5 (future phases only)
|
||||
/gsd-edit-phase 5 --force # Edit phase 5 even if in-progress or completed
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `/gsd-insert-phase`
|
||||
|
||||
Insert urgent work between phases using decimal numbering.
|
||||
|
||||
| Argument | Required | Description |
|
||||
|----------|----------|-------------|
|
||||
| `N` | No | Insert after this phase number |
|
||||
|
||||
```bash
|
||||
/gsd-insert-phase 3 # Insert between phase 3 and 4 → creates 3.1
|
||||
```
|
||||
|
||||
### `/gsd-remove-phase`
|
||||
|
||||
Remove future phase and renumber subsequent phases.
|
||||
|
||||
| Argument | Required | Description |
|
||||
|----------|----------|-------------|
|
||||
| `N` | No | Phase number to remove |
|
||||
|
||||
```bash
|
||||
/gsd-remove-phase 7 # Remove phase 7, renumber 8→7, 9→8, etc.
|
||||
```
|
||||
|
||||
### `/gsd-list-phase-assumptions`
|
||||
|
||||
Preview Claude's intended approach before planning.
|
||||
|
||||
| Argument | Required | Description |
|
||||
|----------|----------|-------------|
|
||||
| `N` | No | Phase number |
|
||||
|
||||
```bash
|
||||
/gsd-list-phase-assumptions 2 # See assumptions for phase 2
|
||||
```
|
||||
|
||||
### `/gsd-analyze-dependencies`
|
||||
|
||||
Analyze phase dependencies and suggest `Depends on` entries for ROADMAP.md before running `/gsd-manager`.
|
||||
| (none) | Append a new integer phase to the end of the current milestone |
|
||||
| `--insert <N>` | Insert urgent work as a decimal phase (e.g., 3.1) after phase N |
|
||||
| `--remove <N>` | Remove a future phase and renumber subsequent phases |
|
||||
| `--edit <N>` | Edit any field of an existing phase in place |
|
||||
| `--force` | Allow editing in-progress or completed phases (used with `--edit`) |
|
||||
|
||||
**Prerequisites:** `.planning/ROADMAP.md` exists
|
||||
**Produces:** Dependency suggestion table; optionally updates `Depends on` fields in ROADMAP.md with confirmation
|
||||
|
||||
**Run this before `/gsd-manager`** when phases have empty `Depends on` fields and you want to avoid merge conflicts from unordered parallel execution.
|
||||
**Produces:** Updated ROADMAP.md
|
||||
|
||||
```bash
|
||||
/gsd-analyze-dependencies # Analyze all phases and suggest dependencies
|
||||
/gsd-phase "Add authentication system" # Append new phase with description
|
||||
/gsd-phase --insert 3 "Fix auth race condition" # Insert between phase 3 and 4 → creates 3.1
|
||||
/gsd-phase --remove 7 # Remove phase 7, renumber 8→7, 9→8, etc.
|
||||
/gsd-phase --edit 5 # Edit any field of phase 5
|
||||
/gsd-phase --edit 5 --force # Edit phase 5 even if in-progress or completed
|
||||
```
|
||||
|
||||
**Detection methods:**
|
||||
- File overlap — phases touching the same files/domains must be ordered
|
||||
- Semantic dependencies — a phase that consumes an API or schema built by another phase
|
||||
- Data flow — a phase that reads output produced by another phase
|
||||
|
||||
---
|
||||
|
||||
### `/gsd-plan-milestone-gaps`
|
||||
|
||||
Create phases to close gaps from milestone audit.
|
||||
|
||||
```bash
|
||||
/gsd-plan-milestone-gaps # Creates phases for each audit gap
|
||||
```
|
||||
|
||||
### `/gsd-research-phase`
|
||||
|
||||
Deep ecosystem research only (standalone — usually use `/gsd-plan-phase` instead).
|
||||
|
||||
| Argument | Required | Description |
|
||||
|----------|----------|-------------|
|
||||
| `N` | No | Phase number |
|
||||
|
||||
```bash
|
||||
/gsd-research-phase 4 # Research phase 4 domain
|
||||
```
|
||||
|
||||
### `/gsd-validate-phase`
|
||||
|
||||
Retroactively audit and fill Nyquist validation gaps.
|
||||
@@ -541,14 +398,26 @@ Retroactively audit and fill Nyquist validation gaps.
|
||||
|
||||
### `/gsd-progress`
|
||||
|
||||
Check project progress, show context, and route to the next action (execute or plan).
|
||||
Show status, next steps, and automatically advance to the next logical workflow step. Reads project state and determines the appropriate action.
|
||||
|
||||
| Flag | Description |
|
||||
|------|-------------|
|
||||
| `--next` | Automatically advance to the next logical workflow step without manual route selection |
|
||||
| `--do "task description"` | Analyze freeform intent and dispatch to the most appropriate GSD command |
|
||||
| `--forensic` | Append a 6-check integrity audit after the standard report (STATE consistency, orphaned handoffs, deferred scope drift, memory-flagged pending work, blocking todos, uncommitted code) |
|
||||
|
||||
**Auto-routing behavior (absorbed from `/gsd-next`):**
|
||||
- No project → suggests `/gsd-new-project`
|
||||
- Phase needs discussion → runs `/gsd-discuss-phase`
|
||||
- Phase needs planning → runs `/gsd-plan-phase`
|
||||
- Phase needs execution → runs `/gsd-execute-phase`
|
||||
- Phase needs verification → runs `/gsd-verify-work`
|
||||
- All phases complete → suggests `/gsd-complete-milestone`
|
||||
|
||||
```bash
|
||||
/gsd-progress # "Where am I? What's next?"
|
||||
/gsd-progress # "Where am I? What's next?" with auto-routing
|
||||
/gsd-progress --next # Advance to next step automatically
|
||||
/gsd-progress --do "fix the auth bug" # Dispatch freeform intent to best GSD command
|
||||
/gsd-progress --forensic # Standard report + integrity audit
|
||||
```
|
||||
|
||||
@@ -704,31 +573,6 @@ Bootstrap or merge a .planning/ setup from existing ADRs, PRDs, SPECs, and docs
|
||||
|
||||
---
|
||||
|
||||
### `/gsd-from-gsd2`
|
||||
|
||||
Reverse migration from GSD-2 format (`.gsd/` with Milestone→Slice→Task hierarchy) back to v1 `.planning/` format.
|
||||
|
||||
| Flag | Required | Description |
|
||||
|------|----------|-------------|
|
||||
| `--dry-run` | No | Preview what would be migrated without writing anything |
|
||||
| `--force` | No | Overwrite existing `.planning/` directory |
|
||||
| `--path <dir>` | No | Specify GSD-2 root directory (defaults to current directory) |
|
||||
|
||||
**Flattening:** Milestone→Slice hierarchy is flattened to sequential phase numbers (M001/S01→phase 01, M001/S02→phase 02, M002/S01→phase 03, etc.).
|
||||
|
||||
**Produces:** `PROJECT.md`, `REQUIREMENTS.md`, `ROADMAP.md`, `STATE.md`, and sequential phase directories in `.planning/`.
|
||||
|
||||
**Safety:** Guards against overwriting an existing `.planning/` directory without `--force`.
|
||||
|
||||
```bash
|
||||
/gsd-from-gsd2 # Migrate .gsd/ in current directory
|
||||
/gsd-from-gsd2 --dry-run # Preview migration without writing
|
||||
/gsd-from-gsd2 --force # Overwrite existing .planning/
|
||||
/gsd-from-gsd2 --path /path/to/gsd2-project # Specify GSD-2 root
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `/gsd-quick`
|
||||
|
||||
Execute ad-hoc task with GSD guarantees.
|
||||
@@ -775,34 +619,6 @@ Run all remaining phases autonomously.
|
||||
/gsd-autonomous --from 3 --to 5 # Run phases 3 through 5
|
||||
```
|
||||
|
||||
### `/gsd-do`
|
||||
|
||||
Route freeform text to the right GSD command.
|
||||
|
||||
```bash
|
||||
/gsd-do # Then describe what you want
|
||||
```
|
||||
|
||||
### `/gsd-note`
|
||||
|
||||
Zero-friction idea capture — append, list, or promote notes to todos.
|
||||
|
||||
| Argument | Required | Description |
|
||||
|----------|----------|-------------|
|
||||
| `text` | No | Note text to capture (default: append mode) |
|
||||
| `list` | No | List all notes from project and global scopes |
|
||||
| `promote N` | No | Convert note N into a structured todo |
|
||||
|
||||
| Flag | Description |
|
||||
|------|-------------|
|
||||
| `--global` | Use global scope for note operations |
|
||||
|
||||
```bash
|
||||
/gsd-note "Consider caching strategy for API responses"
|
||||
/gsd-note list
|
||||
/gsd-note promote 3
|
||||
```
|
||||
|
||||
### `/gsd-debug`
|
||||
|
||||
Systematic debugging with persistent state.
|
||||
@@ -831,26 +647,6 @@ Systematic debugging with persistent state.
|
||||
/gsd-debug continue form-submit-500
|
||||
```
|
||||
|
||||
### `/gsd-add-todo`
|
||||
|
||||
Capture idea or task for later.
|
||||
|
||||
| Argument | Required | Description |
|
||||
|----------|----------|-------------|
|
||||
| `description` | No | Todo description |
|
||||
|
||||
```bash
|
||||
/gsd-add-todo "Consider adding dark mode support"
|
||||
```
|
||||
|
||||
### `/gsd-check-todos`
|
||||
|
||||
List pending todos and select one to work on.
|
||||
|
||||
```bash
|
||||
/gsd-check-todos
|
||||
```
|
||||
|
||||
### `/gsd-add-tests`
|
||||
|
||||
Generate tests for a completed phase.
|
||||
@@ -924,26 +720,16 @@ Run 2–5 focused feasibility experiments before committing to an implementation
|
||||
|----------|----------|-------------|
|
||||
| `idea` | No | The technical question or approach to investigate |
|
||||
| `--quick` | No | Skip intake conversation; use `idea` text directly |
|
||||
| `--wrap-up` | No | Package completed spike findings into a reusable project-local skill |
|
||||
|
||||
**Produces:** `.planning/spikes/NNN-experiment-name/` with code, results, and README; `.planning/spikes/MANIFEST.md`
|
||||
**`--wrap-up` produces:** `.claude/skills/spike-findings-[project]/` skill file
|
||||
|
||||
```bash
|
||||
/gsd-spike # Interactive intake
|
||||
/gsd-spike "can we stream LLM tokens through SSE"
|
||||
/gsd-spike --quick websocket-vs-polling
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `/gsd-spike-wrap-up`
|
||||
|
||||
Package completed spike findings into a reusable project-local skill so future sessions can reference the conclusions.
|
||||
|
||||
**Prerequisites:** `.planning/spikes/` exists with at least one completed spike
|
||||
**Produces:** `.claude/skills/spike-findings-[project]/` skill file
|
||||
|
||||
```bash
|
||||
/gsd-spike-wrap-up
|
||||
/gsd-spike --wrap-up # Package findings into a reusable skill
|
||||
```
|
||||
|
||||
---
|
||||
@@ -957,27 +743,17 @@ Explore design directions through throwaway HTML mockups before committing to im
|
||||
| `idea` | No | The UI design question or direction to explore |
|
||||
| `--quick` | No | Skip mood intake; use `idea` text directly |
|
||||
| `--text` | No | Text-mode fallback — replace interactive prompts with numbered lists (for non-Claude runtimes) |
|
||||
| `--wrap-up` | No | Package winning sketch decisions into a reusable project-local skill |
|
||||
|
||||
**Produces:** `.planning/sketches/NNN-descriptive-name/index.html` (2–3 interactive variants), `README.md`, shared `themes/default.css`; `.planning/sketches/MANIFEST.md`
|
||||
**`--wrap-up` produces:** `.claude/skills/sketch-findings-[project]/` skill file
|
||||
|
||||
```bash
|
||||
/gsd-sketch # Interactive mood intake
|
||||
/gsd-sketch "dashboard layout"
|
||||
/gsd-sketch --quick "sidebar navigation"
|
||||
/gsd-sketch --text "onboarding flow" # Non-Claude runtime
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `/gsd-sketch-wrap-up`
|
||||
|
||||
Package winning sketch decisions into a reusable project-local skill so future sessions inherit the visual direction.
|
||||
|
||||
**Prerequisites:** `.planning/sketches/` exists with at least one completed sketch (winner marked)
|
||||
**Produces:** `.claude/skills/sketch-findings-[project]/` skill file
|
||||
|
||||
```bash
|
||||
/gsd-sketch-wrap-up
|
||||
/gsd-sketch --wrap-up # Package winning sketch into a skill
|
||||
```
|
||||
|
||||
---
|
||||
@@ -1092,11 +868,18 @@ All answers are merged via `gsd-sdk query config-set` into the resolved project
|
||||
/gsd-settings # Interactive config
|
||||
```
|
||||
|
||||
### `/gsd-settings-advanced`
|
||||
### `/gsd-config`
|
||||
|
||||
Power-user configuration for plan bounce, timeouts, branch templates, and cross-AI execution. Use after `/gsd-settings` once the common-case toggles are dialed in.
|
||||
Configure GSD settings interactively — workflow toggles, advanced knobs, integrations, and model profile — with a single consolidated command.
|
||||
|
||||
Six sections, each a focused prompt batch:
|
||||
| Flag | Description |
|
||||
|------|-------------|
|
||||
| (none) | Common-case toggles: model, research, plan_check, verifier, branching |
|
||||
| `--advanced` | Power-user knobs: planning tuning, timeouts, branch templates, cross-AI execution, runtime/output |
|
||||
| `--integrations` | Third-party API keys, code-review CLI routing, agent-skill injection |
|
||||
| `--profile <name>` | Quick profile switch: `quality`, `balanced`, `budget`, or `inherit` |
|
||||
|
||||
**`--advanced` sections:**
|
||||
|
||||
| Section | Keys |
|
||||
|---------|------|
|
||||
@@ -1107,111 +890,44 @@ Six sections, each a focused prompt batch:
|
||||
| Git Customization | `git.base_branch`, `git.phase_branch_template`, `git.milestone_branch_template` |
|
||||
| Runtime / Output | `response_language`, `context_window`, `search_gitignored`, `graphify.build_timeout` |
|
||||
|
||||
Current values are pre-selected; an empty input keeps the existing value. Numeric fields reject non-numeric input and re-prompt. Null-allowed fields (`plan_bounce_script`, `cross_ai_command`, `response_language`) accept an empty input as a clear. Writes route through `gsd-sdk query config-set`, which preserves every unrelated key.
|
||||
All answers merge via `gsd-sdk query config-set`, preserving unrelated keys. API keys are masked (`****<last-4>`) in all output.
|
||||
|
||||
```bash
|
||||
/gsd-settings-advanced # Six-section interactive config
|
||||
/gsd-config # Common-case interactive config
|
||||
/gsd-config --advanced # Power-user knobs (six-section prompt)
|
||||
/gsd-config --integrations # API keys, review CLI routing, agent skills
|
||||
/gsd-config --profile budget # Switch to budget profile
|
||||
/gsd-config --profile quality # Switch to quality profile
|
||||
```
|
||||
|
||||
See [CONFIGURATION.md](CONFIGURATION.md) for the full schema and defaults.
|
||||
|
||||
### `/gsd-settings-integrations`
|
||||
|
||||
Interactive configuration of third-party integrations and cross-tool routing.
|
||||
Distinct from `/gsd-settings` (workflow toggles) — this command handles
|
||||
connectivity: API keys, reviewer CLI routing, and agent-skill injection.
|
||||
|
||||
Covers:
|
||||
|
||||
- **Search integrations:** `brave_search`, `firecrawl`, `exa_search` API keys,
|
||||
and the `search_gitignored` toggle.
|
||||
- **Code-review CLI routing:** `review.models.{claude,codex,gemini,opencode}`
|
||||
— a shell command per reviewer flavor.
|
||||
- **Agent-skill injection:** `agent_skills.<agent-type>` — skill names
|
||||
injected into an agent's spawn frontmatter. Agent-type slugs are validated
|
||||
against `[a-zA-Z0-9_-]+` so path separators and shell metacharacters are
|
||||
rejected.
|
||||
|
||||
API keys are stored plaintext in `.planning/config.json` but displayed masked
|
||||
(`****<last-4>`) in every interactive output, confirmation table, and
|
||||
`config-set` stdout/stderr line. Plaintext is never echoed, never logged,
|
||||
and never written to any file outside `config.json` by this workflow.
|
||||
|
||||
```bash
|
||||
/gsd-settings-integrations # Interactive config (three sections)
|
||||
```
|
||||
|
||||
See [`docs/CONFIGURATION.md`](CONFIGURATION.md) for the per-field reference and
|
||||
[`docs/CLI-TOOLS.md`](CLI-TOOLS.md) for the reviewer-CLI routing contract.
|
||||
|
||||
### `/gsd-set-profile`
|
||||
|
||||
Quick profile switch.
|
||||
|
||||
| Argument | Required | Description |
|
||||
|----------|----------|-------------|
|
||||
| `profile` | **Yes** | `quality`, `balanced`, `budget`, or `inherit` |
|
||||
|
||||
```bash
|
||||
/gsd-set-profile budget # Switch to budget profile
|
||||
/gsd-set-profile quality # Switch to quality profile
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Brownfield Commands
|
||||
|
||||
### `/gsd-map-codebase`
|
||||
|
||||
Analyze existing codebase with parallel mapper agents.
|
||||
Analyze existing codebase with parallel mapper agents. Use `--fast` for a quick single-agent scan, or `--query` to search existing intel.
|
||||
|
||||
| Argument | Required | Description |
|
||||
|----------|----------|-------------|
|
||||
| `area` | No | Scope mapping to a specific area |
|
||||
|
||||
```bash
|
||||
/gsd-map-codebase # Full codebase analysis
|
||||
/gsd-map-codebase auth # Focus on auth area
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `/gsd-scan`
|
||||
|
||||
Rapid single-focus codebase assessment — lightweight alternative to `/gsd-map-codebase` that spawns one mapper agent instead of four parallel ones.
|
||||
| `--fast` | No | Rapid single-focus assessment — spawns one mapper agent instead of four parallel ones (lightweight alternative) |
|
||||
| `--query <term>` | No | Search queryable codebase intel files in `.planning/intel/` (requires `intel.enabled: true`) |
|
||||
|
||||
| Flag | Description |
|
||||
|------|-------------|
|
||||
| `--focus tech\|arch\|quality\|concerns\|tech+arch` | Focus area (default: `tech+arch`) |
|
||||
| `--focus tech\|arch\|quality\|concerns\|tech+arch` | Focus area for `--fast` mode (default: `tech+arch`) |
|
||||
|
||||
**Produces:** Targeted document(s) in `.planning/codebase/`
|
||||
**Produces:** `.planning/codebase/` analysis documents (full mode); targeted document(s) in `.planning/codebase/` (`--fast`); intel query results (`--query`)
|
||||
|
||||
```bash
|
||||
/gsd-scan # Quick tech + arch overview
|
||||
/gsd-scan --focus quality # Quality and code health only
|
||||
/gsd-scan --focus concerns # Surface concerns and risk areas
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `/gsd-intel`
|
||||
|
||||
Query, inspect, or refresh queryable codebase intelligence files stored in `.planning/intel/`. Requires `intel.enabled: true` in `config.json`.
|
||||
|
||||
| Argument | Description |
|
||||
|----------|-------------|
|
||||
| `query <term>` | Search intel files for a term |
|
||||
| `status` | Show intel file freshness (FRESH/STALE) |
|
||||
| `diff` | Show changes since last snapshot |
|
||||
| `refresh` | Rebuild all intel files from codebase analysis |
|
||||
|
||||
**Produces:** `.planning/intel/` JSON files (stack, api-map, dependency-graph, file-roles, arch-decisions)
|
||||
|
||||
```bash
|
||||
/gsd-intel status # Check freshness of intel files
|
||||
/gsd-intel query authentication # Search intel for a term
|
||||
/gsd-intel diff # What changed since last snapshot
|
||||
/gsd-intel refresh # Rebuild intel index
|
||||
/gsd-map-codebase # Full codebase analysis (4 parallel agents)
|
||||
/gsd-map-codebase auth # Focus on auth area
|
||||
/gsd-map-codebase --fast # Quick tech + arch overview (1 agent)
|
||||
/gsd-map-codebase --fast --focus quality # Quality and code health only
|
||||
/gsd-map-codebase --query authentication # Search intel for a term
|
||||
```
|
||||
|
||||
### `/gsd-graphify`
|
||||
@@ -1273,18 +989,17 @@ Audit an executed AI phase's evaluation coverage and produce an EVAL-REVIEW.md r
|
||||
|
||||
### `/gsd-update`
|
||||
|
||||
Update GSD with changelog preview.
|
||||
Update GSD with changelog preview, and optionally sync skills or reapply local patches.
|
||||
|
||||
| Flag | Description |
|
||||
|------|-------------|
|
||||
| `--sync` | Sync skills from the GSD registry after updating |
|
||||
| `--reapply` | Restore local modifications (patches) after updating |
|
||||
|
||||
```bash
|
||||
/gsd-update # Check for updates and install
|
||||
```
|
||||
|
||||
### `/gsd-reapply-patches`
|
||||
|
||||
Restore local modifications after a GSD update.
|
||||
|
||||
```bash
|
||||
/gsd-reapply-patches # Merge back local changes
|
||||
/gsd-update --sync # Update and sync skills
|
||||
/gsd-update --reapply # Update and reapply local patches
|
||||
```
|
||||
|
||||
---
|
||||
@@ -1293,44 +1008,28 @@ Restore local modifications after a GSD update.
|
||||
|
||||
### `/gsd-code-review`
|
||||
|
||||
Review source files changed during a phase for bugs, security vulnerabilities, and code quality problems.
|
||||
Review source files changed during a phase for bugs, security vulnerabilities, and code quality problems. Use `--fix` to auto-fix findings after review.
|
||||
|
||||
| Argument | Required | Description |
|
||||
|----------|----------|-------------|
|
||||
| `N` | **Yes** | Phase number whose changes to review (e.g., `2` or `02`) |
|
||||
| `--depth=quick\|standard\|deep` | No | Review depth level (overrides `workflow.code_review_depth` config). `quick`: pattern-matching only (~2 min). `standard`: per-file analysis with language-specific checks (~5–15 min, default). `deep`: cross-file analysis including import graphs and call chains (~15–30 min) |
|
||||
| `--files file1,file2,...` | No | Explicit comma-separated file list; skips SUMMARY/git scoping entirely |
|
||||
| `--fix` | No | Auto-fix issues after review — reads REVIEW.md, spawns fixer agent, commits each fix atomically |
|
||||
| `--fix --all` | No | Include Info findings in fix scope (default: Critical + Warning only) |
|
||||
| `--fix --auto` | No | Fix + re-review iteration loop, capped at 3 iterations |
|
||||
|
||||
**Prerequisites:** Phase has been executed and has SUMMARY.md or git history
|
||||
**Produces:** `{phase}-REVIEW.md` in phase directory with severity-classified findings
|
||||
**Spawns:** `gsd-code-reviewer` agent
|
||||
**Produces:** `{phase}-REVIEW.md` with severity-classified findings; `{phase}-REVIEW-FIX.md` when `--fix` is used
|
||||
**Spawns:** `gsd-code-reviewer` agent; `gsd-code-fixer` agent (with `--fix`)
|
||||
|
||||
```bash
|
||||
/gsd-code-review 3 # Standard review for phase 3
|
||||
/gsd-code-review 2 --depth=deep # Deep cross-file review
|
||||
/gsd-code-review 4 --files src/auth.ts,src/token.ts # Explicit file list
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `/gsd-code-review-fix`
|
||||
|
||||
Auto-fix issues found by code review in REVIEW.md; commits each fix atomically. Reads `REVIEW.md`, spawns a fixer agent, and produces a `REVIEW-FIX.md` summary.
|
||||
|
||||
| Argument | Required | Description |
|
||||
|----------|----------|-------------|
|
||||
| `N` | **Yes** | Phase number whose REVIEW.md to fix |
|
||||
| `--all` | No | Include Info findings in fix scope (default: Critical + Warning only) |
|
||||
| `--auto` | No | Enable fix + re-review iteration loop, capped at 3 iterations |
|
||||
|
||||
**Prerequisites:** Phase has a `{phase}-REVIEW.md` file (run `/gsd-code-review` first)
|
||||
**Produces:** `{phase}-REVIEW-FIX.md` with applied fixes summary
|
||||
**Spawns:** `gsd-code-fixer` agent
|
||||
|
||||
```bash
|
||||
/gsd-code-review-fix 3 # Fix Critical + Warning findings for phase 3
|
||||
/gsd-code-review-fix 3 --all # Include Info findings
|
||||
/gsd-code-review-fix 3 --auto # Fix and re-review until clean (max 3 iterations)
|
||||
/gsd-code-review 3 --fix # Review then fix Critical + Warning findings
|
||||
/gsd-code-review 3 --fix --all # Review then fix all findings including Info
|
||||
/gsd-code-review 3 --fix --auto # Review, fix, and re-review until clean (max 3 iterations)
|
||||
```
|
||||
|
||||
---
|
||||
@@ -1422,19 +1121,6 @@ Create a clean PR branch by filtering out `.planning/` commits.
|
||||
|
||||
---
|
||||
|
||||
### `/gsd-audit-uat`
|
||||
|
||||
Cross-phase audit of all outstanding UAT and verification items.
|
||||
|
||||
**Prerequisites:** At least one phase has been executed with UAT or verification
|
||||
**Produces:** Categorized audit report with human test plan
|
||||
|
||||
```bash
|
||||
/gsd-audit-uat
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `/gsd-secure-phase`
|
||||
|
||||
Retroactively verify threat mitigations for a completed phase.
|
||||
@@ -1481,21 +1167,34 @@ Each doc writer explores the codebase directly — no hallucinated paths or stal
|
||||
|
||||
---
|
||||
|
||||
## Backlog & Thread Commands
|
||||
## Task Capture & Backlog Commands
|
||||
|
||||
### `/gsd-add-backlog`
|
||||
### `/gsd-capture`
|
||||
|
||||
Add an idea to the backlog parking lot using 999.x numbering.
|
||||
Capture ideas, tasks, notes, and seeds to their appropriate destination. Default mode adds a structured todo; flags route to specialized capture workflows.
|
||||
|
||||
| Argument | Required | Description |
|
||||
|----------|----------|-------------|
|
||||
| `description` | **Yes** | Backlog item description |
|
||||
| Flag | Description |
|
||||
|------|-------------|
|
||||
| (none) | Capture as a structured todo for later work |
|
||||
| `--note [text]` | Zero-friction note — append, list (`--note list`), or promote (`--note promote N`) |
|
||||
| `--backlog <description>` | Add to the backlog parking lot using 999.x numbering |
|
||||
| `--seed [idea summary]` | Capture a forward-looking idea with trigger conditions |
|
||||
| `--list` | List pending todos and select one to work on |
|
||||
| `--global` | Use global scope (for note operations) |
|
||||
|
||||
**999.x numbering** keeps backlog items outside the active phase sequence. Phase directories are created immediately so `/gsd-discuss-phase` and `/gsd-plan-phase` work on them.
|
||||
**Backlog:** 999.x numbering keeps items outside the active phase sequence; phase directories are created immediately so `/gsd-discuss-phase` and `/gsd-plan-phase` work on them.
|
||||
**Seeds:** Preserve full WHY, WHEN to surface, and breadcrumbs — consumed by `/gsd-new-milestone`.
|
||||
|
||||
**Produces:** `.planning/todos/` (default), note files (--note), ROADMAP.md backlog section (--backlog), `.planning/seeds/SEED-NNN-slug.md` (--seed)
|
||||
|
||||
```bash
|
||||
/gsd-add-backlog "GraphQL API layer"
|
||||
/gsd-add-backlog "Mobile responsive redesign"
|
||||
/gsd-capture "Consider adding dark mode support" # Add todo
|
||||
/gsd-capture --note "Caching strategy idea" # Quick note
|
||||
/gsd-capture --note list # List all notes
|
||||
/gsd-capture --note promote 3 # Promote note 3 to todo
|
||||
/gsd-capture --backlog "GraphQL API layer" # Add to backlog
|
||||
/gsd-capture --seed "Add real-time collaboration when WebSocket infra is in place"
|
||||
/gsd-capture --list # Browse and act on todos
|
||||
```
|
||||
|
||||
---
|
||||
@@ -1512,25 +1211,6 @@ Review and promote backlog items to active milestone.
|
||||
|
||||
---
|
||||
|
||||
### `/gsd-plant-seed`
|
||||
|
||||
Capture a forward-looking idea that surfaces automatically at the right milestone.
|
||||
|
||||
| Argument | Required | Description |
|
||||
|----------|----------|-------------|
|
||||
| `idea summary` | No | Seed description (prompted if omitted) |
|
||||
|
||||
Seeds solve context rot: instead of a one-liner in Deferred that nobody reads, a seed preserves the full WHY, WHEN to surface, and breadcrumbs to details.
|
||||
|
||||
**Produces:** `.planning/seeds/SEED-NNN-slug.md`
|
||||
**Consumed by:** `/gsd-new-milestone` (scans seeds and presents matches)
|
||||
|
||||
```bash
|
||||
/gsd-plant-seed "Add real-time collaboration when WebSocket infra is in place"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `/gsd-thread`
|
||||
|
||||
Manage persistent context threads for cross-session work.
|
||||
@@ -1629,13 +1309,9 @@ Enable with:
|
||||
|
||||
---
|
||||
|
||||
### `/gsd-join-discord`
|
||||
### Community Invite
|
||||
|
||||
Open Discord community invite.
|
||||
|
||||
```bash
|
||||
/gsd-join-discord
|
||||
```
|
||||
To join the GSD Discord community, visit the link in the GSD README or run `/gsd-help` and follow the Discord link shown there.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -191,6 +191,7 @@ All workflow toggles follow the **absent = enabled** pattern. If a key is missin
|
||||
| `workflow.skip_discuss` | boolean | `false` | When `true`, `/gsd-autonomous` bypasses the discuss-phase entirely, writing minimal CONTEXT.md from the ROADMAP phase goal. Useful for projects where developer preferences are fully captured in PROJECT.md/REQUIREMENTS.md. Added in v1.28 |
|
||||
| `workflow.text_mode` | boolean | `false` | Replaces AskUserQuestion TUI menus with plain-text numbered lists. Required for Claude Code remote sessions (`/rc` mode) where TUI menus don't render. Can also be set per-session with `--text` flag on discuss-phase. Added in v1.28 |
|
||||
| `workflow.use_worktrees` | boolean | `true` | When `false`, disables git worktree isolation for parallel execution. Users who prefer sequential execution or whose environment does not support worktrees can disable this. Added in v1.31 |
|
||||
| `workflow.worktree_skip_hooks` | boolean | `false` | When `true`, executor agents in worktree mode pass `--no-verify` (skipping pre-commit hooks) and post-wave hook validation runs against the merged result instead. Opt-in escape hatch for projects whose hooks cannot run in agent worktrees. Default `false` runs hooks on every commit (#2924). |
|
||||
| `workflow.code_review` | boolean | `true` | Enable `/gsd-code-review` and `/gsd-code-review-fix` commands. When `false`, the commands exit with a configuration gate message. Added in v1.34 |
|
||||
| `workflow.code_review_depth` | string | `standard` | Default review depth for `/gsd-code-review`: `quick` (pattern-matching only), `standard` (per-file analysis), or `deep` (cross-file with import graphs). Can be overridden per-run with `--depth=`. Added in v1.34 |
|
||||
| `workflow.plan_bounce` | boolean | `false` | Run external validation script against generated plans. When enabled, the plan-phase orchestrator pipes each PLAN.md through the script specified by `plan_bounce_script` and blocks on non-zero exit. Added in v1.36 |
|
||||
|
||||
@@ -813,7 +813,7 @@ against the mapping point, not HEAD.
|
||||
### 27a. Post-Execute Codebase Drift Detection
|
||||
|
||||
**Introduced by:** #2003
|
||||
**Trigger:** Runs automatically at the end of every `/gsd:execute-phase`
|
||||
**Trigger:** Runs automatically at the end of every `/gsd-execute-phase`
|
||||
**Configuration:**
|
||||
- `workflow.drift_threshold` (integer, default `3`) — minimum new
|
||||
structural elements before the gate acts.
|
||||
@@ -837,7 +837,7 @@ continues. Drift detection cannot fail verification.
|
||||
- REQ-DRIFT-02: Action fires only when element count ≥ `workflow.drift_threshold`
|
||||
- REQ-DRIFT-03: `warn` action MUST NOT spawn any agent
|
||||
- REQ-DRIFT-04: `auto-remap` action MUST pass sanitized `--paths` to the mapper
|
||||
- REQ-DRIFT-05: Detection/remap failure MUST be non-blocking for `/gsd:execute-phase`
|
||||
- REQ-DRIFT-05: Detection/remap failure MUST be non-blocking for `/gsd-execute-phase`
|
||||
- REQ-DRIFT-06: `last_mapped_commit` round-trip through YAML frontmatter
|
||||
on each `.planning/codebase/*.md` file
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"generated": "2026-04-27",
|
||||
"generated": "2026-04-30",
|
||||
"families": {
|
||||
"agents": [
|
||||
"gsd-advisor-researcher",
|
||||
@@ -37,83 +37,61 @@
|
||||
"gsd-verifier"
|
||||
],
|
||||
"commands": [
|
||||
"/gsd-add-backlog",
|
||||
"/gsd-add-phase",
|
||||
"/gsd-add-tests",
|
||||
"/gsd-add-todo",
|
||||
"/gsd-ai-integration-phase",
|
||||
"/gsd-analyze-dependencies",
|
||||
"/gsd-audit-fix",
|
||||
"/gsd-audit-milestone",
|
||||
"/gsd-audit-uat",
|
||||
"/gsd-autonomous",
|
||||
"/gsd-check-todos",
|
||||
"/gsd-capture",
|
||||
"/gsd-cleanup",
|
||||
"/gsd-code-review",
|
||||
"/gsd-code-review-fix",
|
||||
"/gsd-complete-milestone",
|
||||
"/gsd-config",
|
||||
"/gsd-debug",
|
||||
"/gsd-discuss-phase",
|
||||
"/gsd-do",
|
||||
"/gsd-docs-update",
|
||||
"/gsd-edit-phase",
|
||||
"/gsd-eval-review",
|
||||
"/gsd-execute-phase",
|
||||
"/gsd-explore",
|
||||
"/gsd-extract_learnings",
|
||||
"/gsd-extract-learnings",
|
||||
"/gsd-fast",
|
||||
"/gsd-forensics",
|
||||
"/gsd-from-gsd2",
|
||||
"/gsd-graphify",
|
||||
"/gsd-health",
|
||||
"/gsd-help",
|
||||
"/gsd-import",
|
||||
"/gsd-inbox",
|
||||
"/gsd-ingest-docs",
|
||||
"/gsd-insert-phase",
|
||||
"/gsd-intel",
|
||||
"/gsd-join-discord",
|
||||
"/gsd-list-phase-assumptions",
|
||||
"/gsd-list-workspaces",
|
||||
"/gsd-manager",
|
||||
"/gsd-map-codebase",
|
||||
"/gsd-milestone-summary",
|
||||
"/gsd-new-milestone",
|
||||
"/gsd-new-project",
|
||||
"/gsd-new-workspace",
|
||||
"/gsd-next",
|
||||
"/gsd-note",
|
||||
"/gsd-ns-context",
|
||||
"/gsd-ns-ideate",
|
||||
"/gsd-ns-manage",
|
||||
"/gsd-ns-project",
|
||||
"/gsd-ns-review",
|
||||
"/gsd-ns-workflow",
|
||||
"/gsd-pause-work",
|
||||
"/gsd-plan-milestone-gaps",
|
||||
"/gsd-phase",
|
||||
"/gsd-plan-phase",
|
||||
"/gsd-plan-review-convergence",
|
||||
"/gsd-plant-seed",
|
||||
"/gsd-pr-branch",
|
||||
"/gsd-profile-user",
|
||||
"/gsd-progress",
|
||||
"/gsd-quick",
|
||||
"/gsd-reapply-patches",
|
||||
"/gsd-remove-phase",
|
||||
"/gsd-remove-workspace",
|
||||
"/gsd-research-phase",
|
||||
"/gsd-resume-work",
|
||||
"/gsd-review",
|
||||
"/gsd-review-backlog",
|
||||
"/gsd-scan",
|
||||
"/gsd-secure-phase",
|
||||
"/gsd-session-report",
|
||||
"/gsd-set-profile",
|
||||
"/gsd-settings",
|
||||
"/gsd-settings-advanced",
|
||||
"/gsd-settings-integrations",
|
||||
"/gsd-ship",
|
||||
"/gsd-sketch",
|
||||
"/gsd-sketch-wrap-up",
|
||||
"/gsd-spec-phase",
|
||||
"/gsd-spike",
|
||||
"/gsd-spike-wrap-up",
|
||||
"/gsd-stats",
|
||||
"/gsd-sync-skills",
|
||||
"/gsd-thread",
|
||||
"/gsd-ui-phase",
|
||||
"/gsd-ui-review",
|
||||
@@ -122,6 +100,7 @@
|
||||
"/gsd-update",
|
||||
"/gsd-validate-phase",
|
||||
"/gsd-verify-work",
|
||||
"/gsd-workspace",
|
||||
"/gsd-workstreams"
|
||||
],
|
||||
"workflows": [
|
||||
@@ -181,6 +160,7 @@
|
||||
"profile-user.md",
|
||||
"progress.md",
|
||||
"quick.md",
|
||||
"reapply-patches.md",
|
||||
"remove-phase.md",
|
||||
"remove-workspace.md",
|
||||
"research-phase.md",
|
||||
@@ -266,9 +246,11 @@
|
||||
"cli_modules": [
|
||||
"artifacts.cjs",
|
||||
"audit.cjs",
|
||||
"command-aliases.generated.cjs",
|
||||
"commands.cjs",
|
||||
"config-schema.cjs",
|
||||
"config.cjs",
|
||||
"context-utilization.cjs",
|
||||
"core.cjs",
|
||||
"decisions.cjs",
|
||||
"docs.cjs",
|
||||
@@ -277,22 +259,30 @@
|
||||
"gap-checker.cjs",
|
||||
"graphify.cjs",
|
||||
"gsd2-import.cjs",
|
||||
"init-command-router.cjs",
|
||||
"init.cjs",
|
||||
"install-profiles.cjs",
|
||||
"intel.cjs",
|
||||
"learnings.cjs",
|
||||
"milestone.cjs",
|
||||
"model-profiles.cjs",
|
||||
"phase-command-router.cjs",
|
||||
"phase.cjs",
|
||||
"phases-command-router.cjs",
|
||||
"planning-workspace.cjs",
|
||||
"profile-output.cjs",
|
||||
"profile-pipeline.cjs",
|
||||
"roadmap-command-router.cjs",
|
||||
"roadmap.cjs",
|
||||
"schema-detect.cjs",
|
||||
"secrets.cjs",
|
||||
"security.cjs",
|
||||
"state-command-router.cjs",
|
||||
"state.cjs",
|
||||
"template.cjs",
|
||||
"uat.cjs",
|
||||
"validate-command-router.cjs",
|
||||
"verify-command-router.cjs",
|
||||
"verify.cjs",
|
||||
"workstream.cjs"
|
||||
],
|
||||
|
||||
@@ -54,18 +54,29 @@ Full roster at `agents/gsd-*.md`. The "Primary doc" column flags whether [`docs/
|
||||
|
||||
---
|
||||
|
||||
## Commands (86 shipped)
|
||||
## Commands (65 shipped)
|
||||
|
||||
Full roster at `commands/gsd/*.md`. The groupings below mirror `docs/COMMANDS.md` section order; each row carries the command name, a one-line role derived from the command's frontmatter `description:`, and a link to the source file. `tests/command-count-sync.test.cjs` locks the count against the filesystem.
|
||||
|
||||
### Namespace Meta-Skills
|
||||
|
||||
These six routers are descriptor-only entries that the model picks first; the body of each contains a routing table that points at the correct concrete sub-skill. They exist to keep the eager skill-listing token cost low while the full surface remains reachable. See [#2792](https://github.com/gsd-build/get-shit-done/issues/2792) for the rationale; the routing tables target the post-[#2790](https://github.com/gsd-build/get-shit-done/issues/2790) consolidated surface.
|
||||
|
||||
| Command | Role | Source |
|
||||
|---------|------|--------|
|
||||
| `/gsd-ns-workflow` | Phase pipeline router — discuss / plan / execute / verify / phase / progress. | [commands/gsd/ns-workflow.md](../commands/gsd/ns-workflow.md) |
|
||||
| `/gsd-ns-project` | Project lifecycle router — milestones, audits, summary. | [commands/gsd/ns-project.md](../commands/gsd/ns-project.md) |
|
||||
| `/gsd-ns-review` | Quality-gate router — code review, debug, audit, security, eval, ui. | [commands/gsd/ns-review.md](../commands/gsd/ns-review.md) |
|
||||
| `/gsd-ns-context` | Codebase-intelligence router — map, graphify, docs, learnings. | [commands/gsd/ns-context.md](../commands/gsd/ns-context.md) |
|
||||
| `/gsd-ns-manage` | Management router — config, workspace, workstreams, thread, update, ship, inbox. | [commands/gsd/ns-manage.md](../commands/gsd/ns-manage.md) |
|
||||
| `/gsd-ns-ideate` | Exploration & capture router — explore, sketch, spike, spec, capture. | [commands/gsd/ns-ideate.md](../commands/gsd/ns-ideate.md) |
|
||||
|
||||
### Core Workflow
|
||||
|
||||
| Command | Role | Source |
|
||||
|---------|------|--------|
|
||||
| `/gsd-new-project` | Initialize a new project with deep context gathering and PROJECT.md. | [commands/gsd/new-project.md](../commands/gsd/new-project.md) |
|
||||
| `/gsd-new-workspace` | Create an isolated workspace with repo copies and independent `.planning/`. | [commands/gsd/new-workspace.md](../commands/gsd/new-workspace.md) |
|
||||
| `/gsd-list-workspaces` | List active GSD workspaces and their status. | [commands/gsd/list-workspaces.md](../commands/gsd/list-workspaces.md) |
|
||||
| `/gsd-remove-workspace` | Remove a GSD workspace and clean up worktrees. | [commands/gsd/remove-workspace.md](../commands/gsd/remove-workspace.md) |
|
||||
| `/gsd-workspace` | Manage GSD workspaces — create (`--new`), list (`--list`), or remove (`--remove`) isolated workspace environments. | [commands/gsd/workspace.md](../commands/gsd/workspace.md) |
|
||||
| `/gsd-discuss-phase` | Gather phase context through adaptive questioning before planning. | [commands/gsd/discuss-phase.md](../commands/gsd/discuss-phase.md) |
|
||||
| `/gsd-spec-phase` | Socratic spec refinement producing a SPEC.md with falsifiable requirements. | [commands/gsd/spec-phase.md](../commands/gsd/spec-phase.md) |
|
||||
| `/gsd-ui-phase` | Generate UI design contract (UI-SPEC.md) for frontend phases. | [commands/gsd/ui-phase.md](../commands/gsd/ui-phase.md) |
|
||||
@@ -73,37 +84,28 @@ Full roster at `commands/gsd/*.md`. The groupings below mirror `docs/COMMANDS.md
|
||||
| `/gsd-plan-phase` | Create detailed phase plan (PLAN.md) with verification loop. | [commands/gsd/plan-phase.md](../commands/gsd/plan-phase.md) |
|
||||
| `/gsd-plan-review-convergence` | Cross-AI plan convergence loop — replan with review feedback until no HIGH concerns remain (max 3 cycles). | [commands/gsd/plan-review-convergence.md](../commands/gsd/plan-review-convergence.md) |
|
||||
| `/gsd-ultraplan-phase` | [BETA] Offload plan phase to Claude Code's ultraplan cloud — drafts remotely, review in browser, import back via `/gsd-import`. Claude Code only. | [commands/gsd/ultraplan-phase.md](../commands/gsd/ultraplan-phase.md) |
|
||||
| `/gsd-spike` | Rapidly spike an idea with throwaway experiments to validate feasibility before planning. | [commands/gsd/spike.md](../commands/gsd/spike.md) |
|
||||
| `/gsd-sketch` | Rapidly sketch UI/design ideas using throwaway HTML mockups with multi-variant exploration. | [commands/gsd/sketch.md](../commands/gsd/sketch.md) |
|
||||
| `/gsd-research-phase` | Research how to implement a phase (standalone). | [commands/gsd/research-phase.md](../commands/gsd/research-phase.md) |
|
||||
| `/gsd-spike` | Rapidly spike an idea with throwaway experiments; use `--wrap-up` to package findings as a persistent skill. | [commands/gsd/spike.md](../commands/gsd/spike.md) |
|
||||
| `/gsd-sketch` | Rapidly sketch UI/design ideas using throwaway HTML mockups; use `--wrap-up` to package findings. | [commands/gsd/sketch.md](../commands/gsd/sketch.md) |
|
||||
| `/gsd-execute-phase` | Execute all plans in a phase with wave-based parallelization. | [commands/gsd/execute-phase.md](../commands/gsd/execute-phase.md) |
|
||||
| `/gsd-verify-work` | Validate built features through conversational UAT with auto-diagnosis. | [commands/gsd/verify-work.md](../commands/gsd/verify-work.md) |
|
||||
| `/gsd-ship` | Create PR, run review, and prepare for merge after verification. | [commands/gsd/ship.md](../commands/gsd/ship.md) |
|
||||
| `/gsd-next` | Automatically advance to the next logical step in the GSD workflow. | [commands/gsd/next.md](../commands/gsd/next.md) |
|
||||
| `/gsd-fast` | Execute a trivial task inline — no subagents, no planning overhead. | [commands/gsd/fast.md](../commands/gsd/fast.md) |
|
||||
| `/gsd-quick` | Execute a quick task with GSD guarantees (atomic commits, state tracking) but skip optional agents. | [commands/gsd/quick.md](../commands/gsd/quick.md) |
|
||||
| `/gsd-ui-review` | Retroactive 6-pillar visual audit of implemented frontend code. | [commands/gsd/ui-review.md](../commands/gsd/ui-review.md) |
|
||||
| `/gsd-code-review` | Review source files changed during a phase for bugs, security, and code-quality problems. | [commands/gsd/code-review.md](../commands/gsd/code-review.md) |
|
||||
| `/gsd-code-review-fix` | Auto-fix issues found by `/gsd-code-review`, committing each fix atomically. | [commands/gsd/code-review-fix.md](../commands/gsd/code-review-fix.md) |
|
||||
| `/gsd-code-review` | Review source files changed during a phase for bugs, security, and code-quality problems; use `--fix` to auto-apply findings. | [commands/gsd/code-review.md](../commands/gsd/code-review.md) |
|
||||
| `/gsd-eval-review` | Retroactively audit an executed AI phase's evaluation coverage; produces EVAL-REVIEW.md. | [commands/gsd/eval-review.md](../commands/gsd/eval-review.md) |
|
||||
|
||||
### Phase & Milestone Management
|
||||
|
||||
| Command | Role | Source |
|
||||
|---------|------|--------|
|
||||
| `/gsd-add-phase` | Add phase to end of current milestone in roadmap. | [commands/gsd/add-phase.md](../commands/gsd/add-phase.md) |
|
||||
| `/gsd-edit-phase` | Edit any field of an existing roadmap phase in place, preserving number and position. | [commands/gsd/edit-phase.md](../commands/gsd/edit-phase.md) |
|
||||
| `/gsd-insert-phase` | Insert urgent work as decimal phase (e.g., 72.1) between existing phases. | [commands/gsd/insert-phase.md](../commands/gsd/insert-phase.md) |
|
||||
| `/gsd-remove-phase` | Remove a future phase from roadmap and renumber subsequent phases. | [commands/gsd/remove-phase.md](../commands/gsd/remove-phase.md) |
|
||||
| `/gsd-phase` | CRUD for phases — add (default), insert (`--insert`), remove (`--remove`), or edit (`--edit`) phases in ROADMAP.md. | [commands/gsd/phase.md](../commands/gsd/phase.md) |
|
||||
| `/gsd-add-tests` | Generate tests for a completed phase based on UAT criteria and implementation. | [commands/gsd/add-tests.md](../commands/gsd/add-tests.md) |
|
||||
| `/gsd-list-phase-assumptions` | Surface Claude's assumptions about a phase approach before planning. | [commands/gsd/list-phase-assumptions.md](../commands/gsd/list-phase-assumptions.md) |
|
||||
| `/gsd-analyze-dependencies` | Analyze phase dependencies and suggest `Depends on` entries for ROADMAP.md. | [commands/gsd/analyze-dependencies.md](../commands/gsd/analyze-dependencies.md) |
|
||||
| `/gsd-validate-phase` | Retroactively audit and fill Nyquist validation gaps for a completed phase. | [commands/gsd/validate-phase.md](../commands/gsd/validate-phase.md) |
|
||||
| `/gsd-secure-phase` | Retroactively verify threat mitigations for a completed phase. | [commands/gsd/secure-phase.md](../commands/gsd/secure-phase.md) |
|
||||
| `/gsd-audit-milestone` | Audit milestone completion against original intent before archiving. | [commands/gsd/audit-milestone.md](../commands/gsd/audit-milestone.md) |
|
||||
| `/gsd-audit-uat` | Cross-phase audit of all outstanding UAT and verification items. | [commands/gsd/audit-uat.md](../commands/gsd/audit-uat.md) |
|
||||
| `/gsd-audit-fix` | Autonomous audit-to-fix pipeline — find issues, classify, fix, test, commit. | [commands/gsd/audit-fix.md](../commands/gsd/audit-fix.md) |
|
||||
| `/gsd-plan-milestone-gaps` | Create phases to close all gaps identified by milestone audit. | [commands/gsd/plan-milestone-gaps.md](../commands/gsd/plan-milestone-gaps.md) |
|
||||
| `/gsd-complete-milestone` | Archive completed milestone and prepare for next version. | [commands/gsd/complete-milestone.md](../commands/gsd/complete-milestone.md) |
|
||||
| `/gsd-new-milestone` | Start a new milestone cycle — update PROJECT.md and route to requirements. | [commands/gsd/new-milestone.md](../commands/gsd/new-milestone.md) |
|
||||
| `/gsd-milestone-summary` | Generate a comprehensive project summary from milestone artifacts. | [commands/gsd/milestone-summary.md](../commands/gsd/milestone-summary.md) |
|
||||
@@ -117,30 +119,22 @@ Full roster at `commands/gsd/*.md`. The groupings below mirror `docs/COMMANDS.md
|
||||
|
||||
| Command | Role | Source |
|
||||
|---------|------|--------|
|
||||
| `/gsd-progress` | Check project progress, show context, and route to next action. | [commands/gsd/progress.md](../commands/gsd/progress.md) |
|
||||
| `/gsd-progress` | Check project progress, show context, and route to next action; use `--next` to advance automatically or `--do` to run a freeform task. | [commands/gsd/progress.md](../commands/gsd/progress.md) |
|
||||
| `/gsd-capture` | Capture ideas, tasks, notes, and seeds — todo (default), `--note`, `--backlog`, `--seed`, or `--list` pending todos. | [commands/gsd/capture.md](../commands/gsd/capture.md) |
|
||||
| `/gsd-stats` | Display project statistics — phases, plans, requirements, git metrics, timeline. | [commands/gsd/stats.md](../commands/gsd/stats.md) |
|
||||
| `/gsd-session-report` | Generate a session report with token usage estimates, work summary, outcomes. | [commands/gsd/session-report.md](../commands/gsd/session-report.md) |
|
||||
| `/gsd-pause-work` | Create context handoff when pausing work mid-phase. | [commands/gsd/pause-work.md](../commands/gsd/pause-work.md) |
|
||||
| `/gsd-resume-work` | Resume work from previous session with full context restoration. | [commands/gsd/resume-work.md](../commands/gsd/resume-work.md) |
|
||||
| `/gsd-explore` | Socratic ideation and idea routing — think through ideas before committing. | [commands/gsd/explore.md](../commands/gsd/explore.md) |
|
||||
| `/gsd-do` | Route freeform text to the right GSD command automatically. | [commands/gsd/do.md](../commands/gsd/do.md) |
|
||||
| `/gsd-note` | Zero-friction idea capture — append, list, or promote notes to todos. | [commands/gsd/note.md](../commands/gsd/note.md) |
|
||||
| `/gsd-add-todo` | Capture idea or task as todo from current conversation context. | [commands/gsd/add-todo.md](../commands/gsd/add-todo.md) |
|
||||
| `/gsd-check-todos` | List pending todos and select one to work on. | [commands/gsd/check-todos.md](../commands/gsd/check-todos.md) |
|
||||
| `/gsd-add-backlog` | Add an idea to the backlog parking lot (999.x numbering). | [commands/gsd/add-backlog.md](../commands/gsd/add-backlog.md) |
|
||||
| `/gsd-review-backlog` | Review and promote backlog items to active milestone. | [commands/gsd/review-backlog.md](../commands/gsd/review-backlog.md) |
|
||||
| `/gsd-plant-seed` | Capture a forward-looking idea with trigger conditions. | [commands/gsd/plant-seed.md](../commands/gsd/plant-seed.md) |
|
||||
| `/gsd-thread` | Manage persistent context threads for cross-session work. | [commands/gsd/thread.md](../commands/gsd/thread.md) |
|
||||
|
||||
### Codebase Intelligence
|
||||
|
||||
| Command | Role | Source |
|
||||
|---------|------|--------|
|
||||
| `/gsd-map-codebase` | Analyze codebase with parallel mapper agents; produces `.planning/codebase/` documents. | [commands/gsd/map-codebase.md](../commands/gsd/map-codebase.md) |
|
||||
| `/gsd-scan` | Rapid codebase assessment — lightweight alternative to `/gsd-map-codebase`. | [commands/gsd/scan.md](../commands/gsd/scan.md) |
|
||||
| `/gsd-intel` | Query, inspect, or refresh codebase intelligence files in `.planning/intel/`. | [commands/gsd/intel.md](../commands/gsd/intel.md) |
|
||||
| `/gsd-map-codebase` | Analyze codebase with parallel mapper agents; use `--fast` for lightweight scan or `--query` for intel queries. | [commands/gsd/map-codebase.md](../commands/gsd/map-codebase.md) |
|
||||
| `/gsd-graphify` | Build, query, and inspect the project knowledge graph in `.planning/graphs/`. | [commands/gsd/graphify.md](../commands/gsd/graphify.md) |
|
||||
| `/gsd-extract-learnings` | Extract decisions, lessons, patterns, and surprises from completed phase artifacts. | [commands/gsd/extract_learnings.md](../commands/gsd/extract_learnings.md) |
|
||||
| `/gsd-extract-learnings` | Extract decisions, lessons, patterns, and surprises from completed phase artifacts. | [commands/gsd/extract-learnings.md](../commands/gsd/extract-learnings.md) |
|
||||
|
||||
### Review, Debug & Recovery
|
||||
|
||||
@@ -151,7 +145,6 @@ Full roster at `commands/gsd/*.md`. The groupings below mirror `docs/COMMANDS.md
|
||||
| `/gsd-forensics` | Post-mortem investigation for failed GSD workflows — analyzes git, artifacts, state. | [commands/gsd/forensics.md](../commands/gsd/forensics.md) |
|
||||
| `/gsd-health` | Diagnose planning directory health and optionally repair issues. | [commands/gsd/health.md](../commands/gsd/health.md) |
|
||||
| `/gsd-import` | Ingest external plans with conflict detection against project decisions. | [commands/gsd/import.md](../commands/gsd/import.md) |
|
||||
| `/gsd-from-gsd2` | Import a GSD-2 (`.gsd/`) project back to GSD v1 (`.planning/`) format. | [commands/gsd/from-gsd2.md](../commands/gsd/from-gsd2.md) |
|
||||
| `/gsd-inbox` | Triage and review all open GitHub issues and PRs against project templates. | [commands/gsd/inbox.md](../commands/gsd/inbox.md) |
|
||||
|
||||
### Docs, Profile & Utilities
|
||||
@@ -160,23 +153,16 @@ Full roster at `commands/gsd/*.md`. The groupings below mirror `docs/COMMANDS.md
|
||||
|---------|------|--------|
|
||||
| `/gsd-docs-update` | Generate or update project documentation verified against the codebase. | [commands/gsd/docs-update.md](../commands/gsd/docs-update.md) |
|
||||
| `/gsd-ingest-docs` | Scan a repo for mixed ADRs/PRDs/SPECs/DOCs and bootstrap or merge the full `.planning/` setup with classification, synthesis, and conflicts report. | [commands/gsd/ingest-docs.md](../commands/gsd/ingest-docs.md) |
|
||||
| `/gsd-spike-wrap-up` | Package spike findings into a persistent project skill for future build conversations. | [commands/gsd/spike-wrap-up.md](../commands/gsd/spike-wrap-up.md) |
|
||||
| `/gsd-sketch-wrap-up` | Package sketch design findings into a persistent project skill for future build conversations. | [commands/gsd/sketch-wrap-up.md](../commands/gsd/sketch-wrap-up.md) |
|
||||
| `/gsd-profile-user` | Generate developer behavioral profile and Claude-discoverable artifacts. | [commands/gsd/profile-user.md](../commands/gsd/profile-user.md) |
|
||||
| `/gsd-settings` | Configure GSD workflow toggles and model profile. | [commands/gsd/settings.md](../commands/gsd/settings.md) |
|
||||
| `/gsd-settings-advanced` | Power-user configuration — plan bounce, timeouts, branch templates, cross-AI execution, runtime knobs. | [commands/gsd/settings-advanced.md](../commands/gsd/settings-advanced.md) |
|
||||
| `/gsd-settings-integrations` | Configure third-party API keys, code-review CLI routing, and agent-skill injection. | [commands/gsd/settings-integrations.md](../commands/gsd/settings-integrations.md) |
|
||||
| `/gsd-set-profile` | Switch model profile for GSD agents (quality/balanced/budget/inherit). | [commands/gsd/set-profile.md](../commands/gsd/set-profile.md) |
|
||||
| `/gsd-config` | Configure GSD settings — workflow toggles (default), advanced knobs (`--advanced`), integrations (`--integrations`), or model profile (`--profile`). | [commands/gsd/config.md](../commands/gsd/config.md) |
|
||||
| `/gsd-pr-branch` | Create a clean PR branch by filtering out `.planning/` commits. | [commands/gsd/pr-branch.md](../commands/gsd/pr-branch.md) |
|
||||
| `/gsd-sync-skills` | Sync managed GSD skill directories across runtime roots for multi-runtime users. | [commands/gsd/sync-skills.md](../commands/gsd/sync-skills.md) |
|
||||
| `/gsd-update` | Update GSD to latest version with changelog display. | [commands/gsd/update.md](../commands/gsd/update.md) |
|
||||
| `/gsd-reapply-patches` | Reapply local modifications after a GSD update. | [commands/gsd/reapply-patches.md](../commands/gsd/reapply-patches.md) |
|
||||
| `/gsd-update` | Update GSD to latest version; use `--sync` to sync skills across runtimes or `--reapply` to reapply local patches. | [commands/gsd/update.md](../commands/gsd/update.md) |
|
||||
| `/gsd-help` | Show available GSD commands and usage guide. | [commands/gsd/help.md](../commands/gsd/help.md) |
|
||||
| `/gsd-join-discord` | Join the GSD Discord community. | [commands/gsd/join-discord.md](../commands/gsd/join-discord.md) |
|
||||
|
||||
---
|
||||
|
||||
## Workflows (84 shipped)
|
||||
## Workflows (85 shipped)
|
||||
|
||||
Full roster at `get-shit-done/workflows/*.md`. Workflows are thin orchestrators that commands reference internally; most are not read directly by end users. Rows below map each workflow file to its role (derived from the `<purpose>` block) and, where applicable, to the command that invokes it.
|
||||
|
||||
@@ -184,14 +170,14 @@ Full roster at `get-shit-done/workflows/*.md`. Workflows are thin orchestrators
|
||||
|----------|------|------------|
|
||||
| `add-phase.md` | Add a new integer phase to the end of the current milestone in the roadmap. | `/gsd-add-phase` |
|
||||
| `add-tests.md` | Generate unit and E2E tests for a completed phase based on its artifacts. | `/gsd-add-tests` |
|
||||
| `add-todo.md` | Capture an idea or task that surfaces during a session as a structured todo. | `/gsd-add-todo`, `/gsd-add-backlog` |
|
||||
| `add-todo.md` | Capture an idea or task that surfaces during a session as a structured todo. | `/gsd-capture` (default), `/gsd-capture --backlog` |
|
||||
| `ai-integration-phase.md` | Orchestrate framework selection → AI research → domain research → eval planning into AI-SPEC.md. | `/gsd-ai-integration-phase` |
|
||||
| `analyze-dependencies.md` | Analyze ROADMAP.md phases for file overlap and semantic dependencies; suggest `Depends on` edges. | `/gsd-analyze-dependencies` |
|
||||
| `audit-fix.md` | Autonomous audit-to-fix pipeline — run audit, parse, classify, fix, test, commit. | `/gsd-audit-fix` |
|
||||
| `audit-milestone.md` | Verify milestone met its definition of done by aggregating phase verifications. | `/gsd-audit-milestone` |
|
||||
| `audit-uat.md` | Cross-phase audit of UAT and verification files; produces prioritized outstanding-items list. | `/gsd-audit-uat` |
|
||||
| `autonomous.md` | Drive milestone phases autonomously — all remaining, a range, or a single phase. | `/gsd-autonomous` |
|
||||
| `check-todos.md` | List pending todos, allow selection, load context, and route to the appropriate action. | `/gsd-check-todos` |
|
||||
| `check-todos.md` | List pending todos, allow selection, load context, and route to the appropriate action. | `/gsd-capture --list` |
|
||||
| `cleanup.md` | Archive accumulated phase directories from completed milestones. | `/gsd-cleanup` |
|
||||
| `code-review-fix.md` | Auto-fix issues from REVIEW.md via gsd-code-fixer with per-fix atomic commits. | `/gsd-code-review-fix` |
|
||||
| `code-review.md` | Review phase source changes via gsd-code-reviewer; produces REVIEW.md. | `/gsd-code-review` |
|
||||
@@ -201,9 +187,9 @@ Full roster at `get-shit-done/workflows/*.md`. Workflows are thin orchestrators
|
||||
| `discuss-phase-assumptions.md` | Assumptions-mode discuss — extract implementation decisions via codebase-first analysis. | `/gsd-discuss-phase` (when `discuss_mode=assumptions`) |
|
||||
| `discuss-phase-power.md` | Power-user discuss — pre-generate all questions into a JSON state file + HTML UI. | `/gsd-discuss-phase --power` |
|
||||
| `discuss-phase.md` | Extract implementation decisions through iterative gray-area discussion. | `/gsd-discuss-phase` |
|
||||
| `do.md` | Route freeform text from the user to the best matching GSD command. | `/gsd-do` |
|
||||
| `do.md` | Route freeform text from the user to the best matching GSD command. | `/gsd-progress --do` |
|
||||
| `docs-update.md` | Generate, update, and verify canonical and hand-written project documentation. | `/gsd-docs-update` |
|
||||
| `edit-phase.md` | Edit any field of an existing phase in ROADMAP.md in place, preserving number and position. | `/gsd-edit-phase` |
|
||||
| `edit-phase.md` | Edit any field of an existing phase in ROADMAP.md in place, preserving number and position. | `/gsd-phase --edit` |
|
||||
| `eval-review.md` | Retroactive audit of an implemented AI phase's evaluation coverage. | `/gsd-eval-review` |
|
||||
| `execute-phase.md` | Execute all plans in a phase using wave-based parallel execution. | `/gsd-execute-phase` |
|
||||
| `execute-plan.md` | Execute a phase prompt (PLAN.md) and create the outcome summary (SUMMARY.md). | `execute-phase.md` (per-plan subagent) |
|
||||
@@ -226,18 +212,19 @@ Full roster at `get-shit-done/workflows/*.md`. Workflows are thin orchestrators
|
||||
| `new-milestone.md` | Start a new milestone cycle — load project context, gather goals, update PROJECT.md/STATE.md. | `/gsd-new-milestone` |
|
||||
| `new-project.md` | Unified new-project flow — questioning, research (optional), requirements, roadmap. | `/gsd-new-project` |
|
||||
| `new-workspace.md` | Create an isolated workspace with repo worktrees/clones and an independent `.planning/`. | `/gsd-new-workspace` |
|
||||
| `next.md` | Detect current project state and automatically advance to the next logical step. | `/gsd-next` |
|
||||
| `next.md` | Detect current project state and automatically advance to the next logical step. | `/gsd-progress --next` |
|
||||
| `node-repair.md` | Autonomous repair operator for failed task verification; invoked by `execute-plan`. | `execute-plan.md` (recovery) |
|
||||
| `note.md` | Zero-friction idea capture — one Write call, one confirmation line. | `/gsd-note` |
|
||||
| `note.md` | Zero-friction idea capture — one Write call, one confirmation line. | `/gsd-capture --note` |
|
||||
| `pause-work.md` | Create structured `.planning/HANDOFF.json` and `.continue-here.md` handoff files. | `/gsd-pause-work` |
|
||||
| `plan-milestone-gaps.md` | Create all phases necessary to close gaps identified by `/gsd-audit-milestone`. | `/gsd-plan-milestone-gaps` |
|
||||
| `plan-phase.md` | Create executable PLAN.md files with integrated research and verification loop. | `/gsd-plan-phase`, `/gsd-quick` |
|
||||
| `plan-review-convergence.md` | Cross-AI plan convergence loop — replan with review feedback until no HIGH concerns remain. | `/gsd-plan-review-convergence` |
|
||||
| `plant-seed.md` | Capture a forward-looking idea as a structured seed file with trigger conditions. | `/gsd-plant-seed` |
|
||||
| `plant-seed.md` | Capture a forward-looking idea as a structured seed file with trigger conditions. | `/gsd-capture --seed` |
|
||||
| `pr-branch.md` | Create a clean branch for pull requests by filtering `.planning/` commits. | `/gsd-pr-branch` |
|
||||
| `profile-user.md` | Orchestrate the full developer profiling flow — consent, session scan, profile generation. | `/gsd-profile-user` |
|
||||
| `progress.md` | Progress rendering — project context, position, and next-action routing. | `/gsd-progress` |
|
||||
| `quick.md` | Quick-task execution with GSD guarantees (atomic commits, state tracking). | `/gsd-quick` |
|
||||
| `reapply-patches.md` | Reapply local modifications after a GSD update. | `/gsd-reapply-patches` |
|
||||
| `remove-phase.md` | Remove a future phase from the roadmap and renumber subsequent phases. | `/gsd-remove-phase` |
|
||||
| `remove-workspace.md` | Remove a GSD workspace and clean up worktrees. | `/gsd-remove-workspace` |
|
||||
| `research-phase.md` | Standalone phase research workflow (usually invoked via `plan-phase`). | `/gsd-research-phase` |
|
||||
@@ -256,8 +243,8 @@ Full roster at `get-shit-done/workflows/*.md`. Workflows are thin orchestrators
|
||||
| `spike.md` | Rapid feasibility validation through focused, throwaway experiments. | `/gsd-spike` |
|
||||
| `spike-wrap-up.md` | Curate spike findings and package them as a persistent `spike-findings-[project]` skill. | `/gsd-spike-wrap-up` |
|
||||
| `stats.md` | Project statistics rendering — phases, plans, requirements, git metrics. | `/gsd-stats` |
|
||||
| `sync-skills.md` | Cross-runtime GSD skill sync — diff and apply `gsd-*` skill directories across runtime roots. | `/gsd-sync-skills` |
|
||||
| `transition.md` | Phase-boundary transition workflow — workstream checks, state advancement. | `execute-phase.md`, `/gsd-next` |
|
||||
| `sync-skills.md` | Cross-runtime GSD skill sync — diff and apply `gsd-*` skill directories across runtime roots. | `/gsd-update --sync` |
|
||||
| `transition.md` | Phase-boundary transition workflow — workstream checks, state advancement. | `execute-phase.md`, `/gsd-progress --next` |
|
||||
| `ui-phase.md` | Generate UI-SPEC.md design contract via gsd-ui-researcher. | `/gsd-ui-phase` |
|
||||
| `ui-review.md` | Retroactive 6-pillar visual audit via gsd-ui-auditor. | `/gsd-ui-review` |
|
||||
| `ultraplan-phase.md` | [BETA] Offload planning to Claude Code's ultraplan cloud; drafts remotely and imports back via `/gsd-import`. | `/gsd-ultraplan-phase` |
|
||||
@@ -361,7 +348,7 @@ The `gsd-planner` agent is decomposed into a core agent plus reference modules t
|
||||
|
||||
---
|
||||
|
||||
## CLI Modules (31 shipped)
|
||||
## CLI Modules (41 shipped)
|
||||
|
||||
Full listing: `get-shit-done/bin/lib/*.cjs`.
|
||||
|
||||
@@ -369,10 +356,12 @@ Full listing: `get-shit-done/bin/lib/*.cjs`.
|
||||
|--------|----------------|
|
||||
| `artifacts.cjs` | Canonical artifact registry — known `.planning/` root file names; used by `gsd-health` W019 lint |
|
||||
| `audit.cjs` | Audit dispatch, audit open sessions, audit storage helpers |
|
||||
| `command-aliases.generated.cjs` | Generated CJS alias/subcommand metadata for manifest-backed family routers |
|
||||
| `commands.cjs` | Misc CLI commands (slug, timestamp, todos, scaffolding, stats) |
|
||||
| `config-schema.cjs` | Single source of truth for `VALID_CONFIG_KEYS` and dynamic key patterns; imported by both the validator and the config-schema-docs parity test |
|
||||
| `config.cjs` | `config.json` read/write, section initialization; imports validator from `config-schema.cjs` |
|
||||
| `core.cjs` | Error handling, output formatting, shared utilities, runtime fallbacks |
|
||||
| `context-utilization.cjs` | Pure classifier for `gsd-health --context` — turns (tokensUsed, contextWindow) into a `{ percent, state }` triage result against the 60%/70% fracture-point thresholds (#2792) |
|
||||
| `core.cjs` | Error handling, output formatting, shared utilities, runtime fallbacks; compatibility re-exports for planning-workspace helpers |
|
||||
| `decisions.cjs` | Shared parser for CONTEXT.md `<decisions>` blocks (D-NN entries); used by `gap-checker.cjs` and intended for #2492 plan/verify decision gates |
|
||||
| `docs.cjs` | Docs-update workflow init, Markdown scanning, monorepo detection |
|
||||
| `drift.cjs` | Post-execute codebase structural drift detector (#2003): classifies file changes into new-dir/barrel/migration/route categories and round-trips `last_mapped_commit` frontmatter |
|
||||
@@ -380,22 +369,30 @@ Full listing: `get-shit-done/bin/lib/*.cjs`.
|
||||
| `gap-checker.cjs` | Post-planning gap analysis (#2493): unified REQUIREMENTS.md + CONTEXT.md decisions vs PLAN.md coverage report (`gsd-tools gap-analysis`) |
|
||||
| `graphify.cjs` | Knowledge-graph build/query/status/diff for `/gsd-graphify` |
|
||||
| `gsd2-import.cjs` | External-plan ingest for `/gsd-from-gsd2` |
|
||||
| `init-command-router.cjs` | Thin CJS subcommand router adapter for `gsd-tools init` |
|
||||
| `init.cjs` | Compound context loading for each workflow type |
|
||||
| `install-profiles.cjs` | Install profile allowlist + skill staging for `--minimal` install (#2762); single source of truth for which `gsd-*` skills/agents land in runtime config dirs |
|
||||
| `intel.cjs` | Codebase intel store backing `/gsd-intel` and `gsd-intel-updater` |
|
||||
| `learnings.cjs` | Cross-phase learnings extraction for `/gsd-extract-learnings` |
|
||||
| `milestone.cjs` | Milestone archival, requirements marking |
|
||||
| `model-profiles.cjs` | Model profile resolution table (authoritative profile data) |
|
||||
| `phase-command-router.cjs` | Thin CJS subcommand router adapter for `gsd-tools phase` |
|
||||
| `phase.cjs` | Phase directory operations, decimal numbering, plan indexing |
|
||||
| `phases-command-router.cjs` | Thin CJS subcommand router adapter for `gsd-tools phases` |
|
||||
| `planning-workspace.cjs` | Planning path/workstream seam (`planningDir`, `planningPaths`, active-workstream routing, `.planning/.lock` orchestration) |
|
||||
| `profile-output.cjs` | Profile rendering, USER-PROFILE.md and dev-preferences.md generation |
|
||||
| `profile-pipeline.cjs` | User behavioral profiling data pipeline, session file scanning |
|
||||
| `roadmap-command-router.cjs` | Thin CJS subcommand router adapter for `gsd-tools roadmap` |
|
||||
| `roadmap.cjs` | ROADMAP.md parsing, phase extraction, plan progress |
|
||||
| `schema-detect.cjs` | Schema-drift detection for ORM patterns (Prisma, Drizzle, etc.) |
|
||||
| `secrets.cjs` | Secret-config masking convention (`****<last-4>`) for integration keys managed by `/gsd-settings-integrations` — keeps plaintext out of `config-set` output |
|
||||
| `security.cjs` | Path traversal prevention, prompt injection detection, safe JSON/shell helpers |
|
||||
| `state-command-router.cjs` | Thin CJS subcommand router adapter for `gsd-tools state` |
|
||||
| `state.cjs` | STATE.md parsing, updating, progression, metrics |
|
||||
| `template.cjs` | Template selection and filling with variable substitution |
|
||||
| `uat.cjs` | UAT file parsing, verification debt tracking, audit-uat support |
|
||||
| `validate-command-router.cjs` | Thin CJS subcommand router adapter for `gsd-tools validate` |
|
||||
| `verify-command-router.cjs` | Thin CJS subcommand router adapter for `gsd-tools verify` |
|
||||
| `verify.cjs` | Plan structure, phase completeness, reference, commit validation |
|
||||
| `workstream.cjs` | Workstream CRUD, migration, session-scoped active pointer |
|
||||
|
||||
|
||||
234
docs/RELEASE-v1.40.0-rc.1.md
Normal file
234
docs/RELEASE-v1.40.0-rc.1.md
Normal file
@@ -0,0 +1,234 @@
|
||||
# v1.40.0-rc.1 Release Notes
|
||||
|
||||
Pre-release candidate. Published to npm under the `next` tag.
|
||||
|
||||
```bash
|
||||
npx get-shit-done-cc@next
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## What's in this release
|
||||
|
||||
rc.1 opens the 1.40.0 train. The headline change is the **skill-surface
|
||||
consolidation** ([#2790](https://github.com/gsd-build/get-shit-done/issues/2790))
|
||||
and the new **two-stage hierarchical routing** that sits on top of it
|
||||
([#2792](https://github.com/gsd-build/get-shit-done/issues/2792)) — together
|
||||
they take the cold-start system-prompt overhead from listing 86 flat skills
|
||||
down to 6 namespace routers. The release also adds the read-side of the
|
||||
phase-lifecycle status-line, hardens the multi-runtime install converters,
|
||||
and clears a backlog of small correctness fixes against Gemini, Copilot,
|
||||
Codex, and the canary publish workflow.
|
||||
|
||||
### Added
|
||||
|
||||
- **Six namespace meta-skills with keyword-tag descriptions**
|
||||
([#2792](https://github.com/gsd-build/get-shit-done/issues/2792)) — replace
|
||||
the flat eager skill listing with a two-stage hierarchical routing layer.
|
||||
The model sees 6 namespace routers instead of 86 entries, selects a
|
||||
namespace, then routes to the sub-skill. Namespaces:
|
||||
`gsd:workflow` (phase pipeline), `gsd:project` (project lifecycle),
|
||||
`gsd:review` (quality gates), `gsd:context` (codebase intelligence),
|
||||
`gsd:manage` (config / workspace / workstreams), `gsd:ideate`
|
||||
(exploration / capture). Descriptions use pipe-separated keyword tags
|
||||
(≤ 60 chars) per the Tool Attention research showing keyword-dense tags
|
||||
outperform prose for routing at ~40% the token cost.
|
||||
|
||||
| | Entries | Approx tokens |
|
||||
|---|---|---|
|
||||
| Pre-1.40 full install | 86 | ~2,150 |
|
||||
| Namespace meta-skills | 6 | ~120 |
|
||||
|
||||
Existing sub-skills are unchanged and still invocable directly — the
|
||||
namespace skills are additive.
|
||||
|
||||
- **`/gsd-health --context` utilization guard**
|
||||
([#2792](https://github.com/gsd-build/get-shit-done/issues/2792)) — adds a
|
||||
context-window quality guard with two thresholds: 60 % utilization warns
|
||||
("consider `/gsd-thread`"), 70 % is critical ("reasoning quality may
|
||||
degrade"; matches the fracture-point per recent context-attention
|
||||
research). Exposed via `/gsd-health --context` and as a structured
|
||||
`gsd-tools validate context` command for status-line / hook callers.
|
||||
|
||||
- **Phase-lifecycle status-line — read-side**
|
||||
([#2833](https://github.com/gsd-build/get-shit-done/issues/2833)) —
|
||||
`parseStateMd()` now reads four new STATE.md frontmatter fields:
|
||||
`active_phase` (phase number when orchestrator is in-flight),
|
||||
`next_action` (recommended next command when idle), `next_phases` (YAML
|
||||
flow array of next phase numbers), and `progress` (nested
|
||||
completed/total/percent block). `formatGsdState()` gains scenes for
|
||||
in-flight, idle, and progress display. All fields default to undefined,
|
||||
so existing STATE.md files keep rendering as before. Write-side and
|
||||
status-line wiring follow in a later RC.
|
||||
|
||||
### Changed
|
||||
|
||||
- **Skill surface consolidated 86 → 59 `commands/gsd/*.md` entries**
|
||||
([#2790](https://github.com/gsd-build/get-shit-done/issues/2790)) — four
|
||||
new grouped skills replace clusters of micro-skills:
|
||||
- `capture` — folds add-todo (default), note (`--note`), add-backlog
|
||||
(`--backlog`), plant-seed (`--seed`), check-todos (`--list`)
|
||||
- `phase` — folds add-phase (default), insert-phase (`--insert`),
|
||||
remove-phase (`--remove`), edit-phase (`--edit`)
|
||||
- `config` — folds settings-advanced (`--advanced`),
|
||||
settings-integrations (`--integrations`), set-profile (`--profile`)
|
||||
- `workspace` — folds new-workspace (`--new`), list-workspaces
|
||||
(`--list`), remove-workspace (`--remove`)
|
||||
|
||||
Six existing parents absorb wrap-up and sub-operations as flags:
|
||||
`update --sync / --reapply`, `sketch --wrap-up`, `spike --wrap-up`,
|
||||
`map-codebase --fast / --query`, `code-review --fix`,
|
||||
`progress --do / --next`. Zero functional loss — every removed
|
||||
micro-skill's behavior survives via a flag on a consolidated parent.
|
||||
31 micro-skills deleted outright; `autonomous.md` corrected to call
|
||||
`gsd:code-review --fix` (was invoking deleted `gsd:code-review-fix`).
|
||||
|
||||
- **Canary release workflow now publishes from `dev` branch only**
|
||||
([#2868](https://github.com/gsd-build/get-shit-done/issues/2868)) —
|
||||
`.github/workflows/canary.yml` swaps its four publish-step guards from
|
||||
`refs/heads/main` to `refs/heads/dev`, aligning with the new branch →
|
||||
dist-tag policy (`dev` → `@canary`, `main` → `@next` / `@latest`).
|
||||
`workflow_dispatch` runs on `main` (or any other branch) now complete
|
||||
build / test / dry-run validation but skip publish + tag, instead of the
|
||||
prior behaviour where `main` published and `dev` silently no-op'd.
|
||||
|
||||
- **PRs missing `Closes #NNN` are auto-closed**
|
||||
([#2872](https://github.com/gsd-build/get-shit-done/issues/2872)) — the
|
||||
`Issue link required` workflow now auto-closes any PR opened without a
|
||||
closing keyword that links a tracking issue, posting a comment that
|
||||
points to the contribution guide. Matches the documented project gate.
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Gemini slash commands are namespaced as `/gsd:<cmd>` instead of
|
||||
`/gsd-<cmd>`** ([#2768](https://github.com/gsd-build/get-shit-done/issues/2768),
|
||||
[#2783](https://github.com/gsd-build/get-shit-done/issues/2783)) — Gemini
|
||||
CLI namespaces commands under `gsd:` so `/gsd-plan-phase` was
|
||||
unexecutable. The Gemini install path now converts every body-text
|
||||
reference via a roster-checked regex (boundary lookbehind + extension-
|
||||
aware lookahead + roster lookup, defense-in-depth) and consistently
|
||||
rewrites command files, agent bodies, and final-banner / patch-reapply
|
||||
hints to colon form. The roster fail-loud guard prevents silent
|
||||
no-op'ing if the source `commands/gsd/` directory is ever missing.
|
||||
|
||||
- **GSD slash-command namespace drift cleaned up across docs, workflows
|
||||
and autocomplete** ([#2858](https://github.com/gsd-build/get-shit-done/pull/2858))
|
||||
— remaining stale `/gsd:<cmd>` references in active surfaces now use
|
||||
canonical `/gsd-<cmd>`, escaped workflow `Skill(skill="gsd:...")`
|
||||
prompts now use hyphenated skill names, `scripts/fix-slash-commands.cjs`
|
||||
rewrites retired colon syntax to hyphen syntax, and the extract-
|
||||
learnings command file is now `extract-learnings.md` so generated
|
||||
Claude / Qwen skill autocomplete exposes `gsd-extract-learnings`
|
||||
instead of `gsd-extract_learnings`.
|
||||
|
||||
- **`SKILL.md` description quoted for Copilot / Antigravity / Trae /
|
||||
CodeBuddy** ([#2876](https://github.com/gsd-build/get-shit-done/issues/2876))
|
||||
— descriptions starting with a YAML 1.2 flow indicator (`[BETA] …`,
|
||||
`{`, `*`, `&`, `!`, `|`, `>`, `%`, `@`, backtick) are parsed as flow
|
||||
sequences / mappings by strict YAML loaders and crash gh-copilot's
|
||||
frontmatter loader. Six emission sites now wrap the description in
|
||||
`yamlQuote(...)` (= `JSON.stringify`, a valid YAML 1.2 double-quoted
|
||||
scalar). The Claude variant already routed through `yamlQuote`; the
|
||||
others are now in line.
|
||||
|
||||
- **`gsd-tools` invocations use the absolute installed path**
|
||||
([#2851](https://github.com/gsd-build/get-shit-done/issues/2851)) — bare
|
||||
`gsd-tools …` calls inside skill bodies relied on PATH resolution that
|
||||
is not guaranteed in every runtime; replaced with the absolute path
|
||||
emitted at install time.
|
||||
|
||||
- **Codex installer preserves trailing newline when stripping legacy
|
||||
hooks** ([#2866](https://github.com/gsd-build/get-shit-done/issues/2866))
|
||||
— the legacy-hook strip in the Codex installer ran against files with
|
||||
no terminating newline at EOF and emitted a config that lost the
|
||||
newline, breaking downstream parsers. Strip path now normalises EOF.
|
||||
|
||||
---
|
||||
|
||||
## What was in rc.7
|
||||
|
||||
[`RELEASE-v1.39.0-rc.7.md`](RELEASE-v1.39.0-rc.7.md) — first 1.39.0 RC to
|
||||
roll the post-rc.5 fixes from `main` into the release branch. Includes
|
||||
the `extractCurrentMilestone` fenced-code-block fix
|
||||
([#2787](https://github.com/gsd-build/get-shit-done/issues/2787)),
|
||||
`audit-uat` frontmatter parse fix
|
||||
([#2788](https://github.com/gsd-build/get-shit-done/issues/2788)), the
|
||||
≤ 100-char skill description budget + lint gate
|
||||
([#2789](https://github.com/gsd-build/get-shit-done/issues/2789)), the
|
||||
`gsd-sdk` workstream + binary-collision fixes
|
||||
([#2791](https://github.com/gsd-build/get-shit-done/issues/2791)),
|
||||
`OpenCode` per-tier model overrides
|
||||
([#2794](https://github.com/gsd-build/get-shit-done/issues/2794)),
|
||||
`roadmap update-plan-progress --phase` flag handling
|
||||
([#2796](https://github.com/gsd-build/get-shit-done/issues/2796)),
|
||||
`context_window` allowlist entry
|
||||
([#2798](https://github.com/gsd-build/get-shit-done/issues/2798)),
|
||||
`/gsd-ingest-docs` init dispatch
|
||||
([#2801](https://github.com/gsd-build/get-shit-done/issues/2801)),
|
||||
`config-get --default` flag
|
||||
([#2803](https://github.com/gsd-build/get-shit-done/issues/2803)),
|
||||
`find-phase` archived-phase null
|
||||
([#2805](https://github.com/gsd-build/get-shit-done/issues/2805)),
|
||||
SKILL.md hyphen-form name migration
|
||||
([#2808](https://github.com/gsd-build/get-shit-done/issues/2808)),
|
||||
canary workflow `workflow_dispatch`
|
||||
([#2828](https://github.com/gsd-build/get-shit-done/issues/2828)),
|
||||
`gsd-sdk` local-mode resolve
|
||||
([#2829](https://github.com/gsd-build/get-shit-done/issues/2829)),
|
||||
OpenCode `@file` HOME expansion
|
||||
([#2831](https://github.com/gsd-build/get-shit-done/issues/2831)),
|
||||
`gsd-sdk auto` Codex detection
|
||||
([#2832](https://github.com/gsd-build/get-shit-done/issues/2832)),
|
||||
CR-INTEGRATION hyphen alignment
|
||||
([#2835](https://github.com/gsd-build/get-shit-done/issues/2835)),
|
||||
`audit-open` SUMMARY filename + UAT terminal status
|
||||
([#2836](https://github.com/gsd-build/get-shit-done/issues/2836)),
|
||||
SUMMARY rescue with gitignored `.planning/`
|
||||
([#2838](https://github.com/gsd-build/get-shit-done/issues/2838)),
|
||||
transactional cleanup tail for `/gsd-code-review-fix`
|
||||
([#2839](https://github.com/gsd-build/get-shit-done/issues/2839)).
|
||||
|
||||
## What was in rc.5 / rc.6
|
||||
|
||||
[`RELEASE-v1.39.0-rc.5.md`](RELEASE-v1.39.0-rc.5.md) and
|
||||
[`RELEASE-v1.39.0-rc.6.md`](RELEASE-v1.39.0-rc.6.md). rc.6 was a
|
||||
content-identical republish of rc.5; rc.5 hardened the Codex hooks
|
||||
migrator across five edge-cases
|
||||
([#2809](https://github.com/gsd-build/get-shit-done/issues/2809)).
|
||||
|
||||
## What was in rc.4
|
||||
|
||||
[`RELEASE-v1.39.0-rc.4.md`](RELEASE-v1.39.0-rc.4.md) — the `--minimal`
|
||||
install flag landed
|
||||
([#2762](https://github.com/gsd-build/get-shit-done/issues/2762)) along
|
||||
with the Codex `~/.codex/config.toml` corruption fix
|
||||
([#2760](https://github.com/gsd-build/get-shit-done/issues/2760)).
|
||||
|
||||
---
|
||||
|
||||
## Installing the pre-release
|
||||
|
||||
```bash
|
||||
# npm
|
||||
npm install -g get-shit-done-cc@next
|
||||
|
||||
# npx (one-shot)
|
||||
npx get-shit-done-cc@next
|
||||
```
|
||||
|
||||
To pin to this exact RC:
|
||||
|
||||
```bash
|
||||
npm install -g get-shit-done-cc@1.40.0-rc.1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## What's next
|
||||
|
||||
- Soak rc.1 against real installs across Claude Code, Codex, Copilot,
|
||||
Gemini, OpenCode, and Antigravity runtimes.
|
||||
- Wire write-side phase-lifecycle status-line on top of the
|
||||
[#2833](https://github.com/gsd-build/get-shit-done/issues/2833) read-side.
|
||||
- Run `finalize` on the release workflow to promote `1.40.0` to `latest`
|
||||
once the train has soaked.
|
||||
177
docs/STATE-MD-LIFECYCLE.md
Normal file
177
docs/STATE-MD-LIFECYCLE.md
Normal file
@@ -0,0 +1,177 @@
|
||||
# STATE.md Phase Lifecycle Frontmatter
|
||||
|
||||
> **Status:** Reference for the phase-lifecycle status-line proposed in
|
||||
> [issue #2833](https://github.com/gsd-build/get-shit-done/issues/2833).
|
||||
> The status-line hook (`hooks/gsd-statusline.js`) reads the fields below;
|
||||
> SDK write-side support to maintain them is tracked separately.
|
||||
|
||||
GSD's `STATE.md` carries YAML frontmatter that the status-line hook reads on
|
||||
every render. This document describes the **phase-lifecycle fields** and the
|
||||
rendering scenes they trigger.
|
||||
|
||||
All four lifecycle fields are **optional and additive**. Existing `STATE.md`
|
||||
files (without these fields) keep rendering exactly as they did before — no
|
||||
visual change, no migration required.
|
||||
|
||||
---
|
||||
|
||||
## Frontmatter fields
|
||||
|
||||
```yaml
|
||||
---
|
||||
gsd_state_version: 1.0
|
||||
milestone: v2.0 # existing
|
||||
milestone_name: Code Quality # existing
|
||||
status: in_progress # existing — see "status semantics" below
|
||||
|
||||
# Phase-lifecycle additions (issue #2833) — all optional
|
||||
active_phase: null # phase number when an orchestrator is in flight
|
||||
next_action: execute-phase # next recommended command when idle
|
||||
next_phases: ["4.5"] # phases that next_action applies to (1-2 ids)
|
||||
|
||||
progress: # nested block (existing key, percent now opt-in for the bar)
|
||||
total_phases: 17
|
||||
completed_phases: 10
|
||||
percent: 59
|
||||
---
|
||||
```
|
||||
|
||||
### Field reference
|
||||
|
||||
| Field | Type | When populated | When null/absent |
|
||||
|---|---|---|---|
|
||||
| `active_phase` | string (e.g. `"4.5"`) | An orchestrator command is in flight on this phase | Idle between phases |
|
||||
| `next_action` | string | Idle, with a recommended command (`discuss-phase` / `plan-phase` / `execute-phase` / `verify-phase`) | An orchestrator is in flight, OR no recommendation available |
|
||||
| `next_phases` | YAML flow array (e.g. `["4.5"]`) | Goes with `next_action` — phases the action applies to | Same as above |
|
||||
| `progress.percent` | integer 0-100 | Milestone progress in **phase dimension** (`completed_phases / total_phases`) | Bar rendering is opt-in — absent → no bar |
|
||||
|
||||
### `next_phases` parser scope
|
||||
|
||||
Only **single-line YAML flow** is parsed: `next_phases: ["4.5", "4.6"]`.
|
||||
|
||||
Block sequences over multiple lines (`- 4.5\n - 4.6`) are intentionally
|
||||
**not parsed** — the status-line only needs the primary recommendation, and a
|
||||
single-line array keeps the regex-based parser predictable. If a project needs
|
||||
to track many candidate next phases for documentation purposes, store the
|
||||
extra ones in the `STATE.md` body.
|
||||
|
||||
### `progress.percent` dimension
|
||||
|
||||
The bar rendered next to the milestone version reflects **phase completion**
|
||||
(`completed_phases / total_phases`), not plan completion.
|
||||
|
||||
Plan dimension (`completed_plans / total_plans`) trends optimistic for any
|
||||
project where future phases haven't been planned yet — `total_plans` only
|
||||
counts plans inside *already-planned* phases, so the denominator is
|
||||
structurally smaller than reality. Reporting that number to stakeholders
|
||||
overstates progress.
|
||||
|
||||
If a project wants to show plan-level progress somewhere, store it elsewhere
|
||||
in frontmatter or the body — the status-line bar is reserved for the
|
||||
phase-dimension number that matches `ROADMAP.md` progress tables and
|
||||
`MILESTONES.md`.
|
||||
|
||||
---
|
||||
|
||||
## Status-line rendering scenes
|
||||
|
||||
`formatGsdState()` checks the lifecycle fields in the order below and emits
|
||||
the **first matching scene**. If none match, the renderer falls through to
|
||||
the original `<status> · <phase>` format (byte-for-byte unchanged from
|
||||
v1.38.x).
|
||||
|
||||
| Scene | Trigger | Display |
|
||||
|---|---|---|
|
||||
| **1. Phase active** | `active_phase` populated | `v2.0 [██░░░] X% · Phase 4.5 executing` |
|
||||
| **2. Idle, next recommended** | `active_phase` null AND `next_action` + `next_phases` populated | `v2.0 [██░░░] X% · next execute-phase 4.5` |
|
||||
| **3. Milestone complete** | `percent: 100` OR `completed_phases == total_phases` | `v2.0 [██████████] 100% · milestone complete` |
|
||||
| **4. Default fallback** | None of the above | `v1.9 Code Quality · executing · ph (1/5)` (existing format) |
|
||||
|
||||
### Scene priority example
|
||||
|
||||
When both `active_phase` and `next_action` are populated, **Scene 1 wins** —
|
||||
an orchestrator is in flight, so any "next recommendation" would be misleading.
|
||||
This is enforced by check order in `formatGsdState()` and by tests in
|
||||
`tests/enh-2833-phase-lifecycle-statusline.test.cjs` (suite *"scene priority"*).
|
||||
|
||||
### Stage labels in Scene 1
|
||||
|
||||
In Scene 1, the second part of `Phase 4.5 <stage>` is whichever value is in
|
||||
the `status` field at that moment. The convention proposed in issue #2833
|
||||
is to use the lifecycle stage:
|
||||
|
||||
| Command | `status` value while in flight |
|
||||
|---|---|
|
||||
| `/gsd-discuss-phase` | `discussing` |
|
||||
| `/gsd-plan-phase` | `planning` |
|
||||
| `/gsd-execute-phase` | `executing` |
|
||||
| `/gsd-verify-phase` | `verifying` |
|
||||
|
||||
If `status` is left at `in_progress` (the milestone-level value), Scene 1
|
||||
renders just `Phase 4.5` without the stage suffix.
|
||||
|
||||
---
|
||||
|
||||
## Frontmatter parsing constraints
|
||||
|
||||
The status-line hook uses regex-based parsing (no full YAML library), so a
|
||||
few constraints apply:
|
||||
|
||||
1. **Frontmatter must start at the very first character of the file.**
|
||||
Anything (including comments) above the opening `---` invalidates the
|
||||
match. The opening `---` line must be exactly that — no trailing spaces.
|
||||
|
||||
2. **Comments inside nested blocks are not supported.**
|
||||
The parser for `progress:` requires the next line to be `[ \t]+\w+:` —
|
||||
inserting `# comment` between `progress:` and the first key breaks the
|
||||
match and the bar disappears. Put any documentation in the body of
|
||||
`STATE.md`, not inside frontmatter blocks.
|
||||
|
||||
3. **`next_phases` accepts only single-line flow format.**
|
||||
See the parser scope note above.
|
||||
|
||||
These constraints are tested in
|
||||
`tests/enh-2833-phase-lifecycle-statusline.test.cjs`. If a future change
|
||||
swaps the regex parser for a real YAML library, the constraints can be
|
||||
relaxed and the tests updated accordingly.
|
||||
|
||||
---
|
||||
|
||||
## Backward compatibility
|
||||
|
||||
This document describes additive fields. The promise is:
|
||||
|
||||
- A `STATE.md` file with **none** of the lifecycle fields populated renders
|
||||
**byte-for-byte identically** to v1.38.x and earlier.
|
||||
- Adding any lifecycle field is **opt-in per project** — the renderer falls
|
||||
through to the existing format when fields are absent.
|
||||
- The progress bar is opt-in even when `progress` block exists — only
|
||||
`progress.percent` triggers the bar; `total_phases` / `completed_phases`
|
||||
alone don't.
|
||||
|
||||
The `formatGsdState #2833 backward compatibility` test suite locks this
|
||||
guarantee in: any change that breaks legacy `STATE.md` rendering will fail
|
||||
the suite.
|
||||
|
||||
---
|
||||
|
||||
## Related issues / PRs
|
||||
|
||||
- **#1989** — *enhancement: surface GSD state in statusline.* The foundation
|
||||
this proposal extends. Established that `STATE.md` frontmatter drives the
|
||||
status-line.
|
||||
- **#2833** — *enhancement: phase-lifecycle status-line — auto-rotate
|
||||
STATE.md frontmatter as phase orchestrators progress.* This document
|
||||
describes the read-side spec from that issue. Write-side SDK / workflow
|
||||
changes to auto-maintain the fields are tracked separately so each piece
|
||||
can be reviewed independently.
|
||||
|
||||
Companion read-side issues this proposal also helps close (each fixed a
|
||||
specific symptom of the same gap):
|
||||
|
||||
- #1102 — STATE.md frontmatter plan counts only update on plan completion
|
||||
- #1103 — STATE.md status / last_activity not updated when a phase starts
|
||||
- #1446 / #1572 — phase complete doesn't update Plans column
|
||||
- #612 — ROADMAP.md not updating
|
||||
- #956 — planning document drift across core workflows
|
||||
- #2018 — verify-work doesn't auto-transition (fixed for verify only)
|
||||
@@ -862,16 +862,16 @@ claude --dangerously-skip-permissions
|
||||
# (normal phase workflow from here)
|
||||
```
|
||||
|
||||
**Post-execute drift detection (#2003).** After every `/gsd:execute-phase`,
|
||||
**Post-execute drift detection (#2003).** After every `/gsd-execute-phase`,
|
||||
GSD checks whether the phase introduced enough structural change
|
||||
(new directories, barrel exports, migrations, or route modules) to make
|
||||
`.planning/codebase/STRUCTURE.md` stale. If it did, the default behavior is
|
||||
to print a one-shot warning suggesting the exact `/gsd:map-codebase --paths …`
|
||||
to print a one-shot warning suggesting the exact `/gsd-map-codebase --paths …`
|
||||
invocation to refresh just the affected subtrees. Flip the behavior with:
|
||||
|
||||
```bash
|
||||
/gsd:settings workflow.drift_action auto-remap # remap automatically
|
||||
/gsd:settings workflow.drift_threshold 5 # tune sensitivity
|
||||
/gsd-settings workflow.drift_action auto-remap # remap automatically
|
||||
/gsd-settings workflow.drift_threshold 5 # tune sensitivity
|
||||
```
|
||||
|
||||
The gate is non-blocking: any internal failure logs and the phase continues.
|
||||
@@ -1297,4 +1297,3 @@ For reference, here is what GSD creates in your project:
|
||||
XX-UI-REVIEW.md # Visual audit scores (from /gsd-ui-review)
|
||||
ui-reviews/ # Screenshots from /gsd-ui-review (gitignored)
|
||||
```
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ Get Shit Done(GSD)フレームワークの包括的なドキュメントで
|
||||
|
||||
## クイックリンク
|
||||
|
||||
- **v1.32 の新機能:** STATE.md 整合性ゲート、`--to N` 自律モード、リサーチゲート、ベリファイヤーマイルストーンスコープフィルタリング、read-before-edit ガード、コンテキスト削減、新規ランタイム(Trae, Cline, Augment Code)、レスポンス言語設定、`--power`/`--diagnose` フラグ、`/gsd-analyze-dependencies`
|
||||
- **v1.39 の新機能:** `--minimal` インストールプロファイル(≥94% コールドスタート削減)、`/gsd-edit-phase`、マージ後ビルド & テストゲート、`review.models.<cli>` ランタイム別レビューモデル、ワークストリーム設定の継承、手動カナリアリリースワークフロー、スキル統合(86 → 59)
|
||||
- **はじめに:** [README](../README.md) → インストール → `/gsd-new-project`
|
||||
- **ワークフロー完全ガイド:** [ユーザーガイド](USER-GUIDE.md)
|
||||
- **コマンド一覧:** [コマンドリファレンス](COMMANDS.md)
|
||||
|
||||
@@ -20,7 +20,7 @@ Get Shit Done (GSD) 프레임워크의 종합 문서입니다. GSD는 AI 코딩
|
||||
|
||||
## 빠른 링크
|
||||
|
||||
- **v1.32의 새로운 기능:** STATE.md 일관성 게이트, `--to N` 자율 모드, 리서치 게이트, 검증자 마일스톤 범위 필터링, read-before-edit 가드, 컨텍스트 축소, 신규 런타임(Trae, Cline, Augment Code), 응답 언어 설정, `--power`/`--diagnose` 플래그, `/gsd-analyze-dependencies`
|
||||
- **v1.39의 새로운 기능:** `--minimal` 설치 프로파일(콜드 스타트 ≥94% 감소), `/gsd-edit-phase`, 머지 후 빌드 & 테스트 게이트, `review.models.<cli>` 런타임별 리뷰 모델, 워크스트림 설정 상속, 수동 카나리 릴리스 워크플로, 스킬 통합(86 → 59)
|
||||
- **시작하기:** [README](../README.md) → 설치 → `/gsd-new-project`
|
||||
- **전체 워크플로우 안내:** [User Guide](USER-GUIDE.md)
|
||||
- **모든 명령어 한눈에 보기:** [Command Reference](COMMANDS.md)
|
||||
|
||||
@@ -18,9 +18,9 @@ Documentação abrangente do framework Get Shit Done (GSD) — um sistema de met
|
||||
| [Referências](references/) | Todos os usuários | Guias complementares de decisão, verificação e padrões |
|
||||
| [Superpowers](superpowers/) | Contribuidores | Planos e specs avançadas do projeto |
|
||||
|
||||
## Novidades v1.32
|
||||
## Novidades v1.39
|
||||
|
||||
STATE.md consistency gates, `--to N` para execução autônoma parcial, research gate, verifier milestone scope filtering, read-before-edit guard, context reduction, novos runtimes (Trae, Cline, Augment Code), `response_language`, `--power`/`--diagnose` flags, `/gsd-analyze-dependencies`.
|
||||
Perfil de instalação `--minimal` (≥94% de redução no cold-start), `/gsd-edit-phase`, build & test gate pós-merge, `review.models.<cli>` para escolha de modelo de review por runtime, herança de configuração de workstream, workflow manual de canary release, consolidação de skills (86 → 59).
|
||||
|
||||
## Links rápidos
|
||||
|
||||
|
||||
@@ -172,7 +172,8 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const core = require('./lib/core.cjs');
|
||||
const { error, findProjectRoot, getActiveWorkstream } = core;
|
||||
const { error, findProjectRoot } = core;
|
||||
const { getActiveWorkstream } = require('./lib/planning-workspace.cjs');
|
||||
const state = require('./lib/state.cjs');
|
||||
const phase = require('./lib/phase.cjs');
|
||||
const roadmap = require('./lib/roadmap.cjs');
|
||||
@@ -189,6 +190,13 @@ const workstream = require('./lib/workstream.cjs');
|
||||
const docs = require('./lib/docs.cjs');
|
||||
const learnings = require('./lib/learnings.cjs');
|
||||
const gapChecker = require('./lib/gap-checker.cjs');
|
||||
const { routeStateCommand } = require('./lib/state-command-router.cjs');
|
||||
const { routeVerifyCommand } = require('./lib/verify-command-router.cjs');
|
||||
const { routeInitCommand } = require('./lib/init-command-router.cjs');
|
||||
const { routePhaseCommand } = require('./lib/phase-command-router.cjs');
|
||||
const { routePhasesCommand } = require('./lib/phases-command-router.cjs');
|
||||
const { routeValidateCommand } = require('./lib/validate-command-router.cjs');
|
||||
const { routeRoadmapCommand } = require('./lib/roadmap-command-router.cjs');
|
||||
|
||||
// ─── Arg parsing helpers ──────────────────────────────────────────────────────
|
||||
|
||||
@@ -429,73 +437,14 @@ function extractField(obj, fieldPath) {
|
||||
async function runCommand(command, args, cwd, raw, defaultValue) {
|
||||
switch (command) {
|
||||
case 'state': {
|
||||
const subcommand = args[1];
|
||||
if (subcommand === 'json') {
|
||||
state.cmdStateJson(cwd, raw);
|
||||
} else if (subcommand === 'update') {
|
||||
state.cmdStateUpdate(cwd, args[2], args[3]);
|
||||
} else if (subcommand === 'get') {
|
||||
state.cmdStateGet(cwd, args[2], raw);
|
||||
} else if (subcommand === 'patch') {
|
||||
const patches = {};
|
||||
for (let i = 2; i < args.length; i += 2) {
|
||||
const key = args[i].replace(/^--/, '');
|
||||
const value = args[i + 1];
|
||||
if (key && value !== undefined) {
|
||||
patches[key] = value;
|
||||
}
|
||||
}
|
||||
state.cmdStatePatch(cwd, patches, raw);
|
||||
} else if (subcommand === 'advance-plan') {
|
||||
state.cmdStateAdvancePlan(cwd, raw);
|
||||
} else if (subcommand === 'record-metric') {
|
||||
const { phase: p, plan, duration, tasks, files } = parseNamedArgs(args, ['phase', 'plan', 'duration', 'tasks', 'files']);
|
||||
state.cmdStateRecordMetric(cwd, { phase: p, plan, duration, tasks, files }, raw);
|
||||
} else if (subcommand === 'update-progress') {
|
||||
state.cmdStateUpdateProgress(cwd, raw);
|
||||
} else if (subcommand === 'add-decision') {
|
||||
const { phase: p, summary, 'summary-file': summary_file, rationale, 'rationale-file': rationale_file } = parseNamedArgs(args, ['phase', 'summary', 'summary-file', 'rationale', 'rationale-file']);
|
||||
state.cmdStateAddDecision(cwd, { phase: p, summary, summary_file, rationale: rationale || '', rationale_file }, raw);
|
||||
} else if (subcommand === 'add-blocker') {
|
||||
const { text, 'text-file': text_file } = parseNamedArgs(args, ['text', 'text-file']);
|
||||
state.cmdStateAddBlocker(cwd, { text, text_file }, raw);
|
||||
} else if (subcommand === 'resolve-blocker') {
|
||||
state.cmdStateResolveBlocker(cwd, parseNamedArgs(args, ['text']).text, raw);
|
||||
} else if (subcommand === 'record-session') {
|
||||
const { 'stopped-at': stopped_at, 'resume-file': resume_file } = parseNamedArgs(args, ['stopped-at', 'resume-file']);
|
||||
state.cmdStateRecordSession(cwd, { stopped_at, resume_file: resume_file || 'None' }, raw);
|
||||
} else if (subcommand === 'begin-phase') {
|
||||
const { phase: p, name, plans } = parseNamedArgs(args, ['phase', 'name', 'plans']);
|
||||
state.cmdStateBeginPhase(cwd, p, name, plans !== null ? parseInt(plans, 10) : null, raw);
|
||||
} else if (subcommand === 'signal-waiting') {
|
||||
const { type, question, options, phase: p } = parseNamedArgs(args, ['type', 'question', 'options', 'phase']);
|
||||
state.cmdSignalWaiting(cwd, type, question, options, p, raw);
|
||||
} else if (subcommand === 'signal-resume') {
|
||||
state.cmdSignalResume(cwd, raw);
|
||||
} else if (subcommand === 'planned-phase') {
|
||||
const { phase: p, name, plans } = parseNamedArgs(args, ['phase', 'name', 'plans']);
|
||||
state.cmdStatePlannedPhase(cwd, p, plans !== null ? parseInt(plans, 10) : null, raw);
|
||||
} else if (subcommand === 'validate') {
|
||||
state.cmdStateValidate(cwd, raw);
|
||||
} else if (subcommand === 'sync') {
|
||||
const { verify } = parseNamedArgs(args, [], ['verify']);
|
||||
state.cmdStateSync(cwd, { verify }, raw);
|
||||
} else if (subcommand === 'prune') {
|
||||
const { 'keep-recent': keepRecent, 'dry-run': dryRun } = parseNamedArgs(args, ['keep-recent'], ['dry-run']);
|
||||
state.cmdStatePrune(cwd, { keepRecent: keepRecent || '3', dryRun: !!dryRun }, raw);
|
||||
} else if (subcommand === 'complete-phase') {
|
||||
state.cmdStateCompletePhase(cwd, raw);
|
||||
} else if (subcommand === 'milestone-switch') {
|
||||
// Bug #2630: reset STATE.md frontmatter + Current Position for new milestone.
|
||||
// NB: the flag is `--milestone`, not `--version` — gsd-tools reserves
|
||||
// `--version` as a globally-invalid help flag (see NEVER_VALID_FLAGS above).
|
||||
const { milestone, name } = parseNamedArgs(args, ['milestone', 'name']);
|
||||
state.cmdStateMilestoneSwitch(cwd, milestone, name, raw);
|
||||
} else if (subcommand === undefined || subcommand === 'load') {
|
||||
state.cmdStateLoad(cwd, raw);
|
||||
} else {
|
||||
error(`Unknown state subcommand: "${subcommand}". Available: load, json, get, patch, update, advance-plan, record-metric, update-progress, add-decision, add-blocker, resolve-blocker, record-session, begin-phase, signal-waiting, signal-resume, planned-phase, validate, sync, prune, complete-phase, milestone-switch`);
|
||||
}
|
||||
routeStateCommand({
|
||||
state,
|
||||
args,
|
||||
cwd,
|
||||
raw,
|
||||
parseNamedArgs,
|
||||
error,
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -589,27 +538,13 @@ async function runCommand(command, args, cwd, raw, defaultValue) {
|
||||
}
|
||||
|
||||
case 'verify': {
|
||||
const subcommand = args[1];
|
||||
if (subcommand === 'plan-structure') {
|
||||
verify.cmdVerifyPlanStructure(cwd, args[2], raw);
|
||||
} else if (subcommand === 'phase-completeness') {
|
||||
verify.cmdVerifyPhaseCompleteness(cwd, args[2], raw);
|
||||
} else if (subcommand === 'references') {
|
||||
verify.cmdVerifyReferences(cwd, args[2], raw);
|
||||
} else if (subcommand === 'commits') {
|
||||
verify.cmdVerifyCommits(cwd, args.slice(2), raw);
|
||||
} else if (subcommand === 'artifacts') {
|
||||
verify.cmdVerifyArtifacts(cwd, args[2], raw);
|
||||
} else if (subcommand === 'key-links') {
|
||||
verify.cmdVerifyKeyLinks(cwd, args[2], raw);
|
||||
} else if (subcommand === 'schema-drift') {
|
||||
const skipFlag = args.includes('--skip');
|
||||
verify.cmdVerifySchemaDrift(cwd, args[2], skipFlag, raw);
|
||||
} else if (subcommand === 'codebase-drift') {
|
||||
verify.cmdVerifyCodebaseDrift(cwd, raw);
|
||||
} else {
|
||||
error('Unknown verify subcommand. Available: plan-structure, phase-completeness, references, commits, artifacts, key-links, schema-drift, codebase-drift');
|
||||
}
|
||||
routeVerifyCommand({
|
||||
verify,
|
||||
args,
|
||||
cwd,
|
||||
raw,
|
||||
error,
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -679,37 +614,25 @@ async function runCommand(command, args, cwd, raw, defaultValue) {
|
||||
}
|
||||
|
||||
case 'phases': {
|
||||
const subcommand = args[1];
|
||||
if (subcommand === 'list') {
|
||||
const typeIndex = args.indexOf('--type');
|
||||
const phaseIndex = args.indexOf('--phase');
|
||||
const options = {
|
||||
type: typeIndex !== -1 ? args[typeIndex + 1] : null,
|
||||
phase: phaseIndex !== -1 ? args[phaseIndex + 1] : null,
|
||||
includeArchived: args.includes('--include-archived'),
|
||||
};
|
||||
phase.cmdPhasesList(cwd, options, raw);
|
||||
} else if (subcommand === 'clear') {
|
||||
milestone.cmdPhasesClear(cwd, raw, args.slice(2));
|
||||
} else {
|
||||
error('Unknown phases subcommand. Available: list, clear');
|
||||
}
|
||||
routePhasesCommand({
|
||||
phase,
|
||||
milestone,
|
||||
args,
|
||||
cwd,
|
||||
raw,
|
||||
error,
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case 'roadmap': {
|
||||
const subcommand = args[1];
|
||||
if (subcommand === 'get-phase') {
|
||||
roadmap.cmdRoadmapGetPhase(cwd, args[2], raw);
|
||||
} else if (subcommand === 'analyze') {
|
||||
roadmap.cmdRoadmapAnalyze(cwd, raw);
|
||||
} else if (subcommand === 'update-plan-progress') {
|
||||
roadmap.cmdRoadmapUpdatePlanProgress(cwd, args[2], raw);
|
||||
} else if (subcommand === 'annotate-dependencies') {
|
||||
roadmap.cmdRoadmapAnnotateDependencies(cwd, args[2], raw);
|
||||
} else {
|
||||
error('Unknown roadmap subcommand. Available: get-phase, analyze, update-plan-progress, annotate-dependencies');
|
||||
}
|
||||
routeRoadmapCommand({
|
||||
roadmap,
|
||||
args,
|
||||
cwd,
|
||||
raw,
|
||||
error,
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -731,42 +654,13 @@ async function runCommand(command, args, cwd, raw, defaultValue) {
|
||||
}
|
||||
|
||||
case 'phase': {
|
||||
const subcommand = args[1];
|
||||
if (subcommand === 'next-decimal') {
|
||||
phase.cmdPhaseNextDecimal(cwd, args[2], raw);
|
||||
} else if (subcommand === 'add') {
|
||||
const idIdx = args.indexOf('--id');
|
||||
let customId = null;
|
||||
const descArgs = [];
|
||||
for (let i = 2; i < args.length; i++) {
|
||||
if (args[i] === '--id' && i + 1 < args.length) {
|
||||
customId = args[i + 1];
|
||||
i++; // skip value
|
||||
} else {
|
||||
descArgs.push(args[i]);
|
||||
}
|
||||
}
|
||||
phase.cmdPhaseAdd(cwd, descArgs.join(' '), raw, customId);
|
||||
} else if (subcommand === 'add-batch') {
|
||||
// Accepts JSON array of descriptions via --descriptions '[...]' or positional args
|
||||
const descFlagIdx = args.indexOf('--descriptions');
|
||||
let descriptions;
|
||||
if (descFlagIdx !== -1 && args[descFlagIdx + 1]) {
|
||||
try { descriptions = JSON.parse(args[descFlagIdx + 1]); } catch (e) { error('--descriptions must be a JSON array'); }
|
||||
} else {
|
||||
descriptions = args.slice(2).filter(a => a !== '--raw');
|
||||
}
|
||||
phase.cmdPhaseAddBatch(cwd, descriptions, raw);
|
||||
} else if (subcommand === 'insert') {
|
||||
phase.cmdPhaseInsert(cwd, args[2], args.slice(3).join(' '), raw);
|
||||
} else if (subcommand === 'remove') {
|
||||
const forceFlag = args.includes('--force');
|
||||
phase.cmdPhaseRemove(cwd, args[2], { force: forceFlag }, raw);
|
||||
} else if (subcommand === 'complete') {
|
||||
phase.cmdPhaseComplete(cwd, args[2], raw);
|
||||
} else {
|
||||
error('Unknown phase subcommand. Available: next-decimal, add, add-batch, insert, remove, complete');
|
||||
}
|
||||
routePhaseCommand({
|
||||
phase,
|
||||
args,
|
||||
cwd,
|
||||
raw,
|
||||
error,
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -783,18 +677,15 @@ async function runCommand(command, args, cwd, raw, defaultValue) {
|
||||
}
|
||||
|
||||
case 'validate': {
|
||||
const subcommand = args[1];
|
||||
if (subcommand === 'consistency') {
|
||||
verify.cmdValidateConsistency(cwd, raw);
|
||||
} else if (subcommand === 'health') {
|
||||
const repairFlag = args.includes('--repair');
|
||||
const backfillFlag = args.includes('--backfill');
|
||||
verify.cmdValidateHealth(cwd, { repair: repairFlag, backfill: backfillFlag }, raw);
|
||||
} else if (subcommand === 'agents') {
|
||||
verify.cmdValidateAgents(cwd, raw);
|
||||
} else {
|
||||
error('Unknown validate subcommand. Available: consistency, health, agents');
|
||||
}
|
||||
routeValidateCommand({
|
||||
verify,
|
||||
args,
|
||||
cwd,
|
||||
raw,
|
||||
parseNamedArgs,
|
||||
output: core.output,
|
||||
error,
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -812,12 +703,15 @@ async function runCommand(command, args, cwd, raw, defaultValue) {
|
||||
|
||||
case 'audit-open': {
|
||||
const { auditOpenArtifacts, formatAuditReport } = require('./lib/audit.cjs');
|
||||
const includeRaw = args.includes('--json');
|
||||
const wantJson = args.includes('--json');
|
||||
const result = auditOpenArtifacts(cwd);
|
||||
if (includeRaw) {
|
||||
if (wantJson) {
|
||||
// core.output JSON-stringifies its first arg; pass the object directly.
|
||||
core.output(result, raw);
|
||||
} else {
|
||||
core.output(formatAuditReport(result), raw);
|
||||
// Human-readable report must bypass JSON encoding — use the rawValue
|
||||
// form (third arg) which core.output emits verbatim.
|
||||
core.output(null, true, formatAuditReport(result));
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -863,66 +757,14 @@ async function runCommand(command, args, cwd, raw, defaultValue) {
|
||||
}
|
||||
|
||||
case 'init': {
|
||||
const workflow = args[1];
|
||||
switch (workflow) {
|
||||
case 'execute-phase': {
|
||||
const { validate: epValidate, tdd: epTdd } = parseNamedArgs(args, [], ['validate', 'tdd']);
|
||||
init.cmdInitExecutePhase(cwd, args[2], raw, { validate: epValidate, tdd: epTdd });
|
||||
break;
|
||||
}
|
||||
case 'plan-phase': {
|
||||
const { validate: ppValidate, tdd: ppTdd } = parseNamedArgs(args, [], ['validate', 'tdd']);
|
||||
init.cmdInitPlanPhase(cwd, args[2], raw, { validate: ppValidate, tdd: ppTdd });
|
||||
break;
|
||||
}
|
||||
case 'new-project':
|
||||
init.cmdInitNewProject(cwd, raw);
|
||||
break;
|
||||
case 'new-milestone':
|
||||
init.cmdInitNewMilestone(cwd, raw);
|
||||
break;
|
||||
case 'quick':
|
||||
init.cmdInitQuick(cwd, args.slice(2).join(' '), raw);
|
||||
break;
|
||||
case 'ingest-docs':
|
||||
init.cmdInitIngestDocs(cwd, raw);
|
||||
break;
|
||||
case 'resume':
|
||||
init.cmdInitResume(cwd, raw);
|
||||
break;
|
||||
case 'verify-work':
|
||||
init.cmdInitVerifyWork(cwd, args[2], raw);
|
||||
break;
|
||||
case 'phase-op':
|
||||
init.cmdInitPhaseOp(cwd, args[2], raw);
|
||||
break;
|
||||
case 'todos':
|
||||
init.cmdInitTodos(cwd, args[2], raw);
|
||||
break;
|
||||
case 'milestone-op':
|
||||
init.cmdInitMilestoneOp(cwd, raw);
|
||||
break;
|
||||
case 'map-codebase':
|
||||
init.cmdInitMapCodebase(cwd, raw);
|
||||
break;
|
||||
case 'progress':
|
||||
init.cmdInitProgress(cwd, raw);
|
||||
break;
|
||||
case 'manager':
|
||||
init.cmdInitManager(cwd, raw);
|
||||
break;
|
||||
case 'new-workspace':
|
||||
init.cmdInitNewWorkspace(cwd, raw);
|
||||
break;
|
||||
case 'list-workspaces':
|
||||
init.cmdInitListWorkspaces(cwd, raw);
|
||||
break;
|
||||
case 'remove-workspace':
|
||||
init.cmdInitRemoveWorkspace(cwd, args[2], raw);
|
||||
break;
|
||||
default:
|
||||
error(`Unknown init workflow: ${workflow}\nAvailable: execute-phase, plan-phase, new-project, new-milestone, quick, ingest-docs, resume, verify-work, phase-op, todos, milestone-op, map-codebase, progress, manager, new-workspace, list-workspaces, remove-workspace`);
|
||||
}
|
||||
routeInitCommand({
|
||||
init,
|
||||
args,
|
||||
cwd,
|
||||
raw,
|
||||
parseNamedArgs,
|
||||
error,
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1228,6 +1070,7 @@ async function runCommand(command, args, cwd, raw, defaultValue) {
|
||||
'agents',
|
||||
path.join('commands', 'gsd'),
|
||||
'hooks',
|
||||
'skills',
|
||||
];
|
||||
|
||||
function walkDir(dir, baseDir) {
|
||||
|
||||
@@ -11,7 +11,8 @@
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { planningDir, toPosixPath } = require('./core.cjs');
|
||||
const { toPosixPath } = require('./core.cjs');
|
||||
const { planningDir } = require('./planning-workspace.cjs');
|
||||
const { extractFrontmatter } = require('./frontmatter.cjs');
|
||||
const { requireSafePath, sanitizeForDisplay } = require('./security.cjs');
|
||||
|
||||
|
||||
118
get-shit-done/bin/lib/command-aliases.generated.cjs
Normal file
118
get-shit-done/bin/lib/command-aliases.generated.cjs
Normal file
@@ -0,0 +1,118 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* GENERATED FILE — state.*, verify.*, init.*, phase.*, phases.*, validate.*, and roadmap.* alias/subcommand metadata for CJS routing.
|
||||
* Source: sdk/src/query/command-manifest.{state,verify,init,phase,phases,validate,roadmap}.ts
|
||||
*/
|
||||
|
||||
const STATE_COMMAND_ALIASES = [
|
||||
{ canonical: 'state.load', aliases: [], subcommand: 'load', mutation: false },
|
||||
{ canonical: 'state.json', aliases: ['state json'], subcommand: 'json', mutation: false },
|
||||
{ canonical: 'state.get', aliases: ['state get'], subcommand: 'get', mutation: false },
|
||||
{ canonical: 'state.update', aliases: ['state update'], subcommand: 'update', mutation: true },
|
||||
{ canonical: 'state.patch', aliases: ['state patch'], subcommand: 'patch', mutation: true },
|
||||
{ canonical: 'state.begin-phase', aliases: ['state begin-phase'], subcommand: 'begin-phase', mutation: true },
|
||||
{ canonical: 'state.advance-plan', aliases: ['state advance-plan'], subcommand: 'advance-plan', mutation: true },
|
||||
{ canonical: 'state.record-metric', aliases: ['state record-metric'], subcommand: 'record-metric', mutation: true },
|
||||
{ canonical: 'state.update-progress', aliases: ['state update-progress'], subcommand: 'update-progress', mutation: true },
|
||||
{ canonical: 'state.add-decision', aliases: ['state add-decision'], subcommand: 'add-decision', mutation: true },
|
||||
{ canonical: 'state.add-blocker', aliases: ['state add-blocker'], subcommand: 'add-blocker', mutation: true },
|
||||
{ canonical: 'state.resolve-blocker', aliases: ['state resolve-blocker'], subcommand: 'resolve-blocker', mutation: true },
|
||||
{ canonical: 'state.record-session', aliases: ['state record-session'], subcommand: 'record-session', mutation: true },
|
||||
{ canonical: 'state.signal-waiting', aliases: ['state signal-waiting'], subcommand: 'signal-waiting', mutation: true },
|
||||
{ canonical: 'state.signal-resume', aliases: ['state signal-resume'], subcommand: 'signal-resume', mutation: true },
|
||||
{ canonical: 'state.planned-phase', aliases: ['state planned-phase'], subcommand: 'planned-phase', mutation: true },
|
||||
{ canonical: 'state.validate', aliases: ['state validate'], subcommand: 'validate', mutation: false },
|
||||
{ canonical: 'state.sync', aliases: ['state sync'], subcommand: 'sync', mutation: true },
|
||||
{ canonical: 'state.prune', aliases: ['state prune'], subcommand: 'prune', mutation: true },
|
||||
{ canonical: 'state.milestone-switch', aliases: ['state milestone-switch'], subcommand: 'milestone-switch', mutation: true },
|
||||
{ canonical: 'state.add-roadmap-evolution', aliases: ['state add-roadmap-evolution'], subcommand: 'add-roadmap-evolution', mutation: true },
|
||||
];
|
||||
|
||||
const VERIFY_COMMAND_ALIASES = [
|
||||
{ canonical: 'verify.plan-structure', aliases: ['verify plan-structure'], subcommand: 'plan-structure', mutation: false },
|
||||
{ canonical: 'verify.phase-completeness', aliases: ['verify phase-completeness'], subcommand: 'phase-completeness', mutation: false },
|
||||
{ canonical: 'verify.references', aliases: ['verify references'], subcommand: 'references', mutation: false },
|
||||
{ canonical: 'verify.commits', aliases: ['verify commits'], subcommand: 'commits', mutation: false },
|
||||
{ canonical: 'verify.artifacts', aliases: ['verify artifacts'], subcommand: 'artifacts', mutation: false },
|
||||
{ canonical: 'verify.key-links', aliases: ['verify key-links'], subcommand: 'key-links', mutation: false },
|
||||
{ canonical: 'verify.schema-drift', aliases: ['verify schema-drift'], subcommand: 'schema-drift', mutation: false },
|
||||
{ canonical: 'verify.codebase-drift', aliases: ['verify codebase-drift'], subcommand: 'codebase-drift', mutation: false },
|
||||
];
|
||||
|
||||
const INIT_COMMAND_ALIASES = [
|
||||
{ canonical: 'init.execute-phase', aliases: ['init execute-phase'], subcommand: 'execute-phase', mutation: false },
|
||||
{ canonical: 'init.plan-phase', aliases: ['init plan-phase'], subcommand: 'plan-phase', mutation: false },
|
||||
{ canonical: 'init.new-project', aliases: ['init new-project'], subcommand: 'new-project', mutation: false },
|
||||
{ canonical: 'init.new-milestone', aliases: ['init new-milestone'], subcommand: 'new-milestone', mutation: false },
|
||||
{ canonical: 'init.quick', aliases: ['init quick'], subcommand: 'quick', mutation: false },
|
||||
{ canonical: 'init.ingest-docs', aliases: ['init ingest-docs'], subcommand: 'ingest-docs', mutation: false },
|
||||
{ canonical: 'init.resume', aliases: ['init resume'], subcommand: 'resume', mutation: false },
|
||||
{ canonical: 'init.verify-work', aliases: ['init verify-work'], subcommand: 'verify-work', mutation: false },
|
||||
{ canonical: 'init.phase-op', aliases: ['init phase-op'], subcommand: 'phase-op', mutation: false },
|
||||
{ canonical: 'init.todos', aliases: ['init todos'], subcommand: 'todos', mutation: false },
|
||||
{ canonical: 'init.milestone-op', aliases: ['init milestone-op'], subcommand: 'milestone-op', mutation: false },
|
||||
{ canonical: 'init.map-codebase', aliases: ['init map-codebase'], subcommand: 'map-codebase', mutation: false },
|
||||
{ canonical: 'init.progress', aliases: ['init progress'], subcommand: 'progress', mutation: false },
|
||||
{ canonical: 'init.manager', aliases: ['init manager'], subcommand: 'manager', mutation: false },
|
||||
{ canonical: 'init.new-workspace', aliases: ['init new-workspace'], subcommand: 'new-workspace', mutation: false },
|
||||
{ canonical: 'init.list-workspaces', aliases: ['init list-workspaces'], subcommand: 'list-workspaces', mutation: false },
|
||||
{ canonical: 'init.remove-workspace', aliases: ['init remove-workspace'], subcommand: 'remove-workspace', mutation: false },
|
||||
];
|
||||
|
||||
const PHASE_COMMAND_ALIASES = [
|
||||
{ canonical: 'phase.list-plans', aliases: ['phase list-plans'], subcommand: 'list-plans', mutation: false },
|
||||
{ canonical: 'phase.list-artifacts', aliases: ['phase list-artifacts'], subcommand: 'list-artifacts', mutation: false },
|
||||
{ canonical: 'phase.next-decimal', aliases: ['phase next-decimal'], subcommand: 'next-decimal', mutation: false },
|
||||
{ canonical: 'phase.add', aliases: ['phase add'], subcommand: 'add', mutation: true },
|
||||
{ canonical: 'phase.add-batch', aliases: ['phase add-batch'], subcommand: 'add-batch', mutation: true },
|
||||
{ canonical: 'phase.insert', aliases: ['phase insert'], subcommand: 'insert', mutation: true },
|
||||
{ canonical: 'phase.remove', aliases: ['phase remove'], subcommand: 'remove', mutation: true },
|
||||
{ canonical: 'phase.complete', aliases: ['phase complete'], subcommand: 'complete', mutation: true },
|
||||
{ canonical: 'phase.scaffold', aliases: ['phase scaffold'], subcommand: 'scaffold', mutation: true },
|
||||
];
|
||||
|
||||
const PHASES_COMMAND_ALIASES = [
|
||||
{ canonical: 'phases.list', aliases: ['phases list'], subcommand: 'list', mutation: false },
|
||||
{ canonical: 'phases.clear', aliases: ['phases clear'], subcommand: 'clear', mutation: true },
|
||||
{ canonical: 'phases.archive', aliases: ['phases archive'], subcommand: 'archive', mutation: true },
|
||||
];
|
||||
|
||||
const VALIDATE_COMMAND_ALIASES = [
|
||||
{ canonical: 'validate.consistency', aliases: ['validate consistency'], subcommand: 'consistency', mutation: false },
|
||||
{ canonical: 'validate.health', aliases: ['validate health'], subcommand: 'health', mutation: false },
|
||||
{ canonical: 'validate.agents', aliases: ['validate agents'], subcommand: 'agents', mutation: false },
|
||||
{ canonical: 'validate.context', aliases: ['validate context'], subcommand: 'context', mutation: false },
|
||||
];
|
||||
|
||||
const ROADMAP_COMMAND_ALIASES = [
|
||||
{ canonical: 'roadmap.analyze', aliases: ['roadmap analyze'], subcommand: 'analyze', mutation: false },
|
||||
{ canonical: 'roadmap.get-phase', aliases: ['roadmap get-phase'], subcommand: 'get-phase', mutation: false },
|
||||
{ canonical: 'roadmap.update-plan-progress', aliases: ['roadmap update-plan-progress'], subcommand: 'update-plan-progress', mutation: true },
|
||||
{ canonical: 'roadmap.annotate-dependencies', aliases: ['roadmap annotate-dependencies'], subcommand: 'annotate-dependencies', mutation: true },
|
||||
];
|
||||
|
||||
const STATE_SUBCOMMANDS = STATE_COMMAND_ALIASES.map((entry) => entry.subcommand);
|
||||
const VERIFY_SUBCOMMANDS = VERIFY_COMMAND_ALIASES.map((entry) => entry.subcommand);
|
||||
const INIT_SUBCOMMANDS = INIT_COMMAND_ALIASES.map((entry) => entry.subcommand);
|
||||
const PHASE_SUBCOMMANDS = PHASE_COMMAND_ALIASES.map((entry) => entry.subcommand);
|
||||
const PHASES_SUBCOMMANDS = PHASES_COMMAND_ALIASES.map((entry) => entry.subcommand);
|
||||
const VALIDATE_SUBCOMMANDS = VALIDATE_COMMAND_ALIASES.map((entry) => entry.subcommand);
|
||||
const ROADMAP_SUBCOMMANDS = ROADMAP_COMMAND_ALIASES.map((entry) => entry.subcommand);
|
||||
|
||||
module.exports = {
|
||||
STATE_COMMAND_ALIASES,
|
||||
VERIFY_COMMAND_ALIASES,
|
||||
INIT_COMMAND_ALIASES,
|
||||
PHASE_COMMAND_ALIASES,
|
||||
PHASES_COMMAND_ALIASES,
|
||||
VALIDATE_COMMAND_ALIASES,
|
||||
ROADMAP_COMMAND_ALIASES,
|
||||
STATE_SUBCOMMANDS,
|
||||
VERIFY_SUBCOMMANDS,
|
||||
INIT_SUBCOMMANDS,
|
||||
PHASE_SUBCOMMANDS,
|
||||
PHASES_SUBCOMMANDS,
|
||||
VALIDATE_SUBCOMMANDS,
|
||||
ROADMAP_SUBCOMMANDS,
|
||||
};
|
||||
@@ -4,7 +4,8 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { execSync } = require('child_process');
|
||||
const { safeReadFile, loadConfig, isGitIgnored, execGit, normalizePhaseName, comparePhaseNum, getArchivedPhaseDirs, generateSlugInternal, getMilestoneInfo, getMilestonePhaseFilter, resolveModelInternal, stripShippedMilestones, extractCurrentMilestone, planningDir, planningPaths, toPosixPath, output, error, findPhaseInternal, extractOneLinerFromBody, getRoadmapPhaseInternal } = require('./core.cjs');
|
||||
const { safeReadFile, loadConfig, isGitIgnored, execGit, normalizePhaseName, comparePhaseNum, getArchivedPhaseDirs, generateSlugInternal, getMilestoneInfo, getMilestonePhaseFilter, resolveModelInternal, stripShippedMilestones, extractCurrentMilestone, toPosixPath, output, error, findPhaseInternal, extractOneLinerFromBody, getRoadmapPhaseInternal } = require('./core.cjs');
|
||||
const { planningDir, planningPaths } = require('./planning-workspace.cjs');
|
||||
const { extractFrontmatter } = require('./frontmatter.cjs');
|
||||
const { MODEL_PROFILES } = require('./model-profiles.cjs');
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ const VALID_CONFIG_KEYS = new Set([
|
||||
'workflow.skip_discuss',
|
||||
'workflow.auto_prune_state',
|
||||
'workflow.use_worktrees',
|
||||
'workflow.worktree_skip_hooks',
|
||||
'workflow.code_review',
|
||||
'workflow.code_review_depth',
|
||||
'workflow.code_review_command',
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { output, error, planningDir, withPlanningLock, CONFIG_DEFAULTS, atomicWriteFileSync } = require('./core.cjs');
|
||||
const { output, error, CONFIG_DEFAULTS, atomicWriteFileSync } = require('./core.cjs');
|
||||
const { planningDir, withPlanningLock } = require('./planning-workspace.cjs');
|
||||
const {
|
||||
VALID_PROFILES,
|
||||
getAgentToModelMapForProfile,
|
||||
@@ -376,6 +377,15 @@ function cmdConfigSet(cwd, keyPath, value, raw) {
|
||||
output(setConfigValueResult, raw, `${keyPath}=${parsedValue}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Schema-level defaults for well-known config keys.
|
||||
* When a key is absent from config.json and no --default flag was supplied,
|
||||
* cmdConfigGet checks here before emitting "Key not found".
|
||||
*/
|
||||
const SCHEMA_DEFAULTS = {
|
||||
'context_window': 200000,
|
||||
};
|
||||
|
||||
function cmdConfigGet(cwd, keyPath, raw, defaultValue) {
|
||||
const configPath = path.join(planningDir(cwd), 'config.json');
|
||||
const hasDefault = defaultValue !== undefined;
|
||||
@@ -405,6 +415,11 @@ function cmdConfigGet(cwd, keyPath, raw, defaultValue) {
|
||||
for (const key of keys) {
|
||||
if (current === undefined || current === null || typeof current !== 'object') {
|
||||
if (hasDefault) { output(defaultValue, raw, String(defaultValue)); return; }
|
||||
if (Object.prototype.hasOwnProperty.call(SCHEMA_DEFAULTS, keyPath)) {
|
||||
const def = SCHEMA_DEFAULTS[keyPath];
|
||||
output(def, raw, String(def));
|
||||
return;
|
||||
}
|
||||
error(`Key not found: ${keyPath}`);
|
||||
}
|
||||
current = current[key];
|
||||
@@ -412,6 +427,11 @@ function cmdConfigGet(cwd, keyPath, raw, defaultValue) {
|
||||
|
||||
if (current === undefined) {
|
||||
if (hasDefault) { output(defaultValue, raw, String(defaultValue)); return; }
|
||||
if (Object.prototype.hasOwnProperty.call(SCHEMA_DEFAULTS, keyPath)) {
|
||||
const def = SCHEMA_DEFAULTS[keyPath];
|
||||
output(def, raw, String(def));
|
||||
return;
|
||||
}
|
||||
error(`Key not found: ${keyPath}`);
|
||||
}
|
||||
|
||||
|
||||
47
get-shit-done/bin/lib/context-utilization.cjs
Normal file
47
get-shit-done/bin/lib/context-utilization.cjs
Normal file
@@ -0,0 +1,47 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Context-utilization classifier for `gsd-health --context`.
|
||||
*
|
||||
* Pure function. Callers pass tokensUsed + contextWindow; the
|
||||
* classifier returns the percent and one of three states. Recommendation
|
||||
* strings are NOT in this module — formatting is the renderer's job
|
||||
* (see `validate context` in gsd-tools.cjs). That separation lets the
|
||||
* copy change without touching this module's tests.
|
||||
*
|
||||
* Thresholds:
|
||||
* < 60% healthy no action
|
||||
* 60–70% warning approaching the fracture zone
|
||||
* ≥ 70% critical reasoning quality may degrade
|
||||
*
|
||||
* State boundaries use the exact ratio. The displayed `percent` is
|
||||
* rounded for human reading and may differ from the boundary by ±1 in
|
||||
* edge cases (e.g. 59.999% displays as 60 but classifies as healthy).
|
||||
*/
|
||||
|
||||
const STATES = Object.freeze({
|
||||
HEALTHY: 'healthy',
|
||||
WARNING: 'warning',
|
||||
CRITICAL: 'critical',
|
||||
});
|
||||
|
||||
function classifyContextUtilization(tokensUsed, contextWindow) {
|
||||
if (!Number.isInteger(tokensUsed) || tokensUsed < 0) {
|
||||
throw new TypeError(`tokensUsed must be a non-negative integer, got: ${tokensUsed} (${typeof tokensUsed})`);
|
||||
}
|
||||
if (!Number.isInteger(contextWindow) || contextWindow <= 0) {
|
||||
throw new TypeError(`contextWindow must be a positive integer, got: ${contextWindow} (${typeof contextWindow})`);
|
||||
}
|
||||
|
||||
const ratio = Math.min(tokensUsed / contextWindow, 1);
|
||||
const percent = Math.min(Math.round(ratio * 100), 100);
|
||||
|
||||
let state;
|
||||
if (ratio < 0.60) state = STATES.HEALTHY;
|
||||
else if (ratio < 0.70) state = STATES.WARNING;
|
||||
else state = STATES.CRITICAL;
|
||||
|
||||
return { percent, state };
|
||||
}
|
||||
|
||||
module.exports = { classifyContextUtilization, STATES };
|
||||
@@ -5,37 +5,17 @@
|
||||
const fs = require('fs');
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
const crypto = require('crypto');
|
||||
const { execSync, execFileSync, spawnSync } = require('child_process');
|
||||
const { MODEL_PROFILES } = require('./model-profiles.cjs');
|
||||
|
||||
const WORKSTREAM_SESSION_ENV_KEYS = [
|
||||
'GSD_SESSION_KEY',
|
||||
'CODEX_THREAD_ID',
|
||||
'CLAUDE_SESSION_ID',
|
||||
'CLAUDE_CODE_SSE_PORT',
|
||||
'OPENCODE_SESSION_ID',
|
||||
'GEMINI_SESSION_ID',
|
||||
'CURSOR_SESSION_ID',
|
||||
'WINDSURF_SESSION_ID',
|
||||
'TERM_SESSION_ID',
|
||||
'WT_SESSION',
|
||||
'TMUX_PANE',
|
||||
'ZELLIJ_SESSION_NAME',
|
||||
];
|
||||
|
||||
let cachedControllingTtyToken = null;
|
||||
let didProbeControllingTtyToken = false;
|
||||
|
||||
// Track all .planning/.lock files held by this process so they can be removed
|
||||
// on exit. process.on('exit') fires even on process.exit(1), unlike try/finally
|
||||
// which is skipped when error() calls process.exit(1) inside a locked region (#1916).
|
||||
const _heldPlanningLocks = new Set();
|
||||
process.on('exit', () => {
|
||||
for (const lockPath of _heldPlanningLocks) {
|
||||
try { fs.unlinkSync(lockPath); } catch { /* already gone */ }
|
||||
}
|
||||
});
|
||||
// Compatibility shim: new imports should use planning-workspace.cjs directly.
|
||||
const {
|
||||
planningDir,
|
||||
planningRoot,
|
||||
planningPaths,
|
||||
withPlanningLock,
|
||||
getActiveWorkstream,
|
||||
setActiveWorkstream,
|
||||
} = require('./planning-workspace.cjs');
|
||||
|
||||
// ─── Path helpers ────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -804,304 +784,7 @@ function pruneOrphanedWorktrees(repoRoot) {
|
||||
return pruned;
|
||||
}
|
||||
|
||||
/**
|
||||
* Acquire a file-based lock for .planning/ writes.
|
||||
* Prevents concurrent worktrees from corrupting shared planning files.
|
||||
* Lock is auto-released after the callback completes.
|
||||
*/
|
||||
function withPlanningLock(cwd, fn) {
|
||||
const lockPath = path.join(planningDir(cwd), '.lock');
|
||||
const lockTimeout = 10000; // 10 seconds
|
||||
const retryDelay = 100;
|
||||
const start = Date.now();
|
||||
|
||||
// Ensure .planning/ exists
|
||||
try { fs.mkdirSync(planningDir(cwd), { recursive: true }); } catch { /* ok */ }
|
||||
|
||||
while (Date.now() - start < lockTimeout) {
|
||||
try {
|
||||
// Atomic create — fails if file exists
|
||||
fs.writeFileSync(lockPath, JSON.stringify({
|
||||
pid: process.pid,
|
||||
cwd,
|
||||
acquired: new Date().toISOString(),
|
||||
}), { flag: 'wx' });
|
||||
|
||||
// Register for exit-time cleanup so process.exit(1) inside a locked region
|
||||
// cannot leave a stale lock file (#1916).
|
||||
_heldPlanningLocks.add(lockPath);
|
||||
|
||||
// Lock acquired — run the function
|
||||
try {
|
||||
return fn();
|
||||
} finally {
|
||||
_heldPlanningLocks.delete(lockPath);
|
||||
try { fs.unlinkSync(lockPath); } catch { /* already released */ }
|
||||
}
|
||||
} catch (err) {
|
||||
if (err.code === 'EEXIST') {
|
||||
// Lock exists — check if stale (>30s old)
|
||||
try {
|
||||
const stat = fs.statSync(lockPath);
|
||||
if (Date.now() - stat.mtimeMs > 30000) {
|
||||
fs.unlinkSync(lockPath);
|
||||
continue; // retry
|
||||
}
|
||||
} catch { continue; }
|
||||
|
||||
// Wait and retry (cross-platform, no shell dependency)
|
||||
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 100);
|
||||
continue;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
// Timeout — force acquire (stale lock recovery)
|
||||
try { fs.unlinkSync(lockPath); } catch { /* ok */ }
|
||||
return fn();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the .planning directory path, project- and workstream-aware.
|
||||
*
|
||||
* Resolution order:
|
||||
* 1. If GSD_PROJECT is set (env var or explicit `project` arg), routes to
|
||||
* `.planning/{project}/` — supports multi-project workspaces where several
|
||||
* independent projects share a single `.planning/` root directory (e.g.,
|
||||
* an Obsidian vault or monorepo knowledge base used as a command center).
|
||||
* 2. If GSD_WORKSTREAM is set, routes to `.planning/workstreams/{ws}/`.
|
||||
* 3. Otherwise returns `.planning/`.
|
||||
*
|
||||
* GSD_PROJECT and GSD_WORKSTREAM can be combined:
|
||||
* `.planning/{project}/workstreams/{ws}/`
|
||||
*
|
||||
* @param {string} cwd - project root
|
||||
* @param {string} [ws] - explicit workstream name; if omitted, checks GSD_WORKSTREAM env var
|
||||
* @param {string} [project] - explicit project name; if omitted, checks GSD_PROJECT env var
|
||||
*/
|
||||
function planningDir(cwd, ws, project) {
|
||||
if (project === undefined) project = process.env.GSD_PROJECT || null;
|
||||
if (ws === undefined) ws = process.env.GSD_WORKSTREAM || null;
|
||||
|
||||
// Reject path separators and traversal components in project/workstream names
|
||||
const BAD_SEGMENT = /[/\\]|\.\./;
|
||||
if (project && BAD_SEGMENT.test(project)) {
|
||||
throw new Error(`GSD_PROJECT contains invalid path characters: ${project}`);
|
||||
}
|
||||
if (ws && BAD_SEGMENT.test(ws)) {
|
||||
throw new Error(`GSD_WORKSTREAM contains invalid path characters: ${ws}`);
|
||||
}
|
||||
|
||||
let base = path.join(cwd, '.planning');
|
||||
if (project) base = path.join(base, project);
|
||||
if (ws) base = path.join(base, 'workstreams', ws);
|
||||
return base;
|
||||
}
|
||||
|
||||
/** Always returns the root .planning/ path, ignoring workstreams and projects. For shared resources. */
|
||||
function planningRoot(cwd) {
|
||||
return path.join(cwd, '.planning');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get common .planning file paths, project-and-workstream-aware.
|
||||
*
|
||||
* All paths route through planningDir(cwd, ws), which honors the GSD_PROJECT
|
||||
* env var and active workstream. This matches loadConfig() above (line 256),
|
||||
* which has always read config.json via planningDir(cwd). Previously project
|
||||
* and config were resolved against the unrouted .planning/ root, which broke
|
||||
* `gsd-tools config-get` in multi-project layouts (the CRUD writers and the
|
||||
* reader pointed at different files).
|
||||
*/
|
||||
function planningPaths(cwd, ws) {
|
||||
const base = planningDir(cwd, ws);
|
||||
return {
|
||||
planning: base,
|
||||
state: path.join(base, 'STATE.md'),
|
||||
roadmap: path.join(base, 'ROADMAP.md'),
|
||||
project: path.join(base, 'PROJECT.md'),
|
||||
config: path.join(base, 'config.json'),
|
||||
phases: path.join(base, 'phases'),
|
||||
requirements: path.join(base, 'REQUIREMENTS.md'),
|
||||
};
|
||||
}
|
||||
|
||||
// ─── Active Workstream Detection ─────────────────────────────────────────────
|
||||
|
||||
function sanitizeWorkstreamSessionToken(value) {
|
||||
if (value === null || value === undefined) return null;
|
||||
const token = String(value).trim().replace(/[^a-zA-Z0-9._-]+/g, '_').replace(/^_+|_+$/g, '');
|
||||
return token ? token.slice(0, 160) : null;
|
||||
}
|
||||
|
||||
function probeControllingTtyToken() {
|
||||
if (didProbeControllingTtyToken) return cachedControllingTtyToken;
|
||||
didProbeControllingTtyToken = true;
|
||||
|
||||
// `tty` reads stdin. When stdin is already non-interactive, spawning it only
|
||||
// adds avoidable failures on the routing hot path and cannot reveal a stable token.
|
||||
if (!(process.stdin && process.stdin.isTTY)) {
|
||||
return cachedControllingTtyToken;
|
||||
}
|
||||
|
||||
try {
|
||||
const ttyPath = execFileSync('tty', [], {
|
||||
encoding: 'utf-8',
|
||||
stdio: ['inherit', 'pipe', 'ignore'],
|
||||
}).trim();
|
||||
if (ttyPath && ttyPath !== 'not a tty') {
|
||||
const token = sanitizeWorkstreamSessionToken(ttyPath.replace(/^\/dev\//, ''));
|
||||
if (token) cachedControllingTtyToken = `tty-${token}`;
|
||||
}
|
||||
} catch {}
|
||||
|
||||
return cachedControllingTtyToken;
|
||||
}
|
||||
|
||||
function getControllingTtyToken() {
|
||||
for (const envKey of ['TTY', 'SSH_TTY']) {
|
||||
const token = sanitizeWorkstreamSessionToken(process.env[envKey]);
|
||||
if (token) return `tty-${token.replace(/^dev_/, '')}`;
|
||||
}
|
||||
|
||||
return probeControllingTtyToken();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a deterministic session key for workstream-local routing.
|
||||
*
|
||||
* Order:
|
||||
* 1. Explicit runtime/session env vars (`GSD_SESSION_KEY`, `CODEX_THREAD_ID`, etc.)
|
||||
* 2. Terminal identity exposed via `TTY` or `SSH_TTY`
|
||||
* 3. One best-effort `tty` probe when stdin is interactive
|
||||
* 4. `null`, which tells callers to use the legacy shared pointer fallback
|
||||
*/
|
||||
function getWorkstreamSessionKey() {
|
||||
for (const envKey of WORKSTREAM_SESSION_ENV_KEYS) {
|
||||
const raw = process.env[envKey];
|
||||
const token = sanitizeWorkstreamSessionToken(raw);
|
||||
if (token) return `${envKey.toLowerCase().replace(/[^a-z0-9]+/g, '-')}-${token}`;
|
||||
}
|
||||
|
||||
return getControllingTtyToken();
|
||||
}
|
||||
|
||||
function getSessionScopedWorkstreamFile(cwd) {
|
||||
const sessionKey = getWorkstreamSessionKey();
|
||||
if (!sessionKey) return null;
|
||||
|
||||
// Use realpathSync.native so the hash is derived from the canonical filesystem
|
||||
// path. On Windows, path.resolve returns whatever case the caller supplied,
|
||||
// while realpathSync.native returns the case the OS recorded — they differ on
|
||||
// case-insensitive NTFS, producing different hashes and different tmpdir slots.
|
||||
// Fall back to path.resolve when the directory does not yet exist.
|
||||
let planningAbs;
|
||||
try {
|
||||
planningAbs = fs.realpathSync.native(planningRoot(cwd));
|
||||
} catch {
|
||||
planningAbs = path.resolve(planningRoot(cwd));
|
||||
}
|
||||
const projectId = crypto
|
||||
.createHash('sha1')
|
||||
.update(planningAbs)
|
||||
.digest('hex')
|
||||
.slice(0, 16);
|
||||
|
||||
const dirPath = path.join(os.tmpdir(), 'gsd-workstream-sessions', projectId);
|
||||
return {
|
||||
sessionKey,
|
||||
dirPath,
|
||||
filePath: path.join(dirPath, sessionKey),
|
||||
};
|
||||
}
|
||||
|
||||
function clearActiveWorkstreamPointer(filePath, cleanupDirPath) {
|
||||
try { fs.unlinkSync(filePath); } catch {}
|
||||
|
||||
// Session-scoped pointers for a repo share one tmp directory. Only remove it
|
||||
// when it is empty so clearing or self-healing one session never deletes siblings.
|
||||
// Explicitly check remaining entries rather than relying on rmdirSync throwing
|
||||
// ENOTEMPTY — that error is not raised reliably on Windows.
|
||||
if (cleanupDirPath) {
|
||||
try {
|
||||
const remaining = fs.readdirSync(cleanupDirPath);
|
||||
if (remaining.length === 0) {
|
||||
fs.rmdirSync(cleanupDirPath);
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pointer files are self-healing: invalid names or deleted-workstream pointers
|
||||
* are removed on read so the session falls back to `null` instead of carrying
|
||||
* silent stale state forward. Session-scoped callers may also prune an empty
|
||||
* per-project tmp directory; shared `.planning/active-workstream` callers do not.
|
||||
*/
|
||||
function readActiveWorkstreamPointer(filePath, cwd, cleanupDirPath = null) {
|
||||
try {
|
||||
const name = fs.readFileSync(filePath, 'utf-8').trim();
|
||||
if (!name || !/^[a-zA-Z0-9_-]+$/.test(name)) {
|
||||
clearActiveWorkstreamPointer(filePath, cleanupDirPath);
|
||||
return null;
|
||||
}
|
||||
const wsDir = path.join(planningRoot(cwd), 'workstreams', name);
|
||||
if (!fs.existsSync(wsDir)) {
|
||||
clearActiveWorkstreamPointer(filePath, cleanupDirPath);
|
||||
return null;
|
||||
}
|
||||
return name;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the active workstream name.
|
||||
*
|
||||
* Resolution priority:
|
||||
* 1. Session-scoped pointer (tmpdir) when the runtime exposes a stable session key
|
||||
* 2. Legacy shared `.planning/active-workstream` file when no session key is available
|
||||
*
|
||||
* The shared file is intentionally ignored when a session key exists so multiple
|
||||
* concurrent sessions do not overwrite each other's active workstream.
|
||||
*/
|
||||
function getActiveWorkstream(cwd) {
|
||||
const sessionScoped = getSessionScopedWorkstreamFile(cwd);
|
||||
if (sessionScoped) {
|
||||
return readActiveWorkstreamPointer(sessionScoped.filePath, cwd, sessionScoped.dirPath);
|
||||
}
|
||||
|
||||
const sharedFilePath = path.join(planningRoot(cwd), 'active-workstream');
|
||||
return readActiveWorkstreamPointer(sharedFilePath, cwd);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the active workstream. Pass null to clear.
|
||||
*
|
||||
* When a stable session key is available, this updates a tmpdir-backed
|
||||
* session-scoped pointer. Otherwise it falls back to the legacy shared
|
||||
* `.planning/active-workstream` file for backward compatibility.
|
||||
*/
|
||||
function setActiveWorkstream(cwd, name) {
|
||||
const sessionScoped = getSessionScopedWorkstreamFile(cwd);
|
||||
const filePath = sessionScoped
|
||||
? sessionScoped.filePath
|
||||
: path.join(planningRoot(cwd), 'active-workstream');
|
||||
|
||||
if (!name) {
|
||||
clearActiveWorkstreamPointer(filePath, sessionScoped ? sessionScoped.dirPath : null);
|
||||
return;
|
||||
}
|
||||
if (!/^[a-zA-Z0-9_-]+$/.test(name)) {
|
||||
throw new Error('Invalid workstream name: must be alphanumeric, hyphens, and underscores only');
|
||||
}
|
||||
|
||||
if (sessionScoped) {
|
||||
fs.mkdirSync(sessionScoped.dirPath, { recursive: true });
|
||||
}
|
||||
fs.writeFileSync(filePath, name + '\n', 'utf-8');
|
||||
}
|
||||
// ─── Planning workspace (pathing + active workstream + lock) moved to planning-workspace.cjs ───
|
||||
|
||||
// ─── Phase utilities ──────────────────────────────────────────────────────────
|
||||
|
||||
@@ -1610,6 +1293,16 @@ const RUNTIME_PROFILE_MAP = {
|
||||
sonnet: { model: 'claude-sonnet-4-6' },
|
||||
haiku: { model: 'claude-haiku-4-5' },
|
||||
},
|
||||
hermes: {
|
||||
// Hermes Agent is provider-agnostic; users pick any provider in ~/.hermes/config.yaml.
|
||||
// Defaults use OpenRouter slugs because (a) OpenRouter is Hermes' default provider and
|
||||
// (b) the same slugs resolve on OpenRouter, native Anthropic, and Copilot via Hermes'
|
||||
// aggregator-aware resolver. Users on a different provider override per-tier via
|
||||
// model_profile_overrides.hermes.{opus,sonnet,haiku} in .planning/config.json.
|
||||
opus: { model: 'anthropic/claude-opus-4-7' },
|
||||
sonnet: { model: 'anthropic/claude-sonnet-4-6' },
|
||||
haiku: { model: 'anthropic/claude-haiku-4-5' },
|
||||
},
|
||||
};
|
||||
|
||||
const RUNTIMES_WITH_REASONING_EFFORT = new Set(['codex']);
|
||||
@@ -1632,7 +1325,7 @@ const RUNTIME_OVERRIDE_TIERS = new Set(['opus', 'sonnet', 'haiku']);
|
||||
const KNOWN_RUNTIMES = new Set([
|
||||
'claude', 'codex', 'opencode', 'kilo', 'gemini', 'qwen',
|
||||
'copilot', 'cursor', 'windsurf', 'augment', 'trae', 'codebuddy',
|
||||
'antigravity', 'cline',
|
||||
'antigravity', 'cline', 'hermes',
|
||||
]);
|
||||
|
||||
const _warnedConfigKeys = new Set();
|
||||
@@ -2155,6 +1848,7 @@ module.exports = {
|
||||
toPosixPath,
|
||||
extractOneLinerFromBody,
|
||||
resolveWorktreeRoot,
|
||||
// Deprecated re-exports — prefer direct import from planning-workspace.cjs
|
||||
withPlanningLock,
|
||||
findProjectRoot,
|
||||
detectSubRepos,
|
||||
|
||||
@@ -16,7 +16,8 @@
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { planningPaths, planningDir, escapeRegex, output, error } = require('./core.cjs');
|
||||
const { escapeRegex, output, error } = require('./core.cjs');
|
||||
const { planningPaths, planningDir } = require('./planning-workspace.cjs');
|
||||
const { parseDecisions } = require('./decisions.cjs');
|
||||
|
||||
/**
|
||||
@@ -30,7 +31,10 @@ function parseRequirements(reqMd) {
|
||||
const out = [];
|
||||
const seen = new Set();
|
||||
|
||||
const checkboxRe = /^\s*-\s*\[[x ]\]\s*\*\*(REQ-[A-Za-z0-9_-]+)\*\*\s*(.*)$/gm;
|
||||
// Prefix-agnostic ID format: REQ-01, TST-01, BACK-07, INSP-04, etc.
|
||||
const ID_PATTERN = '[A-Z][A-Z0-9]*-[A-Za-z0-9_-]+';
|
||||
|
||||
const checkboxRe = new RegExp(`^\\s*-\\s*\\[[x ]\\]\\s*\\*\\*(${ID_PATTERN})\\*\\*\\s*(.*)$`, 'gm');
|
||||
let cm = checkboxRe.exec(reqMd);
|
||||
while (cm !== null) {
|
||||
const id = cm[1];
|
||||
@@ -41,15 +45,25 @@ function parseRequirements(reqMd) {
|
||||
cm = checkboxRe.exec(reqMd);
|
||||
}
|
||||
|
||||
const tableRe = /\|\s*(REQ-[A-Za-z0-9_-]+)\s*\|/g;
|
||||
let tm = tableRe.exec(reqMd);
|
||||
while (tm !== null) {
|
||||
const tableFirstCellRe = new RegExp(`^\\s*\\|\\s*(${ID_PATTERN})\\s*\\|`);
|
||||
const separatorRowRe = /^\s*\|[\s:|-]+\|\s*$/;
|
||||
const lines = reqMd.split(/\r?\n/);
|
||||
|
||||
for (let i = 0; i < lines.length; i += 1) {
|
||||
const line = lines[i];
|
||||
if (!line.includes('|')) continue;
|
||||
|
||||
// Skip markdown table separator rows and header rows immediately preceding them.
|
||||
if (separatorRowRe.test(line)) continue;
|
||||
if (i + 1 < lines.length && separatorRowRe.test(lines[i + 1])) continue;
|
||||
|
||||
const tm = tableFirstCellRe.exec(line);
|
||||
if (!tm) continue;
|
||||
const id = tm[1];
|
||||
if (!seen.has(id)) {
|
||||
seen.add(id);
|
||||
out.push({ id, text: '' });
|
||||
}
|
||||
tm = tableRe.exec(reqMd);
|
||||
}
|
||||
|
||||
return out;
|
||||
|
||||
70
get-shit-done/bin/lib/init-command-router.cjs
Normal file
70
get-shit-done/bin/lib/init-command-router.cjs
Normal file
@@ -0,0 +1,70 @@
|
||||
'use strict';
|
||||
|
||||
const { INIT_SUBCOMMANDS } = require('./command-aliases.generated.cjs');
|
||||
|
||||
function routeInitCommand({ init, args, cwd, raw, parseNamedArgs, error }) {
|
||||
const workflow = args[1];
|
||||
switch (workflow) {
|
||||
case 'execute-phase': {
|
||||
const { validate: epValidate, tdd: epTdd } = parseNamedArgs(args, [], ['validate', 'tdd']);
|
||||
init.cmdInitExecutePhase(cwd, args[2], raw, { validate: epValidate, tdd: epTdd });
|
||||
break;
|
||||
}
|
||||
case 'plan-phase': {
|
||||
const { validate: ppValidate, tdd: ppTdd } = parseNamedArgs(args, [], ['validate', 'tdd']);
|
||||
init.cmdInitPlanPhase(cwd, args[2], raw, { validate: ppValidate, tdd: ppTdd });
|
||||
break;
|
||||
}
|
||||
case 'new-project':
|
||||
init.cmdInitNewProject(cwd, raw);
|
||||
break;
|
||||
case 'new-milestone':
|
||||
init.cmdInitNewMilestone(cwd, raw);
|
||||
break;
|
||||
case 'quick':
|
||||
init.cmdInitQuick(cwd, args.slice(2).join(' '), raw);
|
||||
break;
|
||||
case 'ingest-docs':
|
||||
init.cmdInitIngestDocs(cwd, raw);
|
||||
break;
|
||||
case 'resume':
|
||||
init.cmdInitResume(cwd, raw);
|
||||
break;
|
||||
case 'verify-work':
|
||||
init.cmdInitVerifyWork(cwd, args[2], raw);
|
||||
break;
|
||||
case 'phase-op':
|
||||
init.cmdInitPhaseOp(cwd, args[2], raw);
|
||||
break;
|
||||
case 'todos':
|
||||
init.cmdInitTodos(cwd, args[2], raw);
|
||||
break;
|
||||
case 'milestone-op':
|
||||
init.cmdInitMilestoneOp(cwd, raw);
|
||||
break;
|
||||
case 'map-codebase':
|
||||
init.cmdInitMapCodebase(cwd, raw);
|
||||
break;
|
||||
case 'progress':
|
||||
init.cmdInitProgress(cwd, raw);
|
||||
break;
|
||||
case 'manager':
|
||||
init.cmdInitManager(cwd, raw);
|
||||
break;
|
||||
case 'new-workspace':
|
||||
init.cmdInitNewWorkspace(cwd, raw);
|
||||
break;
|
||||
case 'list-workspaces':
|
||||
init.cmdInitListWorkspaces(cwd, raw);
|
||||
break;
|
||||
case 'remove-workspace':
|
||||
init.cmdInitRemoveWorkspace(cwd, args[2], raw);
|
||||
break;
|
||||
default:
|
||||
error(`Unknown init workflow: ${workflow}\nAvailable: ${INIT_SUBCOMMANDS.join(', ')}`);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
routeInitCommand,
|
||||
};
|
||||
@@ -5,7 +5,8 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { execSync } = require('child_process');
|
||||
const { loadConfig, resolveModelInternal, findPhaseInternal, getRoadmapPhaseInternal, pathExistsInternal, generateSlugInternal, getMilestoneInfo, getMilestonePhaseFilter, stripShippedMilestones, extractCurrentMilestone, normalizePhaseName, planningPaths, planningDir, planningRoot, toPosixPath, output, error, checkAgentsInstalled, phaseTokenMatches } = require('./core.cjs');
|
||||
const { loadConfig, resolveModelInternal, findPhaseInternal, getRoadmapPhaseInternal, pathExistsInternal, generateSlugInternal, getMilestoneInfo, getMilestonePhaseFilter, stripShippedMilestones, extractCurrentMilestone, normalizePhaseName, toPosixPath, output, error, checkAgentsInstalled, phaseTokenMatches } = require('./core.cjs');
|
||||
const { planningPaths, planningDir, planningRoot } = require('./planning-workspace.cjs');
|
||||
|
||||
// Accept all bold/colon variants of the Requirements header (#2769):
|
||||
// **Requirements:** / **Requirements**: / **Requirements** : render the
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { escapeRegex, getMilestonePhaseFilter, extractOneLinerFromBody, normalizeMd, planningPaths, output, error, atomicWriteFileSync } = require('./core.cjs');
|
||||
const { escapeRegex, getMilestonePhaseFilter, extractOneLinerFromBody, normalizeMd, output, error, atomicWriteFileSync } = require('./core.cjs');
|
||||
const { planningPaths } = require('./planning-workspace.cjs');
|
||||
const { extractFrontmatter } = require('./frontmatter.cjs');
|
||||
const { writeStateMd, stateReplaceFieldWithFallback } = require('./state.cjs');
|
||||
|
||||
|
||||
49
get-shit-done/bin/lib/phase-command-router.cjs
Normal file
49
get-shit-done/bin/lib/phase-command-router.cjs
Normal file
@@ -0,0 +1,49 @@
|
||||
'use strict';
|
||||
|
||||
const { PHASE_SUBCOMMANDS } = require('./command-aliases.generated.cjs');
|
||||
|
||||
function routePhaseCommand({ phase, args, cwd, raw, error }) {
|
||||
const subcommand = args[1];
|
||||
|
||||
if (subcommand === 'next-decimal') {
|
||||
phase.cmdPhaseNextDecimal(cwd, args[2], raw);
|
||||
} else if (subcommand === 'add') {
|
||||
let customId = null;
|
||||
const descArgs = [];
|
||||
for (let i = 2; i < args.length; i++) {
|
||||
if (args[i] === '--id' && i + 1 < args.length) {
|
||||
customId = args[i + 1];
|
||||
i++;
|
||||
} else {
|
||||
descArgs.push(args[i]);
|
||||
}
|
||||
}
|
||||
phase.cmdPhaseAdd(cwd, descArgs.join(' '), raw, customId);
|
||||
} else if (subcommand === 'add-batch') {
|
||||
const descFlagIdx = args.indexOf('--descriptions');
|
||||
let descriptions;
|
||||
if (descFlagIdx !== -1 && args[descFlagIdx + 1]) {
|
||||
try {
|
||||
descriptions = JSON.parse(args[descFlagIdx + 1]);
|
||||
} catch {
|
||||
error('--descriptions must be a JSON array');
|
||||
}
|
||||
} else {
|
||||
descriptions = args.slice(2).filter(a => a !== '--raw');
|
||||
}
|
||||
phase.cmdPhaseAddBatch(cwd, descriptions, raw);
|
||||
} else if (subcommand === 'insert') {
|
||||
phase.cmdPhaseInsert(cwd, args[2], args.slice(3).join(' '), raw);
|
||||
} else if (subcommand === 'remove') {
|
||||
const forceFlag = args.includes('--force');
|
||||
phase.cmdPhaseRemove(cwd, args[2], { force: forceFlag }, raw);
|
||||
} else if (subcommand === 'complete') {
|
||||
phase.cmdPhaseComplete(cwd, args[2], raw);
|
||||
} else {
|
||||
error(`Unknown phase subcommand. Available: ${PHASE_SUBCOMMANDS.filter((s) => s !== 'list-plans' && s !== 'list-artifacts' && s !== 'scaffold').join(', ')}`);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
routePhaseCommand,
|
||||
};
|
||||
@@ -4,10 +4,52 @@
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { escapeRegex, loadConfig, normalizePhaseName, comparePhaseNum, findPhaseInternal, getArchivedPhaseDirs, generateSlugInternal, getMilestonePhaseFilter, stripShippedMilestones, extractCurrentMilestone, replaceInCurrentMilestone, toPosixPath, planningDir, withPlanningLock, output, error, readSubdirectories, phaseTokenMatches, atomicWriteFileSync } = require('./core.cjs');
|
||||
const { escapeRegex, loadConfig, normalizePhaseName, comparePhaseNum, findPhaseInternal, getArchivedPhaseDirs, generateSlugInternal, getMilestonePhaseFilter, stripShippedMilestones, extractCurrentMilestone, replaceInCurrentMilestone, toPosixPath, output, error, readSubdirectories, phaseTokenMatches, atomicWriteFileSync } = require('./core.cjs');
|
||||
const { planningDir, withPlanningLock } = require('./planning-workspace.cjs');
|
||||
const { extractFrontmatter } = require('./frontmatter.cjs');
|
||||
const { writeStateMd, readModifyWriteStateMd, stateExtractField, stateReplaceField, stateReplaceFieldWithFallback, updatePerformanceMetricsSection } = require('./state.cjs');
|
||||
|
||||
// #2893 — strict canonical filter: `{padded_phase}-{NN}-PLAN.md` or `PLAN.md`.
|
||||
// Documented in agents/gsd-planner.md (write_phase_prompt step). The wider
|
||||
// "looks like a plan but isn't canonical" probe below is used to surface a
|
||||
// loud warning instead of silently returning zero plans.
|
||||
const isCanonicalPlanFile = (f) => f.endsWith('-PLAN.md') || f === 'PLAN.md';
|
||||
|
||||
// Any .md file with PLAN anywhere in the basename — the diagnostic net for
|
||||
// catching agent deviations like `01-PLAN-01-foundation.md` (#2893).
|
||||
// Excludes derivative files (`-PLAN-OUTLINE.md`, `*.pre-bounce.md`, etc.) that
|
||||
// the planner legitimately produces alongside canonical plans.
|
||||
const PLAN_OUTLINE_RE = /-PLAN-OUTLINE\.md$/i;
|
||||
const PLAN_PRE_BOUNCE_RE = /-PLAN.*\.pre-bounce\.md$/i;
|
||||
const looksLikePlanFile = (f) =>
|
||||
/\.md$/i.test(f)
|
||||
&& /PLAN/i.test(f)
|
||||
&& !PLAN_OUTLINE_RE.test(f)
|
||||
&& !PLAN_PRE_BOUNCE_RE.test(f);
|
||||
|
||||
/**
|
||||
* Detect plan-shaped files that the canonical filter would reject. Returns
|
||||
* a warning string when offenders exist, else null. Centralised so every
|
||||
* read site (phase-plan-index, phases list --type plans, find-phase) emits
|
||||
* the same message.
|
||||
*
|
||||
* @param {string[]} dirFiles — readdirSync output for one phase directory
|
||||
* @param {string[]} matchedFiles — what the canonical filter accepted
|
||||
* @returns {string|null}
|
||||
*/
|
||||
function describeNonCanonicalPlans(dirFiles, matchedFiles) {
|
||||
const matched = new Set(matchedFiles);
|
||||
const offenders = dirFiles.filter((f) => looksLikePlanFile(f) && !matched.has(f));
|
||||
if (offenders.length === 0) return null;
|
||||
return (
|
||||
`Found ${offenders.length} plan-shaped file(s) in this phase that don't match the canonical ` +
|
||||
`naming convention "{padded_phase}-{NN}-PLAN.md" (or bare "PLAN.md") and were skipped: ` +
|
||||
offenders.map((f) => `"${f}"`).join(', ') +
|
||||
`. Rename to the canonical form (e.g. "01-01-PLAN.md") so the executor can detect them. ` +
|
||||
`See agents/gsd-planner.md write_phase_prompt step for the full contract.`
|
||||
);
|
||||
}
|
||||
|
||||
function cmdPhasesList(cwd, options, raw) {
|
||||
const phasesDir = path.join(planningDir(cwd), 'phases');
|
||||
const { type, phase, includeArchived } = options;
|
||||
@@ -52,13 +94,18 @@ function cmdPhasesList(cwd, options, raw) {
|
||||
// If listing files of a specific type
|
||||
if (type) {
|
||||
const files = [];
|
||||
const warnings = [];
|
||||
for (const dir of dirs) {
|
||||
const dirPath = path.join(phasesDir, dir);
|
||||
const dirFiles = fs.readdirSync(dirPath);
|
||||
|
||||
let filtered;
|
||||
if (type === 'plans') {
|
||||
filtered = dirFiles.filter(f => f.endsWith('-PLAN.md') || f === 'PLAN.md');
|
||||
filtered = dirFiles.filter(isCanonicalPlanFile);
|
||||
// #2893 — surface plan-shaped files the canonical filter rejected
|
||||
// so callers (executor init, etc.) don't silently see zero plans.
|
||||
const w = describeNonCanonicalPlans(dirFiles, filtered);
|
||||
if (w) warnings.push(`${dir}: ${w}`);
|
||||
} else if (type === 'summaries') {
|
||||
filtered = dirFiles.filter(f => f.endsWith('-SUMMARY.md') || f === 'SUMMARY.md');
|
||||
} else {
|
||||
@@ -73,6 +120,7 @@ function cmdPhasesList(cwd, options, raw) {
|
||||
count: files.length,
|
||||
phase_dir: phase ? dirs[0].replace(/^\d+(?:\.\d+)*-?/, '') : null,
|
||||
};
|
||||
if (warnings.length) result.warning = warnings.join(' | ');
|
||||
output(result, raw, files.join('\n'));
|
||||
return;
|
||||
}
|
||||
@@ -176,8 +224,10 @@ function cmdFindPhase(cwd, phase, raw) {
|
||||
|
||||
const phaseDir = path.join(phasesDir, match);
|
||||
const phaseFiles = fs.readdirSync(phaseDir);
|
||||
const plans = phaseFiles.filter(f => f.endsWith('-PLAN.md') || f === 'PLAN.md').sort();
|
||||
const plans = phaseFiles.filter(isCanonicalPlanFile).sort();
|
||||
const summaries = phaseFiles.filter(f => f.endsWith('-SUMMARY.md') || f === 'SUMMARY.md').sort();
|
||||
// #2893 — same diagnostic as phase-plan-index for consistency.
|
||||
const planNamingWarning = describeNonCanonicalPlans(phaseFiles, plans);
|
||||
|
||||
const result = {
|
||||
found: true,
|
||||
@@ -187,6 +237,7 @@ function cmdFindPhase(cwd, phase, raw) {
|
||||
plans,
|
||||
summaries,
|
||||
};
|
||||
if (planNamingWarning) result.warning = planNamingWarning;
|
||||
|
||||
output(result, raw, result.directory);
|
||||
} catch {
|
||||
@@ -229,8 +280,11 @@ function cmdPhasePlanIndex(cwd, phase, raw) {
|
||||
|
||||
// Get all files in phase directory
|
||||
const phaseFiles = fs.readdirSync(phaseDir);
|
||||
const planFiles = phaseFiles.filter(f => f.endsWith('-PLAN.md') || f === 'PLAN.md').sort();
|
||||
const planFiles = phaseFiles.filter(isCanonicalPlanFile).sort();
|
||||
const summaryFiles = phaseFiles.filter(f => f.endsWith('-SUMMARY.md') || f === 'SUMMARY.md');
|
||||
// #2893 — surface plan-shaped files the canonical filter rejected so a
|
||||
// misnamed plan never silently produces plan_count: 0 at executor init.
|
||||
const planNamingWarning = describeNonCanonicalPlans(phaseFiles, planFiles);
|
||||
|
||||
// Build set of plan IDs with summaries
|
||||
const completedPlanIds = new Set(
|
||||
@@ -305,6 +359,7 @@ function cmdPhasePlanIndex(cwd, phase, raw) {
|
||||
incomplete,
|
||||
has_checkpoints: hasCheckpoints,
|
||||
};
|
||||
if (planNamingWarning) result.warning = planNamingWarning;
|
||||
|
||||
output(result, raw);
|
||||
}
|
||||
|
||||
36
get-shit-done/bin/lib/phases-command-router.cjs
Normal file
36
get-shit-done/bin/lib/phases-command-router.cjs
Normal file
@@ -0,0 +1,36 @@
|
||||
'use strict';
|
||||
|
||||
const { PHASES_SUBCOMMANDS } = require('./command-aliases.generated.cjs');
|
||||
|
||||
/**
|
||||
* Manifest-backed phases subcommand router.
|
||||
* Keeps gsd-tools.cjs thin while preserving current CJS semantics:
|
||||
* - list
|
||||
* - clear
|
||||
*
|
||||
* Note: `archive` is currently SDK-only (`phases.archive` handler in SDK query
|
||||
* registry). CJS `gsd-tools phases` intentionally supports list/clear only.
|
||||
*/
|
||||
function routePhasesCommand({ phase, milestone, args, cwd, raw, error }) {
|
||||
const subcommand = args[1];
|
||||
|
||||
if (subcommand === 'list') {
|
||||
const typeIndex = args.indexOf('--type');
|
||||
const phaseIndex = args.indexOf('--phase');
|
||||
const options = {
|
||||
type: typeIndex !== -1 ? args[typeIndex + 1] : null,
|
||||
phase: phaseIndex !== -1 ? args[phaseIndex + 1] : null,
|
||||
includeArchived: args.includes('--include-archived'),
|
||||
};
|
||||
phase.cmdPhasesList(cwd, options, raw);
|
||||
} else if (subcommand === 'clear') {
|
||||
milestone.cmdPhasesClear(cwd, raw, args.slice(2));
|
||||
} else {
|
||||
const cjsSupported = PHASES_SUBCOMMANDS.filter((s) => s !== 'archive');
|
||||
error(`Unknown phases subcommand. Available: ${cjsSupported.join(', ')}`);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
routePhasesCommand,
|
||||
};
|
||||
371
get-shit-done/bin/lib/planning-workspace.cjs
Normal file
371
get-shit-done/bin/lib/planning-workspace.cjs
Normal file
@@ -0,0 +1,371 @@
|
||||
/**
|
||||
* Planning Workspace — .planning path resolution + active workstream routing.
|
||||
*
|
||||
* This module owns the planning workspace seam:
|
||||
* - planningDir/planningRoot/planningPaths
|
||||
* - active workstream pointer policy (session-scoped > shared)
|
||||
* - pointer storage adapters (session/shared/memory)
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
const crypto = require('crypto');
|
||||
const { execFileSync } = require('child_process');
|
||||
|
||||
const WORKSTREAM_SESSION_ENV_KEYS = [
|
||||
'GSD_SESSION_KEY',
|
||||
'CODEX_THREAD_ID',
|
||||
'CLAUDE_SESSION_ID',
|
||||
'CLAUDE_CODE_SSE_PORT',
|
||||
'OPENCODE_SESSION_ID',
|
||||
'GEMINI_SESSION_ID',
|
||||
'CURSOR_SESSION_ID',
|
||||
'WINDSURF_SESSION_ID',
|
||||
'TERM_SESSION_ID',
|
||||
'WT_SESSION',
|
||||
'TMUX_PANE',
|
||||
'ZELLIJ_SESSION_NAME',
|
||||
];
|
||||
|
||||
let cachedControllingTtyToken = null;
|
||||
let didProbeControllingTtyToken = false;
|
||||
|
||||
// Track .planning/.lock files held by this process so they can be removed on exit.
|
||||
const _heldPlanningLocks = new Set();
|
||||
process.on('exit', () => {
|
||||
for (const lockPath of _heldPlanningLocks) {
|
||||
try { fs.unlinkSync(lockPath); } catch { /* already gone */ }
|
||||
}
|
||||
});
|
||||
|
||||
function planningDir(cwd, ws, project) {
|
||||
if (project === undefined) project = process.env.GSD_PROJECT || null;
|
||||
if (ws === undefined) ws = process.env.GSD_WORKSTREAM || null;
|
||||
|
||||
// Reject path separators and traversal components in project/workstream names
|
||||
const BAD_SEGMENT = /[/\\]|\.\./;
|
||||
if (project && BAD_SEGMENT.test(project)) {
|
||||
throw new Error(`GSD_PROJECT contains invalid path characters: ${project}`);
|
||||
}
|
||||
if (ws && BAD_SEGMENT.test(ws)) {
|
||||
throw new Error(`GSD_WORKSTREAM contains invalid path characters: ${ws}`);
|
||||
}
|
||||
|
||||
let base = path.join(cwd, '.planning');
|
||||
if (project) base = path.join(base, project);
|
||||
if (ws) base = path.join(base, 'workstreams', ws);
|
||||
return base;
|
||||
}
|
||||
|
||||
function planningRoot(cwd) {
|
||||
return path.join(cwd, '.planning');
|
||||
}
|
||||
|
||||
function planningPaths(cwd, ws) {
|
||||
const base = planningDir(cwd, ws);
|
||||
return {
|
||||
planning: base,
|
||||
state: path.join(base, 'STATE.md'),
|
||||
roadmap: path.join(base, 'ROADMAP.md'),
|
||||
project: path.join(base, 'PROJECT.md'),
|
||||
config: path.join(base, 'config.json'),
|
||||
phases: path.join(base, 'phases'),
|
||||
requirements: path.join(base, 'REQUIREMENTS.md'),
|
||||
};
|
||||
}
|
||||
|
||||
function sanitizeWorkstreamSessionToken(value) {
|
||||
if (value === null || value === undefined) return null;
|
||||
const token = String(value).trim().replace(/[^a-zA-Z0-9._-]+/g, '_').replace(/^_+|_+$/g, '');
|
||||
return token ? token.slice(0, 160) : null;
|
||||
}
|
||||
|
||||
function probeControllingTtyToken() {
|
||||
if (didProbeControllingTtyToken) return cachedControllingTtyToken;
|
||||
didProbeControllingTtyToken = true;
|
||||
|
||||
// `tty` reads stdin. When stdin is already non-interactive, spawning it only
|
||||
// adds avoidable failures on the routing hot path and cannot reveal a stable token.
|
||||
if (!(process.stdin && process.stdin.isTTY)) {
|
||||
return cachedControllingTtyToken;
|
||||
}
|
||||
|
||||
try {
|
||||
const ttyPath = execFileSync('tty', [], {
|
||||
encoding: 'utf-8',
|
||||
stdio: ['inherit', 'pipe', 'ignore'],
|
||||
}).trim();
|
||||
if (ttyPath && ttyPath !== 'not a tty') {
|
||||
const token = sanitizeWorkstreamSessionToken(ttyPath.replace(/^\/dev\//, ''));
|
||||
if (token) cachedControllingTtyToken = `tty-${token}`;
|
||||
}
|
||||
} catch {}
|
||||
|
||||
return cachedControllingTtyToken;
|
||||
}
|
||||
|
||||
function getControllingTtyToken() {
|
||||
for (const envKey of ['TTY', 'SSH_TTY']) {
|
||||
const token = sanitizeWorkstreamSessionToken(process.env[envKey]);
|
||||
if (token) return `tty-${token.replace(/^dev_/, '')}`;
|
||||
}
|
||||
|
||||
return probeControllingTtyToken();
|
||||
}
|
||||
|
||||
function getWorkstreamSessionKey() {
|
||||
for (const envKey of WORKSTREAM_SESSION_ENV_KEYS) {
|
||||
const raw = process.env[envKey];
|
||||
const token = sanitizeWorkstreamSessionToken(raw);
|
||||
if (token) return `${envKey.toLowerCase().replace(/[^a-z0-9]+/g, '-')}-${token}`;
|
||||
}
|
||||
|
||||
return getControllingTtyToken();
|
||||
}
|
||||
|
||||
function getSessionScopedWorkstreamFile(cwd, fixedSessionKey) {
|
||||
const sessionKey = fixedSessionKey || getWorkstreamSessionKey();
|
||||
if (!sessionKey) return null;
|
||||
|
||||
// Use realpathSync.native so the hash is derived from the canonical filesystem
|
||||
// path. On Windows, path.resolve returns whatever case the caller supplied,
|
||||
// while realpathSync.native returns the case the OS recorded — they differ on
|
||||
// case-insensitive NTFS, producing different hashes and different tmpdir slots.
|
||||
// Fall back to path.resolve when the directory does not yet exist.
|
||||
let planningAbs;
|
||||
try {
|
||||
planningAbs = fs.realpathSync.native(planningRoot(cwd));
|
||||
} catch {
|
||||
planningAbs = path.resolve(planningRoot(cwd));
|
||||
}
|
||||
const projectId = crypto
|
||||
.createHash('sha1')
|
||||
.update(planningAbs)
|
||||
.digest('hex')
|
||||
.slice(0, 16);
|
||||
|
||||
const dirPath = path.join(os.tmpdir(), 'gsd-workstream-sessions', projectId);
|
||||
return {
|
||||
sessionKey,
|
||||
dirPath,
|
||||
filePath: path.join(dirPath, sessionKey),
|
||||
};
|
||||
}
|
||||
|
||||
function createSharedPointerAdapter(cwd) {
|
||||
const filePath = path.join(planningRoot(cwd), 'active-workstream');
|
||||
return {
|
||||
read() {
|
||||
try {
|
||||
return fs.readFileSync(filePath, 'utf-8').trim() || null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
write(name) {
|
||||
fs.writeFileSync(filePath, name + '\n', 'utf-8');
|
||||
},
|
||||
clear() {
|
||||
try { fs.unlinkSync(filePath); } catch {}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function createSessionScopedPointerAdapter(cwd, fixedSessionKey) {
|
||||
const scoped = getSessionScopedWorkstreamFile(cwd, fixedSessionKey);
|
||||
if (!scoped) return null;
|
||||
|
||||
return {
|
||||
read() {
|
||||
try {
|
||||
return fs.readFileSync(scoped.filePath, 'utf-8').trim() || null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
write(name) {
|
||||
fs.mkdirSync(scoped.dirPath, { recursive: true });
|
||||
fs.writeFileSync(scoped.filePath, name + '\n', 'utf-8');
|
||||
},
|
||||
clear() {
|
||||
try { fs.unlinkSync(scoped.filePath); } catch {}
|
||||
try {
|
||||
const remaining = fs.readdirSync(scoped.dirPath);
|
||||
if (remaining.length === 0) {
|
||||
fs.rmdirSync(scoped.dirPath);
|
||||
}
|
||||
} catch {}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function createMemoryPointerAdapter(initialName = null) {
|
||||
let value = initialName;
|
||||
return {
|
||||
read() {
|
||||
return value;
|
||||
},
|
||||
write(name) {
|
||||
value = name;
|
||||
},
|
||||
clear() {
|
||||
value = null;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function pickActiveWorkstreamAdapter(cwd, opts = {}) {
|
||||
if (opts.activeWorkstreamAdapter) {
|
||||
return opts.activeWorkstreamAdapter;
|
||||
}
|
||||
|
||||
const sessionKey = getWorkstreamSessionKey();
|
||||
if (sessionKey) {
|
||||
if (opts.activeWorkstreamAdapters && opts.activeWorkstreamAdapters.session) {
|
||||
return opts.activeWorkstreamAdapters.session;
|
||||
}
|
||||
return createSessionScopedPointerAdapter(cwd, sessionKey);
|
||||
}
|
||||
|
||||
if (opts.activeWorkstreamAdapters && opts.activeWorkstreamAdapters.shared) {
|
||||
return opts.activeWorkstreamAdapters.shared;
|
||||
}
|
||||
return createSharedPointerAdapter(cwd);
|
||||
}
|
||||
|
||||
function validateWorkstreamName(name) {
|
||||
return /^[a-zA-Z0-9_-]+$/.test(name);
|
||||
}
|
||||
|
||||
function withPlanningLock(cwd, fn) {
|
||||
const lockPath = path.join(planningDir(cwd), '.lock');
|
||||
const lockTimeout = 10000; // 10 seconds
|
||||
const start = Date.now();
|
||||
|
||||
// Ensure .planning/ exists
|
||||
try { fs.mkdirSync(planningDir(cwd), { recursive: true }); } catch { /* ok */ }
|
||||
|
||||
function runWithHeldLock() {
|
||||
// Atomic create — fails if file exists
|
||||
fs.writeFileSync(lockPath, JSON.stringify({
|
||||
pid: process.pid,
|
||||
cwd,
|
||||
acquired: new Date().toISOString(),
|
||||
}), { flag: 'wx' });
|
||||
|
||||
_heldPlanningLocks.add(lockPath);
|
||||
|
||||
// Lock acquired — run the function
|
||||
try {
|
||||
return fn();
|
||||
} finally {
|
||||
_heldPlanningLocks.delete(lockPath);
|
||||
try { fs.unlinkSync(lockPath); } catch { /* already released */ }
|
||||
}
|
||||
}
|
||||
|
||||
while (Date.now() - start < lockTimeout) {
|
||||
try {
|
||||
return runWithHeldLock();
|
||||
} catch (err) {
|
||||
if (err.code === 'EEXIST') {
|
||||
// Lock exists — check if stale (>30s old)
|
||||
try {
|
||||
const stat = fs.statSync(lockPath);
|
||||
if (Date.now() - stat.mtimeMs > 30000) {
|
||||
fs.unlinkSync(lockPath);
|
||||
continue; // retry
|
||||
}
|
||||
} catch { continue; }
|
||||
|
||||
// Wait and retry (cross-platform, no shell dependency)
|
||||
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 100);
|
||||
continue;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
// Timeout — stale-lock recovery, then re-acquire atomically before entering critical section.
|
||||
try { fs.unlinkSync(lockPath); } catch { /* ok */ }
|
||||
return runWithHeldLock();
|
||||
}
|
||||
|
||||
function createPlanningWorkspace(cwd, opts = {}) {
|
||||
return {
|
||||
paths: {
|
||||
dir(ws, project) {
|
||||
return planningDir(cwd, ws, project);
|
||||
},
|
||||
root() {
|
||||
return planningRoot(cwd);
|
||||
},
|
||||
all(ws) {
|
||||
return planningPaths(cwd, ws);
|
||||
},
|
||||
},
|
||||
activeWorkstream: {
|
||||
get() {
|
||||
const adapter = pickActiveWorkstreamAdapter(cwd, opts);
|
||||
if (!adapter) return null;
|
||||
|
||||
const name = adapter.read();
|
||||
if (!name || !validateWorkstreamName(name)) {
|
||||
adapter.clear();
|
||||
return null;
|
||||
}
|
||||
|
||||
const wsDir = path.join(planningRoot(cwd), 'workstreams', name);
|
||||
if (!fs.existsSync(wsDir)) {
|
||||
adapter.clear();
|
||||
return null;
|
||||
}
|
||||
|
||||
return name;
|
||||
},
|
||||
set(name) {
|
||||
const adapter = pickActiveWorkstreamAdapter(cwd, opts);
|
||||
if (!adapter) return;
|
||||
|
||||
if (!name) {
|
||||
adapter.clear();
|
||||
return;
|
||||
}
|
||||
if (!validateWorkstreamName(name)) {
|
||||
throw new Error('Invalid workstream name: must be alphanumeric, hyphens, and underscores only');
|
||||
}
|
||||
|
||||
const wsDir = path.join(planningRoot(cwd), 'workstreams', name);
|
||||
fs.mkdirSync(wsDir, { recursive: true });
|
||||
adapter.write(name);
|
||||
},
|
||||
clear() {
|
||||
const adapter = pickActiveWorkstreamAdapter(cwd, opts);
|
||||
if (!adapter) return;
|
||||
adapter.clear();
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function getActiveWorkstream(cwd) {
|
||||
return createPlanningWorkspace(cwd).activeWorkstream.get();
|
||||
}
|
||||
|
||||
function setActiveWorkstream(cwd, name) {
|
||||
createPlanningWorkspace(cwd).activeWorkstream.set(name);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
createPlanningWorkspace,
|
||||
createSharedPointerAdapter,
|
||||
createSessionScopedPointerAdapter,
|
||||
createMemoryPointerAdapter,
|
||||
planningDir,
|
||||
planningRoot,
|
||||
planningPaths,
|
||||
withPlanningLock,
|
||||
getActiveWorkstream,
|
||||
setActiveWorkstream,
|
||||
};
|
||||
23
get-shit-done/bin/lib/roadmap-command-router.cjs
Normal file
23
get-shit-done/bin/lib/roadmap-command-router.cjs
Normal file
@@ -0,0 +1,23 @@
|
||||
'use strict';
|
||||
|
||||
const { ROADMAP_SUBCOMMANDS } = require('./command-aliases.generated.cjs');
|
||||
|
||||
function routeRoadmapCommand({ roadmap, args, cwd, raw, error }) {
|
||||
const subcommand = args[1];
|
||||
|
||||
if (subcommand === 'get-phase') {
|
||||
roadmap.cmdRoadmapGetPhase(cwd, args[2], raw);
|
||||
} else if (subcommand === 'analyze') {
|
||||
roadmap.cmdRoadmapAnalyze(cwd, raw);
|
||||
} else if (subcommand === 'update-plan-progress') {
|
||||
roadmap.cmdRoadmapUpdatePlanProgress(cwd, args[2], raw);
|
||||
} else if (subcommand === 'annotate-dependencies') {
|
||||
roadmap.cmdRoadmapAnnotateDependencies(cwd, args[2], raw);
|
||||
} else {
|
||||
error(`Unknown roadmap subcommand. Available: ${ROADMAP_SUBCOMMANDS.join(', ')}`);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
routeRoadmapCommand,
|
||||
};
|
||||
@@ -4,7 +4,8 @@
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { escapeRegex, normalizePhaseName, planningPaths, withPlanningLock, output, error, findPhaseInternal, stripShippedMilestones, extractCurrentMilestone, replaceInCurrentMilestone, phaseTokenMatches, atomicWriteFileSync } = require('./core.cjs');
|
||||
const { escapeRegex, normalizePhaseName, output, error, findPhaseInternal, stripShippedMilestones, extractCurrentMilestone, replaceInCurrentMilestone, phaseTokenMatches, atomicWriteFileSync } = require('./core.cjs');
|
||||
const { planningPaths, withPlanningLock } = require('./planning-workspace.cjs');
|
||||
|
||||
/**
|
||||
* Coerce an arbitrary YAML scalar/object into a string for cross-cutting
|
||||
|
||||
90
get-shit-done/bin/lib/state-command-router.cjs
Normal file
90
get-shit-done/bin/lib/state-command-router.cjs
Normal file
@@ -0,0 +1,90 @@
|
||||
'use strict';
|
||||
|
||||
const { STATE_SUBCOMMANDS } = require('./command-aliases.generated.cjs');
|
||||
|
||||
/**
|
||||
* Manifest-backed state subcommand router.
|
||||
* Keeps gsd-tools.cjs thin while preserving existing command semantics.
|
||||
*/
|
||||
function routeStateCommand({ state, args, cwd, raw, parseNamedArgs, error }) {
|
||||
const subcommand = args[1];
|
||||
|
||||
if (subcommand === 'json') {
|
||||
state.cmdStateJson(cwd, raw);
|
||||
} else if (subcommand === 'update') {
|
||||
state.cmdStateUpdate(cwd, args[2], args[3]);
|
||||
} else if (subcommand === 'get') {
|
||||
state.cmdStateGet(cwd, args[2], raw);
|
||||
} else if (subcommand === 'patch') {
|
||||
const patches = {};
|
||||
for (let i = 2; i < args.length; i += 2) {
|
||||
const key = args[i].replace(/^--/, '');
|
||||
const value = args[i + 1];
|
||||
if (key && value !== undefined) {
|
||||
patches[key] = value;
|
||||
}
|
||||
}
|
||||
state.cmdStatePatch(cwd, patches, raw);
|
||||
} else if (subcommand === 'advance-plan') {
|
||||
state.cmdStateAdvancePlan(cwd, raw);
|
||||
} else if (subcommand === 'record-metric') {
|
||||
const { phase: p, plan, duration, tasks, files } = parseNamedArgs(args, ['phase', 'plan', 'duration', 'tasks', 'files']);
|
||||
state.cmdStateRecordMetric(cwd, { phase: p, plan, duration, tasks, files }, raw);
|
||||
} else if (subcommand === 'update-progress') {
|
||||
state.cmdStateUpdateProgress(cwd, raw);
|
||||
} else if (subcommand === 'add-decision') {
|
||||
const { phase: p, summary, 'summary-file': summary_file, rationale, 'rationale-file': rationale_file } = parseNamedArgs(args, ['phase', 'summary', 'summary-file', 'rationale', 'rationale-file']);
|
||||
state.cmdStateAddDecision(cwd, { phase: p, summary, summary_file, rationale: rationale || '', rationale_file }, raw);
|
||||
} else if (subcommand === 'add-blocker') {
|
||||
const { text, 'text-file': text_file } = parseNamedArgs(args, ['text', 'text-file']);
|
||||
state.cmdStateAddBlocker(cwd, { text, text_file }, raw);
|
||||
} else if (subcommand === 'resolve-blocker') {
|
||||
state.cmdStateResolveBlocker(cwd, parseNamedArgs(args, ['text']).text, raw);
|
||||
} else if (subcommand === 'record-session') {
|
||||
const { 'stopped-at': stopped_at, 'resume-file': resume_file } = parseNamedArgs(args, ['stopped-at', 'resume-file']);
|
||||
state.cmdStateRecordSession(cwd, { stopped_at, resume_file: resume_file || 'None' }, raw);
|
||||
} else if (subcommand === 'begin-phase') {
|
||||
const { phase: p, name, plans } = parseNamedArgs(args, ['phase', 'name', 'plans']);
|
||||
const parsedPlans = plans == null ? null : Number.parseInt(plans, 10);
|
||||
if (plans != null && Number.isNaN(parsedPlans)) {
|
||||
return error('Invalid --plans value. Expected an integer.');
|
||||
}
|
||||
state.cmdStateBeginPhase(cwd, p, name, parsedPlans, raw);
|
||||
} else if (subcommand === 'signal-waiting') {
|
||||
const { type, question, options, phase: p } = parseNamedArgs(args, ['type', 'question', 'options', 'phase']);
|
||||
state.cmdSignalWaiting(cwd, type, question, options, p, raw);
|
||||
} else if (subcommand === 'signal-resume') {
|
||||
state.cmdSignalResume(cwd, raw);
|
||||
} else if (subcommand === 'planned-phase') {
|
||||
const { phase: p, plans } = parseNamedArgs(args, ['phase', 'name', 'plans']);
|
||||
const parsedPlans = plans == null ? null : Number.parseInt(plans, 10);
|
||||
if (plans != null && Number.isNaN(parsedPlans)) {
|
||||
return error('Invalid --plans value. Expected an integer.');
|
||||
}
|
||||
state.cmdStatePlannedPhase(cwd, p, parsedPlans, raw);
|
||||
} else if (subcommand === 'validate') {
|
||||
state.cmdStateValidate(cwd, raw);
|
||||
} else if (subcommand === 'sync') {
|
||||
const { verify } = parseNamedArgs(args, [], ['verify']);
|
||||
state.cmdStateSync(cwd, { verify }, raw);
|
||||
} else if (subcommand === 'prune') {
|
||||
const { 'keep-recent': keepRecent, 'dry-run': dryRun } = parseNamedArgs(args, ['keep-recent'], ['dry-run']);
|
||||
state.cmdStatePrune(cwd, { keepRecent: keepRecent || '3', dryRun: !!dryRun }, raw);
|
||||
} else if (subcommand === 'complete-phase') {
|
||||
state.cmdStateCompletePhase(cwd, raw);
|
||||
} else if (subcommand === 'milestone-switch') {
|
||||
const { milestone, name } = parseNamedArgs(args, ['milestone', 'name']);
|
||||
state.cmdStateMilestoneSwitch(cwd, milestone, name, raw);
|
||||
} else if (subcommand === 'add-roadmap-evolution') {
|
||||
error('state add-roadmap-evolution is SDK-only. Use: gsd-sdk query state.add-roadmap-evolution ...');
|
||||
} else if (subcommand === undefined || subcommand === 'load') {
|
||||
state.cmdStateLoad(cwd, raw);
|
||||
} else {
|
||||
const available = ['load', 'complete-phase', ...STATE_SUBCOMMANDS.filter((s) => s !== 'load')];
|
||||
error(`Unknown state subcommand: "${subcommand}". Available: ${available.join(', ')}`);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
routeStateCommand,
|
||||
};
|
||||
@@ -4,7 +4,8 @@
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { escapeRegex, loadConfig, getMilestoneInfo, getMilestonePhaseFilter, normalizeMd, planningDir, planningPaths, output, error, atomicWriteFileSync } = require('./core.cjs');
|
||||
const { escapeRegex, loadConfig, getMilestoneInfo, getMilestonePhaseFilter, normalizeMd, output, error, atomicWriteFileSync } = require('./core.cjs');
|
||||
const { planningDir, planningPaths } = require('./planning-workspace.cjs');
|
||||
const { extractFrontmatter, reconstructFrontmatter } = require('./frontmatter.cjs');
|
||||
|
||||
// Cache disk scan results from buildStateFrontmatter per cwd per process (#1967).
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user