shipwright-cli 2.0.0 → 2.1.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 (113) hide show
  1. package/README.md +160 -72
  2. package/completions/_shipwright +59 -7
  3. package/completions/shipwright.bash +24 -4
  4. package/completions/shipwright.fish +80 -2
  5. package/dashboard/server.ts +208 -0
  6. package/docs/tmux-research/TMUX-ARCHITECTURE.md +567 -0
  7. package/docs/tmux-research/TMUX-AUDIT.md +925 -0
  8. package/docs/tmux-research/TMUX-BEST-PRACTICES-2025-2026.md +829 -0
  9. package/docs/tmux-research/TMUX-QUICK-REFERENCE.md +543 -0
  10. package/docs/tmux-research/TMUX-RESEARCH-INDEX.md +438 -0
  11. package/package.json +2 -2
  12. package/scripts/lib/helpers.sh +7 -0
  13. package/scripts/sw +116 -2
  14. package/scripts/sw-activity.sh +1 -1
  15. package/scripts/sw-adaptive.sh +1 -1
  16. package/scripts/sw-adversarial.sh +1 -1
  17. package/scripts/sw-architecture-enforcer.sh +1 -1
  18. package/scripts/sw-auth.sh +1 -1
  19. package/scripts/sw-autonomous.sh +128 -38
  20. package/scripts/sw-changelog.sh +1 -1
  21. package/scripts/sw-checkpoint.sh +1 -1
  22. package/scripts/sw-ci.sh +1 -1
  23. package/scripts/sw-cleanup.sh +1 -1
  24. package/scripts/sw-code-review.sh +62 -1
  25. package/scripts/sw-connect.sh +1 -1
  26. package/scripts/sw-context.sh +1 -1
  27. package/scripts/sw-cost.sh +44 -3
  28. package/scripts/sw-daemon.sh +155 -27
  29. package/scripts/sw-dashboard.sh +1 -1
  30. package/scripts/sw-db.sh +958 -118
  31. package/scripts/sw-decompose.sh +1 -1
  32. package/scripts/sw-deps.sh +1 -1
  33. package/scripts/sw-developer-simulation.sh +1 -1
  34. package/scripts/sw-discovery.sh +1 -1
  35. package/scripts/sw-docs-agent.sh +1 -1
  36. package/scripts/sw-docs.sh +1 -1
  37. package/scripts/sw-doctor.sh +49 -1
  38. package/scripts/sw-dora.sh +1 -1
  39. package/scripts/sw-durable.sh +1 -1
  40. package/scripts/sw-e2e-orchestrator.sh +1 -1
  41. package/scripts/sw-eventbus.sh +1 -1
  42. package/scripts/sw-feedback.sh +23 -15
  43. package/scripts/sw-fix.sh +1 -1
  44. package/scripts/sw-fleet-discover.sh +1 -1
  45. package/scripts/sw-fleet-viz.sh +1 -1
  46. package/scripts/sw-fleet.sh +1 -1
  47. package/scripts/sw-github-app.sh +1 -1
  48. package/scripts/sw-github-checks.sh +4 -4
  49. package/scripts/sw-github-deploy.sh +1 -1
  50. package/scripts/sw-github-graphql.sh +1 -1
  51. package/scripts/sw-guild.sh +1 -1
  52. package/scripts/sw-heartbeat.sh +1 -1
  53. package/scripts/sw-hygiene.sh +1 -1
  54. package/scripts/sw-incident.sh +45 -6
  55. package/scripts/sw-init.sh +150 -24
  56. package/scripts/sw-instrument.sh +1 -1
  57. package/scripts/sw-intelligence.sh +1 -1
  58. package/scripts/sw-jira.sh +1 -1
  59. package/scripts/sw-launchd.sh +1 -1
  60. package/scripts/sw-linear.sh +1 -1
  61. package/scripts/sw-logs.sh +1 -1
  62. package/scripts/sw-loop.sh +204 -19
  63. package/scripts/sw-memory.sh +18 -1
  64. package/scripts/sw-mission-control.sh +1 -1
  65. package/scripts/sw-model-router.sh +1 -1
  66. package/scripts/sw-otel.sh +1 -1
  67. package/scripts/sw-oversight.sh +76 -1
  68. package/scripts/sw-pipeline-composer.sh +1 -1
  69. package/scripts/sw-pipeline-vitals.sh +1 -1
  70. package/scripts/sw-pipeline.sh +261 -12
  71. package/scripts/sw-pm.sh +70 -5
  72. package/scripts/sw-pr-lifecycle.sh +1 -1
  73. package/scripts/sw-predictive.sh +8 -1
  74. package/scripts/sw-prep.sh +1 -1
  75. package/scripts/sw-ps.sh +1 -1
  76. package/scripts/sw-public-dashboard.sh +1 -1
  77. package/scripts/sw-quality.sh +1 -1
  78. package/scripts/sw-reaper.sh +1 -1
  79. package/scripts/sw-recruit.sh +1853 -178
  80. package/scripts/sw-regression.sh +1 -1
  81. package/scripts/sw-release-manager.sh +1 -1
  82. package/scripts/sw-release.sh +1 -1
  83. package/scripts/sw-remote.sh +1 -1
  84. package/scripts/sw-replay.sh +1 -1
  85. package/scripts/sw-retro.sh +1 -1
  86. package/scripts/sw-scale.sh +1 -1
  87. package/scripts/sw-security-audit.sh +1 -1
  88. package/scripts/sw-self-optimize.sh +1 -1
  89. package/scripts/sw-session.sh +1 -1
  90. package/scripts/sw-setup.sh +263 -127
  91. package/scripts/sw-standup.sh +1 -1
  92. package/scripts/sw-status.sh +44 -2
  93. package/scripts/sw-strategic.sh +189 -41
  94. package/scripts/sw-stream.sh +1 -1
  95. package/scripts/sw-swarm.sh +42 -5
  96. package/scripts/sw-team-stages.sh +1 -1
  97. package/scripts/sw-templates.sh +4 -4
  98. package/scripts/sw-testgen.sh +66 -15
  99. package/scripts/sw-tmux-pipeline.sh +1 -1
  100. package/scripts/sw-tmux-role-color.sh +58 -0
  101. package/scripts/sw-tmux-status.sh +128 -0
  102. package/scripts/sw-tmux.sh +1 -1
  103. package/scripts/sw-trace.sh +1 -1
  104. package/scripts/sw-tracker.sh +1 -1
  105. package/scripts/sw-triage.sh +61 -37
  106. package/scripts/sw-upgrade.sh +1 -1
  107. package/scripts/sw-ux.sh +1 -1
  108. package/scripts/sw-webhook.sh +1 -1
  109. package/scripts/sw-widgets.sh +1 -1
  110. package/scripts/sw-worktree.sh +1 -1
  111. package/templates/pipelines/autonomous.json +2 -2
  112. package/tmux/shipwright-overlay.conf +35 -17
  113. package/tmux/tmux.conf +23 -21
