Skip to content

Commit 189ac56

Browse files
committed
Terse fork: cut commands ~80%, add strict line limits, generic constitution
Original spec-kit: ~1,400 lines across 9 commands. This fork: ~250 lines. Spec ≤50, plan ≤50, tasks ≤40. Bash scripts from battle-tested Lexgo setup (project-agnostic). Generic constitution template for per-project customization.
1 parent 76cca34 commit 189ac56

15 files changed

Lines changed: 379 additions & 2338 deletions

README.md

Lines changed: 31 additions & 640 deletions
Large diffs are not rendered by default.

scripts/bash/check-prerequisites.sh

100644100755
Lines changed: 25 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -1,166 +1,61 @@
11
#!/usr/bin/env bash
2-
3-
# Consolidated prerequisite checking script
4-
#
5-
# This script provides unified prerequisite checking for Spec-Driven Development workflow.
6-
# It replaces the functionality previously spread across multiple scripts.
7-
#
8-
# Usage: ./check-prerequisites.sh [OPTIONS]
9-
#
10-
# OPTIONS:
11-
# --json Output in JSON format
12-
# --require-tasks Require tasks.md to exist (for implementation phase)
13-
# --include-tasks Include tasks.md in AVAILABLE_DOCS list
14-
# --paths-only Only output path variables (no validation)
15-
# --help, -h Show help message
16-
#
17-
# OUTPUTS:
18-
# JSON mode: {"FEATURE_DIR":"...", "AVAILABLE_DOCS":["..."]}
19-
# Text mode: FEATURE_DIR:... \n AVAILABLE_DOCS: \n ✓/✗ file.md
20-
# Paths only: REPO_ROOT: ... \n BRANCH: ... \n FEATURE_DIR: ... etc.
2+
# Check prerequisites for Speckit workflow phases
213

224
set -e
235

24-
# Parse command line arguments
256
JSON_MODE=false
7+
REQUIRE_PLAN=false
268
REQUIRE_TASKS=false
27-
INCLUDE_TASKS=false
28-
PATHS_ONLY=false
299

3010
for arg in "$@"; do
3111
case "$arg" in
32-
--json)
33-
JSON_MODE=true
34-
;;
35-
--require-tasks)
36-
REQUIRE_TASKS=true
37-
;;
38-
--include-tasks)
39-
INCLUDE_TASKS=true
40-
;;
41-
--paths-only)
42-
PATHS_ONLY=true
43-
;;
12+
--json) JSON_MODE=true ;;
13+
--require-plan) REQUIRE_PLAN=true ;;
14+
--require-tasks) REQUIRE_TASKS=true ;;
4415
--help|-h)
45-
cat << 'EOF'
46-
Usage: check-prerequisites.sh [OPTIONS]
47-
48-
Consolidated prerequisite checking for Spec-Driven Development workflow.
49-
50-
OPTIONS:
51-
--json Output in JSON format
52-
--require-tasks Require tasks.md to exist (for implementation phase)
53-
--include-tasks Include tasks.md in AVAILABLE_DOCS list
54-
--paths-only Only output path variables (no prerequisite validation)
55-
--help, -h Show this help message
56-
57-
EXAMPLES:
58-
# Check task prerequisites (plan.md required)
59-
./check-prerequisites.sh --json
60-
61-
# Check implementation prerequisites (plan.md + tasks.md required)
62-
./check-prerequisites.sh --json --require-tasks --include-tasks
63-
64-
# Get feature paths only (no validation)
65-
./check-prerequisites.sh --paths-only
66-
67-
EOF
16+
echo "Usage: $0 [--json] [--require-plan] [--require-tasks]"
17+
echo " --json Output JSON format"
18+
echo " --require-plan Require plan.md exists"
19+
echo " --require-tasks Require tasks.md exists"
6820
exit 0
6921
;;
70-
*)
71-
echo "ERROR: Unknown option '$arg'. Use --help for usage information." >&2
72-
exit 1
73-
;;
7422
esac
7523
done
7624

