shipwright-cli 3.2.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 (279) 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 +4 -4
  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/dashboard/middleware/auth.ts +134 -0
  17. package/dashboard/middleware/constants.ts +21 -0
  18. package/dashboard/public/index.html +2 -6
  19. package/dashboard/public/styles.css +100 -97
  20. package/dashboard/routes/auth.ts +38 -0
  21. package/dashboard/server.ts +66 -25
  22. package/dashboard/services/config.ts +26 -0
  23. package/dashboard/services/db.ts +118 -0
  24. package/dashboard/src/canvas/pixel-agent.ts +298 -0
  25. package/dashboard/src/canvas/pixel-sprites.ts +440 -0
  26. package/dashboard/src/canvas/shipyard-effects.ts +367 -0
  27. package/dashboard/src/canvas/shipyard-scene.ts +616 -0
  28. package/dashboard/src/canvas/submarine-layout.ts +267 -0
  29. package/dashboard/src/components/header.ts +8 -7
  30. package/dashboard/src/core/router.ts +1 -0
  31. package/dashboard/src/design/submarine-theme.ts +253 -0
  32. package/dashboard/src/main.ts +2 -0
  33. package/dashboard/src/types/api.ts +2 -1
  34. package/dashboard/src/views/activity.ts +2 -1
  35. package/dashboard/src/views/shipyard.ts +39 -0
  36. package/dashboard/types/index.ts +166 -0
  37. package/docs/plans/2026-02-28-compound-audit-and-shipyard-design.md +186 -0
  38. package/docs/plans/2026-02-28-skipper-shipwright-implementation-plan.md +1182 -0
  39. package/docs/plans/2026-02-28-skipper-shipwright-integration-design.md +531 -0
  40. package/docs/plans/2026-03-01-ai-powered-skill-injection-design.md +298 -0
  41. package/docs/plans/2026-03-01-ai-powered-skill-injection-plan.md +1109 -0
  42. package/docs/plans/2026-03-01-capabilities-cleanup-plan.md +658 -0
  43. package/docs/plans/2026-03-01-clean-architecture-plan.md +924 -0
  44. package/docs/plans/2026-03-01-compound-audit-cascade-design.md +191 -0
  45. package/docs/plans/2026-03-01-compound-audit-cascade-plan.md +921 -0
  46. package/docs/plans/2026-03-01-deep-integration-plan.md +851 -0
  47. package/docs/plans/2026-03-01-pipeline-audit-trail-design.md +145 -0
  48. package/docs/plans/2026-03-01-pipeline-audit-trail-plan.md +770 -0
  49. package/docs/plans/2026-03-01-refined-depths-brand-design.md +382 -0
  50. package/docs/plans/2026-03-01-refined-depths-implementation.md +599 -0
  51. package/docs/plans/2026-03-01-skipper-kernel-integration-design.md +203 -0
  52. package/docs/plans/2026-03-01-unified-platform-design.md +272 -0
  53. package/docs/plans/2026-03-07-claude-code-feature-integration-design.md +189 -0
  54. package/docs/plans/2026-03-07-claude-code-feature-integration-plan.md +1165 -0
  55. package/docs/research/BACKLOG_QUICK_REFERENCE.md +352 -0
  56. package/docs/research/CUTTING_EDGE_RESEARCH_2026.md +546 -0
  57. package/docs/research/RESEARCH_INDEX.md +439 -0
  58. package/docs/research/RESEARCH_SOURCES.md +440 -0
  59. package/docs/research/RESEARCH_SUMMARY.txt +275 -0
  60. package/docs/superpowers/specs/2026-03-10-pipeline-quality-revolution-design.md +341 -0
  61. package/package.json +2 -2
  62. package/scripts/lib/adaptive-model.sh +427 -0
  63. package/scripts/lib/adaptive-timeout.sh +316 -0
  64. package/scripts/lib/audit-trail.sh +309 -0
  65. package/scripts/lib/auto-recovery.sh +471 -0
  66. package/scripts/lib/bandit-selector.sh +431 -0
  67. package/scripts/lib/bootstrap.sh +104 -2
  68. package/scripts/lib/causal-graph.sh +455 -0
  69. package/scripts/lib/compat.sh +126 -0
  70. package/scripts/lib/compound-audit.sh +337 -0
  71. package/scripts/lib/constitutional.sh +454 -0
  72. package/scripts/lib/context-budget.sh +359 -0
  73. package/scripts/lib/convergence.sh +594 -0
  74. package/scripts/lib/cost-optimizer.sh +634 -0
  75. package/scripts/lib/daemon-adaptive.sh +10 -0
  76. package/scripts/lib/daemon-dispatch.sh +106 -17
  77. package/scripts/lib/daemon-failure.sh +34 -4
  78. package/scripts/lib/daemon-patrol.sh +23 -2
  79. package/scripts/lib/daemon-poll-github.sh +361 -0
  80. package/scripts/lib/daemon-poll-health.sh +299 -0
  81. package/scripts/lib/daemon-poll.sh +27 -611
  82. package/scripts/lib/daemon-state.sh +112 -66
  83. package/scripts/lib/daemon-triage.sh +10 -0
  84. package/scripts/lib/dod-scorecard.sh +442 -0
  85. package/scripts/lib/error-actionability.sh +300 -0
  86. package/scripts/lib/formal-spec.sh +461 -0
  87. package/scripts/lib/helpers.sh +177 -4
  88. package/scripts/lib/intent-analysis.sh +409 -0
  89. package/scripts/lib/loop-convergence.sh +350 -0
  90. package/scripts/lib/loop-iteration.sh +682 -0
  91. package/scripts/lib/loop-progress.sh +48 -0
  92. package/scripts/lib/loop-restart.sh +185 -0
  93. package/scripts/lib/memory-effectiveness.sh +506 -0
  94. package/scripts/lib/mutation-executor.sh +352 -0
  95. package/scripts/lib/outcome-feedback.sh +521 -0
  96. package/scripts/lib/pipeline-cli.sh +336 -0
  97. package/scripts/lib/pipeline-commands.sh +1216 -0
  98. package/scripts/lib/pipeline-detection.sh +100 -2
  99. package/scripts/lib/pipeline-execution.sh +897 -0
  100. package/scripts/lib/pipeline-github.sh +28 -3
  101. package/scripts/lib/pipeline-intelligence-compound.sh +431 -0
  102. package/scripts/lib/pipeline-intelligence-scoring.sh +407 -0
  103. package/scripts/lib/pipeline-intelligence-skip.sh +181 -0
  104. package/scripts/lib/pipeline-intelligence.sh +100 -1136
  105. package/scripts/lib/pipeline-quality-bash-compat.sh +182 -0
  106. package/scripts/lib/pipeline-quality-checks.sh +17 -715
  107. package/scripts/lib/pipeline-quality-gates.sh +563 -0
  108. package/scripts/lib/pipeline-stages-build.sh +730 -0
  109. package/scripts/lib/pipeline-stages-delivery.sh +965 -0
  110. package/scripts/lib/pipeline-stages-intake.sh +1133 -0
  111. package/scripts/lib/pipeline-stages-monitor.sh +407 -0
  112. package/scripts/lib/pipeline-stages-review.sh +1022 -0
  113. package/scripts/lib/pipeline-stages.sh +59 -2929
  114. package/scripts/lib/pipeline-state.sh +36 -5
  115. package/scripts/lib/pipeline-util.sh +487 -0
  116. package/scripts/lib/policy-learner.sh +438 -0
  117. package/scripts/lib/process-reward.sh +493 -0
  118. package/scripts/lib/project-detect.sh +649 -0
  119. package/scripts/lib/quality-profile.sh +334 -0
  120. package/scripts/lib/recruit-commands.sh +885 -0
  121. package/scripts/lib/recruit-learning.sh +739 -0
  122. package/scripts/lib/recruit-roles.sh +648 -0
  123. package/scripts/lib/reward-aggregator.sh +458 -0
  124. package/scripts/lib/rl-optimizer.sh +362 -0
  125. package/scripts/lib/root-cause.sh +427 -0
  126. package/scripts/lib/scope-enforcement.sh +445 -0
  127. package/scripts/lib/session-restart.sh +493 -0
  128. package/scripts/lib/skill-memory.sh +300 -0
  129. package/scripts/lib/skill-registry.sh +775 -0
  130. package/scripts/lib/spec-driven.sh +476 -0
  131. package/scripts/lib/test-helpers.sh +18 -7
  132. package/scripts/lib/test-holdout.sh +429 -0
  133. package/scripts/lib/test-optimizer.sh +511 -0
  134. package/scripts/shipwright-file-suggest.sh +45 -0
  135. package/scripts/skills/adversarial-quality.md +61 -0
  136. package/scripts/skills/api-design.md +44 -0
  137. package/scripts/skills/architecture-design.md +50 -0
  138. package/scripts/skills/brainstorming.md +43 -0
  139. package/scripts/skills/data-pipeline.md +44 -0
  140. package/scripts/skills/deploy-safety.md +64 -0
  141. package/scripts/skills/documentation.md +38 -0
  142. package/scripts/skills/frontend-design.md +45 -0
  143. package/scripts/skills/generated/.gitkeep +0 -0
  144. package/scripts/skills/generated/_refinements/.gitkeep +0 -0
  145. package/scripts/skills/generated/_refinements/adversarial-quality.patch.md +3 -0
  146. package/scripts/skills/generated/_refinements/architecture-design.patch.md +3 -0
  147. package/scripts/skills/generated/_refinements/brainstorming.patch.md +3 -0
  148. package/scripts/skills/generated/cli-version-management.md +29 -0
  149. package/scripts/skills/generated/collection-system-validation.md +99 -0
  150. package/scripts/skills/generated/large-scale-c-refactoring-coordination.md +97 -0
  151. package/scripts/skills/generated/pattern-matching-similarity-scoring.md +195 -0
  152. package/scripts/skills/generated/test-parallelization-detection.md +65 -0
  153. package/scripts/skills/observability.md +79 -0
  154. package/scripts/skills/performance.md +48 -0
  155. package/scripts/skills/pr-quality.md +49 -0
  156. package/scripts/skills/product-thinking.md +43 -0
  157. package/scripts/skills/security-audit.md +49 -0
  158. package/scripts/skills/systematic-debugging.md +40 -0
  159. package/scripts/skills/testing-strategy.md +47 -0
  160. package/scripts/skills/two-stage-review.md +52 -0
  161. package/scripts/skills/validation-thoroughness.md +55 -0
  162. package/scripts/sw +9 -3
  163. package/scripts/sw-activity.sh +9 -2
  164. package/scripts/sw-adaptive.sh +2 -1
  165. package/scripts/sw-adversarial.sh +2 -1
  166. package/scripts/sw-architecture-enforcer.sh +3 -1
  167. package/scripts/sw-auth.sh +12 -2
  168. package/scripts/sw-autonomous.sh +5 -1
  169. package/scripts/sw-changelog.sh +4 -1
  170. package/scripts/sw-checkpoint.sh +2 -1
  171. package/scripts/sw-ci.sh +5 -1
  172. package/scripts/sw-cleanup.sh +4 -26
  173. package/scripts/sw-code-review.sh +10 -4
  174. package/scripts/sw-connect.sh +2 -1
  175. package/scripts/sw-context.sh +2 -1
  176. package/scripts/sw-cost.sh +48 -3
  177. package/scripts/sw-daemon.sh +66 -9
  178. package/scripts/sw-dashboard.sh +3 -1
  179. package/scripts/sw-db.sh +59 -16
  180. package/scripts/sw-decide.sh +8 -2
  181. package/scripts/sw-decompose.sh +360 -17
  182. package/scripts/sw-deps.sh +4 -1
  183. package/scripts/sw-developer-simulation.sh +4 -1
  184. package/scripts/sw-discovery.sh +325 -2
  185. package/scripts/sw-doc-fleet.sh +4 -1
  186. package/scripts/sw-docs-agent.sh +3 -1
  187. package/scripts/sw-docs.sh +2 -1
  188. package/scripts/sw-doctor.sh +453 -2
  189. package/scripts/sw-dora.sh +4 -1
  190. package/scripts/sw-durable.sh +4 -3
  191. package/scripts/sw-e2e-orchestrator.sh +17 -16
  192. package/scripts/sw-eventbus.sh +7 -1
  193. package/scripts/sw-evidence.sh +364 -12
  194. package/scripts/sw-feedback.sh +550 -9
  195. package/scripts/sw-fix.sh +20 -1
  196. package/scripts/sw-fleet-discover.sh +6 -2
  197. package/scripts/sw-fleet-viz.sh +4 -1
  198. package/scripts/sw-fleet.sh +5 -1
  199. package/scripts/sw-github-app.sh +16 -3
  200. package/scripts/sw-github-checks.sh +3 -2
  201. package/scripts/sw-github-deploy.sh +3 -2
  202. package/scripts/sw-github-graphql.sh +18 -7
  203. package/scripts/sw-guild.sh +5 -1
  204. package/scripts/sw-heartbeat.sh +5 -30
  205. package/scripts/sw-hello.sh +67 -0
  206. package/scripts/sw-hygiene.sh +6 -1
  207. package/scripts/sw-incident.sh +265 -1
  208. package/scripts/sw-init.sh +18 -2
  209. package/scripts/sw-instrument.sh +10 -2
  210. package/scripts/sw-intelligence.sh +42 -6
  211. package/scripts/sw-jira.sh +5 -1
  212. package/scripts/sw-launchd.sh +2 -1
  213. package/scripts/sw-linear.sh +4 -1
  214. package/scripts/sw-logs.sh +4 -1
  215. package/scripts/sw-loop.sh +432 -1128
  216. package/scripts/sw-memory.sh +356 -2
  217. package/scripts/sw-mission-control.sh +6 -1
  218. package/scripts/sw-model-router.sh +481 -26
  219. package/scripts/sw-otel.sh +13 -4
  220. package/scripts/sw-oversight.sh +14 -5
  221. package/scripts/sw-patrol-meta.sh +334 -0
  222. package/scripts/sw-pipeline-composer.sh +5 -1
  223. package/scripts/sw-pipeline-vitals.sh +2 -1
  224. package/scripts/sw-pipeline.sh +53 -2664
  225. package/scripts/sw-pm.sh +12 -5
  226. package/scripts/sw-pr-lifecycle.sh +2 -1
  227. package/scripts/sw-predictive.sh +7 -1
  228. package/scripts/sw-prep.sh +185 -2
  229. package/scripts/sw-ps.sh +5 -25
  230. package/scripts/sw-public-dashboard.sh +15 -3
  231. package/scripts/sw-quality.sh +2 -1
  232. package/scripts/sw-reaper.sh +8 -25
  233. package/scripts/sw-recruit.sh +156 -2303
  234. package/scripts/sw-regression.sh +19 -12
  235. package/scripts/sw-release-manager.sh +3 -1
  236. package/scripts/sw-release.sh +4 -1
  237. package/scripts/sw-remote.sh +3 -1
  238. package/scripts/sw-replay.sh +7 -1
  239. package/scripts/sw-retro.sh +158 -1
  240. package/scripts/sw-review-rerun.sh +3 -1
  241. package/scripts/sw-scale.sh +10 -3
  242. package/scripts/sw-security-audit.sh +6 -1
  243. package/scripts/sw-self-optimize.sh +6 -3
  244. package/scripts/sw-session.sh +9 -3
  245. package/scripts/sw-setup.sh +3 -1
  246. package/scripts/sw-stall-detector.sh +406 -0
  247. package/scripts/sw-standup.sh +15 -7
  248. package/scripts/sw-status.sh +3 -1
  249. package/scripts/sw-strategic.sh +4 -1
  250. package/scripts/sw-stream.sh +7 -1
  251. package/scripts/sw-swarm.sh +18 -6
  252. package/scripts/sw-team-stages.sh +13 -6
  253. package/scripts/sw-templates.sh +5 -29
  254. package/scripts/sw-testgen.sh +7 -1
  255. package/scripts/sw-tmux-pipeline.sh +4 -1
  256. package/scripts/sw-tmux-role-color.sh +2 -0
  257. package/scripts/sw-tmux-status.sh +1 -1
  258. package/scripts/sw-tmux.sh +3 -1
  259. package/scripts/sw-trace.sh +3 -1
  260. package/scripts/sw-tracker-github.sh +3 -0
  261. package/scripts/sw-tracker-jira.sh +3 -0
  262. package/scripts/sw-tracker-linear.sh +3 -0
  263. package/scripts/sw-tracker.sh +3 -1
  264. package/scripts/sw-triage.sh +2 -1
  265. package/scripts/sw-upgrade.sh +3 -1
  266. package/scripts/sw-ux.sh +5 -2
  267. package/scripts/sw-webhook.sh +3 -1
  268. package/scripts/sw-widgets.sh +3 -1
  269. package/scripts/sw-worktree.sh +15 -3
  270. package/scripts/test-skill-injection.sh +1233 -0
  271. package/templates/pipelines/autonomous.json +27 -3
  272. package/templates/pipelines/cost-aware.json +34 -8
  273. package/templates/pipelines/deployed.json +12 -0
  274. package/templates/pipelines/enterprise.json +12 -0
  275. package/templates/pipelines/fast.json +6 -0
  276. package/templates/pipelines/full.json +27 -3
  277. package/templates/pipelines/hotfix.json +6 -0
  278. package/templates/pipelines/standard.json +12 -0
  279. 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
 
