specrails-core 4.1.0 → 4.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/VERSION +1 -1
- package/bin/specrails-core.mjs +302 -0
- package/commands/doctor.md +5 -5
- package/commands/enrich.md +9 -9
- package/dist/installer/cli.js +167 -0
- package/dist/installer/cli.js.map +1 -0
- package/dist/installer/commands/doctor.js +144 -0
- package/dist/installer/commands/doctor.js.map +1 -0
- package/dist/installer/commands/init.js +182 -0
- package/dist/installer/commands/init.js.map +1 -0
- package/dist/installer/commands/perf-check.js +16 -0
- package/dist/installer/commands/perf-check.js.map +1 -0
- package/dist/installer/commands/update.js +170 -0
- package/dist/installer/commands/update.js.map +1 -0
- package/dist/installer/phases/install-config.js +120 -0
- package/dist/installer/phases/install-config.js.map +1 -0
- package/dist/installer/phases/manifest.js +93 -0
- package/dist/installer/phases/manifest.js.map +1 -0
- package/dist/installer/phases/prereqs.js +116 -0
- package/dist/installer/phases/prereqs.js.map +1 -0
- package/dist/installer/phases/provider-detect.js +111 -0
- package/dist/installer/phases/provider-detect.js.map +1 -0
- package/dist/installer/phases/scaffold.js +373 -0
- package/dist/installer/phases/scaffold.js.map +1 -0
- package/dist/installer/util/errors.js +79 -0
- package/dist/installer/util/errors.js.map +1 -0
- package/dist/installer/util/exec.js +151 -0
- package/dist/installer/util/exec.js.map +1 -0
- package/dist/installer/util/fs.js +153 -0
- package/dist/installer/util/fs.js.map +1 -0
- package/dist/installer/util/git.js +113 -0
- package/dist/installer/util/git.js.map +1 -0
- package/dist/installer/util/logger.js +55 -0
- package/dist/installer/util/logger.js.map +1 -0
- package/dist/installer/util/paths.js +66 -0
- package/dist/installer/util/paths.js.map +1 -0
- package/dist/installer/util/prompts.js +49 -0
- package/dist/installer/util/prompts.js.map +1 -0
- package/dist/installer/util/template.js +60 -0
- package/dist/installer/util/template.js.map +1 -0
- package/docs/deployment.md +2 -1
- package/docs/installation.md +6 -3
- package/docs/testing/test-matrix-codex.md +19 -11
- package/docs/updating.md +24 -49
- package/docs/user-docs/faq.md +1 -1
- package/docs/windows.md +53 -0
- package/{templates/settings/integration-contract.json → integration-contract.json} +2 -2
- package/package.json +25 -10
- package/pinned-versions.json +4 -0
- package/schemas/profile.v1.json +11 -3
- package/templates/agents/sr-architect.md +1 -1
- package/templates/agents/sr-reviewer.md +1 -1
- package/templates/commands/specrails/compat-check.md +3 -3
- package/templates/commands/specrails/doctor.md +5 -5
- package/templates/commands/specrails/enrich.md +9 -9
- package/templates/commands/specrails/reconfig.md +2 -2
- package/templates/commands/specrails/refactor-recommender.md +2 -2
- package/templates/commands/specrails/vpc-drift.md +1 -1
- package/templates/skills/sr-compat-check/SKILL.md +3 -3
- package/templates/skills/sr-refactor-recommender/SKILL.md +2 -2
- package/bin/doctor.sh +0 -127
- package/bin/perf-check.sh +0 -21
- package/bin/specrails-core.js +0 -262
- package/commands/setup.md +0 -1461
- package/install.sh +0 -1231
- package/update.sh +0 -870
package/install.sh
DELETED
|
@@ -1,1231 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
set -euo pipefail
|
|
3
|
-
|
|
4
|
-
# specrails installer
|
|
5
|
-
# Installs the agent workflow system into any repository.
|
|
6
|
-
# Step 1 of 2: Prerequisites + scaffold. Step 2: Run /specrails:enrich inside Claude Code.
|
|
7
|
-
#
|
|
8
|
-
# ─────────────────────────────────────────────────────────────────────────────
|
|
9
|
-
# Reserved paths (MUST NOT be created, modified, or deleted by this script):
|
|
10
|
-
# - <repo>/.specrails/profiles/** (project + hub-authored profile JSON)
|
|
11
|
-
# - <repo>/.claude/agents/custom-*.md (user-authored custom agents)
|
|
12
|
-
#
|
|
13
|
-
# Rationale: these paths hold user/team configuration that must survive
|
|
14
|
-
# re-running the installer. specrails-hub writes profile files here.
|
|
15
|
-
# Audited by tests/test-profiles.sh.
|
|
16
|
-
#
|
|
17
|
-
# Other paths under .specrails/ (install-config.yaml, specrails-version,
|
|
18
|
-
# specrails-manifest.json, setup-templates/) ARE managed by this script.
|
|
19
|
-
# ─────────────────────────────────────────────────────────────────────────────
|
|
20
|
-
|
|
21
|
-
# Detect pipe mode (curl | bash) vs local execution
|
|
22
|
-
if [[ -z "${BASH_SOURCE[0]:-}" || "${BASH_SOURCE[0]:-}" == "bash" ]]; then
|
|
23
|
-
# Running via pipe — clone repo to temp dir
|
|
24
|
-
SPECRAILS_TMPDIR="$(mktemp -d)"
|
|
25
|
-
trap 'rm -rf "$SPECRAILS_TMPDIR"' EXIT
|
|
26
|
-
git clone --depth 1 https://github.com/fjpulidop/specrails.git "$SPECRAILS_TMPDIR/specrails" 2>/dev/null || {
|
|
27
|
-
echo "Error: failed to clone specrails repository." >&2
|
|
28
|
-
exit 1
|
|
29
|
-
}
|
|
30
|
-
SCRIPT_DIR="$SPECRAILS_TMPDIR/specrails"
|
|
31
|
-
else
|
|
32
|
-
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
33
|
-
fi
|
|
34
|
-
REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || echo "")"
|
|
35
|
-
|
|
36
|
-
# Colors
|
|
37
|
-
RED='\033[0;31m'
|
|
38
|
-
GREEN='\033[0;32m'
|
|
39
|
-
YELLOW='\033[1;33m'
|
|
40
|
-
BLUE='\033[0;34m'
|
|
41
|
-
CYAN='\033[0;36m'
|
|
42
|
-
BOLD='\033[1m'
|
|
43
|
-
NC='\033[0m'
|
|
44
|
-
|
|
45
|
-
# ─────────────────────────────────────────────
|
|
46
|
-
# Argument parsing
|
|
47
|
-
# ─────────────────────────────────────────────
|
|
48
|
-
|
|
49
|
-
CUSTOM_ROOT_DIR=""
|
|
50
|
-
AUTO_YES=false
|
|
51
|
-
# Set SPECRAILS_SKIP_PREREQS=1 to bypass hard-exit prerequisite checks (for CI/testing).
|
|
52
|
-
SKIP_PREREQS="${SPECRAILS_SKIP_PREREQS:-0}"
|
|
53
|
-
|
|
54
|
-
# Provider detection results (set in Phase 1)
|
|
55
|
-
CLI_PROVIDER=""
|
|
56
|
-
SPECRAILS_DIR=""
|
|
57
|
-
INSTRUCTIONS_FILE=""
|
|
58
|
-
HAS_CLAUDE=false
|
|
59
|
-
HAS_CODEX=false
|
|
60
|
-
AGENT_TEAMS=false
|
|
61
|
-
|
|
62
|
-
# Direct-mode flags (set by bin/specrails-core.js after TUI completes)
|
|
63
|
-
FROM_CONFIG=false # read provider/agent_teams from .specrails/install-config.yaml
|
|
64
|
-
CONFIG_PATH="" # explicit config file path (passed after --from-config)
|
|
65
|
-
HAS_GUM=false # set to true if gum CLI is available (optional UI enhancement)
|
|
66
|
-
TIER="full" # install tier: full (default) or quick (template-only, no enrich)
|
|
67
|
-
HUB_JSON=false # emit JSON checkpoint lines for programmatic consumption (specrails-hub)
|
|
68
|
-
|
|
69
|
-
while [[ $# -gt 0 ]]; do
|
|
70
|
-
case "$1" in
|
|
71
|
-
--root-dir)
|
|
72
|
-
if [[ -z "${2:-}" ]]; then
|
|
73
|
-
echo "Error: --root-dir requires a path argument." >&2
|
|
74
|
-
exit 1
|
|
75
|
-
fi
|
|
76
|
-
CUSTOM_ROOT_DIR="$2"
|
|
77
|
-
shift 2
|
|
78
|
-
;;
|
|
79
|
-
--yes|-y)
|
|
80
|
-
AUTO_YES=true
|
|
81
|
-
shift
|
|
82
|
-
;;
|
|
83
|
-
--provider)
|
|
84
|
-
if [[ -z "${2:-}" ]]; then
|
|
85
|
-
echo "Error: --provider requires a value (claude)." >&2
|
|
86
|
-
exit 1
|
|
87
|
-
fi
|
|
88
|
-
if [[ "$2" == "codex" ]]; then
|
|
89
|
-
echo "" >&2
|
|
90
|
-
echo " ⚠ Codex (OpenAI) support: Coming Soon" >&2
|
|
91
|
-
echo " Currently being tested in our lab. Please use --provider claude for now." >&2
|
|
92
|
-
echo "" >&2
|
|
93
|
-
exit 1
|
|
94
|
-
fi
|
|
95
|
-
if [[ "$2" != "claude" ]]; then
|
|
96
|
-
echo "Error: --provider value must be 'claude', got: $2" >&2
|
|
97
|
-
exit 1
|
|
98
|
-
fi
|
|
99
|
-
CLI_PROVIDER="$2"
|
|
100
|
-
shift 2
|
|
101
|
-
;;
|
|
102
|
-
--from-config)
|
|
103
|
-
# Config was written by the TUI; skip interactive prompts and read
|
|
104
|
-
# provider + agent_teams from install-config.yaml.
|
|
105
|
-
# Optional: --from-config <path> (defaults to .specrails/install-config.yaml)
|
|
106
|
-
FROM_CONFIG=true
|
|
107
|
-
if [[ -n "${2:-}" && "${2:-}" != -* ]]; then
|
|
108
|
-
CONFIG_PATH="${2}"
|
|
109
|
-
shift 2
|
|
110
|
-
else
|
|
111
|
-
shift
|
|
112
|
-
fi
|
|
113
|
-
;;
|
|
114
|
-
--agent-teams)
|
|
115
|
-
# Explicitly enable agent teams commands (alternative to config file).
|
|
116
|
-
AGENT_TEAMS=true
|
|
117
|
-
shift
|
|
118
|
-
;;
|
|
119
|
-
--quick)
|
|
120
|
-
# Template-only install; sets tier=quick in the generated config.
|
|
121
|
-
TIER="quick"
|
|
122
|
-
shift
|
|
123
|
-
;;
|
|
124
|
-
--hub-json)
|
|
125
|
-
# Emit JSON checkpoint lines for programmatic consumption by specrails-hub.
|
|
126
|
-
HUB_JSON=true
|
|
127
|
-
shift
|
|
128
|
-
;;
|
|
129
|
-
*)
|
|
130
|
-
echo "Unknown argument: $1" >&2
|
|
131
|
-
echo "Usage: install.sh [--root-dir <path>] [--yes|-y] [--provider <claude>] [--from-config [<path>]] [--agent-teams] [--quick] [--hub-json]" >&2
|
|
132
|
-
exit 1
|
|
133
|
-
;;
|
|
134
|
-
esac
|
|
135
|
-
done
|
|
136
|
-
|
|
137
|
-
# Override REPO_ROOT if --root-dir was provided
|
|
138
|
-
if [[ -n "$CUSTOM_ROOT_DIR" ]]; then
|
|
139
|
-
REPO_ROOT="$(cd "$CUSTOM_ROOT_DIR" 2>/dev/null && pwd)" || {
|
|
140
|
-
echo "Error: --root-dir path does not exist or is not accessible: $CUSTOM_ROOT_DIR" >&2
|
|
141
|
-
exit 1
|
|
142
|
-
}
|
|
143
|
-
if [[ ! -d "$REPO_ROOT" ]]; then
|
|
144
|
-
echo "Error: --root-dir path is not a directory: $CUSTOM_ROOT_DIR" >&2
|
|
145
|
-
exit 1
|
|
146
|
-
fi
|
|
147
|
-
fi
|
|
148
|
-
|
|
149
|
-
# Detect if running from within the specrails source repo itself
|
|
150
|
-
# Note: REPO_ROOT must be non-empty for the glob match — when empty, * matches everything.
|
|
151
|
-
if [[ -z "$CUSTOM_ROOT_DIR" && -n "$REPO_ROOT" && -f "$SCRIPT_DIR/install.sh" && -d "$SCRIPT_DIR/templates" && "$SCRIPT_DIR" == "$REPO_ROOT"* ]]; then
|
|
152
|
-
# We're inside the specrails source — ask for target repo
|
|
153
|
-
echo ""
|
|
154
|
-
echo -e "${YELLOW}⚠${NC} You're running the installer from inside the specrails source repo."
|
|
155
|
-
echo -e " specrails installs into a ${BOLD}target${NC} repository, not into itself."
|
|
156
|
-
echo ""
|
|
157
|
-
read -p " Enter the path to the target repo (or 'q' to quit): " TARGET_PATH || TARGET_PATH="q"
|
|
158
|
-
if [[ "$TARGET_PATH" == "q" || -z "$TARGET_PATH" ]]; then
|
|
159
|
-
echo " Aborted. No changes made."
|
|
160
|
-
exit 0
|
|
161
|
-
fi
|
|
162
|
-
# Expand ~ and resolve path
|
|
163
|
-
TARGET_PATH="${TARGET_PATH/#\~/$HOME}"
|
|
164
|
-
REPO_ROOT="$(cd "$TARGET_PATH" 2>/dev/null && pwd)" || {
|
|
165
|
-
echo "Error: path does not exist or is not accessible: $TARGET_PATH" >&2
|
|
166
|
-
exit 1
|
|
167
|
-
}
|
|
168
|
-
fi
|
|
169
|
-
|
|
170
|
-
# Auto-init git if the target directory is not a git repository
|
|
171
|
-
if [[ -z "$REPO_ROOT" || ! -d "$REPO_ROOT/.git" ]]; then
|
|
172
|
-
# Resolve REPO_ROOT to cwd if still empty (no git found anywhere)
|
|
173
|
-
if [[ -z "$REPO_ROOT" ]]; then
|
|
174
|
-
REPO_ROOT="$(pwd)"
|
|
175
|
-
fi
|
|
176
|
-
if [[ "$AUTO_YES" == true ]]; then
|
|
177
|
-
git -C "$REPO_ROOT" init -q 2>/dev/null
|
|
178
|
-
else
|
|
179
|
-
echo ""
|
|
180
|
-
echo -e "${YELLOW}⚠${NC} ${REPO_ROOT} is not a git repository."
|
|
181
|
-
read -p " Initialize git? (y/n): " INIT_GIT || INIT_GIT="n"
|
|
182
|
-
if [[ "$INIT_GIT" == "y" || "$INIT_GIT" == "Y" ]]; then
|
|
183
|
-
git -C "$REPO_ROOT" init -q 2>/dev/null
|
|
184
|
-
else
|
|
185
|
-
echo " Aborted. specrails requires a git repository."
|
|
186
|
-
exit 0
|
|
187
|
-
fi
|
|
188
|
-
fi
|
|
189
|
-
fi
|
|
190
|
-
|
|
191
|
-
print_header() {
|
|
192
|
-
echo ""
|
|
193
|
-
echo -e "${BOLD}${CYAN}╔══════════════════════════════════════════════╗${NC}"
|
|
194
|
-
echo -e "${BOLD}${CYAN}║ specrails installer v0.1 ║${NC}"
|
|
195
|
-
echo -e "${BOLD}${CYAN}║ Agent Workflow System (AI-native) ║${NC}"
|
|
196
|
-
echo -e "${BOLD}${CYAN}╚══════════════════════════════════════════════╝${NC}"
|
|
197
|
-
echo ""
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
ok() { echo -e " ${GREEN}✓${NC} $1"; }
|
|
201
|
-
warn() { echo -e " ${YELLOW}⚠${NC} $1"; }
|
|
202
|
-
fail() { echo -e " ${RED}✗${NC} $1"; }
|
|
203
|
-
info() { echo -e " ${BLUE}→${NC} $1"; }
|
|
204
|
-
step() { echo -e "\n${BOLD}$1${NC}"; }
|
|
205
|
-
|
|
206
|
-
generate_manifest() {
|
|
207
|
-
local version
|
|
208
|
-
version="$(cat "$SCRIPT_DIR/VERSION")"
|
|
209
|
-
|
|
210
|
-
local installed_at
|
|
211
|
-
installed_at="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
|
|
212
|
-
|
|
213
|
-
# Write version file
|
|
214
|
-
printf '%s\n' "$version" > "$REPO_ROOT/.specrails/specrails-version"
|
|
215
|
-
|
|
216
|
-
# Build artifact checksums for all files under templates/
|
|
217
|
-
local artifacts_json=""
|
|
218
|
-
local first=true
|
|
219
|
-
while IFS= read -r -d '' filepath; do
|
|
220
|
-
local relpath
|
|
221
|
-
relpath="templates/${filepath#"$SCRIPT_DIR/templates/"}"
|
|
222
|
-
local checksum
|
|
223
|
-
checksum="sha256:$(shasum -a 256 "$filepath" | awk '{print $1}')"
|
|
224
|
-
if [ "$first" = true ]; then
|
|
225
|
-
first=false
|
|
226
|
-
else
|
|
227
|
-
artifacts_json="${artifacts_json},"
|
|
228
|
-
fi
|
|
229
|
-
artifacts_json="${artifacts_json}
|
|
230
|
-
\"${relpath}\": \"${checksum}\""
|
|
231
|
-
done < <(find "$SCRIPT_DIR/templates" -type f -not -path '*/node_modules/*' -not -name 'package-lock.json' -print0 | sort -z)
|
|
232
|
-
|
|
233
|
-
# Include commands/enrich.md
|
|
234
|
-
local enrich_checksum
|
|
235
|
-
enrich_checksum="sha256:$(shasum -a 256 "$SCRIPT_DIR/commands/enrich.md" | awk '{print $1}')"
|
|
236
|
-
if [ -n "$artifacts_json" ]; then
|
|
237
|
-
artifacts_json="${artifacts_json},"
|
|
238
|
-
fi
|
|
239
|
-
artifacts_json="${artifacts_json}
|
|
240
|
-
\"commands/specrails/enrich.md\": \"${enrich_checksum}\""
|
|
241
|
-
|
|
242
|
-
# Include commands/doctor.md
|
|
243
|
-
local doctor_checksum
|
|
244
|
-
doctor_checksum="sha256:$(shasum -a 256 "$SCRIPT_DIR/commands/doctor.md" | awk '{print $1}')"
|
|
245
|
-
artifacts_json="${artifacts_json},
|
|
246
|
-
\"commands/specrails/doctor.md\": \"${doctor_checksum}\""
|
|
247
|
-
|
|
248
|
-
cat > "$REPO_ROOT/.specrails/specrails-manifest.json" << EOF
|
|
249
|
-
{
|
|
250
|
-
"version": "${version}",
|
|
251
|
-
"installed_at": "${installed_at}",
|
|
252
|
-
"artifacts": {${artifacts_json}
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
EOF
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
# ─────────────────────────────────────────────
|
|
259
|
-
# Phase 1: Prerequisites
|
|
260
|
-
# ─────────────────────────────────────────────
|
|
261
|
-
|
|
262
|
-
print_header
|
|
263
|
-
|
|
264
|
-
step "Phase 1: Checking prerequisites"
|
|
265
|
-
|
|
266
|
-
# 1.1 Git repository (should be resolved by now — early init or --root-dir)
|
|
267
|
-
if [[ -z "$REPO_ROOT" ]]; then
|
|
268
|
-
fail "Could not determine target directory."
|
|
269
|
-
echo " Usage: install.sh [--root-dir <path>]"
|
|
270
|
-
exit 1
|
|
271
|
-
fi
|
|
272
|
-
if [[ -n "$CUSTOM_ROOT_DIR" ]]; then
|
|
273
|
-
ok "Install root (--root-dir): $REPO_ROOT"
|
|
274
|
-
else
|
|
275
|
-
ok "Git repository root: $REPO_ROOT"
|
|
276
|
-
fi
|
|
277
|
-
|
|
278
|
-
# 1.2 Provider detection (Claude Code vs Codex)
|
|
279
|
-
if command -v claude &> /dev/null; then
|
|
280
|
-
HAS_CLAUDE=true
|
|
281
|
-
fi
|
|
282
|
-
if command -v codex &> /dev/null; then
|
|
283
|
-
HAS_CODEX=true
|
|
284
|
-
fi
|
|
285
|
-
|
|
286
|
-
# 1.2a Gum detection (optional UI enhancement — used in non-TUI bash paths)
|
|
287
|
-
if command -v gum &> /dev/null; then
|
|
288
|
-
HAS_GUM=true
|
|
289
|
-
ok "gum CLI: found (enhanced UI available)"
|
|
290
|
-
else
|
|
291
|
-
# Attempt a lightweight auto-install on supported platforms (best-effort)
|
|
292
|
-
_GUM_INSTALLED=false
|
|
293
|
-
if command -v brew &> /dev/null; then
|
|
294
|
-
info "Installing gum CLI via Homebrew (optional — adds nicer prompts)..."
|
|
295
|
-
if brew install gum &>/dev/null; then
|
|
296
|
-
HAS_GUM=true
|
|
297
|
-
_GUM_INSTALLED=true
|
|
298
|
-
ok "gum installed via Homebrew"
|
|
299
|
-
fi
|
|
300
|
-
fi
|
|
301
|
-
if [[ "$_GUM_INSTALLED" == false ]]; then
|
|
302
|
-
info "gum CLI not found — install via 'brew install gum' for enhanced prompts (optional)"
|
|
303
|
-
fi
|
|
304
|
-
fi
|
|
305
|
-
|
|
306
|
-
if [[ "$FROM_CONFIG" == true ]]; then
|
|
307
|
-
# Config was written by the TUI — read provider and agent_teams from it.
|
|
308
|
-
# Resolve config path: explicit path > default location.
|
|
309
|
-
if [[ -z "$CONFIG_PATH" ]]; then
|
|
310
|
-
CONFIG_PATH="${REPO_ROOT}/.specrails/install-config.yaml"
|
|
311
|
-
fi
|
|
312
|
-
if [[ -f "$CONFIG_PATH" ]]; then
|
|
313
|
-
_cfg_version=$(grep '^version:' "$CONFIG_PATH" 2>/dev/null | awk '{print $2}' | tr -d '[:space:]' || true)
|
|
314
|
-
_cfg_provider=$(grep '^provider:' "$CONFIG_PATH" 2>/dev/null | awk '{print $2}' | tr -d '[:space:]' || true)
|
|
315
|
-
_cfg_agent_teams=$(grep '^agent_teams:' "$CONFIG_PATH" 2>/dev/null | awk '{print $2}' | tr -d '[:space:]' || true)
|
|
316
|
-
_cfg_tier=$(grep '^tier:' "$CONFIG_PATH" 2>/dev/null | awk '{print $2}' | tr -d '[:space:]' || true)
|
|
317
|
-
_cfg_preset=$(grep '^\s*preset:' "$CONFIG_PATH" 2>/dev/null | head -1 | awk '{print $2}' | tr -d '[:space:]' || true)
|
|
318
|
-
_cfg_has_agents=$(grep -c '^\s*selected:' "$CONFIG_PATH" 2>/dev/null || true)
|
|
319
|
-
|
|
320
|
-
_config_errors=0
|
|
321
|
-
if [[ -z "$_cfg_version" ]]; then
|
|
322
|
-
fail "Invalid config: missing required 'version' field"
|
|
323
|
-
_config_errors=$(( _config_errors + 1 ))
|
|
324
|
-
elif [[ "$_cfg_version" != "1" ]]; then
|
|
325
|
-
fail "Invalid config: unsupported version '$_cfg_version' (expected: 1)"
|
|
326
|
-
_config_errors=$(( _config_errors + 1 ))
|
|
327
|
-
fi
|
|
328
|
-
if [[ -z "$_cfg_provider" ]]; then
|
|
329
|
-
fail "Invalid config: missing required 'provider' field"
|
|
330
|
-
_config_errors=$(( _config_errors + 1 ))
|
|
331
|
-
elif [[ "$_cfg_provider" == "codex" ]]; then
|
|
332
|
-
fail "Codex (OpenAI) support is coming soon — currently being tested in our lab."
|
|
333
|
-
fail "Please edit install-config.yaml and set: provider: claude"
|
|
334
|
-
_config_errors=$(( _config_errors + 1 ))
|
|
335
|
-
elif [[ "$_cfg_provider" != "claude" ]]; then
|
|
336
|
-
fail "Invalid config: unsupported provider '$_cfg_provider' (expected: claude)"
|
|
337
|
-
_config_errors=$(( _config_errors + 1 ))
|
|
338
|
-
fi
|
|
339
|
-
if [[ "$_cfg_has_agents" -eq 0 ]]; then
|
|
340
|
-
fail "Invalid config: missing required 'agents' section with 'selected' list"
|
|
341
|
-
_config_errors=$(( _config_errors + 1 ))
|
|
342
|
-
fi
|
|
343
|
-
if [[ -n "$_cfg_tier" && "$_cfg_tier" != "full" && "$_cfg_tier" != "quick" ]]; then
|
|
344
|
-
fail "Invalid config: unsupported tier '$_cfg_tier' (expected: full or quick)"
|
|
345
|
-
_config_errors=$(( _config_errors + 1 ))
|
|
346
|
-
fi
|
|
347
|
-
if [[ -n "$_cfg_preset" && "$_cfg_preset" != "balanced" && "$_cfg_preset" != "budget" && "$_cfg_preset" != "max" ]]; then
|
|
348
|
-
fail "Invalid config: unsupported preset '$_cfg_preset' (expected: balanced, budget, or max)"
|
|
349
|
-
_config_errors=$(( _config_errors + 1 ))
|
|
350
|
-
fi
|
|
351
|
-
|
|
352
|
-
# Warn about unknown agents in selected list
|
|
353
|
-
if [[ "$_cfg_has_agents" -gt 0 ]]; then
|
|
354
|
-
_selected_line=$(grep '^\s*selected:' "$CONFIG_PATH" 2>/dev/null | head -1 || true)
|
|
355
|
-
if echo "$_selected_line" | grep -q '\[' 2>/dev/null; then
|
|
356
|
-
_selected_agents=$(echo "$_selected_line" | sed 's/.*\[//;s/\].*//;s/,/\n/g' | tr -d ' ')
|
|
357
|
-
else
|
|
358
|
-
_selected_agents=$(grep -A100 '^\s*selected:' "$CONFIG_PATH" 2>/dev/null \
|
|
359
|
-
| tail -n +2 | grep '^\s*-\s*' | sed 's/^\s*-\s*//' | tr -d ' ' || true)
|
|
360
|
-
fi
|
|
361
|
-
while IFS= read -r _agent_name; do
|
|
362
|
-
[[ -z "$_agent_name" ]] && continue
|
|
363
|
-
if [[ ! -f "$SCRIPT_DIR/templates/agents/${_agent_name}.md" ]]; then
|
|
364
|
-
warn "Unknown agent in selected list: ${_agent_name}"
|
|
365
|
-
fi
|
|
366
|
-
done <<< "$_selected_agents"
|
|
367
|
-
fi
|
|
368
|
-
|
|
369
|
-
if [[ "$_config_errors" -gt 0 ]]; then
|
|
370
|
-
fail "Config validation failed with ${_config_errors} error(s) — aborting"
|
|
371
|
-
exit 1
|
|
372
|
-
fi
|
|
373
|
-
|
|
374
|
-
if [[ -n "$_cfg_provider" ]]; then
|
|
375
|
-
CLI_PROVIDER="$_cfg_provider"
|
|
376
|
-
ok "Provider: $CLI_PROVIDER (from install-config.yaml)"
|
|
377
|
-
fi
|
|
378
|
-
if [[ "$_cfg_agent_teams" == "true" ]]; then
|
|
379
|
-
AGENT_TEAMS=true
|
|
380
|
-
fi
|
|
381
|
-
if [[ -n "$_cfg_tier" ]]; then
|
|
382
|
-
TIER="$_cfg_tier"
|
|
383
|
-
fi
|
|
384
|
-
else
|
|
385
|
-
warn "install-config.yaml not found at $CONFIG_PATH — falling back to auto-detection"
|
|
386
|
-
FROM_CONFIG=false
|
|
387
|
-
fi
|
|
388
|
-
fi
|
|
389
|
-
|
|
390
|
-
if [[ "$FROM_CONFIG" != true ]]; then
|
|
391
|
-
if [[ -n "$CLI_PROVIDER" ]]; then
|
|
392
|
-
# --provider flag was set explicitly — skip interactive detection
|
|
393
|
-
ok "Provider: $CLI_PROVIDER (--provider flag)"
|
|
394
|
-
elif [ "$HAS_CLAUDE" = true ] && [ "$HAS_CODEX" = true ]; then
|
|
395
|
-
CLI_PROVIDER="claude"
|
|
396
|
-
info "Claude Code and Codex both detected — Codex support coming soon (in lab), using Claude Code."
|
|
397
|
-
ok "Provider: $CLI_PROVIDER"
|
|
398
|
-
elif [ "$HAS_CLAUDE" = true ]; then
|
|
399
|
-
CLI_PROVIDER="claude"
|
|
400
|
-
CLAUDE_VERSION=$(claude --version 2>/dev/null || echo "unknown")
|
|
401
|
-
ok "Claude Code CLI: $CLAUDE_VERSION"
|
|
402
|
-
elif [ "$HAS_CODEX" = true ]; then
|
|
403
|
-
fail "Only Codex detected — Codex (OpenAI) support is coming soon (currently in our lab)."
|
|
404
|
-
echo ""
|
|
405
|
-
echo " Please install Claude Code to continue: https://claude.ai/download"
|
|
406
|
-
exit 1
|
|
407
|
-
elif [[ "$SKIP_PREREQS" == "1" ]]; then
|
|
408
|
-
CLI_PROVIDER="claude"
|
|
409
|
-
warn "No AI CLI found (skipped — SPECRAILS_SKIP_PREREQS=1)"
|
|
410
|
-
else
|
|
411
|
-
fail "No AI CLI found (claude)."
|
|
412
|
-
echo ""
|
|
413
|
-
echo " Install Claude Code: https://claude.ai/download"
|
|
414
|
-
echo " Codex (OpenAI) support: coming soon — currently in our lab."
|
|
415
|
-
exit 1
|
|
416
|
-
fi
|
|
417
|
-
fi
|
|
418
|
-
|
|
419
|
-
# Derive output directory and instruction file from provider
|
|
420
|
-
if [[ "$CLI_PROVIDER" == "codex" ]]; then
|
|
421
|
-
SPECRAILS_DIR=".codex"
|
|
422
|
-
INSTRUCTIONS_FILE="AGENTS.md"
|
|
423
|
-
else
|
|
424
|
-
SPECRAILS_DIR=".claude"
|
|
425
|
-
INSTRUCTIONS_FILE="CLAUDE.md"
|
|
426
|
-
fi
|
|
427
|
-
|
|
428
|
-
# 1.2b Agent Teams opt-in (Claude Code only)
|
|
429
|
-
if [[ "$CLI_PROVIDER" == "claude" ]]; then
|
|
430
|
-
if [[ "$FROM_CONFIG" == true || "$AGENT_TEAMS" == true ]]; then
|
|
431
|
-
# Already resolved from config or --agent-teams flag
|
|
432
|
-
if [[ "$AGENT_TEAMS" == true ]]; then
|
|
433
|
-
ok "Agent Teams commands: enabled (from install-config.yaml)"
|
|
434
|
-
echo ""
|
|
435
|
-
info "Remember to set the feature flag before using these commands:"
|
|
436
|
-
echo ""
|
|
437
|
-
echo " export CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1"
|
|
438
|
-
echo ""
|
|
439
|
-
else
|
|
440
|
-
info "Agent Teams commands: skipped (from install-config.yaml)"
|
|
441
|
-
fi
|
|
442
|
-
elif [ "$AUTO_YES" = true ]; then
|
|
443
|
-
# --yes flag: default to NOT installing (opt-in, not opt-out)
|
|
444
|
-
AGENT_TEAMS=false
|
|
445
|
-
info "Agent Teams commands: skipped (opt-in, use interactive mode to enable)"
|
|
446
|
-
else
|
|
447
|
-
echo ""
|
|
448
|
-
echo -e " ${BOLD}Agent Teams commands are available (experimental):${NC}"
|
|
449
|
-
echo " /specrails:team-review — Multi-perspective code review with AI reviewers"
|
|
450
|
-
echo " /specrails:team-debug — Collaborative debugging with competing hypotheses"
|
|
451
|
-
echo ""
|
|
452
|
-
echo " These require Claude Code Agent Teams (experimental feature)."
|
|
453
|
-
read -p " Install Agent Teams commands? (y/n, default: n): " INSTALL_AGENT_TEAMS || INSTALL_AGENT_TEAMS="n"
|
|
454
|
-
if [[ "$INSTALL_AGENT_TEAMS" == "y" || "$INSTALL_AGENT_TEAMS" == "Y" ]]; then
|
|
455
|
-
AGENT_TEAMS=true
|
|
456
|
-
ok "Agent Teams commands will be installed"
|
|
457
|
-
echo ""
|
|
458
|
-
info "Remember to set the feature flag before using these commands:"
|
|
459
|
-
echo ""
|
|
460
|
-
echo " export CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1"
|
|
461
|
-
echo ""
|
|
462
|
-
else
|
|
463
|
-
AGENT_TEAMS=false
|
|
464
|
-
info "Agent Teams commands: skipped"
|
|
465
|
-
fi
|
|
466
|
-
fi
|
|
467
|
-
fi
|
|
468
|
-
|
|
469
|
-
# 1.3 API key / authentication (provider-specific)
|
|
470
|
-
if [[ "$CLI_PROVIDER" == "claude" ]]; then
|
|
471
|
-
CLAUDE_AUTHED=false
|
|
472
|
-
if claude config list 2>/dev/null | grep -q "api_key"; then
|
|
473
|
-
CLAUDE_AUTHED=true
|
|
474
|
-
elif [[ -n "${ANTHROPIC_API_KEY:-}" ]]; then
|
|
475
|
-
CLAUDE_AUTHED=true
|
|
476
|
-
elif [[ -f "${HOME}/.claude.json" ]] && grep -q '"oauthAccount"' "${HOME}/.claude.json" 2>/dev/null; then
|
|
477
|
-
CLAUDE_AUTHED=true
|
|
478
|
-
fi
|
|
479
|
-
|
|
480
|
-
if [[ "$CLAUDE_AUTHED" == "true" ]]; then
|
|
481
|
-
ok "Claude: authenticated"
|
|
482
|
-
elif [[ "$SKIP_PREREQS" == "1" ]]; then
|
|
483
|
-
warn "Claude authentication not found (skipped — SPECRAILS_SKIP_PREREQS=1)"
|
|
484
|
-
else
|
|
485
|
-
fail "No Claude authentication found."
|
|
486
|
-
echo ""
|
|
487
|
-
echo " Option 1 (API key): claude config set api_key <your-key>"
|
|
488
|
-
echo " Option 2 (OAuth): claude auth login"
|
|
489
|
-
exit 1
|
|
490
|
-
fi
|
|
491
|
-
else
|
|
492
|
-
# Codex
|
|
493
|
-
CODEX_AUTHED=false
|
|
494
|
-
if [[ -n "${OPENAI_API_KEY:-}" ]]; then
|
|
495
|
-
CODEX_AUTHED=true
|
|
496
|
-
elif codex login status 2>/dev/null | grep -qi "logged in"; then
|
|
497
|
-
CODEX_AUTHED=true
|
|
498
|
-
elif [[ -f "${HOME}/.codex/auth.json" ]] && grep -q '"access_token"' "${HOME}/.codex/auth.json" 2>/dev/null; then
|
|
499
|
-
CODEX_AUTHED=true
|
|
500
|
-
fi
|
|
501
|
-
|
|
502
|
-
if [[ "$CODEX_AUTHED" == "true" ]]; then
|
|
503
|
-
ok "Codex: authenticated"
|
|
504
|
-
elif [[ "$SKIP_PREREQS" == "1" ]]; then
|
|
505
|
-
warn "Codex authentication not found (skipped — SPECRAILS_SKIP_PREREQS=1)"
|
|
506
|
-
else
|
|
507
|
-
fail "No Codex authentication found."
|
|
508
|
-
echo ""
|
|
509
|
-
echo " Option 1 (API key): export OPENAI_API_KEY=<your-key>"
|
|
510
|
-
echo " Option 2 (OAuth): codex login"
|
|
511
|
-
exit 1
|
|
512
|
-
fi
|
|
513
|
-
fi
|
|
514
|
-
|
|
515
|
-
# 1.4 npm
|
|
516
|
-
if command -v npm &> /dev/null; then
|
|
517
|
-
NPM_VERSION=$(npm --version 2>/dev/null)
|
|
518
|
-
ok "npm: v$NPM_VERSION"
|
|
519
|
-
HAS_NPM=true
|
|
520
|
-
else
|
|
521
|
-
warn "npm not found. OpenSpec CLI will be unavailable."
|
|
522
|
-
echo " Install npm: https://docs.npmjs.com/downloading-and-installing-node-js-and-npm"
|
|
523
|
-
HAS_NPM=false
|
|
524
|
-
fi
|
|
525
|
-
|
|
526
|
-
# 1.5 OpenSpec CLI
|
|
527
|
-
# Read pinned version from package.json (specrails.openspecVersion field)
|
|
528
|
-
_openspec_pkg_version=""
|
|
529
|
-
if [[ -f "$SCRIPT_DIR/package.json" ]]; then
|
|
530
|
-
_openspec_pkg_version=$(python3 -c "import json; d=json.load(open('$SCRIPT_DIR/package.json')); print(d.get('specrails',{}).get('openspecVersion',''))" 2>/dev/null || true)
|
|
531
|
-
fi
|
|
532
|
-
_openspec_install_spec="@fission-ai/openspec"
|
|
533
|
-
if [[ -n "$_openspec_pkg_version" ]]; then
|
|
534
|
-
_openspec_install_spec="@fission-ai/openspec@${_openspec_pkg_version}"
|
|
535
|
-
fi
|
|
536
|
-
|
|
537
|
-
if command -v openspec &> /dev/null; then
|
|
538
|
-
OPENSPEC_VERSION=$(openspec --version 2>/dev/null || echo "unknown")
|
|
539
|
-
ok "OpenSpec CLI: $OPENSPEC_VERSION"
|
|
540
|
-
HAS_OPENSPEC=true
|
|
541
|
-
elif [ -f "$REPO_ROOT/node_modules/.bin/openspec" ]; then
|
|
542
|
-
ok "OpenSpec CLI: found in node_modules"
|
|
543
|
-
HAS_OPENSPEC=true
|
|
544
|
-
else
|
|
545
|
-
if [ "$HAS_NPM" = true ]; then
|
|
546
|
-
# Auto-install in --yes mode; ask otherwise
|
|
547
|
-
if [ "$AUTO_YES" = true ]; then
|
|
548
|
-
INSTALL_OPENSPEC="y"
|
|
549
|
-
else
|
|
550
|
-
read -p " OpenSpec CLI not found. Install ${_openspec_install_spec}? (y/n): " INSTALL_OPENSPEC || INSTALL_OPENSPEC="n"
|
|
551
|
-
fi
|
|
552
|
-
if [ "$INSTALL_OPENSPEC" = "y" ] || [ "$INSTALL_OPENSPEC" = "Y" ]; then
|
|
553
|
-
info "Installing ${_openspec_install_spec}..."
|
|
554
|
-
_npm_log="$(mktemp)"
|
|
555
|
-
if npm install -g "${_openspec_install_spec}" >"$_npm_log" 2>&1; then
|
|
556
|
-
ok "OpenSpec CLI installed ($(openspec --version 2>/dev/null || echo "${_openspec_pkg_version}"))"
|
|
557
|
-
HAS_OPENSPEC=true
|
|
558
|
-
rm -f "$_npm_log"
|
|
559
|
-
else
|
|
560
|
-
warn "Global install failed (often EACCES on fresh macOS). Trying local install in ${REPO_ROOT}..."
|
|
561
|
-
# Show last few lines of npm error for context
|
|
562
|
-
tail -n 5 "$_npm_log" | sed 's/^/ /' >&2 || true
|
|
563
|
-
if ( cd "$REPO_ROOT" && npm install "${_openspec_install_spec}" >"$_npm_log" 2>&1 ); then
|
|
564
|
-
ok "OpenSpec CLI installed locally (${REPO_ROOT}/node_modules/.bin/openspec)"
|
|
565
|
-
HAS_OPENSPEC=true
|
|
566
|
-
else
|
|
567
|
-
fail "Could not install OpenSpec CLI. npm output:"
|
|
568
|
-
tail -n 10 "$_npm_log" | sed 's/^/ /' >&2 || true
|
|
569
|
-
echo " Manual fix: npm install -g ${_openspec_install_spec}" >&2
|
|
570
|
-
HAS_OPENSPEC=false
|
|
571
|
-
fi
|
|
572
|
-
rm -f "$_npm_log"
|
|
573
|
-
fi
|
|
574
|
-
else
|
|
575
|
-
warn "Skipping OpenSpec install. Spec-driven workflow will be limited."
|
|
576
|
-
HAS_OPENSPEC=false
|
|
577
|
-
fi
|
|
578
|
-
else
|
|
579
|
-
warn "Cannot install OpenSpec without npm."
|
|
580
|
-
HAS_OPENSPEC=false
|
|
581
|
-
fi
|
|
582
|
-
fi
|
|
583
|
-
|
|
584
|
-
# 1.6 GitHub CLI (optional)
|
|
585
|
-
if command -v gh &> /dev/null; then
|
|
586
|
-
if gh auth status &> /dev/null; then
|
|
587
|
-
ok "GitHub CLI: authenticated"
|
|
588
|
-
HAS_GH=true
|
|
589
|
-
else
|
|
590
|
-
warn "GitHub CLI installed but not authenticated. Run: gh auth login"
|
|
591
|
-
HAS_GH=false
|
|
592
|
-
fi
|
|
593
|
-
else
|
|
594
|
-
warn "GitHub CLI (gh) not found. GitHub Issues backlog will be unavailable."
|
|
595
|
-
HAS_GH=false
|
|
596
|
-
fi
|
|
597
|
-
|
|
598
|
-
# 1.7 OSS detection (requires gh auth; degrades gracefully)
|
|
599
|
-
IS_OSS=false
|
|
600
|
-
HAS_PUBLIC_REPO=false
|
|
601
|
-
HAS_CI=false
|
|
602
|
-
HAS_CONTRIBUTING=false
|
|
603
|
-
|
|
604
|
-
if [ "$HAS_GH" = true ]; then
|
|
605
|
-
_REPO_PRIVATE=$(gh repo view --json isPrivate --jq '.isPrivate' 2>/dev/null || echo "unknown")
|
|
606
|
-
if [ "$_REPO_PRIVATE" = "false" ]; then
|
|
607
|
-
HAS_PUBLIC_REPO=true
|
|
608
|
-
fi
|
|
609
|
-
if ls "$REPO_ROOT/.github/workflows/"*.yml > /dev/null 2>&1; then
|
|
610
|
-
HAS_CI=true
|
|
611
|
-
fi
|
|
612
|
-
if [ -f "$REPO_ROOT/CONTRIBUTING.md" ] || [ -f "$REPO_ROOT/.github/CONTRIBUTING.md" ]; then
|
|
613
|
-
HAS_CONTRIBUTING=true
|
|
614
|
-
fi
|
|
615
|
-
if [ "$HAS_PUBLIC_REPO" = true ] && [ "$HAS_CI" = true ] && [ "$HAS_CONTRIBUTING" = true ]; then
|
|
616
|
-
IS_OSS=true
|
|
617
|
-
ok "OSS project detected (public repo + CI + CONTRIBUTING.md)"
|
|
618
|
-
fi
|
|
619
|
-
fi
|
|
620
|
-
|
|
621
|
-
# 1.8 JIRA CLI (optional)
|
|
622
|
-
if command -v jira &> /dev/null; then
|
|
623
|
-
ok "JIRA CLI: found"
|
|
624
|
-
HAS_JIRA=true
|
|
625
|
-
else
|
|
626
|
-
HAS_JIRA=false
|
|
627
|
-
# Don't warn here — JIRA is only relevant if chosen during /specrails:enrich.
|
|
628
|
-
# If the user selects JIRA in /specrails:enrich and it's not installed, the enrich
|
|
629
|
-
# wizard will offer to install it (go-jira via brew/go, or Atlassian CLI).
|
|
630
|
-
fi
|
|
631
|
-
|
|
632
|
-
# ─────────────────────────────────────────────
|
|
633
|
-
# Phase 2: Detect existing setup
|
|
634
|
-
# ─────────────────────────────────────────────
|
|
635
|
-
|
|
636
|
-
step "Phase 2: Detecting existing setup"
|
|
637
|
-
|
|
638
|
-
EXISTING_SETUP=false
|
|
639
|
-
|
|
640
|
-
if [ -d "$REPO_ROOT/$SPECRAILS_DIR" ]; then
|
|
641
|
-
if [ -d "$REPO_ROOT/$SPECRAILS_DIR/agents" ] && [ "$(ls -A "$REPO_ROOT/$SPECRAILS_DIR/agents" 2>/dev/null)" ]; then
|
|
642
|
-
warn "Existing $SPECRAILS_DIR/agents/ found with content"
|
|
643
|
-
EXISTING_SETUP=true
|
|
644
|
-
fi
|
|
645
|
-
if [ -d "$REPO_ROOT/$SPECRAILS_DIR/commands" ] && [ "$(ls -A "$REPO_ROOT/$SPECRAILS_DIR/commands" 2>/dev/null)" ]; then
|
|
646
|
-
warn "Existing $SPECRAILS_DIR/commands/ found with content"
|
|
647
|
-
EXISTING_SETUP=true
|
|
648
|
-
fi
|
|
649
|
-
if [ -d "$REPO_ROOT/$SPECRAILS_DIR/rules" ] && [ "$(ls -A "$REPO_ROOT/$SPECRAILS_DIR/rules" 2>/dev/null)" ]; then
|
|
650
|
-
warn "Existing $SPECRAILS_DIR/rules/ found with content"
|
|
651
|
-
EXISTING_SETUP=true
|
|
652
|
-
fi
|
|
653
|
-
fi
|
|
654
|
-
|
|
655
|
-
if [ -d "$REPO_ROOT/openspec" ]; then
|
|
656
|
-
warn "Existing openspec/ directory found"
|
|
657
|
-
EXISTING_SETUP=true
|
|
658
|
-
fi
|
|
659
|
-
|
|
660
|
-
if [ "$EXISTING_SETUP" = true ]; then
|
|
661
|
-
echo ""
|
|
662
|
-
warn "This repo already has some agent/command/openspec artifacts."
|
|
663
|
-
if [ "$AUTO_YES" = true ]; then CONTINUE="y"; else read -p " Continue and merge with existing setup? (y/n): " CONTINUE || CONTINUE="n"; fi
|
|
664
|
-
if [ "$CONTINUE" != "y" ] && [ "$CONTINUE" != "Y" ]; then
|
|
665
|
-
info "Aborted. No changes made."
|
|
666
|
-
exit 0
|
|
667
|
-
fi
|
|
668
|
-
else
|
|
669
|
-
ok "Clean repo — no existing .claude/ or openspec/ artifacts"
|
|
670
|
-
fi
|
|
671
|
-
|
|
672
|
-
# ─────────────────────────────────────────────
|
|
673
|
-
# Phase 3: Install artifacts
|
|
674
|
-
# ─────────────────────────────────────────────
|
|
675
|
-
|
|
676
|
-
step "Phase 3: Installing specrails artifacts"
|
|
677
|
-
|
|
678
|
-
# Create directory structure
|
|
679
|
-
mkdir -p "$REPO_ROOT/$SPECRAILS_DIR"
|
|
680
|
-
if [[ "$CLI_PROVIDER" == "codex" ]]; then
|
|
681
|
-
# Codex: install as Agent Skills (Codex doesn't support .codex/commands/)
|
|
682
|
-
mkdir -p "$REPO_ROOT/.agents/skills/enrich"
|
|
683
|
-
mkdir -p "$REPO_ROOT/.agents/skills/doctor"
|
|
684
|
-
else
|
|
685
|
-
mkdir -p "$REPO_ROOT/$SPECRAILS_DIR/commands/specrails"
|
|
686
|
-
fi
|
|
687
|
-
mkdir -p "$REPO_ROOT/.specrails/setup-templates/agents"
|
|
688
|
-
mkdir -p "$REPO_ROOT/.specrails/setup-templates/commands"
|
|
689
|
-
mkdir -p "$REPO_ROOT/.specrails/setup-templates/skills"
|
|
690
|
-
mkdir -p "$REPO_ROOT/.specrails/setup-templates/rules"
|
|
691
|
-
mkdir -p "$REPO_ROOT/.specrails/setup-templates/personas"
|
|
692
|
-
mkdir -p "$REPO_ROOT/.specrails/setup-templates/claude-md"
|
|
693
|
-
mkdir -p "$REPO_ROOT/.specrails/setup-templates/settings"
|
|
694
|
-
|
|
695
|
-
# Ensure .gitignore excludes local runtime artifacts
|
|
696
|
-
_gitignore="${REPO_ROOT}/.gitignore"
|
|
697
|
-
_gitignore_entries=(".claude/agent-memory/" ".specrails/")
|
|
698
|
-
for _entry in "${_gitignore_entries[@]}"; do
|
|
699
|
-
if ! grep -qF "$_entry" "$_gitignore" 2>/dev/null; then
|
|
700
|
-
echo "$_entry" >> "$_gitignore"
|
|
701
|
-
fi
|
|
702
|
-
done
|
|
703
|
-
|
|
704
|
-
# Copy the /specrails:enrich and /specrails:doctor commands (or skills for Codex)
|
|
705
|
-
if [[ "$CLI_PROVIDER" == "codex" ]]; then
|
|
706
|
-
# Codex uses Agent Skills in .agents/skills/<name>/SKILL.md
|
|
707
|
-
{
|
|
708
|
-
echo '---'
|
|
709
|
-
echo 'name: enrich'
|
|
710
|
-
echo 'description: "Interactive wizard to configure the full specrails agent workflow system for this repository. Supports config-driven mode for direct installation from a pre-built config file."'
|
|
711
|
-
echo 'license: MIT'
|
|
712
|
-
echo 'compatibility: "Requires npm, git."'
|
|
713
|
-
echo 'metadata:'
|
|
714
|
-
echo ' author: specrails'
|
|
715
|
-
echo ' version: "1.0"'
|
|
716
|
-
echo '---'
|
|
717
|
-
echo ''
|
|
718
|
-
cat "$SCRIPT_DIR/commands/enrich.md"
|
|
719
|
-
} > "$REPO_ROOT/.agents/skills/enrich/SKILL.md"
|
|
720
|
-
ok "Installed \$enrich skill"
|
|
721
|
-
|
|
722
|
-
{
|
|
723
|
-
echo '---'
|
|
724
|
-
echo 'name: doctor'
|
|
725
|
-
echo 'description: "Health check for the specrails agent workflow system — validates agents, commands, rules, and configuration."'
|
|
726
|
-
echo 'license: MIT'
|
|
727
|
-
echo 'compatibility: "Requires npm, git."'
|
|
728
|
-
echo 'metadata:'
|
|
729
|
-
echo ' author: specrails'
|
|
730
|
-
echo ' version: "1.0"'
|
|
731
|
-
echo '---'
|
|
732
|
-
echo ''
|
|
733
|
-
cat "$SCRIPT_DIR/commands/doctor.md"
|
|
734
|
-
} > "$REPO_ROOT/.agents/skills/doctor/SKILL.md"
|
|
735
|
-
ok "Installed \$doctor skill"
|
|
736
|
-
else
|
|
737
|
-
# Claude Code uses commands in .claude/commands/specrails/ (namespaced as /specrails:*)
|
|
738
|
-
cp "$SCRIPT_DIR/commands/enrich.md" "$REPO_ROOT/$SPECRAILS_DIR/commands/specrails/enrich.md"
|
|
739
|
-
ok "Installed /specrails:enrich command"
|
|
740
|
-
|
|
741
|
-
cp "$SCRIPT_DIR/commands/doctor.md" "$REPO_ROOT/$SPECRAILS_DIR/commands/specrails/doctor.md"
|
|
742
|
-
ok "Installed /specrails:doctor command"
|
|
743
|
-
fi
|
|
744
|
-
|
|
745
|
-
# Install bin/doctor.sh for standalone use
|
|
746
|
-
mkdir -p "$REPO_ROOT/.specrails/bin"
|
|
747
|
-
cp "$SCRIPT_DIR/bin/doctor.sh" "$REPO_ROOT/.specrails/bin/doctor.sh"
|
|
748
|
-
chmod +x "$REPO_ROOT/.specrails/bin/doctor.sh"
|
|
749
|
-
ok "Installed specrails doctor (bin/doctor.sh)"
|
|
750
|
-
|
|
751
|
-
# Write install-config.yaml: copy from --from-config source if provided,
|
|
752
|
-
# otherwise write defaults. Ensures a config file always exists after install
|
|
753
|
-
# so /specrails:enrich --from-config works regardless of how the installer was invoked.
|
|
754
|
-
_install_config="${REPO_ROOT}/.specrails/install-config.yaml"
|
|
755
|
-
if [[ "$FROM_CONFIG" == true && -n "$CONFIG_PATH" && -f "$CONFIG_PATH" && "$CONFIG_PATH" != "$_install_config" ]]; then
|
|
756
|
-
cp "$CONFIG_PATH" "$_install_config"
|
|
757
|
-
ok "Copied install-config.yaml from ${CONFIG_PATH}"
|
|
758
|
-
elif [[ ! -f "$_install_config" ]]; then
|
|
759
|
-
_ic_provider="${CLI_PROVIDER:-claude}"
|
|
760
|
-
_ic_tier="${TIER:-full}"
|
|
761
|
-
_ic_agent_teams="${AGENT_TEAMS:-false}"
|
|
762
|
-
cat > "$_install_config" << YAML
|
|
763
|
-
# specrails install config — generated during install (defaults)
|
|
764
|
-
# Re-run: npx specrails-core@latest init to regenerate with TUI
|
|
765
|
-
version: 1
|
|
766
|
-
provider: ${_ic_provider}
|
|
767
|
-
tier: ${_ic_tier}
|
|
768
|
-
agents:
|
|
769
|
-
selected: [sr-architect, sr-developer, sr-reviewer, sr-test-writer, sr-product-manager]
|
|
770
|
-
excluded: [sr-frontend-developer, sr-backend-developer, sr-frontend-reviewer, sr-backend-reviewer, sr-security-reviewer, sr-performance-reviewer, sr-product-analyst, sr-doc-sync, sr-merge-resolver]
|
|
771
|
-
models:
|
|
772
|
-
preset: balanced
|
|
773
|
-
defaults: { model: sonnet }
|
|
774
|
-
overrides: {}
|
|
775
|
-
agent_teams: ${_ic_agent_teams}
|
|
776
|
-
YAML
|
|
777
|
-
ok "Written .specrails/install-config.yaml (defaults)"
|
|
778
|
-
fi
|
|
779
|
-
|
|
780
|
-
# Copy templates (includes commands, skills, agents, rules, personas, settings)
|
|
781
|
-
# Use tar to exclude node_modules and package-lock.json for performance
|
|
782
|
-
tar -C "$SCRIPT_DIR/templates" --exclude='node_modules' --exclude='package-lock.json' -cf - . \
|
|
783
|
-
| tar -C "$REPO_ROOT/.specrails/setup-templates/" -xf -
|
|
784
|
-
ok "Installed setup templates (commands + skills)"
|
|
785
|
-
|
|
786
|
-
# Filter agent templates to only those listed in agents.selected (when --from-config is active).
|
|
787
|
-
# This allows hub or the TUI to pre-select a subset of agents before enrichment.
|
|
788
|
-
if [[ "$FROM_CONFIG" == true ]]; then
|
|
789
|
-
_cfg_to_read="${CONFIG_PATH:-${REPO_ROOT}/.specrails/install-config.yaml}"
|
|
790
|
-
if [[ -f "$_cfg_to_read" ]]; then
|
|
791
|
-
# Parse selected agents from inline YAML list: selected: [sr-architect, sr-developer, ...]
|
|
792
|
-
_selected_raw=$(grep '^ selected:' "$_cfg_to_read" | sed 's/.*\[//;s/\].*//;s/,/ /g' || true)
|
|
793
|
-
if [[ -n "$_selected_raw" ]]; then
|
|
794
|
-
# Core agents are always kept — the pipeline depends on them
|
|
795
|
-
_core_agents="sr-architect sr-developer sr-reviewer sr-merge-resolver"
|
|
796
|
-
_agents_dir="${REPO_ROOT}/.specrails/setup-templates/agents"
|
|
797
|
-
_removed=0
|
|
798
|
-
for _agent_file in "$_agents_dir/"*.md; do
|
|
799
|
-
[[ -f "$_agent_file" ]] || continue
|
|
800
|
-
_agent_name="$(basename "$_agent_file" .md)"
|
|
801
|
-
|
|
802
|
-
# Never remove core agents
|
|
803
|
-
if echo " $_core_agents " | grep -q " ${_agent_name} "; then
|
|
804
|
-
continue
|
|
805
|
-
fi
|
|
806
|
-
|
|
807
|
-
_in_selected=false
|
|
808
|
-
for _sel in $_selected_raw; do
|
|
809
|
-
# Strip whitespace/commas from parsed token
|
|
810
|
-
_sel="${_sel//,/}"
|
|
811
|
-
_sel="${_sel// /}"
|
|
812
|
-
if [[ "$_sel" == "$_agent_name" ]]; then
|
|
813
|
-
_in_selected=true
|
|
814
|
-
break
|
|
815
|
-
fi
|
|
816
|
-
done
|
|
817
|
-
if [[ "$_in_selected" == false ]]; then
|
|
818
|
-
rm -f "$_agent_file"
|
|
819
|
-
(( _removed++ )) || true
|
|
820
|
-
fi
|
|
821
|
-
done
|
|
822
|
-
if (( _removed > 0 )); then
|
|
823
|
-
ok "Agent templates filtered: removed ${_removed} excluded agent(s)"
|
|
824
|
-
fi
|
|
825
|
-
fi
|
|
826
|
-
|
|
827
|
-
# Apply defaults.model to all agent templates when present.
|
|
828
|
-
# Handles both inline "defaults: { model: haiku }" and block "defaults:\n model: haiku"
|
|
829
|
-
_cfg_default_model=""
|
|
830
|
-
_defaults_line=$(grep '^\s*defaults:' "$_cfg_to_read" 2>/dev/null | head -1 || true)
|
|
831
|
-
if echo "$_defaults_line" | grep -q 'model:' 2>/dev/null; then
|
|
832
|
-
_cfg_default_model=$(echo "$_defaults_line" | sed 's/.*model:[[:space:]]*//' | awk '{print $1}' | tr -d '}[:space:]')
|
|
833
|
-
else
|
|
834
|
-
_cfg_default_model=$(sed -n '/^\s*defaults:/,/^\s*[a-z]/{ /^\s*model:/p; }' "$_cfg_to_read" 2>/dev/null | awk '{print $2}' | tr -d '[:space:]' || true)
|
|
835
|
-
fi
|
|
836
|
-
if [[ -n "$_cfg_default_model" ]]; then
|
|
837
|
-
for _agent_file in "$_agents_dir/"*.md; do
|
|
838
|
-
[[ -f "$_agent_file" ]] || continue
|
|
839
|
-
sed -i.bak "s/^model: .*/model: ${_cfg_default_model}/" "$_agent_file" && rm -f "${_agent_file}.bak"
|
|
840
|
-
done
|
|
841
|
-
fi
|
|
842
|
-
|
|
843
|
-
# Apply model overrides to agent frontmatter when present.
|
|
844
|
-
# Overrides YAML format: " overrides:\n sr-architect: opus"
|
|
845
|
-
_in_overrides=false
|
|
846
|
-
while IFS= read -r _line; do
|
|
847
|
-
if [[ "$_line" =~ ^[[:space:]]*overrides: ]]; then
|
|
848
|
-
# Check if overrides are on same line: overrides: { sr-architect: opus }
|
|
849
|
-
if [[ "$_line" =~ \{.*\} ]]; then
|
|
850
|
-
_inline=$(echo "$_line" | sed 's/.*{//;s/}.*//;s/,/ /g')
|
|
851
|
-
for _pair in $_inline; do
|
|
852
|
-
_key="${_pair%%:*}"
|
|
853
|
-
_val="${_pair##*:}"
|
|
854
|
-
_agent_file="${REPO_ROOT}/.specrails/setup-templates/agents/${_key}.md"
|
|
855
|
-
if [[ -f "$_agent_file" ]]; then
|
|
856
|
-
sed -i.bak "s/^model: .*/model: ${_val}/" "$_agent_file" && rm -f "${_agent_file}.bak"
|
|
857
|
-
fi
|
|
858
|
-
done
|
|
859
|
-
_in_overrides=false
|
|
860
|
-
else
|
|
861
|
-
_in_overrides=true
|
|
862
|
-
fi
|
|
863
|
-
elif [[ "$_in_overrides" == true ]]; then
|
|
864
|
-
if [[ "$_line" =~ ^[[:space:]]{4}([a-z0-9_-]+):[[:space:]]*([a-z]+) ]]; then
|
|
865
|
-
_key="${BASH_REMATCH[1]}"
|
|
866
|
-
_val="${BASH_REMATCH[2]}"
|
|
867
|
-
_agent_file="${REPO_ROOT}/.specrails/setup-templates/agents/${_key}.md"
|
|
868
|
-
if [[ -f "$_agent_file" ]]; then
|
|
869
|
-
sed -i.bak "s/^model: .*/model: ${_val}/" "$_agent_file" && rm -f "${_agent_file}.bak"
|
|
870
|
-
fi
|
|
871
|
-
elif [[ ! "$_line" =~ ^[[:space:]]{4} ]]; then
|
|
872
|
-
_in_overrides=false
|
|
873
|
-
fi
|
|
874
|
-
fi
|
|
875
|
-
done < "$_cfg_to_read"
|
|
876
|
-
fi
|
|
877
|
-
fi
|
|
878
|
-
|
|
879
|
-
# Write OSS detection results for /specrails:enrich
|
|
880
|
-
cat > "$REPO_ROOT/.specrails/setup-templates/.oss-detection.json" << EOF
|
|
881
|
-
{
|
|
882
|
-
"is_oss": $IS_OSS,
|
|
883
|
-
"signals": {
|
|
884
|
-
"public_repo": $HAS_PUBLIC_REPO,
|
|
885
|
-
"has_ci": $HAS_CI,
|
|
886
|
-
"has_contributing": $HAS_CONTRIBUTING
|
|
887
|
-
}
|
|
888
|
-
}
|
|
889
|
-
EOF
|
|
890
|
-
ok "OSS detection results written"
|
|
891
|
-
|
|
892
|
-
# Write provider detection results for /specrails:enrich
|
|
893
|
-
cat > "$REPO_ROOT/.specrails/setup-templates/.provider-detection.json" << EOF
|
|
894
|
-
{
|
|
895
|
-
"cli_provider": "$CLI_PROVIDER",
|
|
896
|
-
"specrails_dir": "$SPECRAILS_DIR",
|
|
897
|
-
"instructions_file": "$INSTRUCTIONS_FILE",
|
|
898
|
-
"agent_teams": $AGENT_TEAMS
|
|
899
|
-
}
|
|
900
|
-
EOF
|
|
901
|
-
ok "Provider detection results written ($CLI_PROVIDER → .specrails/setup-templates/)"
|
|
902
|
-
|
|
903
|
-
# Copy security exemptions config (skip if already exists — preserve user exemptions)
|
|
904
|
-
if [ ! -f "${REPO_ROOT}/$SPECRAILS_DIR/security-exemptions.yaml" ]; then
|
|
905
|
-
cp "${SCRIPT_DIR}/templates/security/security-exemptions.yaml" "${REPO_ROOT}/$SPECRAILS_DIR/security-exemptions.yaml"
|
|
906
|
-
ok "Created $SPECRAILS_DIR/security-exemptions.yaml"
|
|
907
|
-
fi
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
# ─────────────────────────────────────────────
|
|
911
|
-
# Phase 3c: Quick-tier direct placement
|
|
912
|
-
# ─────────────────────────────────────────────
|
|
913
|
-
# For Quick tier, copy agents/commands/rules/personas/settings from
|
|
914
|
-
# setup-templates directly to their final locations under $SPECRAILS_DIR.
|
|
915
|
-
# Placeholders are stripped (enrich not required).
|
|
916
|
-
|
|
917
|
-
if [[ "$TIER" == "quick" ]]; then
|
|
918
|
-
step "Phase 3c: Placing agents and commands (quick install)"
|
|
919
|
-
|
|
920
|
-
_templates="${REPO_ROOT}/.specrails/setup-templates"
|
|
921
|
-
_project_name="$(basename "$REPO_ROOT")"
|
|
922
|
-
|
|
923
|
-
# VPC/persona-dependent agents are excluded from Quick tier (require enrichment)
|
|
924
|
-
_quick_excluded_agents="sr-product-manager sr-product-analyst"
|
|
925
|
-
|
|
926
|
-
# --- Agents ---
|
|
927
|
-
if [[ -d "$_templates/agents" ]] && ls "$_templates/agents/"*.md &>/dev/null; then
|
|
928
|
-
mkdir -p "${REPO_ROOT}/${SPECRAILS_DIR}/agents"
|
|
929
|
-
_agent_count=0
|
|
930
|
-
_agent_skipped=0
|
|
931
|
-
for _src in "$_templates/agents/"*.md; do
|
|
932
|
-
[[ -f "$_src" ]] || continue
|
|
933
|
-
_name="$(basename "$_src" .md)"
|
|
934
|
-
|
|
935
|
-
# Skip VPC-dependent agents in Quick tier
|
|
936
|
-
if echo " $_quick_excluded_agents " | grep -q " ${_name} "; then
|
|
937
|
-
(( _agent_skipped++ )) || true
|
|
938
|
-
continue
|
|
939
|
-
fi
|
|
940
|
-
|
|
941
|
-
_dest="${REPO_ROOT}/${SPECRAILS_DIR}/agents/${_name}.md"
|
|
942
|
-
cp "$_src" "$_dest"
|
|
943
|
-
|
|
944
|
-
# Substitute known placeholders
|
|
945
|
-
sed -i.bak \
|
|
946
|
-
-e "s|{{PROJECT_NAME}}|${_project_name}|g" \
|
|
947
|
-
-e "s|{{MEMORY_PATH}}|.claude/agent-memory/${_name}/|g" \
|
|
948
|
-
-e "s|{{SECURITY_EXEMPTIONS_PATH}}|${SPECRAILS_DIR}/security-exemptions.yaml|g" \
|
|
949
|
-
-e "s|{{PERSONA_DIR}}|${SPECRAILS_DIR}/agents/personas/|g" \
|
|
950
|
-
"$_dest" && rm -f "${_dest}.bak"
|
|
951
|
-
|
|
952
|
-
# Strip remaining {{PLACEHOLDER}} lines (leave blank line)
|
|
953
|
-
sed -i.bak 's/{{[A-Z_]*}}//g' "$_dest" && rm -f "${_dest}.bak"
|
|
954
|
-
|
|
955
|
-
# Create agent memory directory
|
|
956
|
-
mkdir -p "${REPO_ROOT}/.claude/agent-memory/${_name}"
|
|
957
|
-
|
|
958
|
-
# Agents that write explanation records need the shared explanations dir
|
|
959
|
-
if [[ "$_name" == "sr-architect" || "$_name" == "sr-reviewer" ]]; then
|
|
960
|
-
mkdir -p "${REPO_ROOT}/.claude/agent-memory/explanations"
|
|
961
|
-
fi
|
|
962
|
-
|
|
963
|
-
(( _agent_count++ )) || true
|
|
964
|
-
done
|
|
965
|
-
if (( _agent_skipped > 0 )); then
|
|
966
|
-
ok "Installed ${_agent_count} agent(s) to ${SPECRAILS_DIR}/agents/ (skipped ${_agent_skipped} VPC-dependent)"
|
|
967
|
-
else
|
|
968
|
-
ok "Installed ${_agent_count} agent(s) to ${SPECRAILS_DIR}/agents/"
|
|
969
|
-
fi
|
|
970
|
-
fi
|
|
971
|
-
|
|
972
|
-
# Dynamic command exclusion based on installed agents.
|
|
973
|
-
# Each command maps to the agent(s) it depends on — if the agent
|
|
974
|
-
# wasn't installed, the command is excluded automatically.
|
|
975
|
-
#
|
|
976
|
-
# Dependency map (command → required agent):
|
|
977
|
-
# auto-propose-backlog-specs → sr-product-manager
|
|
978
|
-
# get-backlog-specs → sr-product-analyst
|
|
979
|
-
# vpc-drift → sr-product-manager, sr-product-analyst
|
|
980
|
-
# merge-resolve → sr-merge-resolver
|
|
981
|
-
# team-debug, team-review → AGENT_TEAMS flag
|
|
982
|
-
_quick_excluded_cmds=""
|
|
983
|
-
_installed_agents_dir="${REPO_ROOT}/${SPECRAILS_DIR}/agents"
|
|
984
|
-
|
|
985
|
-
# Agent-dependent commands
|
|
986
|
-
if ! ls "$_installed_agents_dir/sr-product-manager.md" &>/dev/null; then
|
|
987
|
-
_quick_excluded_cmds="$_quick_excluded_cmds auto-propose-backlog-specs vpc-drift"
|
|
988
|
-
fi
|
|
989
|
-
if ! ls "$_installed_agents_dir/sr-product-analyst.md" &>/dev/null; then
|
|
990
|
-
_quick_excluded_cmds="$_quick_excluded_cmds get-backlog-specs"
|
|
991
|
-
# vpc-drift needs both product agents
|
|
992
|
-
if ! echo " $_quick_excluded_cmds " | grep -q " vpc-drift "; then
|
|
993
|
-
_quick_excluded_cmds="$_quick_excluded_cmds vpc-drift"
|
|
994
|
-
fi
|
|
995
|
-
fi
|
|
996
|
-
if ! ls "$_installed_agents_dir/sr-merge-resolver.md" &>/dev/null; then
|
|
997
|
-
_quick_excluded_cmds="$_quick_excluded_cmds merge-resolve"
|
|
998
|
-
fi
|
|
999
|
-
|
|
1000
|
-
# Agent Teams commands (flag-dependent, not agent-dependent)
|
|
1001
|
-
if [[ "$AGENT_TEAMS" != "true" ]]; then
|
|
1002
|
-
_quick_excluded_cmds="$_quick_excluded_cmds team-debug team-review"
|
|
1003
|
-
fi
|
|
1004
|
-
|
|
1005
|
-
# --- Commands ---
|
|
1006
|
-
if [[ -d "$_templates/commands/specrails" ]]; then
|
|
1007
|
-
mkdir -p "${REPO_ROOT}/${SPECRAILS_DIR}/commands/specrails"
|
|
1008
|
-
_cmd_count=0
|
|
1009
|
-
for _src in "$_templates/commands/specrails/"*.md; do
|
|
1010
|
-
[[ -f "$_src" ]] || continue
|
|
1011
|
-
_cmd_name="$(basename "$_src" .md)"
|
|
1012
|
-
|
|
1013
|
-
# Skip commands whose required agents are not installed
|
|
1014
|
-
if echo " $_quick_excluded_cmds " | grep -q " ${_cmd_name} "; then
|
|
1015
|
-
continue
|
|
1016
|
-
fi
|
|
1017
|
-
|
|
1018
|
-
_dest="${REPO_ROOT}/${SPECRAILS_DIR}/commands/specrails/${_cmd_name}.md"
|
|
1019
|
-
cp "$_src" "$_dest"
|
|
1020
|
-
sed -i.bak \
|
|
1021
|
-
-e "s|{{PROJECT_NAME}}|${_project_name}|g" \
|
|
1022
|
-
-e "s|{{MEMORY_PATH}}|.claude/agent-memory/|g" \
|
|
1023
|
-
-e "s|{{PERSONA_DIR}}|${SPECRAILS_DIR}/agents/personas/|g" \
|
|
1024
|
-
-e "s|{{SECURITY_EXEMPTIONS_PATH}}|${SPECRAILS_DIR}/security-exemptions.yaml|g" \
|
|
1025
|
-
"$_dest" && rm -f "${_dest}.bak"
|
|
1026
|
-
sed -i.bak 's/{{[A-Z_]*}}//g' "$_dest" && rm -f "${_dest}.bak"
|
|
1027
|
-
(( _cmd_count++ )) || true
|
|
1028
|
-
done
|
|
1029
|
-
ok "Installed ${_cmd_count} command(s) to ${SPECRAILS_DIR}/commands/specrails/"
|
|
1030
|
-
fi
|
|
1031
|
-
|
|
1032
|
-
# --- Rules ---
|
|
1033
|
-
if [[ -d "$_templates/rules" ]]; then
|
|
1034
|
-
mkdir -p "${REPO_ROOT}/${SPECRAILS_DIR}/rules"
|
|
1035
|
-
for _src in "$_templates/rules/"*.md; do
|
|
1036
|
-
[[ -f "$_src" ]] || continue
|
|
1037
|
-
cp "$_src" "${REPO_ROOT}/${SPECRAILS_DIR}/rules/$(basename "$_src")"
|
|
1038
|
-
done
|
|
1039
|
-
ok "Installed rules to ${SPECRAILS_DIR}/rules/"
|
|
1040
|
-
fi
|
|
1041
|
-
|
|
1042
|
-
# Personas are NOT installed in Quick tier (require VPC enrichment)
|
|
1043
|
-
|
|
1044
|
-
# --- Settings ---
|
|
1045
|
-
if [[ -f "$_templates/settings/settings.json" && ! -f "${REPO_ROOT}/${SPECRAILS_DIR}/settings.json" ]]; then
|
|
1046
|
-
cp "$_templates/settings/settings.json" "${REPO_ROOT}/${SPECRAILS_DIR}/settings.json"
|
|
1047
|
-
ok "Installed settings.json"
|
|
1048
|
-
fi
|
|
1049
|
-
if [[ -f "$_templates/settings/integration-contract.json" ]]; then
|
|
1050
|
-
cp "$_templates/settings/integration-contract.json" "${REPO_ROOT}/.specrails/integration-contract.json"
|
|
1051
|
-
ok "Installed integration-contract.json"
|
|
1052
|
-
fi
|
|
1053
|
-
|
|
1054
|
-
# --- Backlog config (default: local provider) ---
|
|
1055
|
-
_backlog_cfg="${REPO_ROOT}/.specrails/backlog-config.json"
|
|
1056
|
-
if [[ ! -f "$_backlog_cfg" ]]; then
|
|
1057
|
-
mkdir -p "${REPO_ROOT}/.specrails"
|
|
1058
|
-
cat > "$_backlog_cfg" <<'BCEOF'
|
|
1059
|
-
{
|
|
1060
|
-
"provider": "local",
|
|
1061
|
-
"write_access": true,
|
|
1062
|
-
"git_auto": true
|
|
1063
|
-
}
|
|
1064
|
-
BCEOF
|
|
1065
|
-
ok "Created .specrails/backlog-config.json (provider: local, git_auto: true)"
|
|
1066
|
-
fi
|
|
1067
|
-
|
|
1068
|
-
# --- Local tickets store ---
|
|
1069
|
-
_tickets_file="${REPO_ROOT}/.specrails/local-tickets.json"
|
|
1070
|
-
if [[ ! -f "$_tickets_file" ]]; then
|
|
1071
|
-
_now="$(date -u +"%Y-%m-%dT%H:%M:%S.000Z")"
|
|
1072
|
-
cat > "$_tickets_file" <<LTEOF
|
|
1073
|
-
{
|
|
1074
|
-
"schema_version": "1.0",
|
|
1075
|
-
"revision": 0,
|
|
1076
|
-
"last_updated": "${_now}",
|
|
1077
|
-
"next_id": 1,
|
|
1078
|
-
"tickets": {}
|
|
1079
|
-
}
|
|
1080
|
-
LTEOF
|
|
1081
|
-
ok "Created .specrails/local-tickets.json"
|
|
1082
|
-
fi
|
|
1083
|
-
|
|
1084
|
-
# --- Skills (OpenSpec) ---
|
|
1085
|
-
# Dynamic skill exclusion based on installed agents (mirrors command logic).
|
|
1086
|
-
# Dependency map (skill → required agent):
|
|
1087
|
-
# sr-auto-propose-backlog-specs → sr-product-manager
|
|
1088
|
-
# sr-get-backlog-specs → sr-product-analyst
|
|
1089
|
-
_quick_excluded_skills=""
|
|
1090
|
-
if ! ls "$_installed_agents_dir/sr-product-manager.md" &>/dev/null; then
|
|
1091
|
-
_quick_excluded_skills="$_quick_excluded_skills sr-auto-propose-backlog-specs"
|
|
1092
|
-
fi
|
|
1093
|
-
if ! ls "$_installed_agents_dir/sr-product-analyst.md" &>/dev/null; then
|
|
1094
|
-
_quick_excluded_skills="$_quick_excluded_skills sr-get-backlog-specs"
|
|
1095
|
-
fi
|
|
1096
|
-
if [[ -d "$_templates/skills" ]]; then
|
|
1097
|
-
mkdir -p "${REPO_ROOT}/${SPECRAILS_DIR}/skills"
|
|
1098
|
-
_skill_count=0
|
|
1099
|
-
for _skill_dir in "$_templates/skills/"*/; do
|
|
1100
|
-
[[ -d "$_skill_dir" ]] || continue
|
|
1101
|
-
_skill_name="$(basename "$_skill_dir")"
|
|
1102
|
-
if echo " $_quick_excluded_skills " | grep -q " ${_skill_name} "; then
|
|
1103
|
-
continue
|
|
1104
|
-
fi
|
|
1105
|
-
cp -r "$_skill_dir" "${REPO_ROOT}/${SPECRAILS_DIR}/skills/${_skill_name}"
|
|
1106
|
-
(( _skill_count++ )) || true
|
|
1107
|
-
done
|
|
1108
|
-
ok "Installed ${_skill_count} skill(s) to ${SPECRAILS_DIR}/skills/"
|
|
1109
|
-
fi
|
|
1110
|
-
|
|
1111
|
-
if [[ "$HUB_JSON" == true ]]; then
|
|
1112
|
-
echo "CHECKPOINT:quick_placement:done:Agents and commands placed"
|
|
1113
|
-
fi
|
|
1114
|
-
fi
|
|
1115
|
-
|
|
1116
|
-
# Initialize OpenSpec if available and not already initialized
|
|
1117
|
-
if [ "$HAS_OPENSPEC" = true ] && [ ! -d "$REPO_ROOT/openspec" ]; then
|
|
1118
|
-
# Map specrails provider to openspec tool name
|
|
1119
|
-
_openspec_tool="claude"
|
|
1120
|
-
[[ "$CLI_PROVIDER" == "codex" ]] && _openspec_tool="codex"
|
|
1121
|
-
|
|
1122
|
-
info "Initializing OpenSpec (--tools ${_openspec_tool} --force)..."
|
|
1123
|
-
cd "$REPO_ROOT" && openspec init --tools "${_openspec_tool}" --force 2>/dev/null && {
|
|
1124
|
-
ok "OpenSpec initialized"
|
|
1125
|
-
} || {
|
|
1126
|
-
warn "OpenSpec init failed — you can run 'openspec init' manually later"
|
|
1127
|
-
}
|
|
1128
|
-
fi
|
|
1129
|
-
|
|
1130
|
-
# ─────────────────────────────────────────────
|
|
1131
|
-
# Phase 3b: Write version and manifest
|
|
1132
|
-
# ─────────────────────────────────────────────
|
|
1133
|
-
|
|
1134
|
-
step "Phase 3b: Writing version and manifest"
|
|
1135
|
-
|
|
1136
|
-
generate_manifest
|
|
1137
|
-
ok "Written .specrails/specrails-version ($(cat "$REPO_ROOT/.specrails/specrails-version"))"
|
|
1138
|
-
ok "Written .specrails/specrails-manifest.json"
|
|
1139
|
-
|
|
1140
|
-
# ─────────────────────────────────────────────
|
|
1141
|
-
# Phase 4: Summary & next steps
|
|
1142
|
-
# ─────────────────────────────────────────────
|
|
1143
|
-
|
|
1144
|
-
step "Phase 4: Installation complete"
|
|
1145
|
-
|
|
1146
|
-
echo ""
|
|
1147
|
-
echo -e "${BOLD}${GREEN}Installation summary:${NC}"
|
|
1148
|
-
echo ""
|
|
1149
|
-
echo " Provider: $CLI_PROVIDER → output to $SPECRAILS_DIR/"
|
|
1150
|
-
if [[ "$AGENT_TEAMS" == "true" ]]; then
|
|
1151
|
-
echo " Agent Teams: installed (/specrails:team-review, /specrails:team-debug)"
|
|
1152
|
-
echo " Feature flag: CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1 (required)"
|
|
1153
|
-
fi
|
|
1154
|
-
if [[ "$TIER" == "quick" ]]; then
|
|
1155
|
-
# Quick tier: agents placed directly
|
|
1156
|
-
_installed_agents=$(ls "${REPO_ROOT}/${SPECRAILS_DIR}/agents/"*.md 2>/dev/null | wc -l | tr -d ' ')
|
|
1157
|
-
_installed_commands=$(ls "${REPO_ROOT}/${SPECRAILS_DIR}/commands/specrails/"*.md 2>/dev/null | wc -l | tr -d ' ')
|
|
1158
|
-
echo ""
|
|
1159
|
-
echo " Installed:"
|
|
1160
|
-
echo " ${_installed_agents} agent(s) → ${SPECRAILS_DIR}/agents/"
|
|
1161
|
-
echo " ${_installed_commands} command(s) → ${SPECRAILS_DIR}/commands/specrails/"
|
|
1162
|
-
echo " .specrails/backlog-config.json (provider: local)"
|
|
1163
|
-
echo " .specrails/local-tickets.json"
|
|
1164
|
-
echo " .specrails/specrails-version"
|
|
1165
|
-
echo " .specrails/specrails-manifest.json"
|
|
1166
|
-
echo ""
|
|
1167
|
-
|
|
1168
|
-
echo -e "${BOLD}Prerequisites:${NC}"
|
|
1169
|
-
echo ""
|
|
1170
|
-
[ "$HAS_NPM" = true ] && ok "npm" || warn "npm (optional)"
|
|
1171
|
-
[ "$HAS_OPENSPEC" = true ] && ok "OpenSpec" || warn "OpenSpec (optional)"
|
|
1172
|
-
[ "$HAS_GH" = true ] && ok "GitHub CLI" || warn "GitHub CLI (optional, for GitHub Issues backlog)"
|
|
1173
|
-
[ "$HAS_JIRA" = true ] && ok "JIRA CLI" || info "JIRA CLI not found (optional, for JIRA backlog)"
|
|
1174
|
-
echo ""
|
|
1175
|
-
|
|
1176
|
-
echo -e "${BOLD}${CYAN}Next steps:${NC}"
|
|
1177
|
-
echo ""
|
|
1178
|
-
echo " 1. Open $CLI_PROVIDER in this repo:"
|
|
1179
|
-
echo ""
|
|
1180
|
-
echo -e " ${BOLD}cd $REPO_ROOT && $CLI_PROVIDER${NC}"
|
|
1181
|
-
echo ""
|
|
1182
|
-
echo " 2. Your agents are ready to use! Start working:"
|
|
1183
|
-
echo ""
|
|
1184
|
-
echo " Agents, commands, and rules are pre-configured."
|
|
1185
|
-
echo " No additional setup required."
|
|
1186
|
-
echo ""
|
|
1187
|
-
else
|
|
1188
|
-
echo ""
|
|
1189
|
-
echo " Files installed:"
|
|
1190
|
-
if [[ "$CLI_PROVIDER" == "codex" ]]; then
|
|
1191
|
-
echo " .agents/skills/enrich/SKILL.md ← The \$enrich skill"
|
|
1192
|
-
echo " .agents/skills/doctor/SKILL.md ← The \$doctor skill"
|
|
1193
|
-
else
|
|
1194
|
-
echo " $SPECRAILS_DIR/commands/specrails/enrich.md ← The /specrails:enrich command"
|
|
1195
|
-
fi
|
|
1196
|
-
echo " .specrails/setup-templates/ ← Templates: commands + skills (temporary, removed after enrich)"
|
|
1197
|
-
echo " .specrails/specrails-version ← Installed specrails version"
|
|
1198
|
-
echo " .specrails/specrails-manifest.json ← Artifact checksums for update detection"
|
|
1199
|
-
echo ""
|
|
1200
|
-
|
|
1201
|
-
echo -e "${BOLD}Prerequisites:${NC}"
|
|
1202
|
-
echo ""
|
|
1203
|
-
[ "$HAS_NPM" = true ] && ok "npm" || warn "npm (optional)"
|
|
1204
|
-
[ "$HAS_OPENSPEC" = true ] && ok "OpenSpec" || warn "OpenSpec (optional)"
|
|
1205
|
-
[ "$HAS_GH" = true ] && ok "GitHub CLI" || warn "GitHub CLI (optional, for GitHub Issues backlog)"
|
|
1206
|
-
[ "$HAS_JIRA" = true ] && ok "JIRA CLI" || info "JIRA CLI not found (optional, for JIRA backlog)"
|
|
1207
|
-
echo ""
|
|
1208
|
-
|
|
1209
|
-
echo -e "${BOLD}${CYAN}Next steps:${NC}"
|
|
1210
|
-
echo ""
|
|
1211
|
-
echo " 1. Open $CLI_PROVIDER in this repo:"
|
|
1212
|
-
echo ""
|
|
1213
|
-
echo -e " ${BOLD}cd $REPO_ROOT && $CLI_PROVIDER${NC}"
|
|
1214
|
-
echo ""
|
|
1215
|
-
echo " 2. Run the enrich wizard:"
|
|
1216
|
-
echo ""
|
|
1217
|
-
if [[ "$CLI_PROVIDER" == "codex" ]]; then
|
|
1218
|
-
echo -e " ${BOLD}\$enrich${NC}"
|
|
1219
|
-
else
|
|
1220
|
-
echo -e " ${BOLD}/specrails:enrich${NC}"
|
|
1221
|
-
fi
|
|
1222
|
-
echo ""
|
|
1223
|
-
if [[ "$CLI_PROVIDER" == "codex" ]]; then
|
|
1224
|
-
echo " Codex will analyze your codebase, ask about your users,"
|
|
1225
|
-
else
|
|
1226
|
-
echo " Claude will analyze your codebase, ask about your users,"
|
|
1227
|
-
fi
|
|
1228
|
-
echo " research the competitive landscape, and generate all agents,"
|
|
1229
|
-
echo " commands, rules, and personas adapted to your project."
|
|
1230
|
-
echo ""
|
|
1231
|
-
fi
|