shipwright-cli 3.1.0 → 3.3.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 (283) hide show
  1. package/.claude/agents/code-reviewer.md +2 -0
  2. package/.claude/agents/devops-engineer.md +2 -0
  3. package/.claude/agents/doc-fleet-agent.md +2 -0
  4. package/.claude/agents/pipeline-agent.md +2 -0
  5. package/.claude/agents/shell-script-specialist.md +2 -0
  6. package/.claude/agents/test-specialist.md +2 -0
  7. package/.claude/hooks/agent-crash-capture.sh +32 -0
  8. package/.claude/hooks/post-tool-use.sh +3 -2
  9. package/.claude/hooks/pre-tool-use.sh +35 -3
  10. package/README.md +22 -8
  11. package/claude-code/hooks/config-change.sh +18 -0
  12. package/claude-code/hooks/instructions-reloaded.sh +7 -0
  13. package/claude-code/hooks/worktree-create.sh +25 -0
  14. package/claude-code/hooks/worktree-remove.sh +20 -0
  15. package/config/code-constitution.json +130 -0
  16. package/config/defaults.json +25 -2
  17. package/config/policy.json +1 -1
  18. package/dashboard/middleware/auth.ts +134 -0
  19. package/dashboard/middleware/constants.ts +21 -0
  20. package/dashboard/public/index.html +8 -6
  21. package/dashboard/public/styles.css +176 -97
  22. package/dashboard/routes/auth.ts +38 -0
  23. package/dashboard/server.ts +117 -25
  24. package/dashboard/services/config.ts +26 -0
  25. package/dashboard/services/db.ts +118 -0
  26. package/dashboard/src/canvas/pixel-agent.ts +298 -0
  27. package/dashboard/src/canvas/pixel-sprites.ts +440 -0
  28. package/dashboard/src/canvas/shipyard-effects.ts +367 -0
  29. package/dashboard/src/canvas/shipyard-scene.ts +616 -0
  30. package/dashboard/src/canvas/submarine-layout.ts +267 -0
  31. package/dashboard/src/components/header.ts +8 -7
  32. package/dashboard/src/core/api.ts +5 -0
  33. package/dashboard/src/core/router.ts +1 -0
  34. package/dashboard/src/design/submarine-theme.ts +253 -0
  35. package/dashboard/src/main.ts +2 -0
  36. package/dashboard/src/types/api.ts +12 -1
  37. package/dashboard/src/views/activity.ts +2 -1
  38. package/dashboard/src/views/metrics.ts +69 -1
  39. package/dashboard/src/views/shipyard.ts +39 -0
  40. package/dashboard/types/index.ts +166 -0
  41. package/docs/plans/2026-02-28-compound-audit-and-shipyard-design.md +186 -0
  42. package/docs/plans/2026-02-28-skipper-shipwright-implementation-plan.md +1182 -0
  43. package/docs/plans/2026-02-28-skipper-shipwright-integration-design.md +531 -0
  44. package/docs/plans/2026-03-01-ai-powered-skill-injection-design.md +298 -0
  45. package/docs/plans/2026-03-01-ai-powered-skill-injection-plan.md +1109 -0
  46. package/docs/plans/2026-03-01-capabilities-cleanup-plan.md +658 -0
  47. package/docs/plans/2026-03-01-clean-architecture-plan.md +924 -0
  48. package/docs/plans/2026-03-01-compound-audit-cascade-design.md +191 -0
  49. package/docs/plans/2026-03-01-compound-audit-cascade-plan.md +921 -0
  50. package/docs/plans/2026-03-01-deep-integration-plan.md +851 -0
  51. package/docs/plans/2026-03-01-pipeline-audit-trail-design.md +145 -0
  52. package/docs/plans/2026-03-01-pipeline-audit-trail-plan.md +770 -0
  53. package/docs/plans/2026-03-01-refined-depths-brand-design.md +382 -0
  54. package/docs/plans/2026-03-01-refined-depths-implementation.md +599 -0
  55. package/docs/plans/2026-03-01-skipper-kernel-integration-design.md +203 -0
  56. package/docs/plans/2026-03-01-unified-platform-design.md +272 -0
  57. package/docs/plans/2026-03-07-claude-code-feature-integration-design.md +189 -0
  58. package/docs/plans/2026-03-07-claude-code-feature-integration-plan.md +1165 -0
  59. package/docs/research/BACKLOG_QUICK_REFERENCE.md +352 -0
  60. package/docs/research/CUTTING_EDGE_RESEARCH_2026.md +546 -0
  61. package/docs/research/RESEARCH_INDEX.md +439 -0
  62. package/docs/research/RESEARCH_SOURCES.md +440 -0
  63. package/docs/research/RESEARCH_SUMMARY.txt +275 -0
  64. package/docs/superpowers/specs/2026-03-10-pipeline-quality-revolution-design.md +341 -0
  65. package/package.json +2 -2
  66. package/scripts/lib/adaptive-model.sh +427 -0
  67. package/scripts/lib/adaptive-timeout.sh +316 -0
  68. package/scripts/lib/audit-trail.sh +309 -0
  69. package/scripts/lib/auto-recovery.sh +471 -0
  70. package/scripts/lib/bandit-selector.sh +431 -0
  71. package/scripts/lib/bootstrap.sh +104 -2
  72. package/scripts/lib/causal-graph.sh +455 -0
  73. package/scripts/lib/compat.sh +126 -0
  74. package/scripts/lib/compound-audit.sh +337 -0
  75. package/scripts/lib/constitutional.sh +454 -0
  76. package/scripts/lib/context-budget.sh +359 -0
  77. package/scripts/lib/convergence.sh +594 -0
  78. package/scripts/lib/cost-optimizer.sh +634 -0
  79. package/scripts/lib/daemon-adaptive.sh +14 -2
  80. package/scripts/lib/daemon-dispatch.sh +106 -17
  81. package/scripts/lib/daemon-failure.sh +34 -4
  82. package/scripts/lib/daemon-patrol.sh +25 -4
  83. package/scripts/lib/daemon-poll-github.sh +361 -0
  84. package/scripts/lib/daemon-poll-health.sh +299 -0
  85. package/scripts/lib/daemon-poll.sh +27 -611
  86. package/scripts/lib/daemon-state.sh +119 -66
  87. package/scripts/lib/daemon-triage.sh +10 -0
  88. package/scripts/lib/dod-scorecard.sh +442 -0
  89. package/scripts/lib/error-actionability.sh +300 -0
  90. package/scripts/lib/formal-spec.sh +461 -0
  91. package/scripts/lib/helpers.sh +180 -5
  92. package/scripts/lib/intent-analysis.sh +409 -0
  93. package/scripts/lib/loop-convergence.sh +350 -0
  94. package/scripts/lib/loop-iteration.sh +682 -0
  95. package/scripts/lib/loop-progress.sh +48 -0
  96. package/scripts/lib/loop-restart.sh +185 -0
  97. package/scripts/lib/memory-effectiveness.sh +506 -0
  98. package/scripts/lib/mutation-executor.sh +352 -0
  99. package/scripts/lib/outcome-feedback.sh +521 -0
  100. package/scripts/lib/pipeline-cli.sh +336 -0
  101. package/scripts/lib/pipeline-commands.sh +1216 -0
  102. package/scripts/lib/pipeline-detection.sh +101 -3
  103. package/scripts/lib/pipeline-execution.sh +897 -0
  104. package/scripts/lib/pipeline-github.sh +28 -3
  105. package/scripts/lib/pipeline-intelligence-compound.sh +431 -0
  106. package/scripts/lib/pipeline-intelligence-scoring.sh +407 -0
  107. package/scripts/lib/pipeline-intelligence-skip.sh +181 -0
  108. package/scripts/lib/pipeline-intelligence.sh +104 -1138
  109. package/scripts/lib/pipeline-quality-bash-compat.sh +182 -0
  110. package/scripts/lib/pipeline-quality-checks.sh +17 -711
  111. package/scripts/lib/pipeline-quality-gates.sh +563 -0
  112. package/scripts/lib/pipeline-stages-build.sh +730 -0
  113. package/scripts/lib/pipeline-stages-delivery.sh +965 -0
  114. package/scripts/lib/pipeline-stages-intake.sh +1133 -0
  115. package/scripts/lib/pipeline-stages-monitor.sh +407 -0
  116. package/scripts/lib/pipeline-stages-review.sh +1022 -0
  117. package/scripts/lib/pipeline-stages.sh +161 -2901
  118. package/scripts/lib/pipeline-state.sh +36 -5
  119. package/scripts/lib/pipeline-util.sh +487 -0
  120. package/scripts/lib/policy-learner.sh +438 -0
  121. package/scripts/lib/process-reward.sh +493 -0
  122. package/scripts/lib/project-detect.sh +649 -0
  123. package/scripts/lib/quality-profile.sh +334 -0
  124. package/scripts/lib/recruit-commands.sh +885 -0
  125. package/scripts/lib/recruit-learning.sh +739 -0
  126. package/scripts/lib/recruit-roles.sh +648 -0
  127. package/scripts/lib/reward-aggregator.sh +458 -0
  128. package/scripts/lib/rl-optimizer.sh +362 -0
  129. package/scripts/lib/root-cause.sh +427 -0
  130. package/scripts/lib/scope-enforcement.sh +445 -0
  131. package/scripts/lib/session-restart.sh +493 -0
  132. package/scripts/lib/skill-memory.sh +300 -0
  133. package/scripts/lib/skill-registry.sh +775 -0
  134. package/scripts/lib/spec-driven.sh +476 -0
  135. package/scripts/lib/test-helpers.sh +18 -7
  136. package/scripts/lib/test-holdout.sh +429 -0
  137. package/scripts/lib/test-optimizer.sh +511 -0
  138. package/scripts/shipwright-file-suggest.sh +45 -0
  139. package/scripts/skills/adversarial-quality.md +61 -0
  140. package/scripts/skills/api-design.md +44 -0
  141. package/scripts/skills/architecture-design.md +50 -0
  142. package/scripts/skills/brainstorming.md +43 -0
  143. package/scripts/skills/data-pipeline.md +44 -0
  144. package/scripts/skills/deploy-safety.md +64 -0
  145. package/scripts/skills/documentation.md +38 -0
  146. package/scripts/skills/frontend-design.md +45 -0
  147. package/scripts/skills/generated/.gitkeep +0 -0
  148. package/scripts/skills/generated/_refinements/.gitkeep +0 -0
  149. package/scripts/skills/generated/_refinements/adversarial-quality.patch.md +3 -0
  150. package/scripts/skills/generated/_refinements/architecture-design.patch.md +3 -0
  151. package/scripts/skills/generated/_refinements/brainstorming.patch.md +3 -0
  152. package/scripts/skills/generated/cli-version-management.md +29 -0
  153. package/scripts/skills/generated/collection-system-validation.md +99 -0
  154. package/scripts/skills/generated/large-scale-c-refactoring-coordination.md +97 -0
  155. package/scripts/skills/generated/pattern-matching-similarity-scoring.md +195 -0
  156. package/scripts/skills/generated/test-parallelization-detection.md +65 -0
  157. package/scripts/skills/observability.md +79 -0
  158. package/scripts/skills/performance.md +48 -0
  159. package/scripts/skills/pr-quality.md +49 -0
  160. package/scripts/skills/product-thinking.md +43 -0
  161. package/scripts/skills/security-audit.md +49 -0
  162. package/scripts/skills/systematic-debugging.md +40 -0
  163. package/scripts/skills/testing-strategy.md +47 -0
  164. package/scripts/skills/two-stage-review.md +52 -0
  165. package/scripts/skills/validation-thoroughness.md +55 -0
  166. package/scripts/sw +9 -3
  167. package/scripts/sw-activity.sh +9 -8
  168. package/scripts/sw-adaptive.sh +8 -7
  169. package/scripts/sw-adversarial.sh +2 -1
  170. package/scripts/sw-architecture-enforcer.sh +3 -1
  171. package/scripts/sw-auth.sh +12 -2
  172. package/scripts/sw-autonomous.sh +5 -1
  173. package/scripts/sw-changelog.sh +4 -1
  174. package/scripts/sw-checkpoint.sh +2 -1
  175. package/scripts/sw-ci.sh +15 -6
  176. package/scripts/sw-cleanup.sh +4 -26
  177. package/scripts/sw-code-review.sh +45 -20
  178. package/scripts/sw-connect.sh +2 -1
  179. package/scripts/sw-context.sh +2 -1
  180. package/scripts/sw-cost.sh +107 -5
  181. package/scripts/sw-daemon.sh +71 -11
  182. package/scripts/sw-dashboard.sh +3 -1
  183. package/scripts/sw-db.sh +71 -20
  184. package/scripts/sw-decide.sh +8 -2
  185. package/scripts/sw-decompose.sh +360 -17
  186. package/scripts/sw-deps.sh +4 -1
  187. package/scripts/sw-developer-simulation.sh +4 -1
  188. package/scripts/sw-discovery.sh +378 -5
  189. package/scripts/sw-doc-fleet.sh +4 -1
  190. package/scripts/sw-docs-agent.sh +3 -1
  191. package/scripts/sw-docs.sh +2 -1
  192. package/scripts/sw-doctor.sh +453 -2
  193. package/scripts/sw-dora.sh +4 -1
  194. package/scripts/sw-durable.sh +12 -7
  195. package/scripts/sw-e2e-orchestrator.sh +17 -16
  196. package/scripts/sw-eventbus.sh +13 -4
  197. package/scripts/sw-evidence.sh +364 -12
  198. package/scripts/sw-feedback.sh +550 -9
  199. package/scripts/sw-fix.sh +20 -1
  200. package/scripts/sw-fleet-discover.sh +6 -2
  201. package/scripts/sw-fleet-viz.sh +9 -4
  202. package/scripts/sw-fleet.sh +5 -1
  203. package/scripts/sw-github-app.sh +18 -4
  204. package/scripts/sw-github-checks.sh +3 -2
  205. package/scripts/sw-github-deploy.sh +3 -2
  206. package/scripts/sw-github-graphql.sh +18 -7
  207. package/scripts/sw-guild.sh +5 -1
  208. package/scripts/sw-heartbeat.sh +5 -30
  209. package/scripts/sw-hello.sh +67 -0
  210. package/scripts/sw-hygiene.sh +10 -3
  211. package/scripts/sw-incident.sh +273 -5
  212. package/scripts/sw-init.sh +18 -2
  213. package/scripts/sw-instrument.sh +10 -2
  214. package/scripts/sw-intelligence.sh +44 -7
  215. package/scripts/sw-jira.sh +5 -1
  216. package/scripts/sw-launchd.sh +2 -1
  217. package/scripts/sw-linear.sh +4 -1
  218. package/scripts/sw-logs.sh +4 -1
  219. package/scripts/sw-loop.sh +436 -1076
  220. package/scripts/sw-memory.sh +357 -3
  221. package/scripts/sw-mission-control.sh +6 -1
  222. package/scripts/sw-model-router.sh +483 -27
  223. package/scripts/sw-otel.sh +15 -4
  224. package/scripts/sw-oversight.sh +14 -5
  225. package/scripts/sw-patrol-meta.sh +334 -0
  226. package/scripts/sw-pipeline-composer.sh +7 -1
  227. package/scripts/sw-pipeline-vitals.sh +12 -6
  228. package/scripts/sw-pipeline.sh +54 -2653
  229. package/scripts/sw-pm.sh +16 -8
  230. package/scripts/sw-pr-lifecycle.sh +2 -1
  231. package/scripts/sw-predictive.sh +17 -5
  232. package/scripts/sw-prep.sh +185 -2
  233. package/scripts/sw-ps.sh +5 -25
  234. package/scripts/sw-public-dashboard.sh +17 -4
  235. package/scripts/sw-quality.sh +14 -6
  236. package/scripts/sw-reaper.sh +8 -25
  237. package/scripts/sw-recruit.sh +156 -2303
  238. package/scripts/sw-regression.sh +19 -12
  239. package/scripts/sw-release-manager.sh +3 -1
  240. package/scripts/sw-release.sh +4 -1
  241. package/scripts/sw-remote.sh +3 -1
  242. package/scripts/sw-replay.sh +7 -1
  243. package/scripts/sw-retro.sh +158 -1
  244. package/scripts/sw-review-rerun.sh +3 -1
  245. package/scripts/sw-scale.sh +14 -5
  246. package/scripts/sw-security-audit.sh +6 -1
  247. package/scripts/sw-self-optimize.sh +173 -6
  248. package/scripts/sw-session.sh +9 -3
  249. package/scripts/sw-setup.sh +3 -1
  250. package/scripts/sw-stall-detector.sh +406 -0
  251. package/scripts/sw-standup.sh +15 -7
  252. package/scripts/sw-status.sh +3 -1
  253. package/scripts/sw-strategic.sh +14 -6
  254. package/scripts/sw-stream.sh +13 -4
  255. package/scripts/sw-swarm.sh +20 -7
  256. package/scripts/sw-team-stages.sh +13 -6
  257. package/scripts/sw-templates.sh +7 -31
  258. package/scripts/sw-testgen.sh +17 -6
  259. package/scripts/sw-tmux-pipeline.sh +4 -1
  260. package/scripts/sw-tmux-role-color.sh +2 -0
  261. package/scripts/sw-tmux-status.sh +1 -1
  262. package/scripts/sw-tmux.sh +37 -1
  263. package/scripts/sw-trace.sh +3 -1
  264. package/scripts/sw-tracker-github.sh +3 -0
  265. package/scripts/sw-tracker-jira.sh +3 -0
  266. package/scripts/sw-tracker-linear.sh +3 -0
  267. package/scripts/sw-tracker.sh +3 -1
  268. package/scripts/sw-triage.sh +3 -2
  269. package/scripts/sw-upgrade.sh +3 -1
  270. package/scripts/sw-ux.sh +5 -2
  271. package/scripts/sw-webhook.sh +5 -2
  272. package/scripts/sw-widgets.sh +9 -4
  273. package/scripts/sw-worktree.sh +15 -3
  274. package/scripts/test-skill-injection.sh +1233 -0
  275. package/templates/pipelines/autonomous.json +27 -3
  276. package/templates/pipelines/cost-aware.json +34 -8
  277. package/templates/pipelines/deployed.json +12 -0
  278. package/templates/pipelines/enterprise.json +12 -0
  279. package/templates/pipelines/fast.json +6 -0
  280. package/templates/pipelines/full.json +27 -3
  281. package/templates/pipelines/hotfix.json +6 -0
  282. package/templates/pipelines/standard.json +12 -0
  283. package/templates/pipelines/tdd.json +12 -0
