specrails-core 4.1.1 → 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.
Files changed (66) hide show
  1. package/README.md +4 -4
  2. package/bin/specrails-core.mjs +302 -0
  3. package/commands/doctor.md +5 -5
  4. package/commands/enrich.md +9 -9
  5. package/dist/installer/cli.js +167 -0
  6. package/dist/installer/cli.js.map +1 -0
  7. package/dist/installer/commands/doctor.js +144 -0
  8. package/dist/installer/commands/doctor.js.map +1 -0
  9. package/dist/installer/commands/init.js +182 -0
  10. package/dist/installer/commands/init.js.map +1 -0
  11. package/dist/installer/commands/perf-check.js +16 -0
  12. package/dist/installer/commands/perf-check.js.map +1 -0
  13. package/dist/installer/commands/update.js +170 -0
  14. package/dist/installer/commands/update.js.map +1 -0
  15. package/dist/installer/phases/install-config.js +120 -0
  16. package/dist/installer/phases/install-config.js.map +1 -0
  17. package/dist/installer/phases/manifest.js +93 -0
  18. package/dist/installer/phases/manifest.js.map +1 -0
  19. package/dist/installer/phases/prereqs.js +116 -0
  20. package/dist/installer/phases/prereqs.js.map +1 -0
  21. package/dist/installer/phases/provider-detect.js +111 -0
  22. package/dist/installer/phases/provider-detect.js.map +1 -0
  23. package/dist/installer/phases/scaffold.js +373 -0
  24. package/dist/installer/phases/scaffold.js.map +1 -0
  25. package/dist/installer/util/errors.js +79 -0
  26. package/dist/installer/util/errors.js.map +1 -0
  27. package/dist/installer/util/exec.js +151 -0
  28. package/dist/installer/util/exec.js.map +1 -0
  29. package/dist/installer/util/fs.js +153 -0
  30. package/dist/installer/util/fs.js.map +1 -0
  31. package/dist/installer/util/git.js +113 -0
  32. package/dist/installer/util/git.js.map +1 -0
  33. package/dist/installer/util/logger.js +55 -0
  34. package/dist/installer/util/logger.js.map +1 -0
  35. package/dist/installer/util/paths.js +66 -0
  36. package/dist/installer/util/paths.js.map +1 -0
  37. package/dist/installer/util/prompts.js +49 -0
  38. package/dist/installer/util/prompts.js.map +1 -0
  39. package/dist/installer/util/template.js +60 -0
  40. package/dist/installer/util/template.js.map +1 -0
  41. package/docs/deployment.md +2 -1
  42. package/docs/installation.md +6 -3
  43. package/docs/testing/test-matrix-codex.md +19 -11
  44. package/docs/updating.md +24 -49
  45. package/docs/user-docs/faq.md +1 -1
  46. package/docs/windows.md +53 -0
  47. package/{templates/settings/integration-contract.json → integration-contract.json} +2 -2
  48. package/package.json +25 -10
  49. package/pinned-versions.json +4 -0
  50. package/schemas/profile.v1.json +11 -3
  51. package/templates/agents/sr-architect.md +1 -1
  52. package/templates/agents/sr-reviewer.md +1 -1
  53. package/templates/commands/specrails/compat-check.md +3 -3
  54. package/templates/commands/specrails/doctor.md +5 -5
  55. package/templates/commands/specrails/enrich.md +9 -9
  56. package/templates/commands/specrails/reconfig.md +2 -2
  57. package/templates/commands/specrails/refactor-recommender.md +2 -2
  58. package/templates/commands/specrails/vpc-drift.md +1 -1
  59. package/templates/skills/sr-compat-check/SKILL.md +3 -3
  60. package/templates/skills/sr-refactor-recommender/SKILL.md +2 -2
  61. package/bin/doctor.sh +0 -127
  62. package/bin/perf-check.sh +0 -21
  63. package/bin/specrails-core.js +0 -262
  64. package/commands/setup.md +0 -1461
  65. package/install.sh +0 -1231
  66. package/update.sh +0 -870