@@ -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="2.0.0"
9
+ VERSION="2.1.0"
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
12
12
 
@@ -7,7 +7,7 @@
7
7
  set -euo pipefail
8
8
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
9
9
 
10
- VERSION="2.0.0"
10
+ VERSION="2.1.0"
11
11
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
12
12
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
13
13
 
@@ -203,31 +203,17 @@ run_analysis_cycle() {
203
203
  if command -v claude &>/dev/null; then
204
204
  info "Running codebase analysis with Claude..."
205
205
 
206
- claude code << 'ANALYSIS_PROMPT'
207
- You are Shipwright's autonomous PM. Analyze this repository for:
208
- 1. **Bugs & Issues**: Missing error handling, potential crashes, edge cases
209
- 2. **Performance**: Bottlenecks, n+1 queries, memory leaks, unnecessary work
210
- 3. **Missing Tests**: Uncovered code paths, critical scenarios
211
- 4. **Stale Documentation**: Outdated guides, missing API docs
212
- 5. **Security**: Input validation, injection risks, credential leaks
213
- 6. **Code Quality**: Dead code, refactoring opportunities, tech debt
214
- 7. **Self-Improvement**: How Shipwright itself could be enhanced
215
-
216
- For each finding, suggest:
217
- - Priority: critical/high/medium/low
218
- - Effort estimate: S/M/L (small/medium/large)
219
- - Labels: e.g. "bug", "performance", "test", "docs", "security", "refactor", "self-improvement"
220
-
221
- Output as JSON array of findings with fields:
222
- {
223
- "title": "...",
224
- "description": "...",
225
- "priority": "high",
226
- "effort": "M",
227
- "labels": ["..."],
228
- "category": "bug|performance|test|docs|security|refactor|self-improvement"
229
- }
230
- ANALYSIS_PROMPT
206
+ claude -p 'You are Shipwright'"'"'s autonomous PM. Analyze this repository for:
207
+ 1. Bugs & Issues: Missing error handling, potential crashes, edge cases
208
+ 2. Performance: Bottlenecks, n+1 queries, memory leaks, unnecessary work
209
+ 3. Missing Tests: Uncovered code paths, critical scenarios
210
+ 4. Stale Documentation: Outdated guides, missing API docs
211
+ 5. Security: Input validation, injection risks, credential leaks
212
+ 6. Code Quality: Dead code, refactoring opportunities, tech debt
213
+ 7. Self-Improvement: How Shipwright itself could be enhanced
214
+
215
+ For each finding, output JSON with fields: title, description, priority (critical/high/medium/low), effort (S/M/L), labels (array), category.
216
+ Output ONLY a JSON array, no other text.' --max-turns 3 > "$findings" 2>/dev/null || true
231
217
 
232
218
  else
233
219
  warn "Claude CLI not available, using static heuristics..."
@@ -289,8 +275,9 @@ create_issue_from_finding() {
289
275
  # Add shipwright label to auto-feed daemon
290
276
  local all_labels="shipwright,$labels"
291
277
 
292
- # Create GitHub issue
293
- gh issue create \
278
+ # Create GitHub issue; capture URL and parse issue number
279
+ local create_out
280
+ create_out=$(gh issue create \
294
281
  --title "$title" \
295
282
  --body "$description
296
283
 
@@ -301,14 +288,83 @@ create_issue_from_finding() {
301
288
  - Created: \`$(now_iso)\`
302
289
  - By: Autonomous loop (sw-autonomous.sh)
303
290
  " \
304
- --label "$all_labels" 2>/dev/null && \
305
- success "Created issue: $title" && \
306
- return 0 || \
307
- warn "Failed to create issue: $title" && \
291
+ --label "$all_labels" 2>/dev/null) || {
292
+ warn "Failed to create issue: $title"
308
293
  return 1
294
+ }
295
+ success "Created issue: $title"
296
+ local issue_num
297
+ issue_num=$(echo "$create_out" | sed -n 's|.*/issues/\([0-9]*\)|\1|p')
298
+ [[ -n "$issue_num" ]] && echo "$issue_num"
299
+ return 0
309
300
  }
310
301
 
311
302
  # ─── Issue Processing from Analysis ────────────────────────────────────────
303
+ # Trigger pipeline for a finding issue (daemon will also pick it up; this runs immediately)
304
+ trigger_pipeline_for_finding() {
305
+ local issue_num="$1"
306
+ local title="$2"
307
+ if [[ -z "$issue_num" || ! "$issue_num" =~ ^[0-9]+$ ]]; then
308
+ return 0
309
+ fi
310
+ if [[ ! -x "$SCRIPT_DIR/sw-pipeline.sh" ]]; then
311
+ return 0
312
+ fi
313
+ info "Triggering pipeline for finding issue #${issue_num}: $title"
314
+ (cd "$REPO_DIR" && export REPO_DIR SCRIPT_DIR && bash "$SCRIPT_DIR/sw-pipeline.sh" start --issue "$issue_num" 2>/dev/null) &
315
+ emit_event "autonomous.pipeline_triggered" "issue=$issue_num" "title=$title"
316
+ }
317
+
318
+ # Record finding outcome for learning (which findings led to successful fixes)
319
+ record_finding_pending() {
320
+ local issue_num="$1"
321
+ local finding_title="$2"
322
+ ensure_state_dir
323
+ local state_file="${STATE_DIR}/state.json"
324
+ local pending_file="${STATE_DIR}/pending_findings.jsonl"
325
+ [[ ! -f "$pending_file" ]] && touch "$pending_file"
326
+ echo "{\"issue_number\":$issue_num,\"finding_title\":\"${finding_title//\"/\\\"}\",\"created_at\":\"$(now_iso)\",\"outcome\":\"pending\"}" >> "$pending_file"
327
+ }
328
+
329
+ # Update outcomes for pending findings (called at start of each cycle)
330
+ update_finding_outcomes() {
331
+ [[ "$NO_GITHUB" == "true" ]] && return 0
332
+ local pending_file="${STATE_DIR}/pending_findings.jsonl"
333
+ [[ ! -f "$pending_file" ]] && return 0
334
+ local updated_file
335
+ updated_file=$(mktemp "${TMPDIR:-/tmp}/sw-autonomous-pending.XXXXXX")
336
+ while IFS= read -r line; do
337
+ [[ -z "$line" ]] && continue
338
+ local issue_num outcome
339
+ issue_num=$(echo "$line" | jq -r '.issue_number // empty')
340
+ outcome=$(echo "$line" | jq -r '.outcome // "pending"')
341
+ if [[ "$outcome" != "pending" || -z "$issue_num" ]]; then
342
+ echo "$line" >> "$updated_file"
343
+ continue
344
+ fi
345
+ local state
346
+ state=$(gh issue view "$issue_num" --json state 2>/dev/null | jq -r '.state // "OPEN"') || state="OPEN"
347
+ if [[ "$state" != "CLOSED" ]]; then
348
+ echo "$line" >> "$updated_file"
349
+ continue
350
+ fi
351
+ local merged=""
352
+ local merged_pipeline merged_daemon
353
+ merged_pipeline=$(gh pr list --head "pipeline/issue-${issue_num}" --json state -q '.[0].state' 2>/dev/null || echo "")
354
+ merged_daemon=$(gh pr list --head "daemon/issue-${issue_num}" --json state -q '.[0].state' 2>/dev/null || echo "")
355
+ [[ "$merged_pipeline" == "MERGED" || "$merged_daemon" == "MERGED" ]] && merged="MERGED"
356
+ if [[ "$merged" == "MERGED" ]]; then
357
+ outcome="success"
358
+ increment_counter "issues_completed"
359
+ emit_event "autonomous.finding_success" "issue=$issue_num"
360
+ else
361
+ outcome="failure"
362
+ emit_event "autonomous.finding_failure" "issue=$issue_num"
363
+ fi
364
+ echo "$line" | jq --arg o "$outcome" '.outcome = $o' >> "$updated_file"
365
+ done < "$pending_file"
366
+ mv "$updated_file" "$pending_file"
367
+ }
312
368
 
313
369
  process_findings() {
314
370
  local findings_file="$1"
@@ -320,7 +376,7 @@ process_findings() {
320
376
  local created=0
321
377
  local total=0
322
378
 
323
- # Parse findings and create issues
379
+ # Parse findings and create issues; trigger pipeline for each; record for outcome tracking
324
380
  while IFS= read -r finding; do
325
381
  [[ -z "$finding" ]] && continue
326
382
 
@@ -344,14 +400,18 @@ process_findings() {
344
400
  labels="${category}${labels:+,$labels}"
345
401
  fi
346
402
 
347
- if create_issue_from_finding "$title" "$description" "$priority" "$effort" "$labels"; then
403
+ local issue_num
404
+ issue_num=$(create_issue_from_finding "$title" "$description" "$priority" "$effort" "$labels")
405
+ if [[ $? -eq 0 && -n "$issue_num" ]]; then
348
406
  created=$((created + 1))
349
407
  increment_counter "issues_created"
350
- emit_event "autonomous.issue_created" "title=$title" "priority=$priority" "effort=$effort"
408
+ emit_event "autonomous.issue_created" "title=$title" "priority=$priority" "effort=$effort" "issue=$issue_num"
409
+ trigger_pipeline_for_finding "$issue_num" "$title"
410
+ record_finding_pending "$issue_num" "$title"
351
411
  fi
352
412
  done < <(jq -c '.[]' "$findings_file" 2>/dev/null)
353
413
 
354
- info "Created $created of $total findings as issues"
414
+ info "Created $created of $total findings as issues (pipelines triggered)"
355
415
  echo "$created"
356
416
  }
357
417
 
@@ -452,6 +512,29 @@ show_history() {
452
512
 
453
513
  # ─── Loop Control ──────────────────────────────────────────────────────────
454
514
 
515
+ # Scheduler: run cycles at interval (real scheduler instead of manual cycle only)
516
+ run_scheduler() {
517
+ ensure_state_dir
518
+ init_state
519
+ update_state "status" "running"
520
+ info "Autonomous scheduler started (cycle every $(get_config "cycle_interval_minutes" "60") minutes)"
521
+ emit_event "autonomous.scheduler_started"
522
+
523
+ while true; do
524
+ local status
525
+ status=$(read_state | jq -r '.status // "running"')
526
+ if [[ "$status" != "running" ]]; then
527
+ info "Status is ${status} — exiting scheduler"
528
+ break
529
+ fi
530
+ run_single_cycle || true
531
+ local interval_mins
532
+ interval_mins=$(get_config "cycle_interval_minutes" "60")
533
+ info "Next cycle in ${interval_mins} minutes"
534
+ sleep $((interval_mins * 60))
535
+ done
536
+ }
537
+
455
538
  start_loop() {
456
539
  ensure_state_dir
457
540
  init_state
@@ -491,6 +574,9 @@ run_single_cycle() {
491
574
  ensure_state_dir
492
575
  update_state "status" "analyzing"
493
576
 
577
+ # Update outcomes for pending findings (which led to successful fixes)
578
+ update_finding_outcomes
579
+
494
580
  info "Running single analysis cycle..."
495
581
 
496
582
  # Step 1: Analysis
@@ -574,11 +660,12 @@ USAGE
574
660
  sw autonomous <command> [options]
575
661
 
576
662
  COMMANDS
577
- start Begin autonomous loop (analyze create → build → learn → repeat)
663
+ start Set status to running (use with external scheduler)
664
+ run Run scheduler: cycle every N minutes until stopped
578
665
  stop Stop the loop gracefully
579
666
  pause Pause without losing state
580
667
  resume Resume from pause
581
- cycle Run one analysis cycle manually
668
+ cycle Run one analysis cycle manually (create issues + trigger pipelines)
582
669
  status Show loop status, recent cycles, issue creation stats
583
670
  config [show|set] Show or set configuration
584
671
  history Show past cycles and their outcomes
@@ -611,6 +698,9 @@ main() {
611
698
  start)
612
699
  start_loop
613
700
  ;;
701
+ run)
702
+ run_scheduler
703
+ ;;
614
704
  stop)
615
705
  stop_loop
616
706
  ;;
@@ -7,7 +7,7 @@
7
7
  set -euo pipefail
8
8
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
9
9
 
10
- VERSION="2.0.0"
10
+ VERSION="2.1.0"
11
11
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
12
12
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
13
13
 
@@ -8,7 +8,7 @@
8
8
  set -euo pipefail
9
9
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
10
10
 
11
- VERSION="2.0.0"
11
+ VERSION="2.1.0"
12
12
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
13
13
 
14
14
  # ─── Colors (matches Seth's tmux theme) ─────────────────────────────────────
package/scripts/sw-ci.sh CHANGED
@@ -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="2.0.0"
9
+ VERSION="2.1.0"
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
12
12
 
@@ -5,7 +5,7 @@
5
5
  # ║ Default: dry-run (shows what would be cleaned). ║
6
6
  # ║ Use --force to actually kill sessions and remove files. ║
7
7
  # ╚═══════════════════════════════════════════════════════════════════════════╝
8
- VERSION="2.0.0"
8
+ VERSION="2.1.0"
9
9
  set -euo pipefail
10
10
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
11
11
 
@@ -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="2.0.0"
9
+ VERSION="2.1.0"
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
12
12
 
@@ -382,6 +382,50 @@ auto_fix() {
382
382
  echo "$fixed"
383
383
  }
384
384
 
385
+ # ─── Claude-powered semantic review (logic, race conditions, API usage, requirements) ──
386
+ run_claude_semantic_review() {
387
+ local diff_content="$1"
388
+ local requirements="${2:-}"
389
+ [[ -z "$diff_content" ]] && return 0
390
+ if ! command -v claude &>/dev/null; then
391
+ return 0
392
+ fi
393
+
394
+ local prompt="You are a senior code reviewer. Review this git diff for semantic issues (not just style).
395
+
396
+ Focus on:
397
+ 1. Logic errors and edge cases (off-by-one, null/empty handling, wrong conditions)
398
+ 2. Race conditions and concurrency issues (shared state, ordering, locks)
399
+ 3. Incorrect or unsafe API usage (wrong arguments, missing error handling, deprecated APIs)
400
+ 4. Security issues (injection, auth bypass, sensitive data exposure)
401
+ 5. Requirements alignment: does the change match the intended behavior?
402
+
403
+ For each issue use this format on its own line:
404
+ - **[SEVERITY]** file:line — brief description
405
+
406
+ Severity: Critical, Bug, Security, Warning, Suggestion.
407
+ If no issues found, reply with exactly: Review clean — no semantic issues found.
408
+
409
+ ## Diff
410
+ ${diff_content}
411
+ "
412
+ [[ -n "$requirements" ]] && prompt="${prompt}
413
+
414
+ ## Requirements / intended behavior
415
+ ${requirements}
416
+ "
417
+
418
+ local claude_out
419
+ claude_out=$(claude -p "$prompt" --max-turns 3 2>/dev/null || true)
420
+ [[ -z "$claude_out" ]] && return 0
421
+
422
+ if echo "$claude_out" | grep -qi "Review clean — no semantic issues found"; then
423
+ return 0
424
+ fi
425
+ echo "$claude_out" | grep -oE '\*\*\[?(Critical|Bug|Security|Warning|Suggestion)\]?\*\*[^—]*—[^$]+' 2>/dev/null || \
426
+ echo "$claude_out" | grep -oE '-\s+\*\*[^*]+\*\*[^\n]+' 2>/dev/null || true
427
+ }
428
+
385
429
  # ─── Review Subcommand ───────────────────────────────────────────────────────
386
430
 
387
431
  review_changes() {
@@ -410,6 +454,23 @@ review_changes() {
410
454
 
411
455
  [[ ${#changed_files[@]} -eq 0 ]] && { success "No changes to review"; return 0; }
412
456
 
457
+ # Claude-powered semantic review (logic, race conditions, API usage) when available
458
+ local diff_content
459
+ if [[ "$review_scope" == "staged" ]]; then
460
+ diff_content=$(cd "$REPO_DIR" && git diff --cached 2>/dev/null || true)
461
+ else
462
+ diff_content=$(cd "$REPO_DIR" && git diff main...HEAD 2>/dev/null || true)
463
+ fi
464
+ local semantic_issues=()
465
+ if [[ -n "$diff_content" ]] && command -v claude &>/dev/null; then
466
+ info "Running Claude semantic review (logic, race conditions, API usage)..."
467
+ mapfile -t semantic_issues < <(run_claude_semantic_review "$diff_content" "${REVIEW_REQUIREMENTS:-}" || true)
468
+ if [[ ${#semantic_issues[@]} -gt 0 ]]; then
469
+ total_issues=$((total_issues + ${#semantic_issues[@]}))
470
+ review_output=$(echo "$review_output" | jq --argjson arr "$(printf '%s\n' "${semantic_issues[@]}" | jq -R . | jq -s .)" '.semantic_findings = $arr' 2>/dev/null || echo "$review_output")
471
+ fi
472
+ fi
473
+
413
474
  for file in "${changed_files[@]}"; do
414
475
  local file_path="${REPO_DIR}/${file}"
415
476
  [[ ! -f "$file_path" ]] && continue
@@ -8,7 +8,7 @@
8
8
  set -euo pipefail
9
9
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
10
10
 
11
- VERSION="2.0.0"
11
+ VERSION="2.1.0"
12
12
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
13
13
 
14
14
  # ─── Colors (matches Seth's tmux theme) ─────────────────────────────────────
@@ -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="2.0.0"
9
+ VERSION="2.1.0"
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
12
12
 
@@ -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="2.0.0"
9
+ VERSION="2.1.0"
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
12
12
 
@@ -70,6 +70,12 @@ COST_DIR="${HOME}/.shipwright"
70
70
  COST_FILE="${COST_DIR}/costs.json"
71
71
  BUDGET_FILE="${COST_DIR}/budget.json"
72
72
 
73
+ # Source sw-db.sh for SQLite cost functions (if available)
74
+ _COST_SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
75
+ if [[ -f "$_COST_SCRIPT_DIR/sw-db.sh" ]]; then
76
+ source "$_COST_SCRIPT_DIR/sw-db.sh" 2>/dev/null || true
77
+ fi
78
+
73
79
  ensure_cost_dir() {
74
80
  mkdir -p "$COST_DIR"
75
81
  [[ -f "$COST_FILE" ]] || echo '{"entries":[],"summary":{}}' > "$COST_FILE"
@@ -143,6 +149,7 @@ cost_calculate() {
143
149
 
144
150
  # cost_record <input_tokens> <output_tokens> <model> <stage> [issue]
145
151
  # Records a cost entry to the cost file and events log.
152
+ # Tries SQLite first, always writes to JSON for backward compat.
146
153
  cost_record() {
147
154
  local input_tokens="${1:-0}"
148
155
  local output_tokens="${2:-0}"
@@ -155,6 +162,12 @@ cost_record() {
155
162
  local cost_usd
156
163
  cost_usd=$(cost_calculate "$input_tokens" "$output_tokens" "$model")
157
164
 
165
+ # Try SQLite first
166
+ if type db_record_cost &>/dev/null; then
167
+ db_record_cost "$input_tokens" "$output_tokens" "$model" "$stage" "$cost_usd" "$issue" 2>/dev/null || true
168
+ fi
169
+
170
+ # Always write to JSON (dual-write period)
158
171
  (
159
172
  if command -v flock &>/dev/null; then
160
173
  flock -w 10 200 2>/dev/null || { warn "Cost lock timeout"; }
@@ -197,9 +210,20 @@ cost_check_budget() {
197
210
 
198
211
  ensure_cost_dir
199
212
 
213
+ # Try DB for budget info
200
214
  local budget_enabled budget_usd
201
- budget_enabled=$(jq -r '.enabled' "$BUDGET_FILE" 2>/dev/null || echo "false")
202
- budget_usd=$(jq -r '.daily_budget_usd' "$BUDGET_FILE" 2>/dev/null || echo "0")
215
+ if type db_get_budget &>/dev/null && type db_available &>/dev/null && db_available 2>/dev/null; then
216
+ local db_budget
217
+ db_budget=$(db_get_budget 2>/dev/null || true)
218
+ if [[ -n "$db_budget" ]]; then
219
+ budget_enabled=$(echo "$db_budget" | cut -d'|' -f2)
220
+ budget_usd=$(echo "$db_budget" | cut -d'|' -f1)
221
+ [[ "$budget_enabled" == "1" ]] && budget_enabled="true"
222
+ fi
223
+ fi
224
+ # Fallback to JSON
225
+ budget_enabled="${budget_enabled:-$(jq -r '.enabled' "$BUDGET_FILE" 2>/dev/null || echo "false")}"
226
+ budget_usd="${budget_usd:-$(jq -r '.daily_budget_usd' "$BUDGET_FILE" 2>/dev/null || echo "0")}"
203
227
 
204
228
  if [[ "$budget_enabled" != "true" || "$budget_usd" == "0" ]]; then
205
229
  return 0
@@ -244,6 +268,17 @@ cost_check_budget() {
244
268
  cost_remaining_budget() {
245
269
  ensure_cost_dir
246
270
 
271
+ # Try DB for remaining budget (single query)
272
+ if type db_remaining_budget &>/dev/null && type db_available &>/dev/null && db_available 2>/dev/null; then
273
+ local db_result
274
+ db_result=$(db_remaining_budget 2>/dev/null || true)
275
+ if [[ -n "$db_result" ]]; then
276
+ echo "$db_result"
277
+ return 0
278
+ fi
279
+ fi
280
+
281
+ # Fallback to JSON
247
282
  local budget_enabled budget_usd
248
283
  budget_enabled=$(jq -r '.enabled' "$BUDGET_FILE" 2>/dev/null || echo "false")
249
284
  budget_usd=$(jq -r '.daily_budget_usd' "$BUDGET_FILE" 2>/dev/null || echo "0")
@@ -792,6 +827,12 @@ budget_set() {
792
827
 
793
828
  ensure_cost_dir
794
829
 
830
+ # Write to DB if available
831
+ if type db_set_budget &>/dev/null; then
832
+ db_set_budget "$amount" 2>/dev/null || true
833
+ fi
834
+
835
+ # Always write to JSON (dual-write)
795
836
  local tmp_file
796
837
  tmp_file=$(mktemp)
797
838
  jq --arg amt "$amount" \