Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,13 @@ public/manifest.json
.claude/settings.local.json
.claude/scheduled_tasks.lock

# Local Specify development tools (keep outputs, ignore scripts/templates)
.specify/scripts/
.specify/templates/
# SpecKit harness scripts and templates ship with the repo so every contributor
# gets working /speckit.* commands without per-machine setup. The scripts under
# .specify/scripts/bash/ are vendored from upstream github/spec-kit with two
# ScriptHammer-specific adaptations:
# - common.sh recurses through features/<category>/<NNN-name>/ instead of flat specs/
# - create-new-feature.sh lands new dirs under features/_uncategorized/
# See .specify/scripts/bash/config.sh for the configurable bits.
plan
tasks
storybook-static/
Expand Down
91 changes: 91 additions & 0 deletions .specify/scripts/bash/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# SpecKit harness scripts

Bash scripts that back the `/speckit.*` slash commands defined in
`.claude/commands/speckit.*.md`. Sourced by Claude Code when running through
the SpecKit cascade defined in `.specify/memory/constitution.md` (v1.0.2,
Principle III).

## Files

| Script | Source | Called by |
| ---------------------------- | --------------------- | ------------------------------------------------------------------ |
| `check-prerequisites.sh` | upstream, verbatim | `/speckit.clarify`, `/speckit.tasks`, `/speckit.implement`, more |
| `common.sh` | upstream + 2 patches | sourced by all the other scripts |
| `config.sh` | ScriptHammer-specific | sourced by `common.sh` (top of file) |
| `create-new-feature.sh` | upstream + 2 patches | `/speckit.specify` |
| `setup-plan.sh` | upstream, verbatim | `/speckit.plan` |
| `setup-tasks.sh` | upstream, verbatim | (chained workflow; not direct slash command) |
| `update-agent-context.sh` | ScriptHammer stub | `/speckit.plan` Phase 1 step 3 |

Upstream: <https://github.com/github/spec-kit/tree/main/scripts/bash>

## ScriptHammer adaptations

Upstream SpecKit assumes flat `specs/<NNN-name>/` layout. ScriptHammer uses
`features/<category>/<NNN-name>/`. Two functions in `common.sh` were patched:

1. `find_feature_dir_by_prefix()` — recurses through
`${SPEC_KIT_FEATURES_ROOT}/*/<prefix>-*/` to match the nested layout.
Returns the absolute path including the `<category>` segment.

2. `get_current_branch()`'s non-git fallback — same recursion change
when scanning for the latest feature dir.

`create-new-feature.sh` was patched in two places:

1. `SPECS_DIR` resolution honors `SPEC_KIT_FEATURES_ROOT` (default `features`).
2. New feature dirs land under `${SPEC_KIT_FEATURES_ROOT}/${SPEC_KIT_DEFAULT_CATEGORY}/`
(default `features/_uncategorized/`). Humans triage from there into the right
category.
3. `get_highest_from_specs()` scans both flat and nested layouts so the
next-feature-number computation respects both patterns.

`update-agent-context.sh` is a deliberate no-op: ScriptHammer's `CLAUDE.md` is
hand-curated and not auto-rewritten.

## Configuration

Edit `.specify/scripts/bash/config.sh` to override:

- `SPEC_KIT_FEATURES_ROOT` (default `features`)
- `SPEC_KIT_DEFAULT_CATEGORY` (default `_uncategorized`)

Or override per-invocation:
```bash
SPEC_KIT_FEATURES_ROOT=specs ./create-new-feature.sh ... # acts like upstream
```

## Smoke test

```bash
# From repo root, with no feature branch checked out
SPECIFY_FEATURE=013-oauth-messaging-password \
.specify/scripts/bash/check-prerequisites.sh --json --paths-only
# Expected: JSON pointing at features/auth-oauth/013-oauth-messaging-password/

.specify/scripts/bash/create-new-feature.sh --dry-run --json \
--short-name "smoke-test" "Smoke test the harness"
# Expected: JSON with BRANCH_NAME 0XX-smoke-test, SPEC_FILE under _uncategorized/

.specify/scripts/bash/update-agent-context.sh claude
# Expected: stderr "[update-agent-context] No-op for ScriptHammer..."
```