@@ -0,0 +1,476 @@
1
+ #!/usr/bin/env bash
2
+ # Module guard - prevent double-sourcing
3
+ [[ -n "${_SPEC_DRIVEN_LOADED:-}" ]] && return 0
4
+ _SPEC_DRIVEN_LOADED=1
5
+
6
+ # ╔═══════════════════════════════════════════════════════════════════════════╗
7
+ # ║ shipwright spec-driven — Specification-Driven Development System ║
8
+ # ║ Issue text → structured JSON spec → agent builds from spec ║
9
+ # ║ Diff spec vs implementation at review stage for misalignment detection ║
10
+ # ╚═══════════════════════════════════════════════════════════════════════════╝
11
+
12
+ # shellcheck disable=SC2034
13
+ VERSION="3.3.0"
14
+
15
+ # ─── Output Helpers ──────────────────────────────────────────────────────────
16
+ [[ "$(type -t info 2>/dev/null)" == "function" ]] || info() { echo -e "\033[38;2;0;212;255m\033[1m▸\033[0m $*"; }
17
+ [[ "$(type -t success 2>/dev/null)" == "function" ]] || success() { echo -e "\033[38;2;74;222;128m\033[1m✓\033[0m $*"; }
18
+ [[ "$(type -t warn 2>/dev/null)" == "function" ]] || warn() { echo -e "\033[38;2;250;204;21m\033[1m⚠\033[0m $*"; }
19
+ [[ "$(type -t error 2>/dev/null)" == "function" ]] || error() { echo -e "\033[38;2;248;113;113m\033[1m✗\033[0m $*" >&2; }
20
+ if [[ "$(type -t now_iso 2>/dev/null)" != "function" ]]; then
21
+ now_iso() { date -u +"%Y-%m-%dT%H:%M:%SZ"; }
22
+ fi
23
+
24
+ # ─── Configuration ───────────────────────────────────────────────────────────
25
+
26
+ SPEC_DIR="${SPEC_DIR:-.claude/specs}"
27
+ SPEC_SCHEMA="${SPEC_SCHEMA:-}" # Path to specification.json schema (auto-detected)
28
+
29
+ # ─── Schema Location ────────────────────────────────────────────────────────
30
+
31
+ _find_spec_schema() {
32
+ if [[ -n "$SPEC_SCHEMA" && -f "$SPEC_SCHEMA" ]]; then
33
+ echo "$SPEC_SCHEMA"
34
+ return 0
35
+ fi
36
+ # Check relative to this script (Shipwright repo)
37
+ local script_dir
38
+ script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
39
+ local repo_dir
40
+ repo_dir="$(cd "$script_dir/../.." && pwd)"
41
+ local candidates=(
42
+ "${repo_dir}/schemas/specification.json"
43
+ "./schemas/specification.json"
44
+ ".claude/schemas/specification.json"
45
+ )
46
+ local c
47
+ for c in "${candidates[@]}"; do
48
+ if [[ -f "$c" ]]; then
49
+ echo "$c"
50
+ return 0
51
+ fi
52
+ done
53
+ return 1
54
+ }
55
+
56
+ # ─── Spec Generation ────────────────────────────────────────────────────────
57
+ # Generate a structured spec from issue text or goal description.
58
+ # This produces a template that Claude (or the pipeline) fills in.
59
+
60
+ spec_generate() {
61
+ local title="${1:-}"
62
+ local body="${2:-}"
63
+ local issue_number="${3:-}"
64
+ local output_file="${4:-}"
65
+ local language="${5:-}"
66
+
67
+ if [[ -z "$title" ]]; then
68
+ error "spec_generate requires a title"
69
+ return 1
70
+ fi
71
+
72
+ mkdir -p "$SPEC_DIR"
73
+
74
+ # Generate spec filename from title if not provided
75
+ if [[ -z "$output_file" ]]; then
76
+ local slug
77
+ slug=$(echo "$title" | tr '[:upper:]' '[:lower:]' | tr ' ' '-' | tr -cd 'a-z0-9-' | head -c 60)
78
+ output_file="${SPEC_DIR}/${slug}.json"
79
+ fi
80
+
81
+ # Estimate complexity from body length and keywords
82
+ local complexity="moderate"
83
+ local body_len=${#body}
84
+ if [[ "$body_len" -lt 100 ]]; then
85
+ complexity="simple"
86
+ elif [[ "$body_len" -lt 300 ]]; then
87
+ complexity="moderate"
88
+ elif [[ "$body_len" -lt 800 ]]; then
89
+ complexity="complex"
90
+ else
91
+ complexity="very_complex"
92
+ fi
93
+
94
+ # Extract potential goals from body (lines starting with - or *)
95
+ local goals_json="[]"
96
+ if [[ -n "$body" ]]; then
97
+ local extracted_goals
98
+ extracted_goals=$(echo "$body" | grep -E '^\s*[-*]' | sed 's/^[ \t]*[-*][ \t]*//' | head -10 || true)
99
+ if [[ -n "$extracted_goals" ]]; then
100
+ goals_json="["
101
+ local first=true
102
+ while IFS= read -r goal; do
103
+ [[ -z "$goal" ]] && continue
104
+ # Escape quotes for JSON
105
+ goal=$(echo "$goal" | sed 's/"/\\"/g')
106
+ if $first; then
107
+ goals_json="${goals_json}\"${goal}\""
108
+ first=false
109
+ else
110
+ goals_json="${goals_json},\"${goal}\""
111
+ fi
112
+ done <<< "$extracted_goals"
113
+ goals_json="${goals_json}]"
114
+ fi
115
+ fi
116
+
117
+ # If no goals extracted, use title as the goal
118
+ if [[ "$goals_json" == "[]" ]]; then
119
+ local escaped_title
120
+ escaped_title=$(echo "$title" | sed 's/"/\\"/g')
121
+ goals_json="[\"${escaped_title}\"]"
122
+ fi
123
+
124
+ # Build source block
125
+ local source_json="{\"type\":\"manual\"}"
126
+ if [[ -n "$issue_number" ]]; then
127
+ source_json="{\"type\":\"github_issue\",\"issue_number\":${issue_number}}"
128
+ fi
129
+
130
+ # Write spec (use local fallback if mktemp unavailable in sandbox)
131
+ local tmp_file
132
+ tmp_file=$(mktemp 2>/dev/null || echo "${output_file}.raw")
133
+ cat > "$tmp_file" <<SPECEOF
134
+ {
135
+ "version": "1.0",
136
+ "title": $(printf '%s' "$title" | jq -Rs .),
137
+ "source": ${source_json},
138
+ "goals": ${goals_json},
139
+ "constraints": [],
140
+ "acceptance_criteria": [
141
+ {
142
+ "criterion": "All existing tests continue to pass",
143
+ "testable": true,
144
+ "verification_method": "unit_test"
145
+ }
146
+ ],
147
+ "edge_cases": [],
148
+ "security_requirements": [],
149
+ "performance_requirements": {},
150
+ "affected_files": [],
151
+ "dependencies": [],
152
+ "metadata": {
153
+ "created_at": "$(now_iso)",
154
+ "complexity": "${complexity}",
155
+ "language": "${language:-unknown}"
156
+ }
157
+ }
158
+ SPECEOF
159
+
160
+ # Validate and pretty-print with jq
161
+ if command -v jq >/dev/null 2>&1; then
162
+ if jq '.' "$tmp_file" > "${output_file}.pp" 2>/dev/null; then
163
+ mv "${output_file}.pp" "$output_file"
164
+ else
165
+ error "Generated spec is invalid JSON"
166
+ mv "$tmp_file" "$output_file"
167
+ fi
168
+ else
169
+ mv "$tmp_file" "$output_file"
170
+ fi
171
+ rm -f "$tmp_file" "${output_file}.pp" "${output_file}.raw" 2>/dev/null || true
172
+
173
+ success "Spec generated: ${output_file}" >&2
174
+
175
+ if type emit_event >/dev/null 2>&1; then
176
+ emit_event "spec_generated" \
177
+ "file=${output_file}" \
178
+ "complexity=${complexity}" \
179
+ "issue=${issue_number:-none}"
180
+ fi
181
+
182
+ echo "$output_file"
183
+ return 0
184
+ }
185
+
186
+ # ─── Spec Validation ────────────────────────────────────────────────────────
187
+ # Validate a spec file against the JSON schema (lightweight jq-based check).
188
+
189
+ spec_validate() {
190
+ local spec_file="${1:-}"
191
+
192
+ if [[ ! -f "$spec_file" ]]; then
193
+ error "Spec file not found: ${spec_file}"
194
+ return 1
195
+ fi
196
+
197
+ if ! command -v jq >/dev/null 2>&1; then
198
+ warn "jq not available — skipping spec validation"
199
+ return 0
200
+ fi
201
+
202
+ # Check required fields
203
+ local errors=""
204
+
205
+ local version
206
+ version=$(jq -r '.version // empty' "$spec_file" 2>/dev/null)
207
+ if [[ "$version" != "1.0" ]]; then
208
+ errors="${errors}Missing or invalid version (expected '1.0')\n"
209
+ fi
210
+
211
+ local title
212
+ title=$(jq -r '.title // empty' "$spec_file" 2>/dev/null)
213
+ if [[ -z "$title" ]]; then
214
+ errors="${errors}Missing required field: title\n"
215
+ fi
216
+
217
+ local goals_count
218
+ goals_count=$(jq '.goals | length' "$spec_file" 2>/dev/null || echo "0")
219
+ if [[ "$goals_count" -eq 0 ]]; then
220
+ errors="${errors}Missing required field: goals (must have at least 1)\n"
221
+ fi
222
+
223
+ local criteria_count
224
+ criteria_count=$(jq '.acceptance_criteria | length' "$spec_file" 2>/dev/null || echo "0")
225
+ if [[ "$criteria_count" -eq 0 ]]; then
226
+ errors="${errors}Missing required field: acceptance_criteria (must have at least 1)\n"
227
+ fi
228
+
229
+ # Check acceptance_criteria structure
230
+ local invalid_criteria
231
+ invalid_criteria=$(jq '[.acceptance_criteria[]? | select(.criterion == null or .testable == null)] | length' "$spec_file" 2>/dev/null || echo "0")
232
+ if [[ "$invalid_criteria" -gt 0 ]]; then
233
+ errors="${errors}${invalid_criteria} acceptance criteria missing 'criterion' or 'testable' field\n"
234
+ fi
235
+
236
+ if [[ -n "$errors" ]]; then
237
+ error "Spec validation failed for ${spec_file}:"
238
+ echo -e "$errors" | while IFS= read -r line; do
239
+ [[ -n "$line" ]] && echo " - $line"
240
+ done
241
+ return 1
242
+ fi
243
+
244
+ success "Spec valid: ${spec_file}"
245
+ return 0
246
+ }
247
+
248
+ # ─── Spec Load / Save ───────────────────────────────────────────────────────
249
+
250
+ spec_load() {
251
+ local spec_file="${1:-}"
252
+ if [[ ! -f "$spec_file" ]]; then
253
+ error "Spec file not found: ${spec_file}"
254
+ return 1
255
+ fi
256
+ cat "$spec_file"
257
+ }
258
+
259
+ spec_save() {
260
+ local spec_file="${1:-}"
261
+ local spec_json="${2:-}"
262
+
263
+ if [[ -z "$spec_file" || -z "$spec_json" ]]; then
264
+ error "spec_save requires file path and JSON content"
265
+ return 1
266
+ fi
267
+
268
+ mkdir -p "$(dirname "$spec_file")"
269
+
270
+ # Atomic write via temp file
271
+ local tmp_file
272
+ tmp_file=$(mktemp 2>/dev/null || echo "${spec_file}.tmp")
273
+ echo "$spec_json" | jq '.' > "$tmp_file" 2>/dev/null || {
274
+ error "Invalid JSON for spec save"
275
+ rm -f "$tmp_file"
276
+ return 1
277
+ }
278
+ mv "$tmp_file" "$spec_file"
279
+
280
+ # Update metadata timestamp
281
+ local updated
282
+ updated=$(jq --arg ts "$(now_iso)" '.metadata.updated_at = $ts' "$spec_file" 2>/dev/null)
283
+ if [[ -n "$updated" ]]; then
284
+ echo "$updated" > "$spec_file"
285
+ fi
286
+
287
+ if type emit_event >/dev/null 2>&1; then
288
+ emit_event "spec_updated" "file=${spec_file}"
289
+ fi
290
+
291
+ return 0
292
+ }
293
+
294
+ # ─── Spec Diff ───────────────────────────────────────────────────────────────
295
+ # Compare spec against implementation. Returns JSON report of misalignments.
296
+ # This is used at the review stage to catch "spec says X, code does Y".
297
+
298
+ spec_diff() {
299
+ local spec_file="${1:-}"
300
+ local project_dir="${2:-.}"
301
+
302
+ if [[ ! -f "$spec_file" ]]; then
303
+ error "Spec file not found: ${spec_file}"
304
+ return 1
305
+ fi
306
+
307
+ if ! command -v jq >/dev/null 2>&1; then
308
+ warn "jq not available — skipping spec diff"
309
+ return 0
310
+ fi
311
+
312
+ local report_file="${SPEC_DIR}/compliance-report.json"
313
+ mkdir -p "$SPEC_DIR"
314
+
315
+ # Extract spec data
316
+ local title goals_count criteria_count edge_count security_count
317
+ title=$(jq -r '.title' "$spec_file" 2>/dev/null)
318
+ goals_count=$(jq '.goals | length' "$spec_file" 2>/dev/null || echo "0")
319
+ criteria_count=$(jq '.acceptance_criteria | length' "$spec_file" 2>/dev/null || echo "0")
320
+ edge_count=$(jq '.edge_cases | length' "$spec_file" 2>/dev/null || echo "0")
321
+ security_count=$(jq '.security_requirements | length' "$spec_file" 2>/dev/null || echo "0")
322
+
323
+ # Check which affected files were actually modified
324
+ local affected_files
325
+ affected_files=$(jq -r '.affected_files[]?' "$spec_file" 2>/dev/null || true)
326
+ local files_modified=0
327
+ local files_missing=0
328
+ local missing_files=""
329
+
330
+ if [[ -n "$affected_files" ]]; then
331
+ while IFS= read -r af; do
332
+ [[ -z "$af" ]] && continue
333
+ if git -C "$project_dir" diff --name-only HEAD 2>/dev/null | grep -q "$af"; then
334
+ files_modified=$((files_modified + 1))
335
+ elif [[ -f "${project_dir}/${af}" ]]; then
336
+ # File exists but wasn't modified
337
+ files_missing=$((files_missing + 1))
338
+ if [[ -n "$missing_files" ]]; then
339
+ missing_files="${missing_files},\"${af}\""
340
+ else
341
+ missing_files="\"${af}\""
342
+ fi
343
+ fi
344
+ done <<< "$affected_files"
345
+ fi
346
+
347
+ # Check testable acceptance criteria have corresponding tests
348
+ local testable_criteria untested_criteria
349
+ testable_criteria=$(jq '[.acceptance_criteria[]? | select(.testable == true)] | length' "$spec_file" 2>/dev/null || echo "0")
350
+ untested_criteria=0 # Would need test discovery to check this properly
351
+
352
+ # Build compliance report
353
+ cat > "$report_file" <<EOF
354
+ {
355
+ "spec_file": "${spec_file}",
356
+ "checked_at": "$(now_iso)",
357
+ "title": $(printf '%s' "$title" | jq -Rs .),
358
+ "coverage": {
359
+ "goals_defined": ${goals_count},
360
+ "acceptance_criteria": ${criteria_count},
361
+ "testable_criteria": ${testable_criteria},
362
+ "edge_cases_defined": ${edge_count},
363
+ "security_requirements": ${security_count}
364
+ },
365
+ "file_coverage": {
366
+ "expected_files": $(jq '.affected_files | length' "$spec_file" 2>/dev/null || echo "0"),
367
+ "files_modified": ${files_modified},
368
+ "files_not_modified": ${files_missing},
369
+ "unmodified_files": [${missing_files}]
370
+ },
371
+ "warnings": [],
372
+ "verdict": "$(if [[ "$files_missing" -gt 0 ]]; then echo "review_needed"; else echo "compliant"; fi)"
373
+ }
374
+ EOF
375
+
376
+ local verdict
377
+ verdict=$(jq -r '.verdict' "$report_file" 2>/dev/null || echo "unknown")
378
+
379
+ if [[ "$verdict" == "compliant" ]]; then
380
+ success "Spec compliance: ${title} — all checks passed"
381
+ else
382
+ warn "Spec compliance: ${title} — review needed (${files_missing} expected files not modified)"
383
+ fi
384
+
385
+ if type emit_event >/dev/null 2>&1; then
386
+ emit_event "spec_compliance_checked" \
387
+ "spec=${spec_file}" \
388
+ "verdict=${verdict}" \
389
+ "criteria=${criteria_count}"
390
+ fi
391
+
392
+ echo "$report_file"
393
+ return 0
394
+ }
395
+
396
+ # ─── Spec List ───────────────────────────────────────────────────────────────
397
+
398
+ spec_list() {
399
+ if [[ ! -d "$SPEC_DIR" ]]; then
400
+ info "No specs directory found"
401
+ return 0
402
+ fi
403
+
404
+ local specs
405
+ specs=$(find "$SPEC_DIR" -name "*.json" -not -name "compliance-report.json" 2>/dev/null | sort)
406
+
407
+ if [[ -z "$specs" ]]; then
408
+ info "No specs found in ${SPEC_DIR}"
409
+ return 0
410
+ fi
411
+
412
+ echo "Specifications:"
413
+ while IFS= read -r spec; do
414
+ local title complexity
415
+ title=$(jq -r '.title // "untitled"' "$spec" 2>/dev/null)
416
+ complexity=$(jq -r '.metadata.complexity // "unknown"' "$spec" 2>/dev/null)
417
+ local goals_count
418
+ goals_count=$(jq '.goals | length' "$spec" 2>/dev/null || echo "0")
419
+ printf " %-50s [%s] %d goals\n" "$title" "$complexity" "$goals_count"
420
+ done <<< "$specs"
421
+ }
422
+
423
+ # ─── Spec for Pipeline Prompt ────────────────────────────────────────────────
424
+ # Format spec as markdown for injection into agent prompts.
425
+
426
+ spec_to_prompt() {
427
+ local spec_file="${1:-}"
428
+
429
+ if [[ ! -f "$spec_file" ]]; then
430
+ return 1
431
+ fi
432
+
433
+ local title goals constraints criteria edge_cases security
434
+
435
+ title=$(jq -r '.title' "$spec_file" 2>/dev/null)
436
+ echo "## Specification: ${title}"
437
+ echo ""
438
+
439
+ echo "### Goals"
440
+ jq -r '.goals[]?' "$spec_file" 2>/dev/null | while IFS= read -r g; do
441
+ echo "- ${g}"
442
+ done
443
+ echo ""
444
+
445
+ local constraints_count
446
+ constraints_count=$(jq '.constraints | length' "$spec_file" 2>/dev/null || echo "0")
447
+ if [[ "$constraints_count" -gt 0 ]]; then
448
+ echo "### Constraints"
449
+ jq -r '.constraints[]?' "$spec_file" 2>/dev/null | while IFS= read -r c; do
450
+ echo "- ${c}"
451
+ done
452
+ echo ""
453
+ fi
454
+
455
+ echo "### Acceptance Criteria"
456
+ jq -r '.acceptance_criteria[]? | "- [\(if .testable then "testable" else "manual" end)] \(.criterion)"' "$spec_file" 2>/dev/null
457
+ echo ""
458
+
459
+ local edge_count
460
+ edge_count=$(jq '.edge_cases | length' "$spec_file" 2>/dev/null || echo "0")
461
+ if [[ "$edge_count" -gt 0 ]]; then
462
+ echo "### Edge Cases"
463
+ jq -r '.edge_cases[]? | "- **\(.scenario)**: \(.expected_behavior)"' "$spec_file" 2>/dev/null
464
+ echo ""
465
+ fi
466
+
467
+ local sec_count
468
+ sec_count=$(jq '.security_requirements | length' "$spec_file" 2>/dev/null || echo "0")
469
+ if [[ "$sec_count" -gt 0 ]]; then
470
+ echo "### Security Requirements"
471
+ jq -r '.security_requirements[]?' "$spec_file" 2>/dev/null | while IFS= read -r s; do
472
+ echo "- ${s}"
473
+ done
474
+ echo ""
475
+ fi
476
+ }
@@ -22,6 +22,7 @@ CYAN='\033[38;2;0;212;255m'
22
22
  GREEN='\033[38;2;74;222;128m'
23
23
  RED='\033[38;2;248;113;113m'
24
24
  YELLOW='\033[38;2;250;204;21m'
25
+ PURPLE='\033[38;2;168;85;247m'
25
26
  DIM='\033[2m'
26
27
  BOLD='\033[1m'
27
28
  RESET='\033[0m'
@@ -31,7 +32,16 @@ PASS=0
31
32
  FAIL=0
32
33
  TOTAL=0
33
34
  FAILURES=()
34
- TEST_TEMP_DIR=""
35
+
36
+ # ─── Auto-initialize TEST_TEMP_DIR ──────────────────────────────────────────
37
+ # Many test files use TEST_TEMP_DIR in their setup_env() without calling
38
+ # setup_test_env(). Auto-create a temp dir so $TEST_TEMP_DIR is never empty.
39
+ # Save originals now so cleanup_test_env() can always restore them.
40
+ ORIG_HOME="${HOME}"
41
+ ORIG_PATH="${PATH}"
42
+ TEST_TEMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/sw-test-auto.XXXXXX")
43
+ mkdir -p "$TEST_TEMP_DIR/home/.shipwright"
44
+ mkdir -p "$TEST_TEMP_DIR/bin"
35
45
 
36
46
  # ─── Assertions ──────────────────────────────────────────────────────────────
37
47
 
@@ -67,7 +77,7 @@ assert_contains() {
67
77
  local desc="$1"
68
78
  local haystack="$2"
69
79
  local needle="$3"
70
- if echo "$haystack" | grep -qF "$needle" 2>/dev/null; then
80
+ if grep -qF -- "$needle" <<< "$haystack" 2>/dev/null; then
71
81
  assert_pass "$desc"
72
82
  else
73
83
  assert_fail "$desc" "output missing: $needle"
@@ -78,7 +88,7 @@ assert_contains_regex() {
78
88
  local desc="$1"
79
89
  local haystack="$2"
80
90
  local pattern="$3"
81
- if echo "$haystack" | grep -qE "$pattern" 2>/dev/null; then
91
+ if grep -qE -- "$pattern" <<< "$haystack" 2>/dev/null; then
82
92
  assert_pass "$desc"
83
93
  else
84
94
  assert_fail "$desc" "output missing pattern: $pattern"
@@ -145,14 +155,15 @@ assert_file_not_exists() {
145
155
 
146
156
  setup_test_env() {
147
157
  local test_name="${1:-sw-test}"
158
+ # Clean up auto-created temp dir and create a named one
159
+ [[ -n "$TEST_TEMP_DIR" && -d "$TEST_TEMP_DIR" ]] && rm -rf "$TEST_TEMP_DIR"
148
160
  TEST_TEMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/${test_name}.XXXXXX")
149
161
  mkdir -p "$TEST_TEMP_DIR/home/.shipwright"
150
162
  mkdir -p "$TEST_TEMP_DIR/bin"
151
163
  mkdir -p "$TEST_TEMP_DIR/project"
152
164
  mkdir -p "$TEST_TEMP_DIR/logs"
153
165
 
154
- ORIG_HOME="${HOME}"
155
- ORIG_PATH="${PATH}"
166
+ # ORIG_HOME/ORIG_PATH already saved at source time
156
167
  export HOME="$TEST_TEMP_DIR/home"
157
168
  export PATH="$TEST_TEMP_DIR/bin:$PATH"
158
169
  export NO_GITHUB=true
@@ -167,8 +178,8 @@ cleanup_test_env() {
167
178
  if [[ -n "$TEST_TEMP_DIR" && -d "$TEST_TEMP_DIR" ]]; then
168
179
  rm -rf "$TEST_TEMP_DIR"
169
180
  fi
170
- [[ -n "${ORIG_HOME:-}" ]] && export HOME="$ORIG_HOME"
171
- [[ -n "${ORIG_PATH:-}" ]] && export PATH="$ORIG_PATH"
181
+ [[ -n "${ORIG_HOME:-}" ]] && export HOME="$ORIG_HOME" || true
182
+ [[ -n "${ORIG_PATH:-}" ]] && export PATH="$ORIG_PATH" || true
172
183
  }
173
184
 
174
185
  # ─── Mock Helpers ────────────────────────────────────────────────────────────