@@ -538,10 +545,11 @@ dequeue_next() {
538
545
 
539
546
  is_priority_issue() {
540
547
  local labels_csv="$1"
541
- local IFS=','
542
- local lane_labels
543
- read -ra lane_labels <<< "$PRIORITY_LANE_LABELS"
544
- 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"
545
553
  # Trim whitespace
546
554
  lane_label="${lane_label## }"
547
555
  lane_label="${lane_label%% }"
@@ -549,6 +557,7 @@ is_priority_issue() {
549
557
  return 0
550
558
  fi
551
559
  done
560
+ IFS="$_old_ifs"
552
561
  return 1
553
562
  }
554
563
 
@@ -599,47 +608,84 @@ claim_issue() {
599
608
 
600
609
  [[ "$NO_GITHUB" == "true" ]] && return 0 # No claiming in no-github mode
601
610
 
602
- # Try dashboard-coordinated claim first (atomic label-based)
603
- local resp
604
- resp=$(curl -s --max-time 5 -X POST "${DASHBOARD_URL}/api/claim" \
605
- -H "Content-Type: application/json" \
606
- -d "$(jq -n --argjson issue "$issue_num" --arg machine "$machine_name" \
607
- '{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}"
608
663
 
609
- if [[ -n "$resp" ]] && echo "$resp" | jq -e '.approved == true' >/dev/null 2>&1; then
610
664
  # VERIFY: re-read labels, ensure only our claim exists
611
665
  if ! _verify_claim_exclusive "$issue_num" "$machine_name"; then
612
666
  daemon_log INFO "Issue #${issue_num} claim race lost (competing claim) — removing our label"
613
667
  gh issue edit "$issue_num" --remove-label "claimed:${machine_name}" 2>/dev/null || true
614
- 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
615
683
  fi
616
- return 0
617
- elif [[ -n "$resp" ]] && echo "$resp" | jq -e '.approved == false' >/dev/null 2>&1; then
618
- local claimed_by
619
- claimed_by=$(echo "$resp" | jq -r '.claimed_by // "another machine"')
620
- daemon_log INFO "Issue #${issue_num} claimed by ${claimed_by} (via dashboard)"
621
- return 1
622
- fi
623
-
624
- # Fallback: direct GitHub label check (dashboard unreachable)
625
- daemon_log WARN "Dashboard unreachable — falling back to direct GitHub label claim"
626
- local existing_claim
627
- existing_claim=$(gh issue view "$issue_num" --json labels --jq \
628
- '[.labels[].name | select(startswith("claimed:"))] | .[0] // ""' 2>/dev/null || true)
629
-
630
- if [[ -n "$existing_claim" ]]; then
631
- daemon_log INFO "Issue #${issue_num} already claimed: ${existing_claim}"
632
- return 1
633
- fi
634
-
635
- gh issue edit "$issue_num" --add-label "claimed:${machine_name}" 2>/dev/null || return 1
636
- # VERIFY: re-read labels, ensure only our claim exists
637
- if ! _verify_claim_exclusive "$issue_num" "$machine_name"; then
638
- daemon_log INFO "Issue #${issue_num} claim race lost (competing claim) — removing our label"
639
- gh issue edit "$issue_num" --remove-label "claimed:${machine_name}" 2>/dev/null || true
640
- return 1
641
- fi
642
- 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
643
689
  }
644
690
 
645
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"