## Updating from upstream

To pull a fresh upstream version (e.g., after a SpecKit release):

```bash
# From a fresh branch
for f in check-prerequisites.sh common.sh create-new-feature.sh setup-plan.sh setup-tasks.sh; do
curl -sSf "https://raw.githubusercontent.com/github/spec-kit/main/scripts/bash/$f" \
-o ".specify/scripts/bash/$f"
done
chmod +x .specify/scripts/bash/*.sh
```

Then re-apply the ScriptHammer adaptations listed above. The patches are small
(~50 lines total) and the diffs in git history show what to re-apply.

The `config.sh` and `update-agent-context.sh` files are ScriptHammer-only — never
overwrite them from upstream.
190 changes: 190 additions & 0 deletions .specify/scripts/bash/check-prerequisites.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
#!/usr/bin/env bash

# Consolidated prerequisite checking script
#
# This script provides unified prerequisite checking for Spec-Driven Development workflow.
# It replaces the functionality previously spread across multiple scripts.
#
# Usage: ./check-prerequisites.sh [OPTIONS]
#
# OPTIONS:
# --json Output in JSON format
# --require-tasks Require tasks.md to exist (for implementation phase)
# --include-tasks Include tasks.md in AVAILABLE_DOCS list
# --paths-only Only output path variables (no validation)
# --help, -h Show help message
#
# OUTPUTS:
# JSON mode: {"FEATURE_DIR":"...", "AVAILABLE_DOCS":["..."]}
# Text mode: FEATURE_DIR:... \n AVAILABLE_DOCS: \n ✓/✗ file.md
# Paths only: REPO_ROOT: ... \n BRANCH: ... \n FEATURE_DIR: ... etc.

set -e

# Parse command line arguments
JSON_MODE=false
REQUIRE_TASKS=false
INCLUDE_TASKS=false
PATHS_ONLY=false

for arg in "$@"; do
case "$arg" in
--json)
JSON_MODE=true
;;
--require-tasks)
REQUIRE_TASKS=true
;;
--include-tasks)
INCLUDE_TASKS=true
;;
--paths-only)
PATHS_ONLY=true
;;
--help|-h)
cat << 'EOF'
Usage: check-prerequisites.sh [OPTIONS]

Consolidated prerequisite checking for Spec-Driven Development workflow.

OPTIONS:
--json Output in JSON format
--require-tasks Require tasks.md to exist (for implementation phase)
--include-tasks Include tasks.md in AVAILABLE_DOCS list
--paths-only Only output path variables (no prerequisite validation)
--help, -h Show this help message

EXAMPLES:
# Check task prerequisites (plan.md required)
./check-prerequisites.sh --json

# Check implementation prerequisites (plan.md + tasks.md required)
./check-prerequisites.sh --json --require-tasks --include-tasks

# Get feature paths only (no validation)
./check-prerequisites.sh --paths-only

EOF
exit 0
;;
*)
echo "ERROR: Unknown option '$arg'. Use --help for usage information." >&2
exit 1
;;
esac
done

# Source common functions
SCRIPT_DIR="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/common.sh"

# Get feature paths and validate branch
_paths_output=$(get_feature_paths) || { echo "ERROR: Failed to resolve feature paths" >&2; exit 1; }
eval "$_paths_output"
unset _paths_output
check_feature_branch "$CURRENT_BRANCH" "$HAS_GIT" || exit 1

