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
@@ -3,10 +3,27 @@
3
3
  [[ -n "${_DAEMON_STATE_LOADED:-}" ]] && return 0
4
4
  _DAEMON_STATE_LOADED=1
5
5
 
6
+ # Defaults for variables normally set by sw-daemon.sh (safe under set -u).
7
+ DAEMON_DIR="${DAEMON_DIR:-${HOME}/.shipwright}"
8
+ STATE_FILE="${STATE_FILE:-${DAEMON_DIR}/daemon-state.json}"
9
+ LOG_FILE="${LOG_FILE:-${DAEMON_DIR}/daemon.log}"
10
+ LOG_DIR="${LOG_DIR:-${DAEMON_DIR}/logs}"
11
+ PAUSE_FLAG="${PAUSE_FLAG:-${DAEMON_DIR}/daemon-pause.flag}"
12
+ DB_FILE="${DB_FILE:-${DAEMON_DIR}/shipwright.db}"
13
+ BASE_BRANCH="${BASE_BRANCH:-main}"
14
+ MAX_PARALLEL="${MAX_PARALLEL:-4}"
15
+ POLL_INTERVAL="${POLL_INTERVAL:-60}"
16
+ WATCH_LABEL="${WATCH_LABEL:-shipwright}"
17
+ WATCH_MODE="${WATCH_MODE:-repo}"
18
+ DASHBOARD_URL="${DASHBOARD_URL:-}"
19
+ SLACK_WEBHOOK="${SLACK_WEBHOOK:-}"
20
+
6
21
  # SQLite persistence (DB as primary read path)
7
22
  _DAEMON_STATE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
8
23
  [[ -f "${_DAEMON_STATE_DIR}/../sw-db.sh" ]] && source "${_DAEMON_STATE_DIR}/../sw-db.sh"
9
24
 
