Skip to content

Commit a366358

Browse files
author
Jeremy Jeanne
committed
Initial release: RustySpec v0.1.0 — Specification-Driven Development CLI
A Rust CLI tool for SDD workflows with 13 commands, 20 AI agent support, 4-layer template resolution, preset/extension ecosystems, and 209 unit tests. Commands: init, specify, clarify, plan, tasks, implement, analyze, checklist, preset, extension, upgrade, completions, check. CI: GitHub Actions with tests on Linux/macOS/Windows, clippy, rustfmt.
1 parent 3242668 commit a366358

22 files changed

Lines changed: 868 additions & 20 deletions

docs/feature-shell-scripts.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# Feature Specification: Shell Helper Scripts
2+
3+
**Feature**: Cross-platform shell scripts for RustySpec project operations
4+
**Status**: Draft
5+
**Priority**: P2
6+
7+
## Overview
8+
9+
RustySpec commands reference shell scripts in their YAML frontmatter (`scripts.sh` and `scripts.ps` keys). These scripts are embedded in the RustySpec binary and copied to `.rustyspec/scripts/` on `init` and `upgrade`. They provide helper functions that AI agents can call during SDD workflows.
10+
11+
## Scripts
12+
13+
### 1. `common.sh` / `common.ps1` — Shared utility functions
14+
15+
- `get_repo_root()` — Find project root by walking up to `rustyspec.toml`
16+
- `get_current_branch()` — Get current git branch name, fallback to `RUSTYSPEC_FEATURE` env
17+
- `find_feature_dir()` — Find feature directory by numeric prefix in `specs/`
18+
- `get_feature_paths()` — Output all feature paths as shell variables
19+
20+
### 2. `check-prerequisites.sh` / `check-prerequisites.ps1`
21+
22+
- Verify git is installed
23+
- Verify rustyspec binary is accessible
24+
- Check `.rustyspec/` structure exists
25+
- Report status
26+
27+
### 3. `create-new-feature.sh` / `create-new-feature.ps1`
28+
29+
- Determine next feature number
30+
- Create git branch
31+
- Create feature directory under `specs/`
32+
33+
### 4. `setup-plan.sh` / `setup-plan.ps1`
34+
35+
- Create plan supporting files (research.md, data-model.md, contracts/)
36+
- Called by the `plan` command frontmatter
37+
38+
### 5. `update-agent-context.sh` / `update-agent-context.ps1`
39+
40+
- Regenerate `.rustyspec/AGENT.md` from constitution + current specs
41+
42+
## Implementation
43+
44+
- Scripts are embedded in the binary via `include_str!`
45+
- Copied to `.rustyspec/scripts/{bash,powershell}/` on `init` and `upgrade`
46+
- `upgrade` always overwrites scripts (they are not user-customizable)
47+
48+
## Acceptance Criteria
49+
50+
- `rustyspec init` copies all scripts to `.rustyspec/scripts/`
51+
- `rustyspec upgrade` refreshes scripts
52+
- Scripts are executable (Unix: shebang + chmod in future)
53+
- Tests verify all scripts are embedded and non-empty

docs/rusty-specification.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -603,7 +603,7 @@ project/
603603
│ │ ├── constitution-template.md
604604
│ │ └── overrides/ # Project-specific template tweaks (user-managed)
605605
│ ├── scripts/
606-
│ │ ├── bash/ # Shell helpers
606+
│ │ ├── bash/ # Shell helpers (refreshed on upgrade)
607607
│ │ └── powershell/
608608
│ ├── presets/
609609
│ │ └── .registry # Installed presets (JSON)
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
#!/usr/bin/env bash
2+
# RustySpec prerequisites check
3+
source "$(dirname "$0")/common.sh"
4+
5+
echo "RustySpec Prerequisites Check"
6+
echo "=============================="
7+
8+
errors=0
9+
10+
# Check git
11+
if command -v git &>/dev/null; then
12+
echo "[OK] git: $(git --version)"
13+
else
14+
echo "[!!] git: not found"
15+
errors=$((errors + 1))
16+
fi
17+
18+
# Check project structure
19+
root="$(get_repo_root 2>/dev/null || true)"
20+
if [ -n "$root" ]; then
21+
echo "[OK] Project root: $root"
22+
23+
if [ -f "$root/.rustyspec/constitution.md" ]; then
24+
echo "[OK] Constitution file present"
25+
else
26+
echo "[!!] Constitution file missing"
27+
errors=$((errors + 1))
28+
fi
29+
30+
if [ -f "$root/rustyspec.toml" ]; then
31+
echo "[OK] rustyspec.toml found"
32+
else
33+
echo "[!!] rustyspec.toml missing"
34+
errors=$((errors + 1))
35+
fi
36+
else
37+
echo "[!!] Not inside a RustySpec project"
38+
errors=$((errors + 1))
39+
fi
40+
41+
echo ""
42+
if [ "$errors" -eq 0 ]; then
43+
echo "All checks passed."
44+
else
45+
echo "$errors issue(s) found."
46+
exit 1
47+
fi

