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
@@ -0,0 +1,406 @@
1
+ #!/usr/bin/env bash
2
+ # ╔═══════════════════════════════════════════════════════════════════════════╗
3
+ # ║ shipwright stall-detector — Pipeline Stall & Deadlock Detection ║
4
+ # ║ Monitor running pipelines, detect stalls, classify issues, auto-abort ║
5
+ # ╚═══════════════════════════════════════════════════════════════════════════╝
6
+ set -euo pipefail
7
+
8
+ VERSION="3.3.0"
9
+ # shellcheck source=lib/bootstrap.sh
10
+ source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/lib/bootstrap.sh"
11
+
12
+ # ─── Constants ──────────────────────────────────────────────────────────────
13
+ HEARTBEAT_DIR="$HOME/.shipwright/heartbeats"
14
+ PIPELINE_ARTIFACTS_DIR="${PIPELINE_ARTIFACTS_DIR:-.claude/pipeline-artifacts}"
15
+ STATE_FILE="${PIPELINE_ARTIFACTS_DIR:-pipeline-state.md}"
16
+ STALL_DETECTOR_STATE="${HOME}/.shipwright/stall-detector-state.json"
17
+
18
+ # Default configuration
19
+ STALL_TIMEOUT_SECONDS="${STALL_TIMEOUT_SECONDS:-300}"
20
+ MAX_STAGE_RETRIES="${MAX_STAGE_RETRIES:-5}"
21
+ HEARTBEAT_MAX_AGE_SECONDS="${HEARTBEAT_MAX_AGE_SECONDS:-120}"
22
+ AUTO_ABORT_ENABLED="${AUTO_ABORT_ENABLED:-true}"
23
+ CREATE_ISSUE_ON_ABORT="${CREATE_ISSUE_ON_ABORT:-false}"
24
+ STALL_DETECTOR_INTERVAL="${STALL_DETECTOR_INTERVAL:-30}"
25
+
26
+ # ─── Help ───────────────────────────────────────────────────────────────────
27
+ show_help() {
28
+ echo ""
29
+ echo -e "${CYAN}${BOLD} Shipwright Stall Detector${RESET} ${DIM}v${VERSION}${RESET}"
30
+ echo -e "${DIM} ════════════════════════════════════════════${RESET}"
31
+ echo ""
32
+ echo -e " ${BOLD}USAGE${RESET}"
33
+ echo -e " shipwright stall-detector <command> [options]"
34
+ echo ""
35
+ echo -e " ${BOLD}COMMANDS${RESET}"
36
+ echo -e " ${CYAN}check${RESET} Single-shot detection, return JSON"
37
+ echo -e " ${CYAN}watch${RESET} Background monitor (runs every N seconds)"
38
+ echo -e " ${CYAN}abort${RESET} Manually abort a stalled pipeline"
39
+ echo -e " ${CYAN}config${RESET} Show current configuration"
40
+ echo -e " ${CYAN}status${RESET} Show detection stats and recent stalls"
41
+ echo ""
42
+ echo -e " ${BOLD}CHECK OPTIONS${RESET}"
43
+ echo -e " (none)"
44
+ echo ""
45
+ echo -e " ${BOLD}WATCH OPTIONS${RESET}"
46
+ echo -e " --interval <secs> Check interval (default: 30)"
47
+ echo -e " --once Run once and exit"
48
+ echo ""
49
+ echo -e " ${BOLD}ABORT OPTIONS${RESET}"
50
+ echo -e " <pipeline-id> Pipeline ID or PID to abort"
51
+ echo -e " --reason <text> Abort reason (optional)"
52
+ echo ""
53
+ echo -e " ${BOLD}EXAMPLES${RESET}"
54
+ echo -e " ${DIM}# One-shot detection${RESET}"
55
+ echo -e " shipwright stall-detector check"
56
+ echo ""
57
+ echo -e " ${DIM}# Monitor in background${RESET}"
58
+ echo -e " shipwright stall-detector watch --interval 60"
59
+ echo ""
60
+ echo -e " ${DIM}# Manually abort a stalled pipeline${RESET}"
61
+ echo -e " shipwright stall-detector abort pipeline-12345 --reason \"build timeout\""
62
+ echo ""
63
+ }
64
+
65
+ # ─── Ensure heartbeat and artifacts directories exist ──────────────────────
66
+ ensure_dirs() {
67
+ mkdir -p "$HEARTBEAT_DIR"
68
+ mkdir -p "${PIPELINE_ARTIFACTS_DIR%/*}" 2>/dev/null || true
69
+ mkdir -p "${STALL_DETECTOR_STATE%/*}" 2>/dev/null || true
70
+ }
71
+
72
+ # ─── Load Configuration ──────────────────────────────────────────────────────
73
+ load_config() {
74
+ local config_file=".claude/daemon-config.json"
75
+
76
+ if [[ -f "$config_file" ]]; then
77
+ STALL_TIMEOUT_SECONDS=$(jq -r '.stall_detection.stall_timeout_seconds // 300' "$config_file" 2>/dev/null || echo "300")
78
+ MAX_STAGE_RETRIES=$(jq -r '.stall_detection.max_stage_retries // 5' "$config_file" 2>/dev/null || echo "5")
79
+ HEARTBEAT_MAX_AGE_SECONDS=$(jq -r '.stall_detection.heartbeat_max_age_seconds // 120' "$config_file" 2>/dev/null || echo "120")
80
+ AUTO_ABORT_ENABLED=$(jq -r '.stall_detection.auto_abort_enabled // true' "$config_file" 2>/dev/null || echo "true")
81
+ CREATE_ISSUE_ON_ABORT=$(jq -r '.stall_detection.create_issue_on_abort // false' "$config_file" 2>/dev/null || echo "false")
82
+ STALL_DETECTOR_INTERVAL=$(jq -r '.stall_detection.interval // 30' "$config_file" 2>/dev/null || echo "30")
83
+ fi
84
+ }
85
+
86
+ # ─── Classify Stall Type ────────────────────────────────────────────────────
87
+ # Returns: {type, reason, severity}
88
+ stall_detector_classify() {
89
+ local heartbeat_age="$1"
90
+ local stage="$2"
91
+ local retry_count="${3:-0}"
92
+
93
+ local classification_json
94
+
95
+ # Determine stall type based on evidence
96
+ if [[ "$heartbeat_age" -gt "$STALL_TIMEOUT_SECONDS" ]]; then
97
+ classification_json=$(jq -n \
98
+ --arg type "heartbeat_stale" \
99
+ --arg reason "No heartbeat update for ${heartbeat_age}s (threshold: ${STALL_TIMEOUT_SECONDS}s)" \
100
+ --arg severity "critical" \
101
+ '{type: $type, reason: $reason, severity: $severity}')
102
+ elif [[ "$retry_count" -gt "$MAX_STAGE_RETRIES" ]]; then
103
+ classification_json=$(jq -n \
104
+ --arg type "loop_detected" \
105
+ --arg reason "Stage ${stage} restarted ${retry_count} times (max: ${MAX_STAGE_RETRIES})" \
106
+ --arg severity "warning" \
107
+ '{type: $type, reason: $reason, severity: $severity}')
108
+ else
109
+ classification_json=$(jq -n \
110
+ --arg type "timeout" \
111
+ --arg reason "Stage ${stage} exceeded max duration" \
112
+ --arg severity "warning" \
113
+ '{type: $type, reason: $reason, severity: $severity}')
114
+ fi
115
+
116
+ echo "$classification_json"
117
+ }
118
+
119
+ # ─── Single-shot stall check ─────────────────────────────────────────────────
120
+ # Returns JSON: {stalled: [...], deadlocked: [...], looping: [...]}
121
+ cmd_check() {
122
+ ensure_dirs
123
+ load_config
124
+
125
+ # Build arrays using jq
126
+ local result_json
127
+
128
+ # Read all heartbeat files
129
+ if [[ ! -d "$HEARTBEAT_DIR" ]]; then
130
+ result_json=$(jq -n '{stalled: [], deadlocked: [], looping: [], checked_at: "'"$(now_iso)"'", pipeline_count: 0}')
131
+ echo "$result_json"
132
+ return 0
133
+ fi
134
+
135
+ local now_epoch
136
+ now_epoch="$(now_epoch)"
137
+
138
+ # Build stalled list using jq
139
+ local stalled_items=""
140
+ for hb_file in "${HEARTBEAT_DIR}"/*.json; do
141
+ [[ ! -f "$hb_file" ]] && continue
142
+
143
+ local job_id updated_at stage
144
+ job_id="$(basename "$hb_file" .json)"
145
+ updated_at=$(jq -r '.updated_at // empty' "$hb_file" 2>/dev/null || true)
146
+ stage=$(jq -r '.stage // empty' "$hb_file" 2>/dev/null || true)
147
+
148
+ if [[ -z "$updated_at" ]]; then
149
+ continue
150
+ fi
151
+
152
+ # Calculate heartbeat age
153
+ local hb_epoch
154
+ if TZ=UTC date -j -f "%Y-%m-%dT%H:%M:%SZ" "$updated_at" +%s >/dev/null 2>&1; then
155
+ hb_epoch="$(TZ=UTC date -j -f "%Y-%m-%dT%H:%M:%SZ" "$updated_at" +%s 2>/dev/null)"
156
+ else
157
+ hb_epoch="$(date -d "$updated_at" +%s 2>/dev/null || echo 0)"
158
+ fi
159
+
160
+ local current_age=$((now_epoch - hb_epoch))
161
+
162
+ # Check for stale heartbeat
163
+ if [[ "$current_age" -gt "$HEARTBEAT_MAX_AGE_SECONDS" ]]; then
164
+ stalled_items="${stalled_items} \"$job_id\""
165
+ emit_event "stall_detector.stalled" "job_id=$job_id" "age_seconds=$current_age" "stage=$stage" 2>/dev/null || true
166
+ fi
167
+ done
168
+
169
+ # Build final JSON result
170
+ result_json=$(jq -n \
171
+ --arg checked_at "$(now_iso)" \
172
+ '{
173
+ stalled: ['"${stalled_items# }"'],
174
+ deadlocked: [],
175
+ looping: [],
176
+ checked_at: $checked_at,
177
+ pipeline_count: 0
178
+ }' 2>/dev/null || jq -n '{stalled: [], deadlocked: [], looping: [], checked_at: "'"$(now_iso)"'", pipeline_count: 0}')
179
+
180
+ # Fix pipeline_count
181
+ result_json=$(echo "$result_json" | jq '.pipeline_count = ((.stalled | length) + (.deadlocked | length) + (.looping | length))')
182
+
183
+ echo "$result_json" | jq '.'
184
+ }
185
+
186
+ # ─── Auto-abort a stalled pipeline ──────────────────────────────────────────
187
+ cmd_abort() {
188
+ local pipeline_id="${1:-}"
189
+ if [[ -z "$pipeline_id" ]]; then
190
+ error "Usage: shipwright stall-detector abort <pipeline-id> [--reason <text>]"
191
+ exit 1
192
+ fi
193
+ shift
194
+
195
+ local reason="User abort"
196
+ while [[ $# -gt 0 ]]; do
197
+ case "$1" in
198
+ --reason) reason="${2:-}"; shift 2 ;;
199
+ *) shift ;;
200
+ esac
201
+ done
202
+
203
+ ensure_dirs
204
+ load_config
205
+
206
+ local hb_file="${HEARTBEAT_DIR}/${pipeline_id}.json"
207
+
208
+ if [[ ! -f "$hb_file" ]]; then
209
+ error "No heartbeat found for pipeline: ${pipeline_id}"
210
+ return 1
211
+ fi
212
+
213
+ local pid
214
+ pid=$(jq -r '.pid // empty' "$hb_file" 2>/dev/null || true)
215
+
216
+ if [[ -z "$pid" ]]; then
217
+ error "Invalid heartbeat file for pipeline: ${pipeline_id}"
218
+ return 1
219
+ fi
220
+
221
+ # Send SIGTERM to pipeline process
222
+ if kill -0 "$pid" 2>/dev/null; then
223
+ info "Sending SIGTERM to pipeline process $pid..."
224
+ kill -TERM "$pid" 2>/dev/null || true
225
+ sleep 2
226
+
227
+ # If still alive, send SIGKILL
228
+ if kill -0 "$pid" 2>/dev/null; then
229
+ warn "Process did not respond to SIGTERM, sending SIGKILL..."
230
+ kill -KILL "$pid" 2>/dev/null || true
231
+ fi
232
+
233
+ success "Aborted pipeline: ${pipeline_id} (reason: ${reason})"
234
+ else
235
+ warn "Pipeline process $pid not running (already dead)"
236
+ fi
237
+
238
+ # Update heartbeat to mark as aborted
239
+ local tmp_file
240
+ tmp_file="$(mktemp "${HEARTBEAT_DIR}/.tmp.XXXXXX")" || { error "mktemp failed"; return 1; }
241
+
242
+ jq --arg reason "$reason" --arg aborted_at "$(now_iso)" \
243
+ '. + {status: "aborted", abort_reason: $reason, aborted_at: $aborted_at}' \
244
+ "$hb_file" > "$tmp_file" || { rm -f "$tmp_file"; return 1; }
245
+
246
+ mv "$tmp_file" "$hb_file" || { rm -f "$tmp_file"; return 1; }
247
+
248
+ # Emit abort event
249
+ emit_event "pipeline.stall_aborted" "pipeline_id=$pipeline_id" "reason=$reason" "pid=$pid" 2>/dev/null || true
250
+
251
+ # Clear heartbeat file (optional - mark as processed)
252
+ rm -f "$hb_file" 2>/dev/null || true
253
+
254
+ return 0
255
+ }
256
+
257
+ # ─── Background watch loop ──────────────────────────────────────────────────
258
+ cmd_watch() {
259
+ local interval="$STALL_DETECTOR_INTERVAL"
260
+ local once=false
261
+
262
+ while [[ $# -gt 0 ]]; do
263
+ case "$1" in
264
+ --interval) interval="${2:-30}"; shift 2 ;;
265
+ --once) once=true; shift ;;
266
+ *) shift ;;
267
+ esac
268
+ done
269
+
270
+ ensure_dirs
271
+ load_config
272
+
273
+ info "Starting stall detector (interval: ${interval}s, auto-abort: ${AUTO_ABORT_ENABLED})"
274
+
275
+ # Write PID to state file
276
+ local tmp_state
277
+ tmp_state="$(mktemp)" || { error "mktemp failed"; return 1; }
278
+ jq -n --arg pid "$$" --arg started_at "$(now_iso)" \
279
+ '{pid: ($pid | tonumber), started_at: $started_at, checks: 0, aborts: 0}' > "$tmp_state" || { rm -f "$tmp_state"; return 1; }
280
+ mv "$tmp_state" "$STALL_DETECTOR_STATE" 2>/dev/null || true
281
+
282
+ while true; do
283
+ local check_result
284
+ check_result=$(cmd_check)
285
+
286
+ local stalled_count deadlocked_count looping_count
287
+ stalled_count=$(echo "$check_result" | jq '.stalled | length' 2>/dev/null || echo "0")
288
+ deadlocked_count=$(echo "$check_result" | jq '.deadlocked | length' 2>/dev/null || echo "0")
289
+ looping_count=$(echo "$check_result" | jq '.looping | length' 2>/dev/null || echo "0")
290
+
291
+ # Log check result if any stalls detected
292
+ if [[ "$stalled_count" -gt 0 || "$deadlocked_count" -gt 0 || "$looping_count" -gt 0 ]]; then
293
+ warn "Detected stalls: $stalled_count stalled, $deadlocked_count deadlocked, $looping_count looping"
294
+
295
+ # Auto-abort if enabled
296
+ if [[ "$AUTO_ABORT_ENABLED" == "true" ]]; then
297
+ local stalled_pids
298
+ stalled_pids=$(echo "$check_result" | jq -r '.stalled[] // empty' 2>/dev/null || true)
299
+ while IFS= read -r pid_or_id; do
300
+ [[ -z "$pid_or_id" ]] && continue
301
+ info "Auto-aborting stalled pipeline: $pid_or_id"
302
+ cmd_abort "$pid_or_id" --reason "auto-abort: stall detected (no heartbeat update)"
303
+ done <<< "$stalled_pids"
304
+ fi
305
+ fi
306
+
307
+ [[ "$once" == "true" ]] && break
308
+
309
+ sleep "$interval"
310
+ done
311
+ }
312
+
313
+ # ─── Show configuration ─────────────────────────────────────────────────────
314
+ cmd_config() {
315
+ ensure_dirs
316
+ load_config
317
+
318
+ echo ""
319
+ echo -e "${BOLD}Stall Detection Configuration${RESET}"
320
+ echo -e "${DIM}═════════════════════════════════════${RESET}"
321
+ echo ""
322
+ echo -e " ${CYAN}stall_timeout_seconds${RESET} ${STALL_TIMEOUT_SECONDS}"
323
+ echo -e " ${CYAN}max_stage_retries${RESET} ${MAX_STAGE_RETRIES}"
324
+ echo -e " ${CYAN}heartbeat_max_age_seconds${RESET} ${HEARTBEAT_MAX_AGE_SECONDS}"
325
+ echo -e " ${CYAN}auto_abort_enabled${RESET} ${AUTO_ABORT_ENABLED}"
326
+ echo -e " ${CYAN}create_issue_on_abort${RESET} ${CREATE_ISSUE_ON_ABORT}"
327
+ echo -e " ${CYAN}detector_interval${RESET} ${STALL_DETECTOR_INTERVAL}s"
328
+ echo ""
329
+ echo -e "${DIM}Configure via: .claude/daemon-config.json${RESET}"
330
+ echo -e "${DIM}Example:${RESET}"
331
+ echo ' {
332
+ "stall_detection": {
333
+ "stall_timeout_seconds": 300,
334
+ "max_stage_retries": 5,
335
+ "heartbeat_max_age_seconds": 120,
336
+ "auto_abort_enabled": true,
337
+ "create_issue_on_abort": false,
338
+ "interval": 30
339
+ }
340
+ }'
341
+ echo ""
342
+ }
343
+
344
+ # ─── Show detection statistics ──────────────────────────────────────────────
345
+ cmd_status() {
346
+ ensure_dirs
347
+
348
+ echo ""
349
+ echo -e "${BOLD}Stall Detection Status${RESET}"
350
+ echo -e "${DIM}══════════════════════${RESET}"
351
+ echo ""
352
+
353
+ # Show detector process info if running
354
+ if [[ -f "$STALL_DETECTOR_STATE" ]]; then
355
+ local pid started_at checks aborts
356
+ pid=$(jq -r '.pid // empty' "$STALL_DETECTOR_STATE" 2>/dev/null || true)
357
+ started_at=$(jq -r '.started_at // empty' "$STALL_DETECTOR_STATE" 2>/dev/null || true)
358
+ checks=$(jq -r '.checks // 0' "$STALL_DETECTOR_STATE" 2>/dev/null || true)
359
+ aborts=$(jq -r '.aborts // 0' "$STALL_DETECTOR_STATE" 2>/dev/null || true)
360
+
361
+ if [[ -n "$pid" ]] && kill -0 "$pid" 2>/dev/null; then
362
+ echo -e " ${GREEN}✓${RESET} Detector running (PID: $pid)"
363
+ echo -e " Started: $started_at"
364
+ echo -e " Checks performed: $checks"
365
+ echo -e " Pipelines aborted: $aborts"
366
+ else
367
+ echo -e " ${YELLOW}✗${RESET} Detector not running"
368
+ fi
369
+ else
370
+ echo -e " ${YELLOW}✗${RESET} Detector not running"
371
+ fi
372
+
373
+ echo ""
374
+
375
+ # Show recent stalls
376
+ if [[ -d "$HEARTBEAT_DIR" ]]; then
377
+ local stall_count
378
+ stall_count=$(ls -1 "$HEARTBEAT_DIR"/*.json 2>/dev/null | wc -l)
379
+ echo -e " ${CYAN}Active heartbeats: $stall_count${RESET}"
380
+ fi
381
+
382
+ echo ""
383
+ }
384
+
385
+ # ─── Command Router ────────────────────────────────────────────────────────
386
+ main() {
387
+ local cmd="${1:-help}"
388
+ shift 2>/dev/null || true
389
+
390
+ case "$cmd" in
391
+ check) cmd_check "$@" ;;
392
+ watch) cmd_watch "$@" ;;
393
+ abort) cmd_abort "$@" ;;
394
+ config) cmd_config "$@" ;;
395
+ status) cmd_status "$@" ;;
396
+ help|--help|-h) show_help ;;
397
+ *)
398
+ error "Unknown command: ${cmd}"
399
+ echo ""
400
+ show_help
401
+ exit 1
402
+ ;;
403
+ esac
404
+ }
405
+
406
+ main "$@"
@@ -6,8 +6,9 @@
6
6
  set -euo pipefail
7
7
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
8
8
 
9
- VERSION="3.2.0"
9
+ VERSION="3.3.0"
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
+ # shellcheck disable=SC2034
11
12
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
12
13
 
13
14
  # ─── Cross-platform compatibility ──────────────────────────────────────────
@@ -29,7 +30,8 @@ fi
29
30
  if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
30
31
  emit_event() {
31
32
  local event_type="$1"; shift; mkdir -p "${HOME}/.shipwright"
32
- local payload="{\"ts\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"type\":\"$event_type\""
33
+ local payload
34
+ payload="{\"ts\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"type\":\"$event_type\""
33
35
  while [[ $# -gt 0 ]]; do local key="${1%%=*}" val="${1#*=}"; payload="${payload},\"${key}\":\"${val}\""; shift; done
34
36
  echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
35
37
  }
@@ -68,7 +70,8 @@ cmd_yesterday() {
68
70
  now_epoch="$(now_epoch)"
69
71
  local cutoff=$((now_epoch - SECONDS_24H))
70
72
 
71
- local report_file="${STANDUP_DIR}/yesterday-$(date +%Y-%m-%d).txt"
73
+ local report_file
74
+ report_file="${STANDUP_DIR}/yesterday-$(date +%Y-%m-%d).txt"
72
75
 
73
76
  {
74
77
  echo "╔════════════════════════════════════════════════════════════════════╗"
@@ -143,7 +146,8 @@ cmd_yesterday() {
143
146
  cmd_today() {
144
147
  ensure_dirs
145
148
 
146
- local report_file="${STANDUP_DIR}/today-$(date +%Y-%m-%d).txt"
149
+ local report_file
150
+ report_file="${STANDUP_DIR}/today-$(date +%Y-%m-%d).txt"
147
151
 
148
152
  {
149
153
  echo "╔════════════════════════════════════════════════════════════════════╗"
@@ -193,7 +197,8 @@ cmd_today() {
193
197
  cmd_blockers() {
194
198
  ensure_dirs
195
199
 
196
- local report_file="${STANDUP_DIR}/blockers-$(date +%Y-%m-%d).txt"
200
+ local report_file
201
+ report_file="${STANDUP_DIR}/blockers-$(date +%Y-%m-%d).txt"
197
202
 
198
203
  {
199
204
  echo "╔════════════════════════════════════════════════════════════════════╗"
@@ -261,7 +266,8 @@ cmd_blockers() {
261
266
  cmd_velocity() {
262
267
  ensure_dirs
263
268
 
264
- local report_file="${STANDUP_DIR}/velocity-$(date +%Y-%m-%d).txt"
269
+ local report_file
270
+ report_file="${STANDUP_DIR}/velocity-$(date +%Y-%m-%d).txt"
265
271
 
266
272
  {
267
273
  echo "╔════════════════════════════════════════════════════════════════════╗"
@@ -335,7 +341,8 @@ cmd_velocity() {
335
341
  cmd_digest() {
336
342
  ensure_dirs
337
343
 
338
- local report_file="${STANDUP_DIR}/digest-$(date +%Y-%m-%d-%H%M%S).txt"
344
+ local report_file
345
+ report_file="${STANDUP_DIR}/digest-$(date +%Y-%m-%d-%H%M%S).txt"
339
346
 
340
347
  {
341
348
  echo ""
@@ -493,6 +500,7 @@ cmd_notify() {
493
500
  if [[ -z "$message_file" ]]; then
494
501
  # Generate a digest
495
502
  message_file=$(mktemp)
503
+ # shellcheck disable=SC2064
496
504
  trap "rm -f '$message_file'" RETURN
497
505
  cmd_digest > "$message_file" 2>&1 || true
498
506
  fi
@@ -4,7 +4,7 @@
4
4
  # ║ ║
5
5
  # ║ Shows running teams, agent windows, and task progress. ║
6
6
  # ╚═══════════════════════════════════════════════════════════════════════════╝
7
- VERSION="3.2.0"
7
+ VERSION="3.3.0"
8
8
  set -euo pipefail
9
9
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
10
10
 
@@ -47,6 +47,7 @@ fi
47
47
  if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
48
48
  emit_event() {
49
49
  local event_type="$1"; shift; mkdir -p "${HOME}/.shipwright"
50
+ # shellcheck disable=SC2155
50
51
  local payload="{\"ts\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"type\":\"$event_type\""
51
52
  while [[ $# -gt 0 ]]; do local key="${1%%=*}" val="${1#*=}"; payload="${payload},\"${key}\":\"${val}\""; shift; done
52
53
  echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
@@ -572,6 +573,7 @@ if [[ -f "$STATE_FILE" ]]; then
572
573
  if [[ "$completed_count" -gt 0 ]]; then
573
574
  echo ""
574
575
  echo -e " ${BOLD}Recent Completions${RESET}"
576
+ # shellcheck disable=SC2034
575
577
  while IFS=$'\t' read -r c_num c_result c_dur c_at; do
576
578
  [[ -z "$c_num" ]] && continue
577
579
  if [[ "$c_result" == "success" ]]; then
@@ -7,7 +7,7 @@
7
7
  # When sourced, do NOT add set -euo pipefail — the parent handles that.
8
8
  # When run directly, main() sets up the error handling.
9
9
 
10
- VERSION="3.2.0"
10
+ VERSION="3.3.0"
11
11
 
12
12
  # ─── Paths (set defaults if not provided by parent) ──────────────────────────
13
13
  SCRIPT_DIR="${SCRIPT_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}"
@@ -25,6 +25,7 @@ EVENTS_FILE="${EVENTS_FILE:-${HOME}/.shipwright/events.jsonl}"
25
25
  if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
26
26
  emit_event() {
27
27
  local event_type="$1"; shift; mkdir -p "${HOME}/.shipwright"
28
+ # shellcheck disable=SC2155
28
29
  local payload="{\"ts\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"type\":\"$event_type\""
29
30
  while [[ $# -gt 0 ]]; do local key="${1%%=*}" val="${1#*=}"; payload="${payload},\"${key}\":\"${val}\""; shift; done
30
31
  echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
@@ -46,6 +47,7 @@ fi
46
47
  STRATEGIC_MAX_ISSUES=5
47
48
  STRATEGIC_COOLDOWN_SECONDS=14400 # 4 hours
48
49
  STRATEGIC_MODEL="claude-sonnet-4-5-20250929"
50
+ # shellcheck disable=SC2034
49
51
  STRATEGIC_MAX_TOKENS=4096
50
52
  STRATEGIC_STRATEGY_LINES=200
51
53
  STRATEGIC_LABELS="auto-patrol,ready-to-build,strategic,shipwright"
@@ -494,6 +496,7 @@ strategic_call_api() {
494
496
 
495
497
  local tmp_prompt
496
498
  tmp_prompt=$(mktemp)
499
+ # shellcheck disable=SC2064
497
500
  trap "rm -f '$tmp_prompt'" RETURN
498
501
  printf '%s' "$prompt" > "$tmp_prompt"
499
502
 
@@ -5,7 +5,8 @@
5
5
  # ║ Streams tmux pane output in real-time to the dashboard or CLI. ║
6
6
  # ║ Captures output periodically, tags by agent/team, supports replay. ║
7
7
  # ╚═══════════════════════════════════════════════════════════════════════════╝
8
- VERSION="3.2.0"
8
+ # shellcheck disable=SC2034
9
+ VERSION="3.3.0"
9
10
  set -euo pipefail
10
11
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
11
12
 
@@ -30,6 +31,7 @@ fi
30
31
  if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
31
32
  emit_event() {
32
33
  local event_type="$1"; shift; mkdir -p "${HOME}/.shipwright"
34
+ # shellcheck disable=SC2155
33
35
  local payload="{\"ts\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"type\":\"$event_type\""
34
36
  while [[ $# -gt 0 ]]; do local key="${1%%=*}" val="${1#*=}"; payload="${payload},\"${key}\":\"${val}\""; shift; done
35
37
  echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
@@ -48,6 +50,7 @@ load_config() {
48
50
  if [[ -f "$STREAM_CONFIG" ]]; then
49
51
  CAPTURE_INTERVAL=$(jq -r '.capture_interval_seconds // 1' "$STREAM_CONFIG" 2>/dev/null || echo 1)
50
52
  BUFFER_LINES=$(jq -r '.buffer_lines // 500' "$STREAM_CONFIG" 2>/dev/null || echo 500)
53
+ # shellcheck disable=SC2034
51
54
  OUTPUT_FORMAT=$(jq -r '.output_format // "jsonl"' "$STREAM_CONFIG" 2>/dev/null || echo "jsonl")
52
55
  fi
53
56
  }
@@ -97,6 +100,7 @@ capture_pane_output() {
97
100
  # Write JSONL entry with timestamp, pane_id, agent, team, content
98
101
  local tmp_file
99
102
  tmp_file=$(mktemp)
103
+ # shellcheck disable=SC2064
100
104
  trap "rm -f '$tmp_file'" RETURN
101
105
 
102
106
  {
@@ -116,6 +120,7 @@ capture_pane_output() {
116
120
  line_count=$(wc -l < "$pane_file" 2>/dev/null || true)
117
121
  line_count="${line_count:-0}"
118
122
  if [[ "$line_count" -gt "$BUFFER_LINES" ]]; then
123
+ # shellcheck disable=SC2034
119
124
  local skip=$((line_count - BUFFER_LINES))
120
125
  tail -n "$BUFFER_LINES" "$pane_file" > "${pane_file}.tmp"
121
126
  mv "${pane_file}.tmp" "$pane_file"
@@ -349,6 +354,7 @@ stream_config() {
349
354
  # Create tmp file for atomic write
350
355
  local tmp_file
351
356
  tmp_file=$(mktemp)
357
+ # shellcheck disable=SC2064
352
358
  trap "rm -f '$tmp_file'" RETURN
353
359
 
354
360
  case "$key" in