25
+ DAEMON_LOG_WRITE_COUNT=0
26
+
10
27
  daemon_log() {
11
28
  local level="$1"
12
29
  shift
@@ -133,47 +150,37 @@ daemon_preflight_auth_check() {
133
150
  pause_json=$(jq -n --arg reason "gh_auth_failure" --arg ts "$(now_iso)" \
134
151
  '{reason: $reason, timestamp: $ts}')
135
152
  local _tmp_pause
136
- _tmp_pause=$(mktemp "${TMPDIR:-/tmp}/sw-pause.XXXXXX")
153
+ _tmp_pause=$(mktemp "${TMPDIR:-/tmp}/sw-pause.XXXXXX") || { daemon_log ERROR "mktemp failed for pause flag"; return 1; }
137
154
  echo "$pause_json" > "$_tmp_pause"
138
- mv "$_tmp_pause" "$PAUSE_FLAG"
155
+ mv "$_tmp_pause" "$PAUSE_FLAG" || rm -f "$_tmp_pause"
139
156
  emit_event "daemon.auto_pause" "reason=gh_auth_failure"
140
157
  return 1
141
158
  fi
142
159
  fi
143
160
 
144
- # claude auth check with 15s timeout (macOS has no timeout command)
161
+ # claude auth check verify CLI is available and responsive
162
+ # Note: `claude --print` hangs in non-interactive environments (tmux, background).
163
+ # Use `claude --version` (fast, non-interactive) to verify the binary works.
164
+ # Actual API auth is validated when pipelines run `claude` with real prompts.
145
165
  local claude_auth_ok=false
146
- local _auth_tmp
147
- _auth_tmp=$(mktemp "${TMPDIR:-/tmp}/sw-auth.XXXXXX")
148
- ( claude --print -p "ok" --max-turns 1 > "$_auth_tmp" 2>/dev/null ) &
149
- local _auth_pid=$!
150
- local _auth_waited=0
151
- while kill -0 "$_auth_pid" 2>/dev/null && [[ "$_auth_waited" -lt 15 ]]; do
152
- sleep 1
153
- _auth_waited=$((_auth_waited + 1))
154
- done
155
- if kill -0 "$_auth_pid" 2>/dev/null; then
156
- kill "$_auth_pid" 2>/dev/null || true
157
- wait "$_auth_pid" 2>/dev/null || true
158
- else
159
- wait "$_auth_pid" 2>/dev/null || true
160
- fi
161
-
162
- if [[ -s "$_auth_tmp" ]]; then
163
- claude_auth_ok=true
166
+ if command -v claude >/dev/null 2>&1; then
167
+ local _ver
168
+ _ver=$(unset CLAUDECODE; claude --version 2>/dev/null || true)
169
+ if [[ -n "$_ver" ]]; then
170
+ claude_auth_ok=true
171
+ fi
164
172
  fi
165
- rm -f "$_auth_tmp"
166
173
 
167
174
  if [[ "$claude_auth_ok" != "true" ]]; then
168
- daemon_log ERROR "Claude auth check failed — auto-pausing daemon"
175
+ daemon_log ERROR "Claude CLI not found or not working — auto-pausing daemon"
169
176
  local pause_json
170
- pause_json=$(jq -n --arg reason "claude_auth_failure" --arg ts "$(now_iso)" \
177
+ pause_json=$(jq -n --arg reason "claude_cli_missing" --arg ts "$(now_iso)" \
171
178
  '{reason: $reason, timestamp: $ts}')
172
179
  local _tmp_pause
173
- _tmp_pause=$(mktemp "${TMPDIR:-/tmp}/sw-pause.XXXXXX")
180
+ _tmp_pause=$(mktemp "${TMPDIR:-/tmp}/sw-pause.XXXXXX") || { daemon_log ERROR "mktemp failed for pause flag"; return 1; }
174
181
  echo "$pause_json" > "$_tmp_pause"
175
- mv "$_tmp_pause" "$PAUSE_FLAG"
176
- emit_event "daemon.auto_pause" "reason=claude_auth_failure"
182
+ mv "$_tmp_pause" "$PAUSE_FLAG" || rm -f "$_tmp_pause"
183
+ emit_event "daemon.auto_pause" "reason=claude_cli_missing"
177
184
  return 1
178
185
  fi
179
186
 
@@ -458,6 +465,13 @@ get_active_count() {
458
465
  echo 0
459
466
  return
460
467
  fi
468
+ # Validate state file JSON before parsing (mid-flight corruption check)
469
+ if ! jq empty "$STATE_FILE" 2>/dev/null; then
470
+ daemon_log WARN "State file corrupted mid-flight — backing up and resetting"
471
+ cp "$STATE_FILE" "${STATE_FILE}.corrupted.$(date +%s)" 2>/dev/null || true
472
+ init_state
473
+ return
474
+ fi
461
475
  jq -r '.active_jobs | length' "$STATE_FILE" 2>/dev/null || echo 0
462
476
  }
463
477
 
@@ -531,10 +545,11 @@ dequeue_next() {
531
545
 
532
546
  is_priority_issue() {
533
547
  local labels_csv="$1"
534
- local IFS=','
535
- local lane_labels
536
- read -ra lane_labels <<< "$PRIORITY_LANE_LABELS"
537
- for lane_label in "${lane_labels[@]}"; do
548
+ # Bash 3.2 compatible: iterate over comma-separated labels directly
549
+ local _old_ifs="$IFS"
550
+ IFS=','
551
+ for lane_label in $PRIORITY_LANE_LABELS; do
552
+ IFS="$_old_ifs"
538
553
  # Trim whitespace
539
554
  lane_label="${lane_label## }"
540
555
  lane_label="${lane_label%% }"
@@ -542,6 +557,7 @@ is_priority_issue() {
542
557
  return 0
543
558
  fi
544
559
  done
560
+ IFS="$_old_ifs"
545
561
  return 1
546
562
  }
547
563
 
@@ -592,47 +608,84 @@ claim_issue() {
592
608
 
593
609
  [[ "$NO_GITHUB" == "true" ]] && return 0 # No claiming in no-github mode
594
610
 
595
- # Try dashboard-coordinated claim first (atomic label-based)
596
- local resp
597
- resp=$(curl -s --max-time 5 -X POST "${DASHBOARD_URL}/api/claim" \
598
- -H "Content-Type: application/json" \
599
- -d "$(jq -n --argjson issue "$issue_num" --arg machine "$machine_name" \
600
- '{issue: $issue, machine: $machine}')" 2>/dev/null || echo "")
611
+ # Serialize claiming on this machine with flock to prevent local race conditions
612
+ local claim_lock_file="${STATE_DIR:-$HOME/.shipwright}/.claim-${issue_num}.lock"
613
+ mkdir -p "$(dirname "$claim_lock_file")"
614
+ local claim_result=1
615
+ (
616
+ if command -v flock >/dev/null 2>&1; then
617
+ flock -w 10 200 2>/dev/null || {
618
+ daemon_log WARN "claim_issue #${issue_num}: local lock timeout"
619
+ exit 1
620
+ }
621
+ fi
622
+
623
+ # Try dashboard-coordinated claim first (atomic label-based)
624
+ local resp
625
+ resp=$(curl -s --max-time 5 -X POST "${DASHBOARD_URL}/api/claim" \
626
+ -H "Content-Type: application/json" \
627
+ -d "$(jq -n --argjson issue "$issue_num" --arg machine "$machine_name" \
628
+ '{issue: $issue, machine: $machine}')" 2>/dev/null || echo "")
629
+
630
+ if [[ -n "$resp" ]] && echo "$resp" | jq -e '.approved == true' >/dev/null 2>&1; then
631
+ # VERIFY: re-read labels after random backoff to detect races
632
+ local backoff_ms=$(( (RANDOM % 500) + 200 ))
633
+ sleep "0.${backoff_ms}"
634
+ if ! _verify_claim_exclusive "$issue_num" "$machine_name"; then
635
+ daemon_log INFO "Issue #${issue_num} claim race lost (competing claim) — removing our label"
636
+ gh issue edit "$issue_num" --remove-label "claimed:${machine_name}" 2>/dev/null || true
637
+ exit 1
638
+ fi
639
+ exit 0
640
+ elif [[ -n "$resp" ]] && echo "$resp" | jq -e '.approved == false' >/dev/null 2>&1; then
641
+ local claimed_by
642
+ claimed_by=$(echo "$resp" | jq -r '.claimed_by // "another machine"')
643
+ daemon_log INFO "Issue #${issue_num} claimed by ${claimed_by} (via dashboard)"
644
+ exit 1
645
+ fi
646
+
647
+ # Fallback: direct GitHub label check (dashboard unreachable)
648
+ daemon_log WARN "Dashboard unreachable — falling back to direct GitHub label claim"
649
+ local existing_claim
650
+ existing_claim=$(gh issue view "$issue_num" --json labels --jq \
651
+ '[.labels[].name | select(startswith("claimed:"))] | .[0] // ""' 2>/dev/null || true)
652
+
653
+ if [[ -n "$existing_claim" ]]; then
654
+ daemon_log INFO "Issue #${issue_num} already claimed: ${existing_claim}"
655
+ exit 1
656
+ fi
657
+
658
+ gh issue edit "$issue_num" --add-label "claimed:${machine_name}" 2>/dev/null || exit 1
659
+
660
+ # Random backoff before verification to desynchronize competing daemons
661
+ local backoff_ms=$(( (RANDOM % 800) + 300 ))
662
+ sleep "0.${backoff_ms}"
601
663
 
602
- if [[ -n "$resp" ]] && echo "$resp" | jq -e '.approved == true' >/dev/null 2>&1; then
603
664
  # VERIFY: re-read labels, ensure only our claim exists
604
665
  if ! _verify_claim_exclusive "$issue_num" "$machine_name"; then
605
666
  daemon_log INFO "Issue #${issue_num} claim race lost (competing claim) — removing our label"
606
667
  gh issue edit "$issue_num" --remove-label "claimed:${machine_name}" 2>/dev/null || true
607
- return 1
668
+ # Retry once after full backoff (break tie between two losers)
669
+ sleep "1.$(( RANDOM % 500 ))"
670
+ local retry_existing
671
+ retry_existing=$(gh issue view "$issue_num" --json labels --jq \
672
+ '[.labels[].name | select(startswith("claimed:"))] | length' 2>/dev/null || echo "1")
673
+ if [[ "$retry_existing" == "0" ]]; then
674
+ daemon_log INFO "Issue #${issue_num} unclaimed after race — retrying claim"
675
+ gh issue edit "$issue_num" --add-label "claimed:${machine_name}" 2>/dev/null || exit 1
676
+ sleep "0.$(( (RANDOM % 500) + 300 ))"
677
+ if _verify_claim_exclusive "$issue_num" "$machine_name"; then
678
+ exit 0
679
+ fi
680
+ gh issue edit "$issue_num" --remove-label "claimed:${machine_name}" 2>/dev/null || true
681
+ fi
682
+ exit 1
608
683
  fi
609
- return 0
610
- elif [[ -n "$resp" ]] && echo "$resp" | jq -e '.approved == false' >/dev/null 2>&1; then
611
- local claimed_by
612
- claimed_by=$(echo "$resp" | jq -r '.claimed_by // "another machine"')
613
- daemon_log INFO "Issue #${issue_num} claimed by ${claimed_by} (via dashboard)"
614
- return 1
615
- fi
616
-
617
- # Fallback: direct GitHub label check (dashboard unreachable)
618
- daemon_log WARN "Dashboard unreachable — falling back to direct GitHub label claim"
619
- local existing_claim
620
- existing_claim=$(gh issue view "$issue_num" --json labels --jq \
621
- '[.labels[].name | select(startswith("claimed:"))] | .[0] // ""' 2>/dev/null || true)
622
-
623
- if [[ -n "$existing_claim" ]]; then
624
- daemon_log INFO "Issue #${issue_num} already claimed: ${existing_claim}"
625
- return 1
626
- fi
627
-
628
- gh issue edit "$issue_num" --add-label "claimed:${machine_name}" 2>/dev/null || return 1
629
- # VERIFY: re-read labels, ensure only our claim exists
630
- if ! _verify_claim_exclusive "$issue_num" "$machine_name"; then
631
- daemon_log INFO "Issue #${issue_num} claim race lost (competing claim) — removing our label"
632
- gh issue edit "$issue_num" --remove-label "claimed:${machine_name}" 2>/dev/null || true
633
- return 1
634
- fi
635
- return 0
684
+ exit 0
685
+ ) 200>"$claim_lock_file"
686
+ claim_result=$?
687
+ rm -f "$claim_lock_file" 2>/dev/null || true
688
+ return $claim_result
636
689
  }
637
690
 
638
691
  release_claim() {
@@ -3,6 +3,12 @@
3
3
  [[ -n "${_DAEMON_TRIAGE_LOADED:-}" ]] && return 0
4
4
  _DAEMON_TRIAGE_LOADED=1
5
5
 
6
+ # Defaults for variables normally set by sw-daemon.sh (safe under set -u).
7
+ SCRIPT_DIR="${SCRIPT_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}"
8
+ REPO_DIR="${REPO_DIR:-$(cd "$SCRIPT_DIR/.." && pwd)}"
9
+ PIPELINE_TEMPLATE="${PIPELINE_TEMPLATE:-autonomous}"
10
+ NO_GITHUB="${NO_GITHUB:-false}"
11
+
6
12
  # Extract dependency issue numbers from issue text
7
13
  extract_issue_dependencies() {
8
14
  local text="$1"
@@ -35,6 +41,9 @@ triage_score_issue() {
35
41
  # Store analysis for downstream use (composer, predictions)
36
42
  export INTELLIGENCE_ANALYSIS="$analysis"
37
43
  export INTELLIGENCE_COMPLEXITY="$ai_complexity"
44
+ local ai_issue_type
45
+ ai_issue_type=$(echo "$analysis" | jq -r '.issue_type // "backend"' 2>/dev/null || echo "backend")
46
+ export INTELLIGENCE_ISSUE_TYPE="$ai_issue_type"
38
47
 
39
48
  # Convert AI analysis to triage score:
40
49
  # Higher success probability + lower complexity = higher score (process sooner)
@@ -55,6 +64,7 @@ triage_score_issue() {
55
64
  "complexity=$ai_complexity" \
56
65
  "risk=$ai_risk" \
57
66
  "success_prob=$ai_success_prob" \
67
+ "issue_type=$ai_issue_type" \
58
68
  "score=$ai_score"
59
69
 
60
70
  echo "$ai_score"