scripts/bash/common.sh

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
#!/usr/bin/env bash
2+
# RustySpec common shell functions
3+
# Sourced by other scripts: source "$(dirname "$0")/common.sh"
4+
5+
set -euo pipefail
6+
7+
# Find the project root by walking up to rustyspec.toml
8+
get_repo_root() {
9+
local dir="$PWD"
10+
while [ "$dir" != "/" ]; do
11+
if [ -f "$dir/rustyspec.toml" ] || [ -d "$dir/.rustyspec" ]; then
12+
echo "$dir"
13+
return 0
14+
fi
15+
dir="$(dirname "$dir")"
16+
done
17+
echo "Error: not inside a RustySpec project" >&2
18+
return 1
19+
}
20+
21+
# Get the current feature branch or fallback
22+
get_current_branch() {
23+
# Level 1: RUSTYSPEC_FEATURE env var
24+
if [ -n "${RUSTYSPEC_FEATURE:-}" ]; then
25+
echo "$RUSTYSPEC_FEATURE"
26+
return 0
27+
fi
28+
29+
# Level 2: git branch
30+
if command -v git &>/dev/null && git rev-parse --is-inside-work-tree &>/dev/null; then
31+
local branch
32+
branch="$(git rev-parse --abbrev-ref HEAD 2>/dev/null || true)"
33+
if [[ "$branch" =~ ^[0-9]{3}- ]]; then
34+
echo "$branch"
35+
return 0
36+
fi
37+
fi
38+
39+
# Level 3: latest specs/ directory
40+
local root
41+
root="$(get_repo_root)" || return 1
42+
local latest
43+
latest="$(ls -d "$root"/specs/[0-9][0-9][0-9]-* 2>/dev/null | sort | tail -1 || true)"
44+
if [ -n "$latest" ]; then
45+
basename "$latest"
46+
return 0
47+
fi
48+
49+
echo "Error: no feature found" >&2
50+
return 1
51+
}
52+
53+
# Find a feature directory by numeric prefix
54+
find_feature_dir() {
55+
local prefix="${1:?Usage: find_feature_dir <prefix>}"
56+
local root
57+
root="$(get_repo_root)" || return 1
58+
59+
local matches
60+
matches="$(ls -d "$root"/specs/"${prefix}"-* 2>/dev/null || true)"
61+
local count
62+
count="$(echo "$matches" | grep -c . || true)"
63+
64+
if [ "$count" -eq 0 ]; then
65+
echo "Error: no feature matching '$prefix' in specs/" >&2
66+
return 1
67+
elif [ "$count" -eq 1 ]; then
68+
basename "$matches"
69+
else
70+
basename "$(echo "$matches" | sort | tail -1)"
71+
fi
72+
}
73+
74+
# Output all feature paths as shell variables
75+
get_feature_paths() {
76+
local root
77+
root="$(get_repo_root)" || return 1
78+
local branch
79+
branch="$(get_current_branch)" || return 1
80+
local feature_dir="$root/specs/$branch"
81+
82+
echo "REPO_ROOT=\"$root\""
83+
echo "CURRENT_BRANCH=\"$branch\""
84+
echo "HAS_GIT=$(git rev-parse --is-inside-work-tree 2>/dev/null && echo true || echo false)"
85+
echo "FEATURE_DIR=\"$feature_dir\""
86+
echo "FEATURE_SPEC=\"$feature_dir/spec.md\""
87+
echo "IMPL_PLAN=\"$feature_dir/plan.md\""
88+
echo "TASKS=\"$feature_dir/tasks.md\""
89+
echo "RESEARCH=\"$feature_dir/research.md\""
90+
echo "DATA_MODEL=\"$feature_dir/data-model.md\""
91+
echo "QUICKSTART=\"$feature_dir/quickstart.md\""
92+
echo "CONTRACTS_DIR=\"$feature_dir/contracts\""
93+
}
94+
95+
# Escape string for safe JSON embedding
96+
json_escape() {
97+
local str="${1:-}"
98+
str="${str//\\/\\\\}"
99+
str="${str//\"/\\\"}"
100+
str="${str//$'\n'/\\n}"
101+
str="${str//$'\t'/\\t}"
102+
str="${str//$'\r'/\\r}"
103+
echo "$str"
104+
}