# If paths-only mode, output paths and exit (support JSON + paths-only combined)
if $PATHS_ONLY; then
if $JSON_MODE; then
# Minimal JSON paths payload (no validation performed)
if has_jq; then
jq -cn \
--arg repo_root "$REPO_ROOT" \
--arg branch "$CURRENT_BRANCH" \
--arg feature_dir "$FEATURE_DIR" \
--arg feature_spec "$FEATURE_SPEC" \
--arg impl_plan "$IMPL_PLAN" \
--arg tasks "$TASKS" \
'{REPO_ROOT:$repo_root,BRANCH:$branch,FEATURE_DIR:$feature_dir,FEATURE_SPEC:$feature_spec,IMPL_PLAN:$impl_plan,TASKS:$tasks}'
else
printf '{"REPO_ROOT":"%s","BRANCH":"%s","FEATURE_DIR":"%s","FEATURE_SPEC":"%s","IMPL_PLAN":"%s","TASKS":"%s"}\n' \
"$(json_escape "$REPO_ROOT")" "$(json_escape "$CURRENT_BRANCH")" "$(json_escape "$FEATURE_DIR")" "$(json_escape "$FEATURE_SPEC")" "$(json_escape "$IMPL_PLAN")" "$(json_escape "$TASKS")"
fi
else
echo "REPO_ROOT: $REPO_ROOT"
echo "BRANCH: $CURRENT_BRANCH"
echo "FEATURE_DIR: $FEATURE_DIR"
echo "FEATURE_SPEC: $FEATURE_SPEC"
echo "IMPL_PLAN: $IMPL_PLAN"
echo "TASKS: $TASKS"
fi
exit 0
fi

# Validate required directories and files
if [[ ! -d "$FEATURE_DIR" ]]; then
echo "ERROR: Feature directory not found: $FEATURE_DIR" >&2
echo "Run /speckit.specify first to create the feature structure." >&2
exit 1
fi

if [[ ! -f "$IMPL_PLAN" ]]; then
echo "ERROR: plan.md not found in $FEATURE_DIR" >&2
echo "Run /speckit.plan first to create the implementation plan." >&2
exit 1
fi

# Check for tasks.md if required
if $REQUIRE_TASKS && [[ ! -f "$TASKS" ]]; then
echo "ERROR: tasks.md not found in $FEATURE_DIR" >&2
echo "Run /speckit.tasks first to create the task list." >&2
exit 1
fi

# Build list of available documents
docs=()

# Always check these optional docs
[[ -f "$RESEARCH" ]] && docs+=("research.md")
[[ -f "$DATA_MODEL" ]] && docs+=("data-model.md")

# Check contracts directory (only if it exists and has files)
if [[ -d "$CONTRACTS_DIR" ]] && [[ -n "$(ls -A "$CONTRACTS_DIR" 2>/dev/null)" ]]; then
docs+=("contracts/")
fi

[[ -f "$QUICKSTART" ]] && docs+=("quickstart.md")

# Include tasks.md if requested and it exists
if $INCLUDE_TASKS && [[ -f "$TASKS" ]]; then
docs+=("tasks.md")
fi

# Output results
if $JSON_MODE; then
# Build JSON array of documents
if has_jq; then
if [[ ${#docs[@]} -eq 0 ]]; then
json_docs="[]"
else
json_docs=$(printf '%s\n' "${docs[@]}" | jq -R . | jq -s .)
fi
jq -cn \
--arg feature_dir "$FEATURE_DIR" \
--argjson docs "$json_docs" \
'{FEATURE_DIR:$feature_dir,AVAILABLE_DOCS:$docs}'
else
if [[ ${#docs[@]} -eq 0 ]]; then
json_docs="[]"
else
json_docs=$(for d in "${docs[@]}"; do printf '"%s",' "$(json_escape "$d")"; done)
json_docs="[${json_docs%,}]"
fi
printf '{"FEATURE_DIR":"%s","AVAILABLE_DOCS":%s}\n' "$(json_escape "$FEATURE_DIR")" "$json_docs"
fi
else
# Text output
echo "FEATURE_DIR:$FEATURE_DIR"
echo "AVAILABLE_DOCS:"

# Show status of each potential document
check_file "$RESEARCH" "research.md"
check_file "$DATA_MODEL" "data-model.md"
check_dir "$CONTRACTS_DIR" "contracts/"
check_file "$QUICKSTART" "quickstart.md"

if $INCLUDE_TASKS; then
check_file "$TASKS" "tasks.md"
fi
fi
Loading
Loading