77-
# Source common functions
25+
# Load common functions
7826
SCRIPT_DIR="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
7927
source "$SCRIPT_DIR/common.sh"
8028

81-
# Get feature paths and validate branch
29+
# Get paths
8230
eval $(get_feature_paths)
8331
check_feature_branch "$CURRENT_BRANCH" "$HAS_GIT" || exit 1
8432

85-
# If paths-only mode, output paths and exit (support JSON + paths-only combined)
86-
if $PATHS_ONLY; then
87-
if $JSON_MODE; then
88-
# Minimal JSON paths payload (no validation performed)
89-
printf '{"REPO_ROOT":"%s","BRANCH":"%s","FEATURE_DIR":"%s","FEATURE_SPEC":"%s","IMPL_PLAN":"%s","TASKS":"%s"}\n' \
90-
"$REPO_ROOT" "$CURRENT_BRANCH" "$FEATURE_DIR" "$FEATURE_SPEC" "$IMPL_PLAN" "$TASKS"
91-
else
92-
echo "REPO_ROOT: $REPO_ROOT"
93-
echo "BRANCH: $CURRENT_BRANCH"
94-
echo "FEATURE_DIR: $FEATURE_DIR"
95-
echo "FEATURE_SPEC: $FEATURE_SPEC"
96-
echo "IMPL_PLAN: $IMPL_PLAN"
97-
echo "TASKS: $TASKS"
98-
fi
99-
exit 0
100-
fi
101-
102-
# Validate required directories and files
33+
# Check feature directory
10334
if [[ ! -d "$FEATURE_DIR" ]]; then
10435
echo "ERROR: Feature directory not found: $FEATURE_DIR" >&2
105-
echo "Run /speckit.specify first to create the feature structure." >&2
36+
echo "Run /speckit.specify first." >&2
10637
exit 1
10738
fi
10839

109-
if [[ ! -f "$IMPL_PLAN" ]]; then
110-
echo "ERROR: plan.md not found in $FEATURE_DIR" >&2
111-
echo "Run /speckit.plan first to create the implementation plan." >&2
40+
# Check required files
41+
if $REQUIRE_PLAN && [[ ! -f "$IMPL_PLAN" ]]; then
42+
echo "ERROR: plan.md not found. Run /speckit.plan first." >&2
11243
exit 1
11344
fi
11445

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

