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
@@ -4,7 +4,7 @@
4
4
  # ║ ║
5
5
  # ║ Checks prerequisites, installed files, PATH, and common issues. ║
6
6
  # ╚═══════════════════════════════════════════════════════════════════════════╝
7
- VERSION="3.1.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
 
@@ -27,6 +27,7 @@ fi
27
27
  if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
28
28
  emit_event() {
29
29
  local event_type="$1"; shift; mkdir -p "${HOME}/.shipwright"
30
+ # shellcheck disable=SC2155
30
31
  local payload="{\"ts\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"type\":\"$event_type\""
31
32
  while [[ $# -gt 0 ]]; do local key="${1%%=*}" val="${1#*=}"; payload="${payload},\"${key}\":\"${val}\""; shift; done
32
33
  echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
@@ -39,10 +40,14 @@ SKIP_PLATFORM_SCAN=false
39
40
 
40
41
  # Parse doctor flags
41
42
  INTELLIGENCE_ONLY=false
43
+ DOCTOR_FIX_MODE=false
44
+ DOCTOR_FIX_DRY_RUN=false
42
45
  for _arg in "$@"; do
43
46
  case "$_arg" in
44
47
  --skip-platform-scan) SKIP_PLATFORM_SCAN=true ;;
45
48
  --intelligence) INTELLIGENCE_ONLY=true ;;
49
+ --fix) DOCTOR_FIX_MODE=true ;;
50
+ --fix-dry) DOCTOR_FIX_DRY_RUN=true; DOCTOR_FIX_MODE=true ;;
46
51
  --version|-V) echo "sw-doctor $VERSION"; exit 0 ;;
47
52
  esac
48
53
  done
@@ -51,6 +56,291 @@ check_pass() { success "$*"; PASS=$((PASS + 1)); }
51
56
  check_warn() { warn "$*"; WARN=$((WARN + 1)); }
52
57
  check_fail() { error "$*"; FAIL=$((FAIL + 1)); }
53
58
 