package/update.sh DELETED
@@ -1,870 +0,0 @@
1
- #!/bin/bash
2
- set -euo pipefail
3
-
4
- # specrails updater
5
- # Updates an existing specrails installation in a target repository.
6
- # Preserves project-specific customizations (agents, personas, rules).
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 survives across
14
- # specrails-core upgrades. specrails-hub writes profile files here. Breaking
15
- # this contract silently destroys user work. 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
- SPECRAILS_TMPDIR="$(mktemp -d)"
24
- trap 'rm -rf "$SPECRAILS_TMPDIR"' EXIT
25
- git clone --depth 1 https://github.com/fjpulidop/specrails.git "$SPECRAILS_TMPDIR/specrails" 2>/dev/null || {
26
- echo "Error: failed to clone specrails repository." >&2
27
- exit 1
28
- }
29
- SCRIPT_DIR="$SPECRAILS_TMPDIR/specrails"
30
- else
31
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
32
- fi
33
- REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || echo "")"
34
-
35
- # Colors
36
- RED='\033[0;31m'
37
- GREEN='\033[0;32m'
38
- YELLOW='\033[1;33m'
39
- BLUE='\033[0;34m'
40
- CYAN='\033[0;36m'
41
- BOLD='\033[1m'
42
- NC='\033[0m'
43
-
44
- # ─────────────────────────────────────────────
45
- # Argument parsing
46
- # ─────────────────────────────────────────────
47
-
48
- CUSTOM_ROOT_DIR=""
49
- UPDATE_COMPONENT="all"
50
- FORCE_UPDATE=false
51
-
52
- while [[ $# -gt 0 ]]; do
53
- case "$1" in
54
- --root-dir)
55
- if [[ -z "${2:-}" ]]; then
56
- echo "Error: --root-dir requires a path argument." >&2
57
- exit 1
58
- fi
59
- CUSTOM_ROOT_DIR="$2"
60
- shift 2
61
- ;;
62
- --only)
63
- if [[ -z "${2:-}" ]]; then
64
- echo "Error: --only requires a component argument." >&2
65
- echo "Usage: update.sh [--root-dir <path>] [--only <commands|agents|core|all>] [--force]" >&2
66
- exit 1
67
- fi
68
- UPDATE_COMPONENT="$2"
69
- case "$UPDATE_COMPONENT" in
70
- commands|agents|core|all) ;;
71
- *)
72
- echo "Error: unknown component '$UPDATE_COMPONENT'." >&2
73
- echo "Valid values: commands, agents, core, all" >&2
74
- exit 1
75
- ;;
76
- esac
77
- shift 2
78
- ;;
79
- --force)
80
- FORCE_UPDATE=true
81
- shift
82
- ;;
83
- *)
84
- echo "Unknown argument: $1" >&2
85
- echo "Usage: update.sh [--root-dir <path>] [--only <commands|agents|core|all>] [--force]" >&2
86
- exit 1
87
- ;;
88
- esac
89
- done
90
-
91
- # Override REPO_ROOT if --root-dir was provided
92
- if [[ -n "$CUSTOM_ROOT_DIR" ]]; then
93
- REPO_ROOT="$(cd "$CUSTOM_ROOT_DIR" 2>/dev/null && pwd)" || {
94
- echo "Error: --root-dir path does not exist or is not accessible: $CUSTOM_ROOT_DIR" >&2
95
- exit 1
96
- }
97
- if [[ ! -d "$REPO_ROOT" ]]; then
98
- echo "Error: --root-dir path is not a directory: $CUSTOM_ROOT_DIR" >&2
99
- exit 1
100
- fi
101
- fi
102
-
103
- # Detect if running from within the specrails source repo itself
104
- if [[ -z "$CUSTOM_ROOT_DIR" && -f "$SCRIPT_DIR/install.sh" && -d "$SCRIPT_DIR/templates" && "$SCRIPT_DIR" == "$REPO_ROOT"* ]]; then
105
- # We're inside the specrails source — ask for target repo
106
- echo ""
107
- echo -e "${YELLOW}⚠${NC} You're running the updater from inside the specrails source repo."
108
- echo -e " specrails updates a ${BOLD}target${NC} repository, not itself."
109
- echo ""
110
- read -p " Enter the path to the target repo (or 'q' to quit): " TARGET_PATH || TARGET_PATH="q"
111
- if [[ "$TARGET_PATH" == "q" || -z "$TARGET_PATH" ]]; then
112
- echo " Aborted. No changes made."
113
- exit 0
114
- fi
115
- # Expand ~ and resolve path
116
- TARGET_PATH="${TARGET_PATH/#\~/$HOME}"
117
- REPO_ROOT="$(cd "$TARGET_PATH" 2>/dev/null && pwd)" || {
118
- echo "Error: path does not exist or is not accessible: $TARGET_PATH" >&2
119
- exit 1
120
- }
121
- if [[ ! -d "$REPO_ROOT/.git" ]]; then
122
- echo -e "${YELLOW}⚠${NC} Warning: $REPO_ROOT does not appear to be a git repository."
123
- read -p " Continue anyway? (y/n): " CONTINUE_NOGIT || CONTINUE_NOGIT="n"
124
- if [[ "$CONTINUE_NOGIT" != "y" && "$CONTINUE_NOGIT" != "Y" ]]; then
125
- echo " Aborted. No changes made."
126
- exit 0
127
- fi
128
- fi
129
- fi
130
-
131
- # ─────────────────────────────────────────────
132
- # Helpers
133
- # ─────────────────────────────────────────────
134
-
135
- ok() { echo -e " ${GREEN}✓${NC} $1"; }
136
- warn() { echo -e " ${YELLOW}⚠${NC} $1"; }
137
- fail() { echo -e " ${RED}✗${NC} $1"; }
138
- info() { echo -e " ${BLUE}→${NC} $1"; }
139
- step() { echo -e "\n${BOLD}$1${NC}"; }
140
-
141
- AVAILABLE_VERSION="$(cat "$SCRIPT_DIR/VERSION")"
142
-
143
- print_header() {
144
- local installed_ver="${1:-unknown}"
145
- echo ""
146
- echo -e "${BOLD}${CYAN}╔══════════════════════════════════════════════╗${NC}"
147
- echo -e "${BOLD}${CYAN}║ specrails update v${AVAILABLE_VERSION} ║${NC}"
148
- echo -e "${BOLD}${CYAN}║ Agent Workflow System for Claude Code ║${NC}"
149
- echo -e "${BOLD}${CYAN}╚══════════════════════════════════════════════╝${NC}"
150
- echo ""
151
- if [[ "$installed_ver" != "$AVAILABLE_VERSION" ]]; then
152
- info "Installed: v${installed_ver} → Available: v${AVAILABLE_VERSION}"
153
- fi
154
- echo ""
155
- }
156
-
157
- generate_manifest() {
158
- local version
159
- version="$(cat "$SCRIPT_DIR/VERSION")"
160
-
161
- local updated_at
162
- updated_at="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
163
-
164
- # Write version file
165
- printf '%s\n' "$version" > "$REPO_ROOT/.specrails/specrails-version"
166
-
167
- # Build artifact checksums for all files under templates/
168
- local artifacts_json=""
169
- local first=true
170
- while IFS= read -r -d '' filepath; do
171
- local relpath
172
- relpath="templates/${filepath#"$SCRIPT_DIR/templates/"}"
173
- local checksum
174
- checksum="sha256:$(shasum -a 256 "$filepath" | awk '{print $1}')"
175
- if [ "$first" = true ]; then
176
- first=false
177
- else
178
- artifacts_json="${artifacts_json},"
179
- fi
180
- artifacts_json="${artifacts_json}
181
- \"${relpath}\": \"${checksum}\""
182
- done < <(find "$SCRIPT_DIR/templates" -type f -not -path '*/node_modules/*' -not -name 'package-lock.json' -print0 | sort -z)
183
-
184
- # Include commands/enrich.md
185
- local enrich_checksum
186
- enrich_checksum="sha256:$(shasum -a 256 "$SCRIPT_DIR/commands/enrich.md" | awk '{print $1}')"
187
- if [ -n "$artifacts_json" ]; then
188
- artifacts_json="${artifacts_json},"
189
- fi
190
- artifacts_json="${artifacts_json}
191
- \"commands/specrails/enrich.md\": \"${enrich_checksum}\""
192
-
193
- # Include commands/doctor.md
194
- local doctor_checksum
195
- doctor_checksum="sha256:$(shasum -a 256 "$SCRIPT_DIR/commands/doctor.md" | awk '{print $1}')"
196
- artifacts_json="${artifacts_json},
197
- \"commands/specrails/doctor.md\": \"${doctor_checksum}\""
198
-
199
- # Include prompts/
200
- if [[ -d "$SCRIPT_DIR/prompts" ]]; then
201
- while IFS= read -r -d '' filepath; do
202
- local relpath
203
- relpath="prompts/${filepath#"$SCRIPT_DIR/prompts/"}"
204
- local checksum
205
- checksum="sha256:$(shasum -a 256 "$filepath" | awk '{print $1}')"
206
- artifacts_json="${artifacts_json},
207
- \"${relpath}\": \"${checksum}\""
208
- done < <(find "$SCRIPT_DIR/prompts" -type f -print0 | sort -z)
209
- fi
210
-
211
- # Include .claude/skills/
212
- if [[ -d "$SCRIPT_DIR/.claude/skills" ]]; then
213
- while IFS= read -r -d '' filepath; do
214
- local relpath
215
- relpath=".claude/skills/${filepath#"$SCRIPT_DIR/.claude/skills/"}"
216
- local checksum
217
- checksum="sha256:$(shasum -a 256 "$filepath" | awk '{print $1}')"
218
- artifacts_json="${artifacts_json},
219
- \"${relpath}\": \"${checksum}\""
220
- done < <(find "$SCRIPT_DIR/.claude/skills" -type f -print0 | sort -z)
221
- fi
222
-
223
- cat > "$REPO_ROOT/.specrails/specrails-manifest.json" << EOF
224
- {
225
- "version": "${version}",
226
- "installed_at": "${updated_at}",
227
- "artifacts": {${artifacts_json}
228
- }
229
- }
230
- EOF
231
- }
232
-
233
- # ─────────────────────────────────────────────
234
- # Phase 1: Prerequisites + version check
235
- # ─────────────────────────────────────────────
236
-
237
- # Resolve REPO_ROOT before printing header
238
- if [[ -z "$REPO_ROOT" ]]; then
239
- echo ""
240
- fail "Not inside a git repository and no --root-dir provided."
241
- echo " Usage: update.sh [--root-dir <path>]"
242
- exit 1
243
- fi
244
-
245
- VERSION_FILE="$REPO_ROOT/.specrails/specrails-version"
246
- AGENTS_DIR="$REPO_ROOT/.claude/agents"
247
-
248
- # Migrate old root-level metadata files to .specrails/ (path change introduced in v3.6.0)
249
- if [[ ! -f "$VERSION_FILE" ]] && [[ -f "$REPO_ROOT/.specrails-version" ]]; then
250
- mkdir -p "$REPO_ROOT/.specrails"
251
- mv "$REPO_ROOT/.specrails-version" "$REPO_ROOT/.specrails/specrails-version"
252
- ok "Migrated .specrails-version → .specrails/specrails-version"
253
- fi
254
- if [[ -f "$REPO_ROOT/.specrails-manifest.json" ]]; then
255
- mkdir -p "$REPO_ROOT/.specrails"
256
- mv "$REPO_ROOT/.specrails-manifest.json" "$REPO_ROOT/.specrails/specrails-manifest.json"
257
- ok "Migrated .specrails-manifest.json → .specrails/specrails-manifest.json"
258
- fi
259
- # Migrate old provider-specific setup-templates to .specrails/setup-templates/
260
- _MIGRATED_SETUP_TEMPLATES=false
261
- for _old_templates in "$REPO_ROOT/.claude/setup-templates" "$REPO_ROOT/.codex/setup-templates"; do
262
- if [[ -d "$_old_templates" ]]; then
263
- mkdir -p "$REPO_ROOT/.specrails/setup-templates"
264
- cp -r "$_old_templates/." "$REPO_ROOT/.specrails/setup-templates/"
265
- rm -rf "$_old_templates"
266
- ok "Migrated ${_old_templates#"$REPO_ROOT/"} → .specrails/setup-templates/"
267
- _MIGRATED_SETUP_TEMPLATES=true
268
- fi
269
- done
270
- unset _old_templates
271
-
272
- # Detect installation state
273
- INSTALLED_VERSION=""
274
- IS_LEGACY=false
275
-
276
- if [[ -f "$VERSION_FILE" ]]; then
277
- INSTALLED_VERSION="$(cat "$VERSION_FILE" | tr -d '[:space:]')"
278
- elif [[ -d "$AGENTS_DIR" ]] && [[ -n "$(ls -A "$AGENTS_DIR" 2>/dev/null)" ]]; then
279
- IS_LEGACY=true
280
- INSTALLED_VERSION="0.1.0"
281
- else
282
- echo ""
283
- fail "No specrails installation found. Run install.sh first."
284
- echo ""
285
- exit 1
286
- fi
287
-
288
- print_header "$INSTALLED_VERSION"
289
-
290
- if [[ -n "$CUSTOM_ROOT_DIR" ]]; then
291
- ok "Update root (--root-dir): $REPO_ROOT"
292
- else
293
- ok "Git repository root: $REPO_ROOT"
294
- fi
295
-
296
- # Content-aware up-to-date check (skip for legacy migrations and agent-only runs)
297
- if [[ "$INSTALLED_VERSION" == "$AVAILABLE_VERSION" ]] && [[ "$IS_LEGACY" == false ]] && [[ "$UPDATE_COMPONENT" != "agents" ]] && [[ "$FORCE_UPDATE" == false ]]; then
298
- # Same version — check if any template content has actually changed
299
- local_manifest="$REPO_ROOT/.specrails/specrails-manifest.json"
300
- HAS_CHANGES=false
301
-
302
- if [[ -f "$local_manifest" ]]; then
303
- while IFS= read -r -d '' filepath; do
304
- relpath="templates/${filepath#"$SCRIPT_DIR/templates/"}"
305
- current_checksum="sha256:$(shasum -a 256 "$filepath" | awk '{print $1}')"
306
- manifest_checksum="$(python3 -c "
307
- import json, sys
308
- try:
309
- data = json.load(open(sys.argv[1]))
310
- print(data['artifacts'].get(sys.argv[2], ''))
311
- except Exception:
312
- print('')
313
- " "$local_manifest" "$relpath" 2>/dev/null || echo "")"
314
-
315
- if [[ -z "$manifest_checksum" ]] || [[ "$current_checksum" != "$manifest_checksum" ]]; then
316
- HAS_CHANGES=true
317
- break
318
- fi
319
- done < <(find "$SCRIPT_DIR/templates" -type f -not -path '*/node_modules/*' -not -name 'package-lock.json' -print0 | sort -z)
320
-
321
- # Also check commands/enrich.md
322
- if [[ "$HAS_CHANGES" == false ]] && [[ -f "$SCRIPT_DIR/commands/enrich.md" ]]; then
323
- enrich_checksum="sha256:$(shasum -a 256 "$SCRIPT_DIR/commands/enrich.md" | awk '{print $1}')"
324
- manifest_enrich="$(python3 -c "
325
- import json, sys
326
- try:
327
- data = json.load(open(sys.argv[1]))
328
- print(data['artifacts'].get('commands/specrails/enrich.md', data['artifacts'].get('commands/enrich.md', '')))
329
- except Exception:
330
- print('')
331
- " "$local_manifest" 2>/dev/null || echo "")"
332
- if [[ "$enrich_checksum" != "$manifest_enrich" ]]; then
333
- HAS_CHANGES=true
334
- fi
335
- fi
336
- else
337
- # No manifest — can't verify, assume changes exist
338
- HAS_CHANGES=true
339
- fi
340
-
341
- if [[ "$HAS_CHANGES" == false ]]; then
342
- ok "Already up to date (v${AVAILABLE_VERSION}) — all templates match"
343
- echo ""
344
- exit 0
345
- else
346
- info "Same version (v${AVAILABLE_VERSION}) but template content has changed — updating"
347
- fi
348
- fi
349
-
350
- # ─────────────────────────────────────────────
351
- # Phase 2: Legacy migration
352
- # ─────────────────────────────────────────────
353
-
354
- if [[ "$IS_LEGACY" == true ]]; then
355
- step "Phase 2: Legacy migration"
356
- warn "No .specrails/specrails-version found — assuming v0.1.0 (pre-versioning install)"
357
- info "Generating baseline manifest from current specrails templates..."
358
- generate_manifest
359
- # Overwrite with legacy version so the update flow sees "0.1.0 → current"
360
- printf '0.1.0\n' > "$VERSION_FILE"
361
- ok "Written .specrails/specrails-version as 0.1.0"
362
- ok "Written .specrails/specrails-manifest.json"
363
- fi
364
-
365
- # ─────────────────────────────────────────────
366
- # Phase 3: Backup
367
- # ─────────────────────────────────────────────
368
-
369
- step "Phase 3: Creating backup"
370
-
371
- BACKUP_DIR="$REPO_ROOT/.claude.specrails.backup"
372
- UPDATE_SUCCESS=false
373
-
374
- # Trap: on exit, if update did not succeed, warn about backup
375
- cleanup_on_exit() {
376
- if [[ "$UPDATE_SUCCESS" != true ]] && [[ -d "$BACKUP_DIR" ]]; then
377
- echo ""
378
- warn "Update did not complete successfully."
379
- warn "Your previous .claude/ is backed up at: $BACKUP_DIR"
380
- warn "To restore: rm -rf \"$REPO_ROOT/.claude\" && mv \"$BACKUP_DIR\" \"$REPO_ROOT/.claude\""
381
- echo ""
382
- fi
383
- }
384
- trap cleanup_on_exit EXIT
385
-
386
- rsync -a --exclude='node_modules' "$REPO_ROOT/.claude/" "$BACKUP_DIR/"
387
- ok "Backed up .claude/ to .claude.specrails.backup/ (excluding node_modules)"
388
-
389
- # ─────────────────────────────────────────────
390
- # Update functions
391
- # ─────────────────────────────────────────────
392
-
393
- NEEDS_SETUP_UPDATE=false
394
- if [[ "$_MIGRATED_SETUP_TEMPLATES" == true ]]; then
395
- NEEDS_SETUP_UPDATE=true
396
- fi
397
- FORCE_AGENTS=false
398
-
399
- do_migrate_sr_prefix() {
400
- # Detect and migrate legacy installations that use unprefixed agent/command names.
401
- # A legacy installation is one where .claude/agents/architect.md exists (without sr- prefix).
402
- local agents_dir="$REPO_ROOT/.claude/agents"
403
- local commands_dir="$REPO_ROOT/.claude/commands"
404
- local memory_dir="$REPO_ROOT/.claude/agent-memory"
405
-
406
- if [[ ! -f "$agents_dir/architect.md" ]]; then
407
- return # Nothing to migrate
408
- fi
409
-
410
- step "Migration: adding sr- prefix namespace"
411
- info "Legacy installation detected (unprefixed agent names). Migrating to sr- prefix..."
412
-
413
- local migrated_agents=0
414
- local migrated_commands=0
415
- local migrated_memory=0
416
-
417
- # Migrate agent files
418
- local known_agents=(
419
- "architect"
420
- "developer"
421
- "reviewer"
422
- "product-manager"
423
- "product-analyst"
424
- "test-writer"
425
- "doc-sync"
426
- "frontend-developer"
427
- "backend-developer"
428
- "frontend-reviewer"
429
- "backend-reviewer"
430
- "security-reviewer"
431
- )
432
-
433
- for agent in "${known_agents[@]}"; do
434
- local src="$agents_dir/${agent}.md"
435
- local dst="$agents_dir/sr-${agent}.md"
436
- if [[ -f "$src" ]] && [[ ! -f "$dst" ]]; then
437
- mv "$src" "$dst"
438
- info "Renamed: agents/${agent}.md → agents/sr-${agent}.md"
439
- migrated_agents=$(( migrated_agents + 1 ))
440
- fi
441
- done
442
-
443
- # Migrate persona files in .claude/agents/personas/
444
- local personas_dir="$agents_dir/personas"
445
- if [[ -d "$personas_dir" ]]; then
446
- while IFS= read -r -d '' persona_file; do
447
- local persona_basename
448
- persona_basename="$(basename "$persona_file")"
449
- # Skip files already prefixed with sr-
450
- if [[ "$persona_basename" == sr-* ]]; then
451
- continue
452
- fi
453
- local persona_dst="$personas_dir/sr-${persona_basename}"
454
- if [[ ! -f "$persona_dst" ]]; then
455
- mv "$persona_file" "$persona_dst"
456
- info "Renamed: personas/${persona_basename} → personas/sr-${persona_basename}"
457
- migrated_agents=$(( migrated_agents + 1 ))
458
- fi
459
- done < <(find "$personas_dir" -maxdepth 1 -name "*.md" -not -name "sr-*.md" -print0 2>/dev/null)
460
- fi
461
-
462
- # Create .claude/commands/specrails/ and migrate workflow commands
463
- local workflow_commands=(
464
- "implement"
465
- "batch-implement"
466
- "get-backlog-specs"
467
- "auto-propose-backlog-specs"
468
- "compat-check"
469
- "refactor-recommender"
470
- "why"
471
- )
472
-
473
- if [[ -d "$commands_dir" ]]; then
474
- mkdir -p "$commands_dir/sr"
475
- for cmd in "${workflow_commands[@]}"; do
476
- local src="$commands_dir/${cmd}.md"
477
- local dst="$commands_dir/sr/${cmd}.md"
478
- if [[ -f "$src" ]] && [[ ! -f "$dst" ]]; then
479
- mv "$src" "$dst"
480
- info "Moved: commands/${cmd}.md → commands/specrails/${cmd}.md"
481
- migrated_commands=$(( migrated_commands + 1 ))
482
- fi
483
- done
484
- fi
485
-
486
- # Migrate agent memory directories (only known agent dirs, not failures/ or explanations/)
487
- if [[ -d "$memory_dir" ]]; then
488
- for agent in "${known_agents[@]}"; do
489
- local src="$memory_dir/${agent}"
490
- local dst="$memory_dir/sr-${agent}"
491
- if [[ -d "$src" ]] && [[ ! -d "$dst" ]]; then
492
- mv "$src" "$dst"
493
- info "Renamed: agent-memory/${agent}/ → agent-memory/sr-${agent}/"
494
- migrated_memory=$(( migrated_memory + 1 ))
495
- fi
496
- done
497
- fi
498
-
499
- # Summary
500
- if [[ "$migrated_agents" -gt 0 ]] || [[ "$migrated_commands" -gt 0 ]] || [[ "$migrated_memory" -gt 0 ]]; then
501
- ok "Migration complete: ${migrated_agents} agents/personas, ${migrated_commands} commands, ${migrated_memory} memory dirs"
502
- else
503
- ok "Migration check complete — nothing to migrate"
504
- fi
505
- }
506
-
507
- do_core() {
508
- step "Updating core artifacts (commands, skills, setup-templates)"
509
-
510
- local manifest_file="$REPO_ROOT/.specrails/specrails-manifest.json"
511
- local updated_count=0
512
- local added_count=0
513
-
514
- # Helper: check if a source file differs from its manifest checksum
515
- # Returns 0 (true) if file is new or changed, 1 if unchanged
516
- _file_changed() {
517
- local source_file="$1"
518
- local manifest_key="$2"
519
-
520
- if [[ ! -f "$manifest_file" ]]; then
521
- return 0 # No manifest — assume changed
522
- fi
523
-
524
- local current_checksum
525
- current_checksum="sha256:$(shasum -a 256 "$source_file" | awk '{print $1}')"
526
- local manifest_checksum
527
- manifest_checksum="$(python3 -c "
528
- import json, sys
529
- try:
530
- data = json.load(open(sys.argv[1]))
531
- print(data['artifacts'].get(sys.argv[2], ''))
532
- except Exception:
533
- print('')
534
- " "$manifest_file" "$manifest_key" 2>/dev/null || echo "")"
535
-
536
- if [[ -z "$manifest_checksum" ]]; then
537
- return 0 # New file
538
- elif [[ "$current_checksum" != "$manifest_checksum" ]]; then
539
- return 0 # Changed
540
- fi
541
- return 1 # Unchanged
542
- }
543
-
544
- # Update /specrails:enrich command (selective)
545
- mkdir -p "$REPO_ROOT/.claude/commands/specrails"
546
- if _file_changed "$SCRIPT_DIR/commands/enrich.md" "commands/specrails/enrich.md"; then
547
- cp "$SCRIPT_DIR/commands/enrich.md" "$REPO_ROOT/.claude/commands/specrails/enrich.md"
548
- ok "Updated /specrails:enrich command"
549
- updated_count=$(( updated_count + 1 ))
550
- fi
551
-
552
- # Update setup templates (selective — only copy changed/new files)
553
- while IFS= read -r -d '' filepath; do
554
- local relpath
555
- relpath="templates/${filepath#"$SCRIPT_DIR/templates/"}"
556
-
557
- if _file_changed "$filepath" "$relpath"; then
558
- local dest="$REPO_ROOT/.specrails/setup-templates/${filepath#"$SCRIPT_DIR/templates/"}"
559
- mkdir -p "$(dirname "$dest")"
560
- cp "$filepath" "$dest"
561
-
562
- # Determine if new or changed
563
- local manifest_checksum
564
- manifest_checksum="$(python3 -c "
565
- import json, sys
566
- try:
567
- data = json.load(open(sys.argv[1]))
568
- print(data['artifacts'].get(sys.argv[2], ''))
569
- except Exception:
570
- print('')
571
- " "$manifest_file" "$relpath" 2>/dev/null || echo "")"
572
- if [[ -z "$manifest_checksum" ]]; then
573
- info "New: $relpath"
574
- added_count=$(( added_count + 1 ))
575
- else
576
- info "Changed: $relpath"
577
- updated_count=$(( updated_count + 1 ))
578
- fi
579
- fi
580
- done < <(find "$SCRIPT_DIR/templates" -type f -not -path '*/node_modules/*' -not -name 'package-lock.json' -print0 | sort -z)
581
-
582
- # Update prompts (selective — uses manifest checksums)
583
- if [[ -d "$SCRIPT_DIR/prompts" ]] && [[ -n "$(ls -A "$SCRIPT_DIR/prompts" 2>/dev/null)" ]]; then
584
- while IFS= read -r -d '' filepath; do
585
- local relpath
586
- relpath="prompts/${filepath#"$SCRIPT_DIR/prompts/"}"
587
-
588
- if _file_changed "$filepath" "$relpath"; then
589
- local dest="$REPO_ROOT/.specrails/setup-templates/prompts/${filepath#"$SCRIPT_DIR/prompts/"}"
590
- mkdir -p "$(dirname "$dest")"
591
- cp "$filepath" "$dest"
592
-
593
- local manifest_checksum
594
- manifest_checksum="$(python3 -c "
595
- import json, sys
596
- try:
597
- data = json.load(open(sys.argv[1]))
598
- print(data['artifacts'].get(sys.argv[2], ''))
599
- except Exception:
600
- print('')
601
- " "$manifest_file" "$relpath" 2>/dev/null || echo "")"
602
- if [[ -z "$manifest_checksum" ]]; then
603
- info "New: $relpath"
604
- added_count=$(( added_count + 1 ))
605
- else
606
- info "Changed: $relpath"
607
- updated_count=$(( updated_count + 1 ))
608
- fi
609
- fi
610
- done < <(find "$SCRIPT_DIR/prompts" -type f -print0 | sort -z)
611
- fi
612
-
613
- # Update skills (selective — uses manifest checksums)
614
- if [[ -d "$SCRIPT_DIR/.claude/skills" ]] && [[ -n "$(ls -A "$SCRIPT_DIR/.claude/skills" 2>/dev/null)" ]]; then
615
- while IFS= read -r -d '' filepath; do
616
- local relpath
617
- relpath=".claude/skills/${filepath#"$SCRIPT_DIR/.claude/skills/"}"
618
-
619
- if _file_changed "$filepath" "$relpath"; then
620
- local dest="$REPO_ROOT/$relpath"
621
- mkdir -p "$(dirname "$dest")"
622
- cp "$filepath" "$dest"
623
-
624
- local manifest_checksum
625
- manifest_checksum="$(python3 -c "
626
- import json, sys
627
- try:
628
- data = json.load(open(sys.argv[1]))
629
- print(data['artifacts'].get(sys.argv[2], ''))
630
- except Exception:
631
- print('')
632
- " "$manifest_file" "$relpath" 2>/dev/null || echo "")"
633
- if [[ -z "$manifest_checksum" ]]; then
634
- info "New: $relpath"
635
- added_count=$(( added_count + 1 ))
636
- else
637
- info "Changed: $relpath"
638
- updated_count=$(( updated_count + 1 ))
639
- fi
640
- fi
641
- done < <(find "$SCRIPT_DIR/.claude/skills" -type f -print0 | sort -z)
642
- fi
643
-
644
- if [[ "$updated_count" -eq 0 ]] && [[ "$added_count" -eq 0 ]]; then
645
- ok "All core artifacts unchanged"
646
- else
647
- ok "Core update: ${updated_count} changed, ${added_count} new"
648
- fi
649
- }
650
-
651
- do_agents() {
652
- step "Checking adapted artifacts (agents, rules)"
653
-
654
- local manifest_file="$REPO_ROOT/.specrails/specrails-manifest.json"
655
-
656
- if [[ ! -f "$manifest_file" ]]; then
657
- warn "No .specrails/specrails-manifest.json found — cannot detect template changes."
658
- warn "Run update.sh without --only to regenerate the manifest."
659
- return
660
- fi
661
-
662
- local changed_templates=()
663
- local new_templates=()
664
-
665
- # Check templates/agents/ and templates/rules/ for changes
666
- while IFS= read -r -d '' filepath; do
667
- local relpath
668
- relpath="templates/${filepath#"$SCRIPT_DIR/templates/"}"
669
- local current_checksum
670
- current_checksum="sha256:$(shasum -a 256 "$filepath" | awk '{print $1}')"
671
-
672
- # Look up this path in the manifest
673
- local manifest_checksum
674
- manifest_checksum="$(python3 -c "
675
- import json, sys
676
- manifest_file = sys.argv[1]
677
- relpath = sys.argv[2]
678
- try:
679
- data = json.load(open(manifest_file))
680
- print(data['artifacts'].get(relpath, ''))
681
- except Exception:
682
- print('')
683
- " "$manifest_file" "$relpath" 2>/dev/null || echo "")"
684
-
685
- if [[ -z "$manifest_checksum" ]]; then
686
- new_templates+=("$relpath")
687
- elif [[ "$current_checksum" != "$manifest_checksum" ]]; then
688
- changed_templates+=("$relpath")
689
- fi
690
- done < <(find "$SCRIPT_DIR/templates/agents" "$SCRIPT_DIR/templates/rules" -type f -print0 2>/dev/null | sort -z)
691
-
692
- # Handle changed templates
693
- if [[ "${#changed_templates[@]}" -gt 0 ]] || [[ "$FORCE_AGENTS" == true ]]; then
694
- if [[ "$FORCE_AGENTS" == true ]]; then
695
- info "Agent regeneration forced via --only agents."
696
- else
697
- echo ""
698
- warn "The following agent/rule templates have changed:"
699
- for t in "${changed_templates[@]}"; do
700
- echo " $t"
701
- done
702
- echo ""
703
- fi
704
-
705
- local answer
706
- read -p " Regenerate agents? (y/N): " answer || answer="n"
707
- if [[ "$answer" == "y" ]] || [[ "$answer" == "Y" ]]; then
708
- NEEDS_SETUP_UPDATE=true
709
- ok "Will regenerate agents via /specrails:setup --update"
710
- else
711
- warn "Workflow may break with outdated agents. Run '/specrails:setup --update' inside Claude Code when ready."
712
- fi
713
- else
714
- ok "All agent/rule templates unchanged — no regeneration needed"
715
- fi
716
-
717
- # Handle new templates
718
- if [[ "${#new_templates[@]}" -gt 0 ]]; then
719
- echo ""
720
- info "New agent/rule templates are available:"
721
- for t in "${new_templates[@]}"; do
722
- echo " $t"
723
- done
724
- info "These will be evaluated during /specrails:setup --update"
725
- NEEDS_SETUP_UPDATE=true
726
- fi
727
- }
728
-
729
- do_settings() {
730
- step "Merging settings"
731
-
732
- local user_settings="$REPO_ROOT/.claude/settings.json"
733
- local template_settings="$SCRIPT_DIR/templates/settings/settings.json"
734
-
735
- if [[ -f "$user_settings" ]] && [[ -f "$template_settings" ]]; then
736
- if command -v python3 &>/dev/null; then
737
- python3 -c "
738
- import json, sys
739
-
740
- template_path = sys.argv[1]
741
- user_path = sys.argv[2]
742
-
743
- with open(template_path) as f:
744
- template = json.load(f)
745
-
746
- with open(user_path) as f:
747
- user = json.load(f)
748
-
749
- def merge_additive(base, overlay):
750
- for key, value in overlay.items():
751
- if key not in base:
752
- base[key] = value
753
- elif isinstance(base[key], dict) and isinstance(value, dict):
754
- merge_additive(base[key], value)
755
- elif isinstance(base[key], list) and isinstance(value, list):
756
- existing = set(str(i) for i in base[key])
757
- for item in value:
758
- if isinstance(item, str) and '{{' in item:
759
- continue
760
- if str(item) not in existing:
761
- base[key].append(item)
762
- existing.add(str(item))
763
- return base
764
-
765
- merged = merge_additive(user, template)
766
-
767
- with open(user_path, 'w') as f:
768
- json.dump(merged, f, indent=2)
769
- f.write('\n')
770
-
771
- " "$template_settings" "$user_settings" >/dev/null 2>&1 && ok "Merged settings.json (new keys added, existing preserved)" || {
772
- warn "settings.json merge failed — skipping. Inspect manually."
773
- }
774
- else
775
- warn "python3 not found — skipping settings.json merge."
776
- fi
777
- elif [[ ! -f "$user_settings" ]] && [[ -f "$template_settings" ]]; then
778
- mkdir -p "$REPO_ROOT/.claude"
779
- cp "$template_settings" "$user_settings"
780
- ok "Installed settings.json (was missing)"
781
- fi
782
-
783
- # security-exemptions.yaml: skip if already exists (preserve user exemptions)
784
- local user_exemptions="$REPO_ROOT/.claude/security-exemptions.yaml"
785
- local template_exemptions="$SCRIPT_DIR/templates/security/security-exemptions.yaml"
786
- if [[ ! -f "$user_exemptions" ]] && [[ -f "$template_exemptions" ]]; then
787
- cp "$template_exemptions" "$user_exemptions"
788
- ok "Installed security-exemptions.yaml (was missing)"
789
- else
790
- ok "security-exemptions.yaml preserved (user customizations kept)"
791
- fi
792
- }
793
-
794
- do_stamp() {
795
- step "Writing version stamp and manifest"
796
- generate_manifest
797
- ok "Updated .specrails/specrails-version to v${AVAILABLE_VERSION}"
798
- ok "Updated .specrails/specrails-manifest.json"
799
- }
800
-
801
- # ─────────────────────────────────────────────
802
- # Phase 4: Run selected components
803
- # ─────────────────────────────────────────────
804
-
805
- step "Phase 4: Running update (component: ${UPDATE_COMPONENT})"
806
-
807
- case "$UPDATE_COMPONENT" in
808
- all)
809
- do_migrate_sr_prefix
810
- do_core
811
- do_agents
812
- do_settings
813
- do_stamp
814
- ;;
815
- commands)
816
- do_migrate_sr_prefix
817
- do_core
818
- do_stamp
819
- ;;
820
- agents)
821
- do_migrate_sr_prefix
822
- FORCE_AGENTS=true
823
- do_agents
824
- do_stamp
825
- ;;
826
- core)
827
- do_migrate_sr_prefix
828
- do_core
829
- do_stamp
830
- ;;
831
- esac
832
-
833
- # ─────────────────────────────────────────────
834
- # Phase 5: Cleanup and summary
835
- # ─────────────────────────────────────────────
836
-
837
- step "Phase 5: Cleanup"
838
-
839
- UPDATE_SUCCESS=true
840
- rm -rf "$BACKUP_DIR"
841
- ok "Backup removed"
842
-
843
- # Clean up setup-templates if no /specrails:setup re-run is needed
844
- if [[ "$NEEDS_SETUP_UPDATE" != true ]] && [[ -d "$REPO_ROOT/.specrails/setup-templates" ]]; then
845
- rm -rf "$REPO_ROOT/.specrails/setup-templates"
846
- ok "Cleaned up .specrails/setup-templates (no /specrails:setup re-run needed)"
847
- fi
848
-
849
- echo ""
850
- echo -e "${BOLD}${GREEN}Update complete — v${INSTALLED_VERSION} → v${AVAILABLE_VERSION}${NC}"
851
- echo ""
852
- echo " Component updated: ${UPDATE_COMPONENT}"
853
- echo ""
854
-
855
- if [[ "$NEEDS_SETUP_UPDATE" == true ]]; then
856
- echo -e "${BOLD}${CYAN}Next step: regenerate adapted agents${NC}"
857
- echo ""
858
- echo " Open Claude Code in this repo and run:"
859
- echo ""
860
- echo -e " ${BOLD}/specrails:setup --update${NC}"
861
- echo ""
862
- echo " Claude will re-analyze your codebase and regenerate only the"
863
- echo " agents and rules whose templates have changed."
864
- echo ""
865
- else
866
- echo -e "${BOLD}${CYAN}No agent regeneration needed.${NC}"
867
- echo ""
868
- echo " Open Claude Code and continue working normally."
869
- echo ""
870
- fi