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
@@ -6,7 +6,7 @@
6
6
  set -euo pipefail
7
7
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
8
8
 
9
- VERSION="3.1.0"
9
+ VERSION="3.3.0"
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
12
12
 
@@ -29,7 +29,7 @@ fi
29
29
  if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
30
30
  emit_event() {
31
31
  local event_type="$1"; shift; mkdir -p "${HOME}/.shipwright"
32
- local payload="{\"ts\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"type\":\"$event_type\""
32
+ local payload; payload="{\"ts\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"type\":\"$event_type\""
33
33
  while [[ $# -gt 0 ]]; do local key="${1%%=*}" val="${1#*=}"; payload="${payload},\"${key}\":\"${val}\""; shift; done
34
34
  echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
35
35
  }
@@ -61,17 +61,22 @@ cmd_metrics() {
61
61
  # Status breakdown
62
62
  local status_success=0
63
63
  local status_failed=0
64
+ # shellcheck disable=SC2034
64
65
  local status_running=0
65
66
 
66
67
  # Template counts
68
+ # shellcheck disable=SC2034
67
69
  declare -a templates
70
+ # shellcheck disable=SC2034
68
71
  declare -a template_counts
69
72
 
70
73
  # Stage timing
74
+ # shellcheck disable=SC2034
71
75
  declare -a stages
72
76
  declare -a stage_durations
73
77
 
74
78
  # Model costs
79
+ # shellcheck disable=SC2034
75
80
  declare -a models
76
81
  declare -a model_costs
77
82
 
@@ -216,11 +221,13 @@ EOF
216
221
  # ─── OpenTelemetry Traces ────────────────────────────────────────────────────
217
222
 
218
223
  cmd_trace() {
224
+ # shellcheck disable=SC2034
219
225
  local pipeline_id="${1:-latest}"
220
226
 
221
227
  ensure_otel_dir
222
228
 
223
229
  # Build trace from events
230
+ # shellcheck disable=SC2034
224
231
  local traces='[]'
225
232
  local spans='[]'
226
233
  local root_span=""
@@ -262,7 +269,7 @@ EOF
262
269
  ;;
263
270
  stage_start)
264
271
  stage=$(echo "$line" | jq -r '.stage // "unknown"' 2>/dev/null || true)
265
- local span_id="${stage:0:8}$(printf '%08x' $((RANDOM * 256 + RANDOM)))"
272
+ local span_id; span_id="${stage:0:8}$(printf '%08x' $((RANDOM * 256 + RANDOM)))"
266
273
  spans=$(echo "$spans" | jq --arg span_id "$span_id" --arg stage "$stage" --arg ts "$ts" \
267
274
  '. += [{
268
275
  "traceId": "'${pipeline:0:16}'",
@@ -316,6 +323,7 @@ cmd_export() {
316
323
  local auth_header=""
317
324
 
318
325
  if [[ -n "${OTEL_EXPORTER_OTLP_HEADERS:-}" ]]; then
326
+ # shellcheck disable=SC2089
319
327
  auth_header="-H '${OTEL_EXPORTER_OTLP_HEADERS}'"
320
328
  fi
321
329
 
@@ -325,6 +333,7 @@ cmd_export() {
325
333
  if [[ "$format" == "trace" ]]; then
326
334
  payload=$(cmd_trace)
327
335
  local response
336
+ # shellcheck disable=SC2090
328
337
  response=$(curl -s --connect-timeout 10 --max-time 30 -X POST \
329
338
  "$endpoint/v1/traces" \
330
339
  -H "Content-Type: application/json" \
@@ -340,6 +349,7 @@ cmd_export() {
340
349
  else
341
350
  payload=$(cmd_metrics text)
342
351
  local response
352
+ # shellcheck disable=SC2090
343
353
  response=$(curl -s --connect-timeout 10 --max-time 30 -X POST \
344
354
  "$endpoint/metrics" \
345
355
  -H "Content-Type: text/plain" \
@@ -462,7 +472,8 @@ cmd_report() {
462
472
  local last_event_ts=""
463
473
 
464
474
  if [[ -f "$EVENTS_FILE" ]]; then
465
- event_count=$(wc -l < "$EVENTS_FILE" || echo "0")
475
+ event_count=$(wc -l < "$EVENTS_FILE" || true)
476
+ event_count="${event_count:-0}"
466
477
  export_count=$(grep -c '"type":"otel_export"' "$EVENTS_FILE" 2>/dev/null || true)
467
478
  export_count="${export_count:-0}"
468
479
  webhook_count=$(grep -c '"type":"webhook_sent"' "$EVENTS_FILE" 2>/dev/null || true)
@@ -7,8 +7,10 @@
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)"
13
+ # shellcheck disable=SC2034
12
14
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
13
15
 
14
16
  # ─── Cross-platform compatibility ──────────────────────────────────────────
@@ -29,17 +31,20 @@ fi
29
31
  if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
30
32
  emit_event() {
31
33
  local event_type="$1"; shift; mkdir -p "${HOME}/.shipwright"
32
- local payload="{\"ts\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"type\":\"$event_type\""
34
+ local payload
35
+ payload="{\"ts\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"type\":\"$event_type\""
33
36
  while [[ $# -gt 0 ]]; do local key="${1%%=*}" val="${1#*=}"; payload="${payload},\"${key}\":\"${val}\""; shift; done
34
37
  echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
35
38
  }
36
39
  fi
37
40
  # ─── Structured Event Log ────────────────────────────────────────────────
41
+ # shellcheck disable=SC2034
38
42
  EVENTS_FILE="${HOME}/.shipwright/events.jsonl"
39
43
 
40
44
  # ─── State & Configuration ────────────────────────────────────────────────
41
45
  OVERSIGHT_ROOT="${HOME}/.shipwright/oversight"
42
46
  BOARD_CONFIG="${OVERSIGHT_ROOT}/config.json"
47
+ # shellcheck disable=SC2034
43
48
  REVIEW_LOG="${OVERSIGHT_ROOT}/reviews.jsonl"
44
49
  HISTORY_DIR="${OVERSIGHT_ROOT}/history"
45
50
  MEMBERS_FILE="${OVERSIGHT_ROOT}/members.json"
@@ -206,6 +211,7 @@ cmd_vote() {
206
211
 
207
212
  # Update review with vote
208
213
  local tmp_file="${review_file}.tmp"
214
+ # shellcheck disable=SC2046
209
215
  jq --arg reviewer "$reviewer" \
210
216
  --arg decision "$decision" \
211
217
  --arg reasoning "${reasoning//\"/\\\"}" \
@@ -214,7 +220,7 @@ cmd_vote() {
214
220
  "decision": $decision,
215
221
  "reasoning": $reasoning,
216
222
  "confidence": ($confidence | tonumber),
217
- "voted_at": "'$(now_iso)'"
223
+ "voted_at": "'"$(now_iso)"'"
218
224
  }' "$review_file" > "$tmp_file"
219
225
  mv "$tmp_file" "$review_file"
220
226
 
@@ -278,9 +284,11 @@ _update_verdict() {
278
284
  approve_ratio=$(echo "$approve_count / $active_votes" | bc -l 2>/dev/null || echo "0")
279
285
 
280
286
  local quorum_num
287
+ # shellcheck disable=SC2034
281
288
  quorum_num=$(echo "$quorum * 100" | bc 2>/dev/null || echo "50")
282
289
 
283
290
  local approve_pct
291
+ # shellcheck disable=SC2034
284
292
  approve_pct=$(echo "$approve_ratio * 100" | bc 2>/dev/null || echo "0")
285
293
 
286
294
  # Check if quorum met and decision reached
@@ -375,7 +383,7 @@ cmd_gate() {
375
383
  "decision": $decision,
376
384
  "reasoning": $reasoning,
377
385
  "confidence": ($confidence | tonumber),
378
- "voted_at": "'$(now_iso)'"
386
+ "voted_at": "'"$(now_iso)"'"
379
387
  }' "$review_file" > "$tmp_file"
380
388
  mv "$tmp_file" "$review_file"
381
389
 
@@ -626,7 +634,8 @@ cmd_appeal() {
626
634
  fi
627
635
 
628
636
  local tmp_file="${review_file}.tmp"
629
- jq --arg message "$message" '.appeals += [{"message": $message, "appealed_at": "'$(now_iso)'"}]' "$review_file" > "$tmp_file"
637
+ # shellcheck disable=SC2046
638
+ jq --arg message "$message" '.appeals += [{"message": $message, "appealed_at": "'"$(now_iso)"'"}]' "$review_file" > "$tmp_file"
630
639
  mv "$tmp_file" "$review_file"
631
640
 
632
641
  success "Appeal submitted ($((appeal_count + 1))/$max_appeals)"
@@ -22,6 +22,7 @@ fi
22
22
  if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
23
23
  emit_event() {
24
24
  local event_type="$1"; shift; mkdir -p "${HOME}/.shipwright"
25
+ # shellcheck disable=SC2155
25
26
  local payload="{\"ts\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"type\":\"$event_type\""
26
27
  while [[ $# -gt 0 ]]; do local key="${1%%=*}" val="${1#*=}"; payload="${payload},\"${key}\":\"${val}\""; shift; done
27
28
  echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
@@ -174,6 +175,7 @@ _Auto-generated by Shipwright meta-patrol_"
174
175
  # ─── Check 3: VERSION Sync ─────────────────────────────────────────────────
175
176
  patrol_meta_version_sync() {
176
177
  info " Checking VERSION consistency..."
178
+ # shellcheck disable=SC2034
177
179
  local versions=""
178
180
  local mismatches=""
179
181
  local reference_version=""
@@ -297,6 +299,7 @@ patrol_meta_template_effectiveness() {
297
299
  local best_template best_rate worst_template worst_rate
298
300
  best_template=$(echo "$template_stats" | jq -r '.[0].template' 2>/dev/null || echo "unknown")
299
301
  best_rate=$(echo "$template_stats" | jq -r '.[0].rate' 2>/dev/null || echo "0")
302
+ # shellcheck disable=SC2034
300
303
  worst_template=$(echo "$template_stats" | jq -r '.[-1].template' 2>/dev/null || echo "unknown")
301
304
  worst_rate=$(echo "$template_stats" | jq -r '.[-1].rate' 2>/dev/null || echo "0")
302
305
 
@@ -426,6 +429,337 @@ _Auto-generated by Shipwright meta-patrol_"
426
429
  fi
427
430
  }
428
431
 
432
+ # ─── Score Impact: Estimate DORA Metric Impact ────────────────────────────
433
+ patrol_meta_score_impact() {
434
+ local title="$1"
435
+ local category="${2:-general}"
436
+
437
+ # Assign impact scores (0-100) based on category and title keywords
438
+ local score=50 # Default
439
+
440
+ case "$category" in
441
+ bash-compat)
442
+ # Bash compatibility affects reliability
443
+ score=65
444
+ ;;
445
+ version-sync)
446
+ # Version inconsistencies affect deployment
447
+ score=55
448
+ ;;
449
+ untested-scripts)
450
+ # Test coverage directly affects change failure rate
451
+ score=75
452
+ ;;
453
+ dora-regression)
454
+ # DORA regressions are critical
455
+ score=85
456
+ ;;
457
+ template-effectiveness)
458
+ # Template improvements affect lead time
459
+ score=70
460
+ ;;
461
+ memory-pruning)
462
+ # Memory cleanup affects reliability
463
+ score=45
464
+ ;;
465
+ recurring-failures)
466
+ # Recurring failures directly affect MTTR and CFR
467
+ score=80
468
+ ;;
469
+ esac
470
+
471
+ # Adjust based on title keywords
472
+ if echo "$title" | grep -qi "critical\|regression\|failure"; then
473
+ score=$((score + 15))
474
+ fi
475
+ if echo "$title" | grep -qi "lead.*time\|deployment"; then
476
+ score=$((score + 10))
477
+ fi
478
+
479
+ # Cap at 100
480
+ [[ $score -gt 100 ]] && score=100
481
+
482
+ echo "$score"
483
+ }
484
+
485
+ # ─── Execute Fix: Trigger Pipeline for Improvement Issue ──────────────────
486
+ patrol_meta_execute_fix() {
487
+ local issue_number="$1"
488
+ local title="$2"
489
+
490
+ if [[ "${NO_GITHUB:-false}" == "true" ]]; then
491
+ info " [dry-run] Would start pipeline for issue #${issue_number}"
492
+ return 0
493
+ fi
494
+
495
+ if ! command -v shipwright >/dev/null 2>&1 && ! command -v sw >/dev/null 2>&1; then
496
+ warn " shipwright CLI not available, skipping pipeline execution"
497
+ return 1
498
+ fi
499
+
500
+ info " Starting pipeline for issue #${issue_number}: ${title}"
501
+
502
+ # Run pipeline in isolated worktree to avoid conflicts
503
+ if shipwright pipeline start --issue "$issue_number" --worktree 2>/dev/null; then
504
+ emit_event "patrol.meta_pipeline_started" "issue=$issue_number" "title=$title"
505
+ return 0
506
+ else
507
+ warn " Failed to start pipeline for issue #${issue_number}"
508
+ return 1
509
+ fi
510
+ }
511
+
512
+ # ─── Verify Fix: Re-run Relevant Check After Fix ──────────────────────────
513
+ patrol_meta_verify_fix() {
514
+ local category="$1"
515
+ local issue_number="$2"
516
+
517
+ if [[ "${NO_GITHUB:-false}" == "true" ]]; then
518
+ info " [dry-run] Would verify fix for issue #${issue_number}"
519
+ return 0
520
+ fi
521
+
522
+ info " Verifying fix for issue #${issue_number} (category: ${category})"
523
+
524
+ case "$category" in
525
+ untested-scripts)
526
+ patrol_meta_untested_scripts > /dev/null 2>&1
527
+ ;;
528
+ bash-compat)
529
+ patrol_meta_bash_compat > /dev/null 2>&1
530
+ ;;
531
+ version-sync)
532
+ patrol_meta_version_sync > /dev/null 2>&1
533
+ ;;
534
+ dora-regression)
535
+ patrol_meta_dora_trends > /dev/null 2>&1
536
+ ;;
537
+ template-effectiveness)
538
+ patrol_meta_template_effectiveness > /dev/null 2>&1
539
+ ;;
540
+ memory-pruning)
541
+ patrol_meta_memory_pruning > /dev/null 2>&1
542
+ ;;
543
+ recurring-failures)
544
+ patrol_meta_event_analysis > /dev/null 2>&1
545
+ ;;
546
+ *)
547
+ warn " Unknown category: ${category}"
548
+ return 1
549
+ ;;
550
+ esac
551
+
552
+ emit_event "patrol.meta_verified" "issue=$issue_number" "category=$category"
553
+ return 0
554
+ }
555
+
556
+ # ─── Rollback on Regression: Auto-revert if Metrics Regress ────────────────
557
+ patrol_meta_rollback() {
558
+ local issue_number="$1"
559
+
560
+ if [[ "${NO_GITHUB:-false}" == "true" ]]; then
561
+ info " [dry-run] Would rollback issue #${issue_number}"
562
+ return 0
563
+ fi
564
+
565
+ warn " Metrics regression detected for issue #${issue_number}"
566
+
567
+ # Get the PR associated with this issue
568
+ local pr_number
569
+ pr_number=$(gh issue view "$issue_number" --json "pullRequests" --jq ".pullRequests[0].number" 2>/dev/null || echo "")
570
+
571
+ if [[ -n "$pr_number" ]]; then
572
+ info " Reverting PR #${pr_number} due to metric regression"
573
+
574
+ # Check out the branch, revert the PR commit, force push
575
+ local branch
576
+ branch=$(gh pr view "$pr_number" --json "headRefName" --jq ".headRefName" 2>/dev/null || echo "")
577
+
578
+ if [[ -n "$branch" ]]; then
579
+ (
580
+ git fetch origin "$branch" 2>/dev/null || true
581
+ git checkout "$branch" 2>/dev/null || return 1
582
+ git revert HEAD --no-edit 2>/dev/null || return 1
583
+ git push origin "$branch" --force 2>/dev/null || return 1
584
+ emit_event "patrol.meta_rollback" "issue=$issue_number" "pr=$pr_number"
585
+ ) || {
586
+ warn " Failed to rollback PR #${pr_number}"
587
+ return 1
588
+ }
589
+ fi
590
+ fi
591
+
592
+ return 0
593
+ }
594
+
595
+ # ─── Learn: Record Outcome in Memory System ────────────────────────────────
596
+ patrol_meta_learn() {
597
+ local issue_number="$1"
598
+ local category="$2"
599
+ local success="$3"
600
+ local metrics_change="${4:-}"
601
+
602
+ local memory_dir="$HOME/.shipwright/memory"
603
+ mkdir -p "$memory_dir"
604
+
605
+ local learning_file="$memory_dir/self-improvements.jsonl"
606
+ local ts
607
+ ts=$(now_iso)
608
+
609
+ local outcome="success"
610
+ [[ "$success" != "true" ]] && outcome="failure"
611
+
612
+ # Append to learning log
613
+ local entry
614
+ entry=$(jq -n \
615
+ --arg issue "$issue_number" \
616
+ --arg category "$category" \
617
+ --arg outcome "$outcome" \
618
+ --arg metrics "$metrics_change" \
619
+ --arg ts "$ts" \
620
+ '{issue:$issue, category:$category, outcome:$outcome, metrics_impact:$metrics, timestamp:$ts}')
621
+
622
+ echo "$entry" >> "$learning_file"
623
+ emit_event "patrol.meta_learned" "issue=$issue_number" "category=$category" "outcome=$outcome"
624
+
625
+ success " Recorded learning: issue #${issue_number} ${outcome}"
626
+ }
627
+
628
+ # ─── Batch Improve: Process Multiple Issues in Parallel ─────────────────────
629
+ patrol_meta_batch_improve() {
630
+ local -a issues_to_fix
631
+ local count=0
632
+
633
+ info " Collecting recent meta-patrol issues to fix..."
634
+
635
+ if [[ "${NO_GITHUB:-false}" == "true" ]]; then
636
+ info " [dry-run] Would process batch improvements"
637
+ return 0
638
+ fi
639
+
640
+ # Find recent open meta-improvement issues
641
+ while IFS= read -r line; do
642
+ if [[ -z "$line" ]]; then continue; fi
643
+ local issue_num category title
644
+ issue_num=$(echo "$line" | jq -r '.number' 2>/dev/null || echo "")
645
+ title=$(echo "$line" | jq -r '.title' 2>/dev/null || echo "")
646
+
647
+ if [[ -n "$issue_num" && -n "$title" ]]; then
648
+ # Determine category from title
649
+ if echo "$title" | grep -qi "test"; then
650
+ category="untested-scripts"
651
+ elif echo "$title" | grep -qi "bash"; then
652
+ category="bash-compat"
653
+ elif echo "$title" | grep -qi "version"; then
654
+ category="version-sync"
655
+ elif echo "$title" | grep -qi "lead.*time\|regression"; then
656
+ category="dora-regression"
657
+ elif echo "$title" | grep -qi "template"; then
658
+ category="template-effectiveness"
659
+ elif echo "$title" | grep -qi "memory"; then
660
+ category="memory-pruning"
661
+ else
662
+ category="recurring-failures"
663
+ fi
664
+
665
+ issues_to_fix+=("$issue_num|$category|$title")
666
+ count=$((count + 1))
667
+ fi
668
+ done < <(gh issue list --label "meta-improvement" --state open --json "number,title" --jq ".[]" 2>/dev/null || echo "")
669
+
670
+ if [[ $count -eq 0 ]]; then
671
+ info " No meta-improvement issues to process"
672
+ return 0
673
+ fi
674
+
675
+ info " Found ${count} issue(s) to process, executing in parallel..."
676
+
677
+ # Sort by impact score (descending) - use temp file for Bash 3.2 compat
678
+ local scored_file
679
+ scored_file=$(mktemp)
680
+
681
+ for issue_spec in "${issues_to_fix[@]}"; do
682
+ local issue_num category title
683
+ issue_num="${issue_spec%%|*}"
684
+ category="${issue_spec#*|}"
685
+ category="${category%%|*}"
686
+ title="${issue_spec##*|}"
687
+
688
+ local score
689
+ score=$(patrol_meta_score_impact "$title" "$category")
690
+ echo "$score|$issue_num|$category|$title" >> "$scored_file"
691
+ done
692
+
693
+ # Sort by score (highest first) using temp file (Bash 3.2 compatible)
694
+ local sorted_file
695
+ sorted_file=$(mktemp)
696
+ printf '%s\n' "${scored_issues[@]}" | sort -rn > "$sorted_file"
697
+
698
+ # Process in parallel (limit to 3 concurrent pipelines)
699
+ local active_count=0
700
+ local max_parallel=3
701
+
702
+ while IFS= read -r item; do
703
+ local score issue_num category title
704
+ score="${item%%|*}"
705
+ issue_num="${item#*|}"
706
+ issue_num="${issue_num%%|*}"
707
+ category="${issue_num#*|}"
708
+ category="${category%%|*}"
709
+ title="${item##*|}"
710
+
711
+ # Wait if we have max_parallel active jobs
712
+ while [[ $active_count -ge $max_parallel ]]; do
713
+ # Count running background jobs
714
+ active_count=$(jobs -r 2>/dev/null | wc -l)
715
+ [[ $active_count -ge $max_parallel ]] && sleep 2
716
+ done
717
+
718
+ # Start pipeline in background
719
+ (
720
+ patrol_meta_execute_fix "$issue_num" "$title"
721
+ sleep 2
722
+ patrol_meta_verify_fix "$category" "$issue_num"
723
+ ) &
724
+
725
+ active_count=$((active_count + 1))
726
+ done < "$sorted_file"
727
+
728
+ rm -f "$sorted_file"
729
+
730
+ # Wait for all to complete
731
+ wait
732
+
733
+ success " Batch processing complete"
734
+ }
735
+
736
+ # ─── Main Autonomous Loop: Detect → Score → Fix → Verify → Learn ──────────
737
+ patrol_meta_auto() {
738
+ echo -e "\n ${BOLD}Meta Self-Improvement Autonomous Loop${RESET}"
739
+
740
+ info "Stage 1: Detection"
741
+ patrol_meta_untested_scripts
742
+ patrol_meta_bash_compat
743
+ patrol_meta_version_sync
744
+ patrol_meta_dora_trends
745
+ patrol_meta_template_effectiveness
746
+ patrol_meta_memory_pruning
747
+ patrol_meta_event_analysis
748
+
749
+ echo ""
750
+ info "Stage 2: Impact Scoring & Batch Processing"
751
+ patrol_meta_batch_improve
752
+
753
+ echo ""
754
+ info "Stage 3: Learning from Outcomes"
755
+ # This would be populated by results from batch_improve
756
+ # For now, log that autonomous loop completed
757
+ emit_event "patrol.meta_autonomous_complete" "timestamp=$(now_iso)"
758
+
759
+ success "Autonomous self-improvement loop completed"
760
+ echo ""
761
+ }
762
+
429
763
  # ─── Main Entry Point ──────────────────────────────────────────────────────
430
764
  patrol_meta_run() {
431
765
  echo -e "\n ${BOLD}Meta Self-Improvement Checks${RESET}"
@@ -6,7 +6,8 @@
6
6
  set -euo pipefail
7
7
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
8
8
 
9
- VERSION="3.1.0"
9
+ # shellcheck disable=SC2034
10
+ VERSION="3.3.0"
10
11
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
12
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
12
13
 
@@ -17,6 +18,8 @@ REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
17
18
  # Canonical helpers (colors, output, events)
18
19
  # shellcheck source=lib/helpers.sh
19
20
  [[ -f "$SCRIPT_DIR/lib/helpers.sh" ]] && source "$SCRIPT_DIR/lib/helpers.sh"
21
+ # shellcheck source=lib/config.sh
22
+ [[ -f "$SCRIPT_DIR/lib/config.sh" ]] && source "$SCRIPT_DIR/lib/config.sh"
20
23
  # Fallbacks when helpers not loaded (e.g. test env with overridden SCRIPT_DIR)
21
24
  [[ "$(type -t info 2>/dev/null)" == "function" ]] || info() { echo -e "\033[38;2;0;212;255m\033[1m▸\033[0m $*"; }
22
25
  [[ "$(type -t success 2>/dev/null)" == "function" ]] || success() { echo -e "\033[38;2;74;222;128m\033[1m✓\033[0m $*"; }
@@ -29,6 +32,7 @@ fi
29
32
  if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
30
33
  emit_event() {
31
34
  local event_type="$1"; shift; mkdir -p "${HOME}/.shipwright"
35
+ # shellcheck disable=SC2155
32
36
  local payload="{\"ts\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"type\":\"$event_type\""
33
37
  while [[ $# -gt 0 ]]; do local key="${1%%=*}" val="${1#*=}"; payload="${payload},\"${key}\":\"${val}\""; shift; done
34
38
  echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
@@ -114,6 +118,7 @@ composer_create_pipeline() {
114
118
  # Atomic write
115
119
  local tmp_file
116
120
  tmp_file=$(mktemp "${output_file}.XXXXXX")
121
+ # shellcheck disable=SC2064
117
122
  trap "rm -f '$tmp_file'" RETURN
118
123
  echo "$composed" | jq '.' > "$tmp_file"
119
124
  mv "$tmp_file" "$output_file"
@@ -143,6 +148,7 @@ composer_create_pipeline() {
143
148
  info "Using fallback template: standard" >&2
144
149
  local tmp_file
145
150
  tmp_file=$(mktemp "${output_file}.XXXXXX")
151
+ # shellcheck disable=SC2064
146
152
  trap "rm -f '$tmp_file'" RETURN
147
153
  cp "$fallback_template" "$tmp_file"
148
154
  mv "$tmp_file" "$output_file"
@@ -6,7 +6,7 @@
6
6
  set -euo pipefail
7
7
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
8
8
 
9
- VERSION="3.1.0"
9
+ VERSION="3.3.0"
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
12
12
 
@@ -29,6 +29,7 @@ fi
29
29
  if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
30
30
  emit_event() {
31
31
  local event_type="$1"; shift; mkdir -p "${HOME}/.shipwright"
32
+ # shellcheck disable=SC2155
32
33
  local payload="{\"ts\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"type\":\"$event_type\""
33
34
  while [[ $# -gt 0 ]]; do local key="${1%%=*}" val="${1#*=}"; payload="${payload},\"${key}\":\"${val}\""; shift; done
34
35
  echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
@@ -150,7 +151,8 @@ _compute_convergence() {
150
151
  fi
151
152
 
152
153
  local total_errors
153
- total_errors=$(wc -l < "$error_log" 2>/dev/null | tr -d ' ' || echo "0")
154
+ total_errors=$(wc -l < "$error_log" 2>/dev/null | tr -d ' ' || true)
155
+ total_errors="${total_errors:-0}"
154
156
  total_errors=$(_safe_num "$total_errors")
155
157
 
156
158
  if [[ "$total_errors" -eq 0 ]]; then
@@ -270,7 +272,8 @@ _compute_error_maturity() {
270
272
  fi
271
273
 
272
274
  local total_errors
273
- total_errors=$(wc -l < "$error_log" 2>/dev/null | tr -d ' ' || echo "0")
275
+ total_errors=$(wc -l < "$error_log" 2>/dev/null | tr -d ' ' || true)
276
+ total_errors="${total_errors:-0}"
274
277
  total_errors=$(_safe_num "$total_errors")
275
278
 
276
279
  if [[ "$total_errors" -eq 0 ]]; then
@@ -280,7 +283,8 @@ _compute_error_maturity() {
280
283
 
281
284
  # Count unique error signatures
282
285
  local unique_errors
283
- unique_errors=$(jq -r '.signature // "unknown"' "$error_log" 2>/dev/null | sort -u | wc -l | tr -d ' ' || echo "0")
286
+ unique_errors=$(jq -r '.signature // "unknown"' "$error_log" 2>/dev/null | sort -u | wc -l | tr -d ' ' || true)
287
+ unique_errors="${unique_errors:-0}"
284
288
  unique_errors=$(_safe_num "$unique_errors")
285
289
 
286
290
  if [[ "$unique_errors" -eq 0 ]]; then
@@ -507,8 +511,10 @@ pipeline_compute_vitals() {
507
511
  # ── Error counts ──
508
512
  local total_errors=0 unique_errors=0
509
513
  if [[ -f "$error_log" ]]; then
510
- total_errors=$(wc -l < "$error_log" 2>/dev/null | tr -d ' ' || echo "0")
511
- unique_errors=$(jq -r '.signature // "unknown"' "$error_log" 2>/dev/null | sort -u | wc -l | tr -d ' ' || echo "0")
514
+ total_errors=$(wc -l < "$error_log" 2>/dev/null | tr -d ' ' || true)
515
+ total_errors="${total_errors:-0}"
516
+ unique_errors=$(jq -r '.signature // "unknown"' "$error_log" 2>/dev/null | sort -u | wc -l | tr -d ' ' || true)
517
+ unique_errors="${unique_errors:-0}"
512
518
  fi
513
519
 
514
520
  # ── Output JSON ──