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
@@ -5,7 +5,7 @@
5
5
  # ╚═══════════════════════════════════════════════════════════════════════════╝
6
6
  set -euo pipefail
7
7
 
8
- VERSION="3.1.0"
8
+ VERSION="3.3.0"
9
9
 
10
10
  # ─── Script directory resolution ────────────────────────────────────────────
11
11
  SOURCE="${BASH_SOURCE[0]}"
@@ -19,6 +19,7 @@ SCRIPT_DIR="$(cd "$(dirname "$SOURCE")" && pwd)"
19
19
  # ─── State directories ──────────────────────────────────────────────────────
20
20
  E2E_DIR="${HOME}/.shipwright/e2e"
21
21
  SUITE_REGISTRY="$E2E_DIR/suite-registry.json"
22
+ # shellcheck disable=SC2034
22
23
  FLAKY_CACHE="$E2E_DIR/flaky-cache.json"
23
24
  RESULTS_LOG="$E2E_DIR/results.jsonl"
24
25
  LATEST_REPORT="$E2E_DIR/latest-report.json"
@@ -123,7 +124,7 @@ cmd_register() {
123
124
  fi
124
125
 
125
126
  # Parse existing registry
126
- local registry=$(load_registry)
127
+ local registry; registry=$(load_registry)
127
128
  local new_suite
128
129
 
129
130
  # Create feature array
@@ -179,7 +180,7 @@ cmd_quarantine() {
179
180
  init_registry
180
181
  fi
181
182
 
182
- local registry=$(load_registry)
183
+ local registry; registry=$(load_registry)
183
184
 
184
185
  if [[ "$action" == "quarantine" ]]; then
185
186
  # Add to quarantine list if not already present
@@ -207,20 +208,20 @@ cmd_quarantine() {
207
208
  # ─── Run a single test suite ────────────────────────────────────────────────
208
209
  run_suite() {
209
210
  local suite_id="$1"
210
- local registry=$(load_registry)
211
+ local registry; registry=$(load_registry)
211
212
 
212
213
  # Find suite
213
- local suite=$(echo "$registry" | jq ".suites[] | select(.id == \"$suite_id\")")
214
+ local suite; suite=$(echo "$registry" | jq ".suites[] | select(.id == \"$suite_id\")")
214
215
 
215
216
  if [[ -z "$suite" ]]; then
216
217
  error "Suite not found: $suite_id"
217
218
  return 1
218
219
  fi
219
220
 
220
- local suite_name=$(echo "$suite" | jq -r '.name')
221
- local script=$(echo "$suite" | jq -r '.script')
222
- local timeout=$(echo "$suite" | jq -r '.timeout_seconds')
223
- local features=$(echo "$suite" | jq -r '.features | join(", ")')
221
+ local suite_name; suite_name=$(echo "$suite" | jq -r '.name')
222
+ local script; script=$(echo "$suite" | jq -r '.script')
223
+ local timeout; timeout=$(echo "$suite" | jq -r '.timeout_seconds')
224
+ local features; features=$(echo "$suite" | jq -r '.features | join(", ")')
224
225
 
225
226
  local test_script="$SCRIPT_DIR/$script"
226
227
 
@@ -230,7 +231,7 @@ run_suite() {
230
231
  fi
231
232
 
232
233
  info "Running: $suite_name ($features)"
233
- local start_time=$(date +%s)
234
+ local start_time; start_time=$(date +%s)
234
235
 
235
236
  # Run with timeout (gtimeout on macOS, timeout on Linux)
236
237
  local timeout_cmd="timeout"
@@ -238,7 +239,7 @@ run_suite() {
238
239
  local exit_code=0
239
240
  "$timeout_cmd" "$timeout" bash "$test_script" || exit_code=$?
240
241
 
241
- local end_time=$(date +%s)
242
+ local end_time; end_time=$(date +%s)
242
243
  local duration=$((end_time - start_time))
243
244
 
244
245
  # Log result
@@ -257,9 +258,9 @@ run_parallel() {
257
258
  local max_parallel=${2:-3}
258
259
 
259
260
  ensure_state_dir
260
- > "$RESULTS_LOG" # Clear results log
261
+ true > "$RESULTS_LOG" # Clear results log
261
262
 
262
- local registry=$(load_registry)
263
+ local registry; registry=$(load_registry)
263
264
 
264
265
  # Filter suites by category and enabled status
265
266
  local suites
@@ -341,7 +342,7 @@ cmd_report() {
341
342
  local skip=0
342
343
 
343
344
  while IFS= read -r line; do
344
- local exit_code=$(echo "$line" | jq -r '.exit_code // 0')
345
+ local exit_code; exit_code=$(echo "$line" | jq -r '.exit_code // 0')
345
346
  if [[ $exit_code -eq 0 ]]; then
346
347
  pass=$((pass + 1))
347
348
  elif [[ $exit_code -eq 124 ]]; then
@@ -354,7 +355,7 @@ cmd_report() {
354
355
  local total=$((pass + fail + timeout + skip))
355
356
 
356
357
  # Create report
357
- local report=$(jq -n \
358
+ local report; report=$(jq -n \
358
359
  --arg ts "$(date -Iseconds)" \
359
360
  --arg version "$VERSION" \
360
361
  --argjson p "$pass" \
@@ -409,7 +410,7 @@ cmd_flaky() {
409
410
  info "Analyzing flaky tests..."
410
411
 
411
412
  # Group by test name, count passes/fails
412
- local flaky_analysis=$(jq -s 'group_by(.suite_id) | map({
413
+ local flaky_analysis; flaky_analysis=$(jq -s 'group_by(.suite_id) | map({
413
414
  test: .[0].suite_id,
414
415
  runs: length,
415
416
  passes: (map(select(.exit_code == 0)) | length),
@@ -7,7 +7,8 @@
7
7
  set -euo pipefail
8
8
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
9
9
 
10
- VERSION="3.1.0"
10
+ # shellcheck disable=SC2034
11
+ VERSION="3.3.0"
11
12
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
12
13
 
13
14
  # ─── Cross-platform compatibility ──────────────────────────────────────────
@@ -31,6 +32,7 @@ fi
31
32
  if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
32
33
  emit_event() {
33
34
  local event_type="$1"; shift; mkdir -p "${HOME}/.shipwright"
35
+ # shellcheck disable=SC2155
34
36
  local payload="{\"ts\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"type\":\"$event_type\""
35
37
  while [[ $# -gt 0 ]]; do local key="${1%%=*}" val="${1#*=}"; payload="${payload},\"${key}\":\"${val}\""; shift; done
36
38
  echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
@@ -94,6 +96,8 @@ cmd_subscribe() {
94
96
  while true; do
95
97
  if db_available 2>/dev/null; then
96
98
  local events batch_last_id=0
99
+ # Numeric validation for last_id
100
+ [[ ! "$last_id" =~ ^[0-9]+$ ]] && last_id=0
97
101
  events=$(sqlite3 -json "$DB_FILE" "SELECT * FROM events WHERE id > $last_id ORDER BY id ASC LIMIT 50;" 2>/dev/null || echo "[]")
98
102
  if [[ "$events" != "[]" && -n "$events" ]]; then
99
103
  while IFS= read -r event; do
@@ -124,6 +128,7 @@ cmd_subscribe() {
124
128
 
125
129
  # ─── Process reaper (SIGCHLD monitor) ──────────────────────────────────────
126
130
  cmd_reaper() {
131
+ # shellcheck disable=SC2034
127
132
  local pid_list=()
128
133
 
129
134
  info "Starting process reaper. Press Ctrl+C to exit."
@@ -145,6 +150,7 @@ cmd_reaper() {
145
150
  # Check if process is still alive
146
151
  if ! kill -0 "$pid" 2>/dev/null; then
147
152
  # Process died — emit event
153
+ # shellcheck disable=SC2155
148
154
  local payload="{\"pid\": $pid, \"timestamp\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"}"
149
155
  cmd_publish "process.exited" "reaper" "$(generate_uuid)" "$payload"
150
156
  fi
@@ -251,7 +257,8 @@ cmd_status() {
251
257
  fi
252
258
  elif [[ -f "$EVENTS_FILE" ]]; then
253
259
  local total_events last_event_ts
254
- total_events=$(wc -l < "$EVENTS_FILE" || echo 0)
260
+ total_events=$(wc -l < "$EVENTS_FILE" || true)
261
+ total_events="${total_events:-0}"
255
262
  last_event_ts=$(tail -1 "$EVENTS_FILE" | jq -r '.ts // "never"' 2>/dev/null || echo "never")
256
263
  echo -e " ${CYAN}Event Store:${RESET} $EVENTS_FILE (file fallback)"
257
264
  echo -e " ${CYAN}Total Events:${RESET} ${BOLD}${total_events}${RESET}"
@@ -290,7 +297,8 @@ cmd_clean() {
290
297
  elif [[ -f "$EVENTS_FILE" ]]; then
291
298
  info "Cleaning events older than ${ttl_days} days..."
292
299
  local old_count tmp_file new_count removed
293
- old_count=$(grep -c "ts" "$EVENTS_FILE" 2>/dev/null || echo 0)
300
+ old_count=$(grep -c "ts" "$EVENTS_FILE" 2>/dev/null || true)
301
+ old_count="${old_count:-0}"
294
302
  tmp_file="$(mktemp)"
295
303
  while IFS= read -r line; do
296
304
  [[ -z "$line" ]] && continue
@@ -299,7 +307,8 @@ cmd_clean() {
299
307
  [[ -n "$ts" && "$ts" > "$cutoff_iso" ]] && echo "$line" >> "$tmp_file"
300
308
  done < "$EVENTS_FILE"
301
309
  mv "$tmp_file" "$EVENTS_FILE"
302
- new_count=$(wc -l < "$EVENTS_FILE" || echo 0)
310
+ new_count=$(wc -l < "$EVENTS_FILE" || true)
311
+ new_count="${new_count:-0}"
303
312
  removed=$((old_count - new_count))
304
313
  success "Removed $removed old events. Remaining: $new_count"
305
314
  else
@@ -8,7 +8,8 @@
8
8
  set -euo pipefail
9
9
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
10
10
 
11
- VERSION="3.1.0"
11
+ # shellcheck disable=SC2034
12
+ VERSION="3.3.0"
12
13
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
13
14
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
14
15
 
@@ -187,6 +188,7 @@ collect_api() {
187
188
  local name="$1"
188
189
  local collector_json="$2"
189
190
 
191
+ # shellcheck disable=SC2034
190
192
  local url method expected_status headers_json body timeout
191
193
  url=$(echo "$collector_json" | jq -r '.url // ""')
192
194
  method=$(echo "$collector_json" | jq -r '.method // "GET"')
@@ -444,6 +446,191 @@ collect_custom() {
444
446
  '{command: $cmd, exit_code: $exit_code, expected_exit_code: $expected, output_preview: $output_preview}')"
445
447
  }
446
448
 
449
+ # ─── Mutation Testing: Verify mutations are caught by test suite ───────────────
450
+
451
+ collect_mutation() {
452
+ local name="$1"
453
+ local collector_json="$2"
454
+
455
+ local test_cmd target_files threshold
456
+ test_cmd=$(echo "$collector_json" | jq -r '.testCommand // ""')
457
+ target_files=$(echo "$collector_json" | jq -r '.targetFiles // ""')
458
+ threshold=$(echo "$collector_json" | jq -r '.mutationThreshold // 60')
459
+
460
+ if [[ -z "$test_cmd" ]] || [[ -z "$target_files" ]]; then
461
+ error "[mutation] ${name}: testCommand and targetFiles required"
462
+ write_evidence_record "$name" "mutation" "false" '{"error": "testCommand and targetFiles required"}'
463
+ return
464
+ fi
465
+
466
+ info "[mutation] ${name}: testing mutation coverage (threshold: ${threshold}%)"
467
+
468
+ local mutation_dir
469
+ mutation_dir=$(mktemp -d "${TMPDIR:-/tmp}/sw-evidence-mutations.XXXXXX")
470
+ trap "rm -rf '$mutation_dir'" RETURN
471
+
472
+ local total_mutants=0
473
+ local killed_mutants=0
474
+
475
+ # For each target file, create mutations
476
+ local file
477
+ while IFS= read -r file; do
478
+ [[ -z "$file" ]] && continue
479
+ [[ ! -f "$REPO_DIR/$file" ]] && continue
480
+
481
+ # Copy file to mutation dir for testing
482
+ local file_copy="$mutation_dir/$(basename "$file")"
483
+ cp "$REPO_DIR/$file" "$file_copy"
484
+
485
+ # Apply mutations: swap operators, negate conditions, change exit codes
486
+ local mutations=()
487
+
488
+ # Mutation 1: swap == to !=
489
+ if grep -q "==" "$file_copy"; then
490
+ mutations+=("sed 's/==/!=/g'")
491
+ fi
492
+
493
+ # Mutation 2: swap != to ==
494
+ if grep -q "!=" "$file_copy"; then
495
+ mutations+=("sed 's/!=/==/g'")
496
+ fi
497
+
498
+ # Mutation 3: change -gt to -lt
499
+ if grep -q "\-gt" "$file_copy"; then
500
+ mutations+=("sed 's/-gt/-lt/g'")
501
+ fi
502
+
503
+ # Mutation 4: change -lt to -gt
504
+ if grep -q "\-lt" "$file_copy"; then
505
+ mutations+=("sed 's/-lt/-gt/g'")
506
+ fi
507
+
508
+ # Mutation 5: change exit 0 to exit 1
509
+ if grep -q "exit 0" "$file_copy"; then
510
+ mutations+=("sed 's/exit 0/exit 1/g'")
511
+ fi
512
+
513
+ # Mutation 6: comment out error traps
514
+ if grep -q "trap.*ERR" "$file_copy"; then
515
+ mutations+=("sed 's/^trap /#trap /g'")
516
+ fi
517
+
518
+ # Apply each mutation and test
519
+ for i in "${!mutations[@]}"; do
520
+ total_mutants=$((total_mutants + 1))
521
+ local mutated_copy="$file_copy.mutant.$i"
522
+ cp "$file_copy" "$mutated_copy"
523
+
524
+ # Apply mutation
525
+ eval "${mutations[$i]} \"$mutated_copy\" > \"${mutated_copy}.tmp\" && mv \"${mutated_copy}.tmp\" \"$mutated_copy\"" 2>/dev/null || true
526
+
527
+ # Run test with mutation — test should fail (mutation caught)
528
+ local test_result=0
529
+ (cd "$REPO_DIR" && _run_with_timeout 30 bash -c "$test_cmd" > /dev/null 2>&1) || test_result=$?
530
+
531
+ # If test failed (non-zero), mutation was caught
532
+ if [[ "$test_result" -ne 0 ]]; then
533
+ killed_mutants=$((killed_mutants + 1))
534
+ fi
535
+
536
+ rm -f "$mutated_copy"
537
+ done
538
+
539
+ done <<< "$target_files"
540
+
541
+ local mutation_score=0
542
+ local passed="false"
543
+ if [[ "$total_mutants" -gt 0 ]]; then
544
+ mutation_score=$((killed_mutants * 100 / total_mutants))
545
+ [[ "$mutation_score" -ge "$threshold" ]] && passed="true"
546
+ fi
547
+
548
+ write_evidence_record "$name" "mutation" "$passed" \
549
+ "$(jq -n --argjson total "$total_mutants" --argjson killed "$killed_mutants" \
550
+ --argjson score "$mutation_score" --argjson threshold "$threshold" \
551
+ '{total_mutants: $total, killed_mutants: $killed, mutation_score: $score, threshold: $threshold}')"
552
+ }
553
+
554
+ # ─── Property-Based Testing: Verify properties hold over iterations ──────────
555
+
556
+ collect_property() {
557
+ local name="$1"
558
+ local collector_json="$2"
559
+
560
+ local property_cmd iterations
561
+ property_cmd=$(echo "$collector_json" | jq -r '.propertyCommand // ""')
562
+ iterations=$(echo "$collector_json" | jq -r '.iterations // 100')
563
+
564
+ if [[ -z "$property_cmd" ]]; then
565
+ error "[property] ${name}: propertyCommand required"
566
+ write_evidence_record "$name" "property" "false" '{"error": "propertyCommand required"}'
567
+ return
568
+ fi
569
+
570
+ info "[property] ${name}: running property test (${iterations} iterations)"
571
+
572
+ local passed_count=0
573
+ local failed_count=0
574
+ local counterexamples="[]"
575
+
576
+ # Run property test multiple times
577
+ local i
578
+ for ((i = 0; i < iterations; i++)); do
579
+ local output=0
580
+ local result_output=""
581
+ result_output=$(cd "$REPO_DIR" && _run_with_timeout 10 bash -c "$property_cmd" 2>&1) || output=$?
582
+
583
+ if [[ "$output" -eq 0 ]]; then
584
+ passed_count=$((passed_count + 1))
585
+ else
586
+ failed_count=$((failed_count + 1))
587
+ # Capture counterexample (first 200 chars of output)
588
+ counterexamples=$(echo "$counterexamples" | jq \
589
+ --arg ce "${result_output:0:200}" \
590
+ '. += [{"iteration": '$i', "output": $ce}]')
591
+ fi
592
+ done
593
+
594
+ local passed="false"
595
+ [[ "$failed_count" -eq 0 ]] && passed="true"
596
+
597
+ write_evidence_record "$name" "property" "$passed" \
598
+ "$(jq -n --argjson passed_count "$passed_count" --argjson failed_count "$failed_count" \
599
+ --argjson iterations "$iterations" --argjson counterexamples "$counterexamples" \
600
+ '{passed_count: $passed_count, failed_count: $failed_count, total_iterations: $iterations, counterexamples: $counterexamples}')"
601
+ }
602
+
603
+ # ─── Invariant Checking: Verify system invariants ────────────────────────────
604
+
605
+ collect_invariant() {
606
+ local name="$1"
607
+ local collector_json="$2"
608
+
609
+ local check_cmd invariant_name
610
+ check_cmd=$(echo "$collector_json" | jq -r '.checkCommand // ""')
611
+ invariant_name=$(echo "$collector_json" | jq -r '.invariantName // "unnamed"')
612
+
613
+ if [[ -z "$check_cmd" ]]; then
614
+ error "[invariant] ${name}: checkCommand required"
615
+ write_evidence_record "$name" "invariant" "false" '{"error": "checkCommand required"}'
616
+ return
617
+ fi
618
+
619
+ info "[invariant] ${name}: checking invariant '${invariant_name}'"
620
+
621
+ local exit_code=0
622
+ local output=""
623
+ output=$(cd "$REPO_DIR" && _run_with_timeout 30 bash -c "$check_cmd" 2>&1) || exit_code=$?
624
+
625
+ local passed="false"
626
+ [[ "$exit_code" -eq 0 ]] && passed="true"
627
+
628
+ write_evidence_record "$name" "invariant" "$passed" \
629
+ "$(jq -n --arg invariant_name "$invariant_name" --argjson exit_code "$exit_code" \
630
+ --arg output "${output:0:2000}" \
631
+ '{invariant_name: $invariant_name, check_exit_code: $exit_code, output: $output}')"
632
+ }
633
+
447
634
  # ═════════════════════════════════════════════════════════════════════════════
448
635
  # EVIDENCE RECORD WRITER
449
636
  # ═════════════════════════════════════════════════════════════════════════════
@@ -477,6 +664,142 @@ write_evidence_record() {
477
664
  fi
478
665
  }
479
666
 
667
+ # ═════════════════════════════════════════════════════════════════════════════
668
+ # ARTIFACT CAPTURE
669
+ # Stores build logs, test reports, coverage as evidence artifacts with manifest
670
+ # ═════════════════════════════════════════════════════════════════════════════
671
+
672
+ evidence_capture_artifact() {
673
+ local artifact_name="$1"
674
+ local artifact_path="$2"
675
+
676
+ if [[ ! -f "$artifact_path" ]]; then
677
+ warn "[artifact] ${artifact_name}: file not found"
678
+ return 1
679
+ fi
680
+
681
+ local artifacts_dir="${EVIDENCE_DIR}/artifacts"
682
+ mkdir -p "$artifacts_dir"
683
+
684
+ # Copy artifact to artifacts directory
685
+ local dest_file="$artifacts_dir/${artifact_name}"
686
+ cp "$artifact_path" "$dest_file"
687
+
688
+ # Compute SHA-256 of artifact
689
+ local artifact_sha256
690
+ if command -v shasum >/dev/null 2>&1; then
691
+ artifact_sha256=$(shasum -a 256 "$dest_file" | awk '{print $1}')
692
+ elif command -v sha256sum >/dev/null 2>&1; then
693
+ artifact_sha256=$(sha256sum "$dest_file" | awk '{print $1}')
694
+ else
695
+ artifact_sha256="unknown"
696
+ fi
697
+
698
+ info "[artifact] ${artifact_name}: captured (${artifact_sha256:0:16}...)"
699
+
700
+ # Append to artifacts manifest
701
+ local artifacts_manifest="${EVIDENCE_DIR}/artifacts-manifest.json"
702
+ if [[ ! -f "$artifacts_manifest" ]]; then
703
+ echo "[]" > "$artifacts_manifest"
704
+ fi
705
+
706
+ local tmp_manifest
707
+ tmp_manifest=$(mktemp "${TMPDIR:-/tmp}/sw-evidence-artifacts.XXXXXX")
708
+ jq \
709
+ --arg name "$artifact_name" \
710
+ --arg path "$dest_file" \
711
+ --arg sha256 "$artifact_sha256" \
712
+ --arg captured_at "$(now_iso)" \
713
+ '. += [{"name": $name, "path": $path, "sha256": $sha256, "captured_at": $captured_at}]' \
714
+ "$artifacts_manifest" > "$tmp_manifest"
715
+ mv "$tmp_manifest" "$artifacts_manifest"
716
+
717
+ return 0
718
+ }
719
+
720
+ # ═════════════════════════════════════════════════════════════════════════════
721
+ # QUALITY SCORE COMPUTATION
722
+ # Weights: mutation (30%), property tests (25%), invariants (25%), collectors (20%)
723
+ # ═════════════════════════════════════════════════════════════════════════════
724
+
725
+ evidence_quality_score() {
726
+ ensure_evidence_dir
727
+
728
+ if [[ ! -f "$MANIFEST_FILE" ]]; then
729
+ echo "0"
730
+ return 1
731
+ fi
732
+
733
+ # Extract collector results
734
+ local mutation_score=0
735
+ local property_score=0
736
+ local invariant_score=0
737
+ local collector_pass_rate=0
738
+
739
+ # Mutation test score (from manifest)
740
+ local mutation_evidence="${EVIDENCE_DIR}/mutation_test.json"
741
+ if [[ -f "$mutation_evidence" ]]; then
742
+ local mutation_passed
743
+ mutation_passed=$(jq -r '.passed // false' "$mutation_evidence" 2>/dev/null || echo "false")
744
+ if [[ "$mutation_passed" == "true" ]]; then
745
+ mutation_score=$(jq -r '.details.mutation_score // 0' "$mutation_evidence" 2>/dev/null || echo "0")
746
+ [[ -z "$mutation_score" ]] && mutation_score="0"
747
+ fi
748
+ fi
749
+
750
+ # Property test score (failed_count == 0 => 100, else 0)
751
+ local property_evidence="${EVIDENCE_DIR}/property_test.json"
752
+ if [[ -f "$property_evidence" ]]; then
753
+ local prop_failed
754
+ prop_failed=$(jq -r '.details.failed_count // 0' "$property_evidence" 2>/dev/null || echo "0")
755
+ [[ -z "$prop_failed" ]] && prop_failed="0"
756
+ if [[ "$prop_failed" -eq 0 ]]; then
757
+ property_score=100
758
+ fi
759
+ fi
760
+
761
+ # Invariant score (all pass => 100, else 50)
762
+ local invariant_evidence="${EVIDENCE_DIR}/invariant_check.json"
763
+ if [[ -f "$invariant_evidence" ]]; then
764
+ local invariant_passed
765
+ invariant_passed=$(jq -r '.passed // false' "$invariant_evidence" 2>/dev/null || echo "false")
766
+ if [[ "$invariant_passed" == "true" ]]; then
767
+ invariant_score=100
768
+ else
769
+ invariant_score=50
770
+ fi
771
+ fi
772
+
773
+ # Collector pass rate
774
+ local collector_count
775
+ collector_count=$(jq -r '.collector_count // 0' "$MANIFEST_FILE" 2>/dev/null || echo "0")
776
+ [[ -z "$collector_count" ]] && collector_count="0"
777
+
778
+ local passed_count
779
+ passed_count=$(jq -r '.passed // 0' "$MANIFEST_FILE" 2>/dev/null || echo "0")
780
+ [[ -z "$passed_count" ]] && passed_count="0"
781
+
782
+ if [[ "$collector_count" -gt 0 ]]; then
783
+ collector_pass_rate=$((passed_count * 100 / collector_count))
784
+ fi
785
+
786
+ # Weighted score: 30% mutation + 25% property + 25% invariant + 20% collector
787
+ local weighted_score=0
788
+ weighted_score=$((
789
+ (mutation_score * 30) +
790
+ (property_score * 25) +
791
+ (invariant_score * 25) +
792
+ (collector_pass_rate * 20)
793
+ ))
794
+ weighted_score=$((weighted_score / 100))
795
+
796
+ # Cap at 100
797
+ [[ "$weighted_score" -gt 100 ]] && weighted_score=100
798
+
799
+ echo "$weighted_score"
800
+ return 0
801
+ }
802
+
480
803
  # ═════════════════════════════════════════════════════════════════════════════
481
804
  # COMMANDS
482
805
  # ═════════════════════════════════════════════════════════════════════════════
@@ -514,6 +837,9 @@ cmd_capture() {
514
837
  database) collect_database "$cname" "$collector" ;;
515
838
  webhook) collect_webhook "$cname" "$collector" ;;
516
839
  custom) collect_custom "$cname" "$collector" ;;
840
+ mutation) collect_mutation "$cname" "$collector" ;;
841
+ property) collect_property "$cname" "$collector" ;;
842
+ invariant) collect_invariant "$cname" "$collector" ;;
517
843
  *) warn "Unknown collector type: ${ctype} (skipping ${cname})" ; continue ;;
518
844
  esac
519
845
 
@@ -675,26 +1001,40 @@ cmd_status() {
675
1001
  cmd_list_types() {
676
1002
  echo "Supported evidence types:"
677
1003
  echo ""
678
- echo " browser HTTP page load — verifies UI renders correctly"
679
- echo " api REST/GraphQL endpoint — verifies response status, body, content-type"
680
- echo " database Schema/migration check — verifies DB integrity via command"
681
- echo " cli Command execution — verifies exit code and output"
682
- echo " webhook Callback verification — verifies webhook endpoint responds"
683
- echo " custom User-defined script — any verification logic"
1004
+ echo " browser HTTP page load — verifies UI renders correctly"
1005
+ echo " api REST/GraphQL endpoint — verifies response status, body, content-type"
1006
+ echo " database Schema/migration check — verifies DB integrity via command"
1007
+ echo " cli Command execution — verifies exit code and output"
1008
+ echo " webhook Callback verification — verifies webhook endpoint responds"
1009
+ echo " custom User-defined script — any verification logic"
1010
+ echo " mutation Mutation testing — verifies test suite catches code mutations"
1011
+ echo " property Property-based testing — verifies properties hold over iterations"
1012
+ echo " invariant Invariant checking — verifies system invariants hold"
684
1013
  echo ""
685
1014
  echo "Configure collectors in config/policy.json under the 'evidence' section."
686
1015
  }
687
1016
 
1017
+ cmd_quality_score() {
1018
+ ensure_evidence_dir
1019
+ local score
1020
+ score=$(evidence_quality_score)
1021
+ echo "Evidence-based quality score: ${score}/100"
1022
+ emit_event "evidence.quality_score" "score=${score}"
1023
+ return 0
1024
+ }
1025
+
688
1026
  show_help() {
689
1027
  cat << 'EOF'
690
1028
  Usage: shipwright evidence <command> [args]
691
1029
 
692
1030
  Commands:
693
- capture [type] Capture evidence (optionally filter by type)
694
- verify Verify evidence manifest and freshness
695
- pre-pr [type] Capture + verify (run before PR creation)
696
- status Show current evidence state grouped by type
697
- types List supported evidence types
1031
+ capture [type] Capture evidence (optionally filter by type)
1032
+ verify Verify evidence manifest and freshness
1033
+ pre-pr [type] Capture + verify (run before PR creation)
1034
+ status Show current evidence state grouped by type
1035
+ types List supported evidence types
1036
+ quality-score Compute quality score from evidence
1037
+ artifact <name> <path> Capture artifact with SHA-256 manifest
698
1038
 
699
1039
  Evidence Types:
700
1040
  browser HTTP page load verification
@@ -703,11 +1043,17 @@ Evidence Types:
703
1043
  cli Command execution and exit code
704
1044
  webhook Callback endpoint verification
705
1045
  custom User-defined verification scripts
1046
+ mutation Mutation testing (verify test suite catches mutations)
1047
+ property Property-based testing (verify properties hold)
1048
+ invariant Invariant checking (verify system invariants)
706
1049
 
707
1050
  Evidence collectors are defined in config/policy.json under the
708
1051
  'evidence.collectors' array. Each collector specifies a type,
709
1052
  target, and assertions.
710
1053
 
1054
+ Machine-verifiable proof: mutation testing, property-based testing,
1055
+ invariant checking, artifact capture with SHA-256 manifests.
1056
+
711
1057
  Part of the Code Factory pattern for machine-verifiable proof.
712
1058
  EOF
713
1059
  }
@@ -732,6 +1078,12 @@ main() {
732
1078
  types)
733
1079
  cmd_list_types
734
1080
  ;;
1081
+ quality-score)
1082
+ cmd_quality_score
1083
+ ;;
1084
+ artifact)
1085
+ evidence_capture_artifact "$@"
1086
+ ;;
735
1087
  help|--help|-h)
736
1088
  show_help
737
1089
  ;;