59
+ # ─── Auto-fix helper functions ──────────────────────────────────────────────
60
+ doctor_fix_missing_dirs() {
61
+ local result="fixed"
62
+ local dirs=(
63
+ "$HOME/.shipwright"
64
+ "$HOME/.shipwright/optimization"
65
+ "$HOME/.shipwright/memory"
66
+ ".claude"
67
+ ".claude/pipeline-artifacts"
68
+ ".claude/agents"
69
+ ".claude/hooks"
70
+ )
71
+
72
+ for dir in "${dirs[@]}"; do
73
+ if [[ ! -d "$dir" ]]; then
74
+ if [[ "$DOCTOR_FIX_DRY_RUN" == "true" ]]; then
75
+ info " [DRY] Would create directory: $dir"
76
+ else
77
+ if ! mkdir -p "$dir" 2>/dev/null; then
78
+ result="skipped"
79
+ else
80
+ emit_event "doctor_fix" "type=mkdir" "path=$dir"
81
+ fi
82
+ fi
83
+ fi
84
+ done
85
+ echo "$result"
86
+ }
87
+
88
+ doctor_fix_permissions() {
89
+ local result="fixed"
90
+ local script_dir="${1:-.}"
91
+
92
+ if [[ ! -d "$script_dir" ]]; then
93
+ echo "skipped"
94
+ return
95
+ fi
96
+
97
+ # Fix script permissions
98
+ for script in "$script_dir"/sw-*.sh; do
99
+ if [[ -f "$script" && ! -x "$script" ]]; then
100
+ if [[ "$DOCTOR_FIX_DRY_RUN" == "true" ]]; then
101
+ info " [DRY] Would chmod +x: $script"
102
+ else
103
+ chmod +x "$script"
104
+ emit_event "doctor_fix" "type=chmod" "path=$script"
105
+ fi
106
+ fi
107
+ done
108
+
109
+ echo "$result"
110
+ }
111
+
112
+ doctor_fix_missing_config() {
113
+ local result="fixed"
114
+
115
+ # Ensure .claude directory exists
116
+ mkdir -p .claude 2>/dev/null || { result="skipped"; echo "$result"; return; }
117
+
118
+ # Create .claude/daemon-config.json
119
+ local daemon_cfg=".claude/daemon-config.json"
120
+ if [[ ! -f "$daemon_cfg" ]]; then
121
+ if [[ "$DOCTOR_FIX_DRY_RUN" == "true" ]]; then
122
+ info " [DRY] Would create: $daemon_cfg"
123
+ else
124
+ local tmp_cfg="${daemon_cfg}.tmp.$$"
125
+ cat > "$tmp_cfg" <<'EOF'
126
+ {
127
+ "max_parallel": 2,
128
+ "auto_scale": false,
129
+ "max_workers": 8,
130
+ "min_workers": 1,
131
+ "auto_scale_interval": 5,
132
+ "worker_mem_gb": 4,
133
+ "estimated_cost_per_job_usd": 5.0,
134
+ "auto_template": false,
135
+ "max_retries": 2,
136
+ "priority_lane": false,
137
+ "self_optimize": false,
138
+ "intelligence": {
139
+ "enabled": "auto",
140
+ "composer_enabled": "auto",
141
+ "prediction_enabled": true,
142
+ "cache_ttl_seconds": 3600,
143
+ "adversarial_enabled": false,
144
+ "simulation_enabled": false,
145
+ "architecture_enabled": false,
146
+ "ab_test_ratio": 0.2,
147
+ "anomaly_threshold": 3.0
148
+ }
149
+ }
150
+ EOF
151
+ mv "$tmp_cfg" "$daemon_cfg"
152
+ emit_event "doctor_fix" "type=create_config" "path=$daemon_cfg"
153
+ fi
154
+ fi
155
+
156
+ # Create .claude/settings.json
157
+ local settings_cfg=".claude/settings.json"
158
+ if [[ ! -f "$settings_cfg" ]]; then
159
+ if [[ "$DOCTOR_FIX_DRY_RUN" == "true" ]]; then
160
+ info " [DRY] Would create: $settings_cfg"
161
+ else
162
+ local tmp_cfg="${settings_cfg}.tmp.$$"
163
+ cat > "$tmp_cfg" <<'EOF'
164
+ {
165
+ "hooks": {
166
+ "pre-tool-use": ".claude/hooks/pre-tool-use.sh",
167
+ "post-tool-use": ".claude/hooks/post-tool-use.sh",
168
+ "session-started": ".claude/hooks/session-started.sh"
169
+ }
170
+ }
171
+ EOF
172
+ mv "$tmp_cfg" "$settings_cfg"
173
+ emit_event "doctor_fix" "type=create_config" "path=$settings_cfg"
174
+ fi
175
+ fi
176
+
177
+ # Create ~/.shipwright/budget.json
178
+ local budget_file="$HOME/.shipwright/budget.json"
179
+ if [[ ! -f "$budget_file" ]]; then
180
+ if [[ "$DOCTOR_FIX_DRY_RUN" == "true" ]]; then
181
+ info " [DRY] Would create: $budget_file"
182
+ else
183
+ local tmp_file="${budget_file}.tmp.$$"
184
+ cat > "$tmp_file" <<'EOF'
185
+ {
186
+ "daily_limit_usd": 10.0,
187
+ "reset_hour_utc": 0,
188
+ "enabled": true
189
+ }
190
+ EOF
191
+ mkdir -p "$(dirname "$budget_file")"
192
+ mv "$tmp_file" "$budget_file"
193
+ emit_event "doctor_fix" "type=create_config" "path=$budget_file"
194
+ fi
195
+ fi
196
+
197
+ echo "$result"
198
+ }
199
+
200
+ doctor_fix_tmux_config() {
201
+ local result="fixed"
202
+ local home_tmux_conf="$HOME/.tmux.conf"
203
+
204
+ # Check if overlay exists
205
+ local overlay_path="$HOME/.tmux/shipwright-overlay.conf"
206
+ if [[ ! -f "$overlay_path" ]]; then
207
+ # Try to find it in the Shipwright repo
208
+ local repo_overlay="${SCRIPT_DIR}/../tmux/shipwright-overlay.conf"
209
+ if [[ -f "$repo_overlay" ]]; then
210
+ if [[ "$DOCTOR_FIX_DRY_RUN" == "true" ]]; then
211
+ info " [DRY] Would copy tmux overlay to: $overlay_path"
212
+ else
213
+ mkdir -p "$(dirname "$overlay_path")"
214
+ cp "$repo_overlay" "$overlay_path"
215
+ emit_event "doctor_fix" "type=copy_tmux" "path=$overlay_path"
216
+ fi
217
+ else
218
+ result="skipped"
219
+ fi
220
+ fi
221
+
222
+ # Check if .tmux.conf sources the overlay
223
+ if [[ -f "$home_tmux_conf" ]] && ! grep -q "shipwright-overlay" "$home_tmux_conf"; then
224
+ if [[ "$DOCTOR_FIX_DRY_RUN" == "true" ]]; then
225
+ info " [DRY] Would update .tmux.conf to source overlay"
226
+ else
227
+ # Backup existing config
228
+ cp "$home_tmux_conf" "${home_tmux_conf}.bak"
229
+ echo "source-file ~/.tmux/shipwright-overlay.conf" >> "$home_tmux_conf"
230
+ emit_event "doctor_fix" "type=update_tmux" "path=$home_tmux_conf"
231
+ fi
232
+ fi
233
+
234
+ echo "$result"
235
+ }
236
+
237
+ doctor_fix_hooks() {
238
+ local result="fixed"
239
+ local hooks_dir=".claude/hooks"
240
+
241
+ mkdir -p "$hooks_dir" 2>/dev/null || true
242
+
243
+ # Try to copy hooks from Shipwright repo templates
244
+ local repo_hooks="${SCRIPT_DIR}/../templates/hooks"
245
+ if [[ -d "$repo_hooks" ]]; then
246
+ for hook_file in "$repo_hooks"/*.sh; do
247
+ if [[ -f "$hook_file" ]]; then
248
+ local hook_name="$(basename "$hook_file")"
249
+ local dest_hook="$hooks_dir/$hook_name"
250
+ if [[ ! -f "$dest_hook" ]]; then
251
+ if [[ "$DOCTOR_FIX_DRY_RUN" == "true" ]]; then
252
+ info " [DRY] Would install hook: $dest_hook"
253
+ else
254
+ cp "$hook_file" "$dest_hook"
255
+ chmod +x "$dest_hook"
256
+ emit_event "doctor_fix" "type=install_hook" "hook=$hook_name"
257
+ fi
258
+ fi
259
+ fi
260
+ done
261
+ else
262
+ result="skipped"
263
+ fi
264
+
265
+ echo "$result"
266
+ }
267
+
268
+ doctor_auto_fix() {
269
+ echo ""
270
+ echo -e "${PURPLE}${BOLD} AUTO-FIX SUMMARY${RESET}"
271
+ echo -e "${DIM} ──────────────────────────────────────────${RESET}"
272
+ echo ""
273
+
274
+ local fixes_applied=0
275
+ local fixes_skipped=0
276
+
277
+ # Fix 1: Missing directories
278
+ info "Creating missing directories..."
279
+ result=$(doctor_fix_missing_dirs)
280
+ if [[ "$result" == "fixed" ]]; then
281
+ success " Directories created/verified"
282
+ fixes_applied=$((fixes_applied + 1))
283
+ else
284
+ warn " Some directories could not be created"
285
+ fixes_skipped=$((fixes_skipped + 1))
286
+ fi
287
+
288
+ # Fix 2: Permissions
289
+ info "Fixing script permissions..."
290
+ result=$(doctor_fix_permissions "${SCRIPT_DIR}")
291
+ if [[ "$result" == "fixed" ]]; then
292
+ success " Script permissions fixed"
293
+ fixes_applied=$((fixes_applied + 1))
294
+ else
295
+ warn " Could not fix some permissions"
296
+ fixes_skipped=$((fixes_skipped + 1))
297
+ fi
298
+
299
+ # Fix 3: Missing config files
300
+ info "Creating missing config files..."
301
+ result=$(doctor_fix_missing_config)
302
+ if [[ "$result" == "fixed" ]]; then
303
+ success " Config files created"
304
+ fixes_applied=$((fixes_applied + 1))
305
+ else
306
+ warn " Could not create some config files"
307
+ fixes_skipped=$((fixes_skipped + 1))
308
+ fi
309
+
310
+ # Fix 4: tmux configuration
311
+ info "Configuring tmux..."
312
+ result=$(doctor_fix_tmux_config)
313
+ if [[ "$result" == "fixed" ]]; then
314
+ success " tmux configured"
315
+ fixes_applied=$((fixes_applied + 1))
316
+ elif [[ "$result" == "skipped" ]]; then
317
+ warn " tmux configuration skipped (overlay not found)"
318
+ fixes_skipped=$((fixes_skipped + 1))
319
+ fi
320
+
321
+ # Fix 5: Install hooks
322
+ info "Installing hooks..."
323
+ result=$(doctor_fix_hooks)
324
+ if [[ "$result" == "fixed" ]]; then
325
+ success " Hooks installed"
326
+ fixes_applied=$((fixes_applied + 1))
327
+ elif [[ "$result" == "skipped" ]]; then
328
+ warn " Hooks skipped (templates not found)"
329
+ fixes_skipped=$((fixes_skipped + 1))
330
+ fi
331
+
332
+ echo ""
333
+ echo -e " ${GREEN}${BOLD}${fixes_applied}${RESET} fixes applied ${YELLOW}${BOLD}${fixes_skipped}${RESET} skipped"
334
+ echo ""
335
+
336
+ if [[ "$DOCTOR_FIX_DRY_RUN" == "true" ]]; then
337
+ info "Dry-run complete — no changes made"
338
+ else
339
+ info "Re-running doctor checks to verify fixes..."
340
+ echo ""
341
+ fi
342
+ }
343
+
54
344
  # ─── Header ─────────────────────────────────────────────────────────────────
55
345
  echo ""
56
346
  echo -e "${CYAN}${BOLD} Shipwright — Doctor${RESET}"
@@ -221,6 +511,7 @@ fi
221
511
 
222
512
  # Bash version
223
513
  BASH_MAJOR="${BASH_VERSINFO[0]:-0}"
514
+ # shellcheck disable=SC2034
224
515
  BASH_MINOR="${BASH_VERSINFO[1]:-0}"
225
516
  if [[ "$BASH_MAJOR" -ge 5 ]]; then
226
517
  check_pass "bash ${BASH_VERSION}"
@@ -266,6 +557,33 @@ else
266
557
  echo -e " ${DIM}Copy from settings.json.template${RESET}"
267
558
  fi
268
559
 
560
+ # ─── File Permission Validation ───────────────────────────────────
561
+ # Check sensitive config files have restrictive permissions (600)
562
+ _perm_issues=0
563
+ for config_file in "$HOME/.claude/settings.json" "$HOME/.shipwright/daemon-config.json" "$(pwd)/.claude/daemon-config.json"; do
564
+ if [[ -f "$config_file" ]]; then
565
+ # Get file permissions
566
+ _perms=""
567
+ if command -v stat >/dev/null 2>&1; then
568
+ # GNU stat: stat -c %a, BSD stat: stat -f %OLp
569
+ if [[ "$(uname -s)" == "Darwin" ]]; then
570
+ _perms=$(stat -f %OLp "$config_file" 2>/dev/null | tail -c 4)
571
+ else
572
+ _perms=$(stat -c %a "$config_file" 2>/dev/null)
573
+ fi
574
+ fi
575
+
576
+ if [[ -n "${_perms:-}" && "$_perms" != "600" ]]; then
577
+ check_warn "File $config_file is world-readable (perms: $_perms, should be 600)"
578
+ _perm_issues=$((_perm_issues + 1))
579
+ fi
580
+ fi
581
+ done
582
+
583
+ if [[ $_perm_issues -eq 0 ]]; then
584
+ check_pass "File permissions: all sensitive configs restricted to owner-only"
585
+ fi
586
+
269
587
  # Hooks directory
270
588
  HOOKS_DIR="$HOME/.claude/hooks"
271
589
  if [[ -d "$HOOKS_DIR" ]]; then
@@ -320,6 +638,39 @@ if [[ -d "$HOOKS_DIR" && -f "$HOME/.claude/settings.json" ]] && jq -e '.' "$HOME
320
638
  fi
321
639
  fi
322
640
 
641
+ # Hook security validation — check for untrusted repo-level hooks
642
+ # Warn if repo-level .claude/hooks/ contains unexpected commands
643
+ if [[ -d "$(pwd)/.claude/hooks" ]]; then
644
+ _repo_hook_dir="$(pwd)/.claude/hooks"
645
+ _trusted_hooks_dir="${HOME}/.claude/hooks"
646
+ _untrusted_hook_count=0
647
+
648
+ # Check if CLAUDE_CODE_VERIFY_HOOKS is enabled for extra caution
649
+ if [[ -n "${CLAUDE_CODE_VERIFY_HOOKS:-}" ]]; then
650
+ for repo_hook in "$_repo_hook_dir"/*.sh; do
651
+ [[ -f "$repo_hook" ]] || continue
652
+ _hook_name="$(basename "$repo_hook")"
653
+ _trusted_hook="$_trusted_hooks_dir/$_hook_name"
654
+
655
+ # If a trusted version exists, compare checksums
656
+ if [[ -f "$_trusted_hook" ]]; then
657
+ if ! cmp -s "$repo_hook" "$_trusted_hook"; then
658
+ check_warn "Repo hook differs from trusted version: .claude/hooks/$_hook_name"
659
+ _untrusted_hook_count=$((_untrusted_hook_count + 1))
660
+ fi
661
+ else
662
+ # No trusted version — this is an unknown hook
663
+ check_warn "Repo contains unknown hook: .claude/hooks/$_hook_name"
664
+ _untrusted_hook_count=$((_untrusted_hook_count + 1))
665
+ fi
666
+ done
667
+
668
+ if [[ $_untrusted_hook_count -gt 0 ]]; then
669
+ echo -e " ${DIM}Enable hook verification with: export CLAUDE_CODE_VERIFY_HOOKS=1${RESET}"
670
+ fi
671
+ fi
672
+ fi
673
+
323
674
  # ═════════════════════════════════════════════════════════════════════════════
324
675
  # 3. Agent Teams
325
676
  # ═════════════════════════════════════════════════════════════════════════════
@@ -853,7 +1204,7 @@ if [[ -f "$MACHINES_FILE" ]]; then
853
1204
 
854
1205
  if [[ -n "$m_host" ]]; then
855
1206
  ssh_target="${m_user:+${m_user}@}${m_host}"
856
- if ssh -o ConnectTimeout=5 -o BatchMode=yes -p "$m_port" "$ssh_target" true 2>/dev/null; then
1207
+ if ssh -n -o ConnectTimeout=5 -o BatchMode=yes -p "$m_port" "$ssh_target" true 2>/dev/null; then
857
1208
  check_pass "SSH: ${m_name} (${ssh_target}) reachable"
858
1209
  else
859
1210
  check_warn "SSH: ${m_name} (${ssh_target}) unreachable"
@@ -1127,6 +1478,86 @@ else
1127
1478
  echo -e " ${DIM}Install: brew install sqlite (macOS) or apt install sqlite3 (Linux)${RESET}"
1128
1479
  fi
1129
1480
 
1481
+ # ═════════════════════════════════════════════════════════════════════════════
1482
+ # 15a. Claude Code Feature Configuration
1483
+ # ═════════════════════════════════════════════════════════════════════════════
1484
+ echo ""
1485
+ echo -e "${PURPLE}${BOLD} CLAUDE CODE FEATURES${RESET}"
1486
+ echo -e "${DIM} ──────────────────────────────────────────${RESET}"
1487
+
1488
+ _SETTINGS_FILE="$(pwd)/.claude/settings.json"
1489
+ if [[ ! -f "$_SETTINGS_FILE" ]]; then
1490
+ _SCRIPT_DIR_CLAUDE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
1491
+ _REPO_ROOT_CLAUDE="$(cd "$_SCRIPT_DIR_CLAUDE/.." 2>/dev/null && pwd)"
1492
+ [[ -f "$_REPO_ROOT_CLAUDE/.claude/settings.json" ]] && _SETTINGS_FILE="$_REPO_ROOT_CLAUDE/.claude/settings.json"
1493
+ fi
1494
+
1495
+ if [[ -f "$_SETTINGS_FILE" ]] && command -v jq >/dev/null 2>&1; then
1496
+ # Check effort level
1497
+ _effort_val=$(jq -r '.env.CLAUDE_CODE_EFFORT_LEVEL // empty' "$_SETTINGS_FILE" 2>/dev/null || echo "")
1498
+ if [[ -n "$_effort_val" ]]; then
1499
+ case "$_effort_val" in
1500
+ low|medium|high) check_pass "Effort level configured: ${_effort_val}" ;;
1501
+ *) check_fail "Invalid effort level in settings.json: ${_effort_val} (must be low/medium/high)" ;;
1502
+ esac
1503
+ else
1504
+ check_warn "CLAUDE_CODE_EFFORT_LEVEL not configured"
1505
+ fi
1506
+
1507
+ # Check ENABLE_TOOL_SEARCH
1508
+ _tool_search=$(jq -r '.env.ENABLE_TOOL_SEARCH // empty' "$_SETTINGS_FILE" 2>/dev/null || echo "")
1509
+ if [[ -n "$_tool_search" ]]; then
1510
+ check_pass "MCP Tool Search: ${_tool_search}"
1511
+ else
1512
+ check_warn "ENABLE_TOOL_SEARCH not set (recommend: auto)"
1513
+ fi
1514
+
1515
+ # Check MAX_MCP_OUTPUT_TOKENS
1516
+ _mcp_tokens=$(jq -r '.env.MAX_MCP_OUTPUT_TOKENS // empty' "$_SETTINGS_FILE" 2>/dev/null || echo "")
1517
+ if [[ -n "$_mcp_tokens" ]]; then
1518
+ check_pass "MCP output limit: ${_mcp_tokens} tokens"
1519
+ else
1520
+ check_warn "MAX_MCP_OUTPUT_TOKENS not set (recommend: 50000)"
1521
+ fi
1522
+
1523
+ # Check managed-mcp.json
1524
+ if [[ -f "$_SETTINGS_FILE" ]]; then
1525
+ _settings_dir=$(dirname "$_SETTINGS_FILE")
1526
+ if [[ -f "$_settings_dir/managed-mcp.json" ]]; then
1527
+ if jq empty "$_settings_dir/managed-mcp.json" 2>/dev/null; then
1528
+ check_pass "managed-mcp.json present and valid"
1529
+ else
1530
+ check_fail "managed-mcp.json has invalid JSON"
1531
+ fi
1532
+ fi
1533
+ fi
1534
+
1535
+ # Check schemas directory
1536
+ if [[ -d "$(pwd)/schemas" ]]; then
1537
+ _schema_count=$(find "$(pwd)/schemas" -name "*.json" 2>/dev/null | wc -l | tr -d ' ')
1538
+ check_pass "Schemas directory: ${_schema_count} schema(s) found"
1539
+ elif [[ -d "$(pwd)/../schemas" ]]; then
1540
+ _schema_count=$(find "$(pwd)/../schemas" -name "*.json" 2>/dev/null | wc -l | tr -d ' ')
1541
+ check_pass "Schemas directory: ${_schema_count} schema(s) found"
1542
+ fi
1543
+
1544
+ # Check lifecycle hooks registered
1545
+ _hook_count=0
1546
+ for _hook_name in WorktreeCreate WorktreeRemove InstructionsLoaded ConfigChange; do
1547
+ if jq -e ".hooks.${_hook_name}" "$_SETTINGS_FILE" >/dev/null 2>&1; then
1548
+ _hook_count=$((_hook_count + 1))
1549
+ fi
1550
+ done
1551
+ if [[ "$_hook_count" -eq 4 ]]; then
1552
+ check_pass "Lifecycle hooks: all 4 registered"
1553
+ elif [[ "$_hook_count" -gt 0 ]]; then
1554
+ check_warn "Lifecycle hooks: ${_hook_count}/4 registered"
1555
+ fi
1556
+ else
1557
+ check_warn "Claude Code configuration not found or jq unavailable"
1558
+ echo -e " ${DIM}Run: shipwright prep${RESET}"
1559
+ fi
1560
+
1130
1561
  # ═════════════════════════════════════════════════════════════════════════════
1131
1562
  # 15b. Intelligence Features
1132
1563
  # ═════════════════════════════════════════════════════════════════════════════
@@ -1162,6 +1593,26 @@ else
1162
1593
  check_warn "Platform hygiene not run — run: shipwright hygiene platform-refactor"
1163
1594
  fi
1164
1595
 
1596
+ # ═════════════════════════════════════════════════════════════════════════════
1597
+ # Auto-fix (if enabled)
1598
+ # ═════════════════════════════════════════════════════════════════════════════
1599
+ if [[ "$DOCTOR_FIX_MODE" == "true" ]]; then
1600
+ doctor_auto_fix
1601
+
1602
+ # Re-run checks after fixes if not in dry-run mode
1603
+ if [[ "$DOCTOR_FIX_DRY_RUN" != "true" ]]; then
1604
+ info "Re-running doctor checks..."
1605
+ # Reset counters
1606
+ PASS=0
1607
+ WARN=0
1608
+ FAIL=0
1609
+ # Re-execute the doctor script to get fresh results
1610
+ # We'll just continue with a message for now
1611
+ echo ""
1612
+ echo -e "${DIM} (Running full doctor check with fixes applied)${RESET}"
1613
+ fi
1614
+ fi
1615
+
1165
1616
  # ═════════════════════════════════════════════════════════════════════════════
1166
1617
  # Summary
1167
1618
  # ═════════════════════════════════════════════════════════════════════════════
@@ -8,11 +8,13 @@
8
8
  set -euo pipefail
9
9
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
10
10
 
11
- VERSION="3.1.0"
11
+ # shellcheck disable=SC2034
12
+ VERSION="3.3.0"
12
13
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
13
14
 
14
15
  # ─── Cross-platform compatibility ──────────────────────────────────────────
15
16
  _COMPAT="$SCRIPT_DIR/lib/compat.sh"
17
+ # shellcheck disable=SC1090
16
18
  [[ -f "$_COMPAT" ]] && source "$_COMPAT"
17
19
 
18
20
  # Canonical helpers (colors, output, events)
@@ -32,6 +34,7 @@ fi
32
34
  if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
33
35
  emit_event() {
34
36
  local event_type="$1"; shift; mkdir -p "${HOME}/.shipwright"
37
+ # shellcheck disable=SC2155
35
38
  local payload="{\"ts\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"type\":\"$event_type\""
36
39
  while [[ $# -gt 0 ]]; do local key="${1%%=*}" val="${1#*=}"; payload="${payload},\"${key}\":\"${val}\""; shift; done
37
40
  echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
@@ -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="3.1.0"
10
+ VERSION="3.3.0"
11
11
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
12
12
 
13
13
  # ─── Cross-platform compatibility ──────────────────────────────────────────
@@ -31,6 +31,7 @@ fi
31
31
  if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
32
32
  emit_event() {
33
33
  local event_type="$1"; shift; mkdir -p "${HOME}/.shipwright"
34
+ # shellcheck disable=SC2155
34
35
  local payload="{\"ts\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"type\":\"$event_type\""
35
36
  while [[ $# -gt 0 ]]; do local key="${1%%=*}" val="${1#*=}"; payload="${payload},\"${key}\":\"${val}\""; shift; done
36
37
  echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
@@ -50,8 +51,8 @@ ensure_durable_dir() {
50
51
  # ─── Event ID Generation ────────────────────────────────────────────────────
51
52
  generate_event_id() {
52
53
  local prefix="${1:-evt}"
53
- local ts=$(now_epoch)
54
- local rand=$(od -An -N4 -tu4 /dev/urandom | tr -d ' ')
54
+ local ts; ts=$(now_epoch)
55
+ local rand; rand=$(od -An -N4 -tu4 /dev/urandom | tr -d ' ')
55
56
  echo "${prefix}-${ts}-${rand}"
56
57
  }
57
58
 
@@ -481,17 +482,21 @@ cmd_status() {
481
482
  locks_dir="${DURABLE_DIR}/locks"
482
483
 
483
484
  local log_events log_size
484
- log_events=$(wc -l < "$log_file" 2>/dev/null || echo "0")
485
+ log_events=$(wc -l < "$log_file" 2>/dev/null || true)
486
+ log_events="${log_events:-0}"
485
487
  log_size=$(du -h "$log_file" 2>/dev/null | awk '{print $1}' || echo "0")
486
488
 
487
489
  local dlq_events
488
- dlq_events=$(wc -l < "$dlq_file" 2>/dev/null || echo "0")
490
+ dlq_events=$(wc -l < "$dlq_file" 2>/dev/null || true)
491
+ dlq_events="${dlq_events:-0}"
489
492
 
490
493
  local consumer_count
491
- consumer_count=$(find "$offsets_dir" -name "consumer-*.offset" 2>/dev/null | wc -l || echo "0")
494
+ consumer_count=$(find "$offsets_dir" -name "consumer-*.offset" 2>/dev/null | wc -l || true)
495
+ consumer_count="${consumer_count:-0}"
492
496
 
493
497
  local active_locks
494
- active_locks=$(find "$locks_dir" -type d -mindepth 1 2>/dev/null | wc -l || echo "0")
498
+ active_locks=$(find "$locks_dir" -type d -mindepth 1 2>/dev/null | wc -l || true)
499
+ active_locks="${active_locks:-0}"
495
500
 
496
501
  echo ""
497
502
  echo -e "${CYAN}${BOLD} Durable Workflow Status${RESET} ${DIM}v${VERSION}${RESET}"