122-
# Build list of available documents
123-
docs=()
124-
125-
# Always check these optional docs
126-
[[ -f "$RESEARCH" ]] && docs+=("research.md")
127-
[[ -f "$DATA_MODEL" ]] && docs+=("data-model.md")
128-
129-
# Check contracts directory (only if it exists and has files)
130-
if [[ -d "$CONTRACTS_DIR" ]] && [[ -n "$(ls -A "$CONTRACTS_DIR" 2>/dev/null)" ]]; then
131-
docs+=("contracts/")
132-
fi
133-
134-
[[ -f "$QUICKSTART" ]] && docs+=("quickstart.md")
135-
136-
# Include tasks.md if requested and it exists
137-
if $INCLUDE_TASKS && [[ -f "$TASKS" ]]; then
138-
docs+=("tasks.md")
139-
fi
140-
141-
# Output results
51+
# Output
14252
if $JSON_MODE; then
143-
# Build JSON array of documents
144-
if [[ ${#docs[@]} -eq 0 ]]; then
145-
json_docs="[]"
146-
else
147-
json_docs=$(printf '"%s",' "${docs[@]}")
148-
json_docs="[${json_docs%,}]"
149-
fi
150-
151-
printf '{"FEATURE_DIR":"%s","AVAILABLE_DOCS":%s}\n' "$FEATURE_DIR" "$json_docs"
53+
printf '{"FEATURE_DIR":"%s","SPEC":"%s","PLAN":"%s","TASKS":"%s","BRANCH":"%s"}\n' \
54+
"$FEATURE_DIR" "$FEATURE_SPEC" "$IMPL_PLAN" "$TASKS" "$CURRENT_BRANCH"
15255
else
153-
# Text output
154-
echo "FEATURE_DIR:$FEATURE_DIR"
155-
echo "AVAILABLE_DOCS:"
156-
157-
# Show status of each potential document
158-
check_file "$RESEARCH" "research.md"
159-
check_file "$DATA_MODEL" "data-model.md"
160-
check_dir "$CONTRACTS_DIR" "contracts/"
161-
check_file "$QUICKSTART" "quickstart.md"
162-
163-
if $INCLUDE_TASKS; then
164-
check_file "$TASKS" "tasks.md"
165-
fi
56+
echo "FEATURE_DIR: $FEATURE_DIR"
57+
echo "BRANCH: $CURRENT_BRANCH"
58+
check_file "$FEATURE_SPEC" "spec.md"
59+
check_file "$IMPL_PLAN" "plan.md"
60+
check_file "$TASKS" "tasks.md"
16661
fi

scripts/bash/common.sh

100644100755
Lines changed: 21 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,31 @@
11
#!/usr/bin/env bash
2-
# Common functions and variables for all scripts
2+
# Common functions for Speckit scripts
33

4-
# Get repository root, with fallback for non-git repositories
4+
# Get repository root
55
get_repo_root() {
66
if git rev-parse --show-toplevel >/dev/null 2>&1; then
77
git rev-parse --show-toplevel
88
else
9-
# Fall back to script location for non-git repos
109
local script_dir="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
1110
(cd "$script_dir/../../.." && pwd)
1211
fi
1312
}
1413

15-
# Get current branch, with fallback for non-git repositories
14+
# Get current branch
1615
get_current_branch() {
17-
# First check if SPECIFY_FEATURE environment variable is set
16+
# Check SPECIFY_FEATURE env var first
1817
if [[ -n "${SPECIFY_FEATURE:-}" ]]; then
1918
echo "$SPECIFY_FEATURE"
2019
return
2120
fi
2221

23-
# Then check git if available
22+
# Then check git
2423
if git rev-parse --abbrev-ref HEAD >/dev/null 2>&1; then
2524
git rev-parse --abbrev-ref HEAD
2625
return
2726
fi
2827

29-
# For non-git repos, try to find the latest feature directory
28+
# Fallback: find latest feature directory
3029
local repo_root=$(get_repo_root)
3130
local specs_dir="$repo_root/specs"
3231

@@ -37,9 +36,9 @@ get_current_branch() {
3736
for dir in "$specs_dir"/*; do
3837
if [[ -d "$dir" ]]; then
3938
local dirname=$(basename "$dir")
40-
if [[ "$dirname" =~ ^([0-9]{3})- ]]; then
39+
# Match issue-based naming: N-name or N-M-name (any number length)
40+
if [[ "$dirname" =~ ^([0-9]+)(-[0-9]+)?- ]]; then
4141
local number=${BASH_REMATCH[1]}
42-
number=$((10#$number))
4342
if [[ "$number" -gt "$highest" ]]; then
4443
highest=$number
4544
latest_feature=$dirname
@@ -54,52 +53,49 @@ get_current_branch() {
5453
fi
5554
fi
5655

57-
echo "main" # Final fallback
56+
echo "main"
5857
}
5958

60-
# Check if we have git available
6159
has_git() {
6260
git rev-parse --show-toplevel >/dev/null 2>&1
6361
}
6462

63+
# Check if branch follows issue-based naming: N-name or N-M-name
6564
check_feature_branch() {
6665
local branch="$1"
6766
local has_git_repo="$2"
6867

69-
# For non-git repos, we can't enforce branch naming but still provide output
7068
if [[ "$has_git_repo" != "true" ]]; then
71-
echo "[specify] Warning: Git repository not detected; skipped branch validation" >&2
69+
echo "[speckit] Warning: Git not detected; skipped branch validation" >&2
7270
return 0
7371
fi
7472

75-
if [[ ! "$branch" =~ ^[0-9]{3}- ]]; then
73+
# Match: 1223-name or 1225-1-name (issue number, optional sub-issue, then name)
74+
if [[ ! "$branch" =~ ^[0-9]+(-[0-9]+)?- ]]; then
7675
echo "ERROR: Not on a feature branch. Current branch: $branch" >&2
77-
echo "Feature branches should be named like: 001-feature-name" >&2
76+
echo "Feature branches should be named like: 1223-feature-name or 1225-1-sub-feature" >&2
7877
return 1
7978
fi
8079

8180
return 0
8281
}
8382

84-
get_feature_dir() { echo "$1/specs/$2"; }
85-
86-
# Find feature directory by numeric prefix instead of exact branch match
87-
# This allows multiple branches to work on the same spec (e.g., 004-fix-bug, 004-add-feature)
83+
# Find feature directory by issue number prefix
8884
find_feature_dir_by_prefix() {
8985
local repo_root="$1"
9086
local branch_name="$2"
9187
local specs_dir="$repo_root/specs"
9288

93-
# Extract numeric prefix from branch (e.g., "004" from "004-whatever")
94-
if [[ ! "$branch_name" =~ ^([0-9]{3})- ]]; then
95-
# If branch doesn't have numeric prefix, fall back to exact match
89+
# Extract issue prefix: N or N-M from branch name
90+
if [[ ! "$branch_name" =~ ^([0-9]+(-[0-9]+)?)-(.+)$ ]]; then
91+
# No numeric prefix, fall back to exact match
9692
echo "$specs_dir/$branch_name"
9793
return
9894
fi
9995

10096
local prefix="${BASH_REMATCH[1]}"
10197

102-
# Search for directories in specs/ that start with this prefix
98+
# Search for matching directories
10399
local matches=()
104100
if [[ -d "$specs_dir" ]]; then
105101
for dir in "$specs_dir"/"$prefix"-*; do
@@ -109,31 +105,22 @@ find_feature_dir_by_prefix() {
109105
done
110106
fi
111107

112-
# Handle results
113108
if [[ ${#matches[@]} -eq 0 ]]; then
114-
# No match found - return the branch name path (will fail later with clear error)
115109
echo "$specs_dir/$branch_name"
116110
elif [[ ${#matches[@]} -eq 1 ]]; then
117-
# Exactly one match - perfect!
118111
echo "$specs_dir/${matches[0]}"
119112
else
120-
# Multiple matches - this shouldn't happen with proper naming convention
121113
echo "ERROR: Multiple spec directories found with prefix '$prefix': ${matches[*]}" >&2
122-
echo "Please ensure only one spec directory exists per numeric prefix." >&2
123-
echo "$specs_dir/$branch_name" # Return something to avoid breaking the script
114+
echo "$specs_dir/$branch_name"
124115
fi
125116
}
126117

127118
get_feature_paths() {
128119
local repo_root=$(get_repo_root)
129120
local current_branch=$(get_current_branch)
130121
local has_git_repo="false"
122+
has_git && has_git_repo="true"
131123

132-
if has_git; then
133-
has_git_repo="true"
134-
fi
135-
136-
# Use prefix-based lookup to support multiple branches per spec
137124
local feature_dir=$(find_feature_dir_by_prefix "$repo_root" "$current_branch")
138125

139126
cat <<EOF
@@ -144,13 +131,8 @@ FEATURE_DIR='$feature_dir'
144131
FEATURE_SPEC='$feature_dir/spec.md'
145132
IMPL_PLAN='$feature_dir/plan.md'
146133
TASKS='$feature_dir/tasks.md'
147-
RESEARCH='$feature_dir/research.md'
148-
DATA_MODEL='$feature_dir/data-model.md'
149-
QUICKSTART='$feature_dir/quickstart.md'
150-
CONTRACTS_DIR='$feature_dir/contracts'
151134
EOF
152135
}
153136

154137
check_file() { [[ -f "$1" ]] && echo "$2" || echo "$2"; }
155138
check_dir() { [[ -d "$1" && -n $(ls -A "$1" 2>/dev/null) ]] && echo "$2" || echo "$2"; }
156-

0 commit comments

Comments
 (0)