scripts/bash/create-new-feature.sh

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#!/usr/bin/env bash
2+
# Create a new feature branch and directory
3+
source "$(dirname "$0")/common.sh"
4+
5+
description="${1:?Usage: create-new-feature.sh <description>}"
6+
root="$(get_repo_root)"
7+
8+
specs_dir="$root/specs"
9+
mkdir -p "$specs_dir"
10+
11+
# Find next feature number
12+
max_num=0
13+
for dir in "$specs_dir"/[0-9][0-9][0-9]-*/; do
14+
[ -d "$dir" ] || continue
15+
num="${dir##*/}"
16+
num="${num%%-*}"
17+
num=$((10#$num))
18+
if [ "$num" -gt "$max_num" ]; then
19+
max_num=$num
20+
fi
21+
done
22+
23+
next_num=$((max_num + 1))
24+
if [ "$next_num" -gt 999 ]; then
25+
echo "Error: feature number overflow (max 999)" >&2
26+
exit 1
27+
fi
28+
29+
feature_id=$(printf "%03d" "$next_num")
30+
31+
# Generate short branch name (first 5 words, lowercase, hyphened)
32+
short_name=$(echo "$description" | tr '[:upper:]' '[:lower:]' | tr -cs 'a-z0-9' '-' | sed 's/^-//;s/-$//' | cut -c1-50)
33+
branch_name="${feature_id}-${short_name}"
34+
35+
# Create git branch if in a repo
36+
if git rev-parse --is-inside-work-tree &>/dev/null; then
37+
git checkout -b "$branch_name" 2>/dev/null || echo "Warning: could not create branch $branch_name"
38+
fi
39+
40+
# Create feature directory
41+
feature_dir="$specs_dir/$branch_name"
42+
mkdir -p "$feature_dir/checklists"
43+
mkdir -p "$feature_dir/contracts"
44+
45+
echo "$branch_name"

scripts/bash/setup-plan.sh

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
#!/usr/bin/env bash
2+
# Setup plan supporting files for a feature
3+
source "$(dirname "$0")/common.sh"
4+
5+
feature="${1:-}"
6+
root="$(get_repo_root)"
7+
8+
if [ -z "$feature" ]; then
9+
feature="$(get_current_branch)"
10+
fi
11+
12+
feature_dir="$root/specs/$feature"
13+
if [ ! -d "$feature_dir" ]; then
14+
echo "Error: feature directory not found: $feature_dir" >&2
15+
exit 1
16+
fi
17+
18+
date="$(date +%Y-%m-%d)"
19+
20+
# Create research.md if missing
21+
if [ ! -f "$feature_dir/research.md" ]; then
22+
cat > "$feature_dir/research.md" <<EOF
23+
# Research: $feature
24+
25+
**Date**: $date
26+
27+
## Technology Investigation
28+
29+
[Research findings to be filled]
30+
EOF
31+
echo "Created research.md"
32+
fi
33+
34+
# Create data-model.md if missing
35+
if [ ! -f "$feature_dir/data-model.md" ]; then
36+
cat > "$feature_dir/data-model.md" <<EOF
37+
# Data Model: $feature
38+
39+
## Entities
40+
41+
[Entities to be defined based on spec]
42+
EOF
43+
echo "Created data-model.md"
44+
fi
45+
46+
# Create quickstart.md if missing
47+
if [ ! -f "$feature_dir/quickstart.md" ]; then
48+
cat > "$feature_dir/quickstart.md" <<EOF
49+
# Quickstart: $feature
50+
51+
## Key Validation Scenarios
52+
53+
[Validation scenarios to be defined]
54+
EOF
55+
echo "Created quickstart.md"
56+
fi
57+
58+
# Create contracts directory
59+
mkdir -p "$feature_dir/contracts"
60+
if [ ! -f "$feature_dir/contracts/api.md" ]; then
61+
cat > "$feature_dir/contracts/api.md" <<EOF
62+
# API Contracts: $feature
63+
64+
[To be defined based on plan]
65+
EOF
66+
echo "Created contracts/api.md"
67+
fi
68+
69+
echo "Plan setup complete for $feature"

0 commit comments

Comments
 (0)