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
@@ -0,0 +1,455 @@
1
+ #!/usr/bin/env bash
2
+ # Module guard - prevent double-sourcing
3
+ [[ -n "${_CAUSAL_GRAPH_LOADED:-}" ]] && return 0
4
+ _CAUSAL_GRAPH_LOADED=1
5
+
6
+ # ╔═══════════════════════════════════════════════════════════════════════════╗
7
+ # ║ shipwright causal-graph — Causal Dependency Graph for Root-Cause ║
8
+ # ║ Builds entity-relationship graph from pipeline context ║
9
+ # ║ Traces failure chains: test → function → variable → config ║
10
+ # ║ Enables causal debugging instead of blind pattern matching ║
11
+ # ╚═══════════════════════════════════════════════════════════════════════════╝
12
+
13
+ # shellcheck disable=SC2034
14
+ VERSION="3.3.0"
15
+
16
+ # ─── Output Helpers ──────────────────────────────────────────────────────────
17
+ [[ "$(type -t info 2>/dev/null)" == "function" ]] || info() { echo -e "\033[38;2;0;212;255m\033[1m▸\033[0m $*"; }
18
+ [[ "$(type -t success 2>/dev/null)" == "function" ]] || success() { echo -e "\033[38;2;74;222;128m\033[1m✓\033[0m $*"; }
19
+ [[ "$(type -t warn 2>/dev/null)" == "function" ]] || warn() { echo -e "\033[38;2;250;204;21m\033[1m⚠\033[0m $*"; }
20
+ [[ "$(type -t error 2>/dev/null)" == "function" ]] || error() { echo -e "\033[38;2;248;113;113m\033[1m✗\033[0m $*" >&2; }
21
+ if [[ "$(type -t now_iso 2>/dev/null)" != "function" ]]; then
22
+ now_iso() { date -u +"%Y-%m-%dT%H:%M:%SZ"; }
23
+ fi
24
+
25
+ # ─── Configuration ───────────────────────────────────────────────────────────
26
+
27
+ CAUSAL_GRAPH_FILE="${CAUSAL_GRAPH_FILE:-.claude/causal-graph.json}"
28
+ CAUSAL_MAX_DEPTH="${CAUSAL_MAX_DEPTH:-5}"
29
+
30
+ # ─── Node ID Generation ─────────────────────────────────────────────────────
31
+
32
+ _causal_node_id() {
33
+ local type="$1" name="$2"
34
+ printf '%s:%s' "$type" "$name"
35
+ }
36
+
37
+ # ─── Build Graph ─────────────────────────────────────────────────────────────
38
+ # Build entity-relationship graph from current pipeline state.
39
+ # Analyzes: changed files, functions defined/called, test files, configs.
40
+
41
+ causal_build_graph() {
42
+ local project_dir="${1:-.}"
43
+ local base_ref="${2:-HEAD~1}"
44
+
45
+ info "Building causal dependency graph..."
46
+
47
+ # Get changed files
48
+ local changed_files
49
+ changed_files=$(git -C "$project_dir" diff --name-only "$base_ref" 2>/dev/null || true)
50
+
51
+ if [[ -z "$changed_files" ]]; then
52
+ changed_files=$(git -C "$project_dir" diff --cached --name-only 2>/dev/null || true)
53
+ fi
54
+
55
+ if [[ -z "$changed_files" ]]; then
56
+ warn "No changed files detected — building minimal graph"
57
+ fi
58
+
59
+ local nodes=""
60
+ local edges=""
61
+ local node_count=0
62
+ local edge_count=0
63
+
64
+ # ─── Pass 1: Create file nodes ───────────────────────────────────────
65
+ while IFS= read -r file; do
66
+ [[ -z "$file" ]] && continue
67
+ [[ ! -f "${project_dir}/${file}" ]] && continue
68
+
69
+ local file_type="source"
70
+ if echo "$file" | grep -qiE '(test|spec)'; then
71
+ file_type="test"
72
+ elif echo "$file" | grep -qiE '(config|\.json$|\.ya?ml$|\.toml$|\.env)'; then
73
+ file_type="config"
74
+ fi
75
+
76
+ local node
77
+ node=$(printf '{"id":"file:%s","type":"file","subtype":"%s","name":"%s"}' \
78
+ "$file" "$file_type" "$file")
79
+
80
+ if [[ -n "$nodes" ]]; then
81
+ nodes="${nodes},${node}"
82
+ else
83
+ nodes="${node}"
84
+ fi
85
+ node_count=$((node_count + 1))
86
+ done <<< "$changed_files"
87
+
88
+ # ─── Pass 2: Extract functions from changed files ────────────────────
89
+ while IFS= read -r file; do
90
+ [[ -z "$file" ]] && continue
91
+ [[ ! -f "${project_dir}/${file}" ]] && continue
92
+
93
+ local ext="${file##*.}"
94
+ local func_pattern=""
95
+
96
+ case "$ext" in
97
+ sh|bash)
98
+ func_pattern='^\s*[a-zA-Z_][a-zA-Z0-9_]*\s*\(\)'
99
+ ;;
100
+ ts|js|tsx|jsx)
101
+ func_pattern='(function\s+[a-zA-Z_]\w*|const\s+[a-zA-Z_]\w*\s*=\s*(async\s+)?\(|export\s+(async\s+)?function)'
102
+ ;;
103
+ py)
104
+ func_pattern='^\s*def\s+[a-zA-Z_]\w*'
105
+ ;;
106
+ go)
107
+ func_pattern='^\s*func\s+[a-zA-Z_]\w*'
108
+ ;;
109
+ rs)
110
+ func_pattern='^\s*(pub\s+)?fn\s+[a-zA-Z_]\w*'
111
+ ;;
112
+ *)
113
+ continue
114
+ ;;
115
+ esac
116
+
117
+ local funcs
118
+ funcs=$(grep -oE "$func_pattern" "${project_dir}/${file}" 2>/dev/null | \
119
+ sed 's/.*function\s\+//' | sed 's/.*def\s\+//' | sed 's/.*func\s\+//' | \
120
+ sed 's/.*fn\s\+//' | sed 's/.*const\s\+//' | \
121
+ sed 's/\s*[=(].*//' | tr -d '(){}' | sort -u | head -20 || true)
122
+
123
+ while IFS= read -r func; do
124
+ [[ -z "$func" ]] && continue
125
+ func=$(echo "$func" | tr -d ' ')
126
+ [[ -z "$func" ]] && continue
127
+
128
+ local node
129
+ node=$(printf '{"id":"func:%s:%s","type":"function","name":"%s","file":"%s"}' \
130
+ "$file" "$func" "$func" "$file")
131
+ nodes="${nodes},${node}"
132
+ node_count=$((node_count + 1))
133
+
134
+ # Edge: file contains function
135
+ local edge
136
+ edge=$(printf '{"from":"file:%s","to":"func:%s:%s","type":"defines"}' \
137
+ "$file" "$file" "$func")
138
+ if [[ -n "$edges" ]]; then
139
+ edges="${edges},${edge}"
140
+ else
141
+ edges="${edge}"
142
+ fi
143
+ edge_count=$((edge_count + 1))
144
+ done <<< "$funcs"
145
+
146
+ done <<< "$changed_files"
147
+
148
+ # ─── Pass 3: Find cross-file dependencies ───────────────────────────
149
+ while IFS= read -r file; do
150
+ [[ -z "$file" ]] && continue
151
+ [[ ! -f "${project_dir}/${file}" ]] && continue
152
+
153
+ # Find imports/requires/sources from this file
154
+ local imports
155
+ imports=$(grep -oE "(import|require|source|from)\s+['\"]([^'\"]+)['\"]" "${project_dir}/${file}" 2>/dev/null | \
156
+ sed "s/.*['\"]//;s/['\"].*//" | head -20 || true)
157
+
158
+ while IFS= read -r imp; do
159
+ [[ -z "$imp" ]] && continue
160
+
161
+ # Check if imported file is in our changed set
162
+ local match
163
+ match=$(echo "$changed_files" | grep -F "$imp" | head -1 || true)
164
+
165
+ if [[ -n "$match" ]]; then
166
+ local edge
167
+ edge=$(printf '{"from":"file:%s","to":"file:%s","type":"imports"}' "$file" "$match")
168
+ if [[ -n "$edges" ]]; then
169
+ edges="${edges},${edge}"
170
+ else
171
+ edges="${edge}"
172
+ fi
173
+ edge_count=$((edge_count + 1))
174
+ fi
175
+ done <<< "$imports"
176
+
177
+ done <<< "$changed_files"
178
+
179
+ # ─── Pass 4: Find test → source relationships ───────────────────────
180
+ while IFS= read -r file; do
181
+ [[ -z "$file" ]] && continue
182
+ if ! echo "$file" | grep -qiE '(test|spec)'; then
183
+ continue
184
+ fi
185
+ [[ ! -f "${project_dir}/${file}" ]] && continue
186
+
187
+ # Test files typically import/source the file they test
188
+ local tested_files
189
+ tested_files=$(grep -oE "(import|require|source)\s+['\"]([^'\"]+)['\"]" "${project_dir}/${file}" 2>/dev/null | \
190
+ sed "s/.*['\"]//;s/['\"].*//" | head -10 || true)
191
+
192
+ while IFS= read -r tf; do
193
+ [[ -z "$tf" ]] && continue
194
+ local match
195
+ match=$(echo "$changed_files" | grep -F "$tf" | head -1 || true)
196
+ if [[ -n "$match" ]]; then
197
+ local edge
198
+ edge=$(printf '{"from":"file:%s","to":"file:%s","type":"tests"}' "$file" "$match")
199
+ if [[ -n "$edges" ]]; then
200
+ edges="${edges},${edge}"
201
+ else
202
+ edges="${edge}"
203
+ fi
204
+ edge_count=$((edge_count + 1))
205
+ fi
206
+ done <<< "$tested_files"
207
+ done <<< "$changed_files"
208
+
209
+ # ─── Write Graph ─────────────────────────────────────────────────────
210
+ mkdir -p "$(dirname "$CAUSAL_GRAPH_FILE")"
211
+
212
+ local tmp_file
213
+ tmp_file=$(mktemp 2>/dev/null || echo "${CAUSAL_GRAPH_FILE}.raw")
214
+ cat > "$tmp_file" <<EOF
215
+ {
216
+ "built_at": "$(now_iso)",
217
+ "base_ref": "${base_ref}",
218
+ "node_count": ${node_count},
219
+ "edge_count": ${edge_count},
220
+ "nodes": [${nodes}],
221
+ "edges": [${edges}]
222
+ }
223
+ EOF
224
+
225
+ # Pretty-print if jq available, then atomic move
226
+ if command -v jq >/dev/null 2>&1; then
227
+ if jq '.' "$tmp_file" > "${CAUSAL_GRAPH_FILE}.pp" 2>/dev/null; then
228
+ mv "${CAUSAL_GRAPH_FILE}.pp" "$CAUSAL_GRAPH_FILE"
229
+ else
230
+ mv "$tmp_file" "$CAUSAL_GRAPH_FILE"
231
+ fi
232
+ else
233
+ mv "$tmp_file" "$CAUSAL_GRAPH_FILE"
234
+ fi
235
+ rm -f "$tmp_file" "${CAUSAL_GRAPH_FILE}.pp" "${CAUSAL_GRAPH_FILE}.raw" 2>/dev/null || true
236
+
237
+ success "Causal graph: ${node_count} nodes, ${edge_count} edges"
238
+
239
+ if type emit_event >/dev/null 2>&1; then
240
+ emit_event "causal_graph_built" \
241
+ "nodes=${node_count}" \
242
+ "edges=${edge_count}" \
243
+ "base_ref=${base_ref}"
244
+ fi
245
+
246
+ return 0
247
+ }
248
+
249
+ # ─── Trace Failure ───────────────────────────────────────────────────────────
250
+ # Given a failing test file, trace the causal chain to identify root cause.
251
+ # Returns JSON with the causal chain and suggested root cause.
252
+
253
+ causal_trace_failure() {
254
+ local failing_test="${1:-}"
255
+ local project_dir="${2:-.}"
256
+
257
+ if [[ -z "$failing_test" ]]; then
258
+ error "causal_trace_failure requires a failing test file path"
259
+ return 1
260
+ fi
261
+
262
+ if [[ ! -f "$CAUSAL_GRAPH_FILE" ]]; then
263
+ warn "No causal graph — building now..."
264
+ causal_build_graph "$project_dir"
265
+ fi
266
+
267
+ if ! command -v jq >/dev/null 2>&1; then
268
+ error "jq required for causal tracing"
269
+ return 1
270
+ fi
271
+
272
+ info "Tracing causal chain for: ${failing_test}"
273
+
274
+ local chain=""
275
+ local current="file:${failing_test}"
276
+ local visited=""
277
+ local depth=0
278
+
279
+ # BFS through graph following edges from test → source → dependencies
280
+ while [[ "$depth" -lt "$CAUSAL_MAX_DEPTH" ]]; do
281
+ # Find all edges FROM current node
282
+ local targets
283
+ targets=$(jq -r --arg from "$current" \
284
+ '.edges[] | select(.from == $from) | "\(.to)|\(.type)"' \
285
+ "$CAUSAL_GRAPH_FILE" 2>/dev/null || true)
286
+
287
+ if [[ -z "$targets" ]]; then
288
+ break
289
+ fi
290
+
291
+ while IFS='|' read -r target edge_type; do
292
+ [[ -z "$target" ]] && continue
293
+
294
+ # Skip already visited
295
+ if echo "$visited" | grep -qF "$target" 2>/dev/null; then
296
+ continue
297
+ fi
298
+ visited="${visited} ${target}"
299
+
300
+ local node_info
301
+ node_info=$(jq -c --arg id "$target" '.nodes[] | select(.id == $id)' \
302
+ "$CAUSAL_GRAPH_FILE" 2>/dev/null || echo "{}")
303
+
304
+ local entry
305
+ entry=$(printf '{"node":"%s","edge_type":"%s","depth":%d,"info":%s}' \
306
+ "$target" "$edge_type" "$depth" "${node_info:-\"{}\"}")
307
+
308
+ if [[ -n "$chain" ]]; then
309
+ chain="${chain},${entry}"
310
+ else
311
+ chain="${entry}"
312
+ fi
313
+
314
+ # Follow the chain deeper for source files
315
+ if echo "$target" | grep -q "^file:"; then
316
+ current="$target"
317
+ fi
318
+ done <<< "$targets"
319
+
320
+ depth=$((depth + 1))
321
+ done
322
+
323
+ # Identify likely root cause (deepest source file in chain)
324
+ local root_cause=""
325
+ if [[ -n "$chain" ]]; then
326
+ root_cause=$(echo "[${chain}]" | jq -r \
327
+ '[.[] | select(.edge_type != "tests")] | last | .node // "unknown"' 2>/dev/null || echo "unknown")
328
+ fi
329
+
330
+ # Build trace result
331
+ local trace_file="${CAUSAL_GRAPH_FILE%.json}-trace.json"
332
+ cat > "$trace_file" <<EOF
333
+ {
334
+ "traced_at": "$(now_iso)",
335
+ "failing_test": "${failing_test}",
336
+ "root_cause": "${root_cause}",
337
+ "chain_depth": ${depth},
338
+ "chain": [${chain}]
339
+ }
340
+ EOF
341
+
342
+ if [[ "$root_cause" != "unknown" && -n "$root_cause" ]]; then
343
+ success "Root cause identified: ${root_cause}"
344
+ else
345
+ warn "Could not determine root cause from graph (may need deeper analysis)"
346
+ fi
347
+
348
+ if type emit_event >/dev/null 2>&1; then
349
+ emit_event "causal_trace_completed" \
350
+ "test=${failing_test}" \
351
+ "root_cause=${root_cause}" \
352
+ "depth=${depth}"
353
+ fi
354
+
355
+ echo "$trace_file"
356
+ return 0
357
+ }
358
+
359
+ # ─── Find Dependencies ──────────────────────────────────────────────────────
360
+ # Find all entities affected by a change to a given file/function.
361
+
362
+ causal_find_dependencies() {
363
+ local node_id="${1:-}"
364
+
365
+ if [[ -z "$node_id" ]]; then
366
+ error "causal_find_dependencies requires a node ID (e.g., file:src/foo.ts)"
367
+ return 1
368
+ fi
369
+
370
+ if [[ ! -f "$CAUSAL_GRAPH_FILE" ]]; then
371
+ warn "No causal graph available"
372
+ return 1
373
+ fi
374
+
375
+ # Find all nodes that depend on this one (reverse edge traversal)
376
+ local dependents
377
+ dependents=$(jq -r --arg to "$node_id" \
378
+ '.edges[] | select(.to == $to) | .from' \
379
+ "$CAUSAL_GRAPH_FILE" 2>/dev/null || true)
380
+
381
+ if [[ -z "$dependents" ]]; then
382
+ info "No dependents found for ${node_id}"
383
+ return 0
384
+ fi
385
+
386
+ echo "Dependents of ${node_id}:"
387
+ echo "$dependents" | while IFS= read -r dep; do
388
+ local dep_info
389
+ dep_info=$(jq -r --arg id "$dep" '.nodes[] | select(.id == $id) | "\(.type): \(.name)"' \
390
+ "$CAUSAL_GRAPH_FILE" 2>/dev/null || echo "$dep")
391
+ echo " - ${dep_info}"
392
+ done
393
+
394
+ return 0
395
+ }
396
+
397
+ # ─── Suggest Fix ─────────────────────────────────────────────────────────────
398
+ # Given a causal trace, suggest what to fix based on the root cause type.
399
+
400
+ causal_suggest_fix() {
401
+ local trace_file="${1:-}"
402
+
403
+ if [[ ! -f "$trace_file" ]]; then
404
+ error "Trace file not found: ${trace_file}"
405
+ return 1
406
+ fi
407
+
408
+ local root_cause
409
+ root_cause=$(jq -r '.root_cause // "unknown"' "$trace_file" 2>/dev/null)
410
+
411
+ if [[ "$root_cause" == "unknown" ]]; then
412
+ echo "Unable to suggest fix — root cause not identified"
413
+ return 1
414
+ fi
415
+
416
+ # Extract file type from root cause
417
+ local cause_file
418
+ cause_file=$(echo "$root_cause" | sed 's/^file://')
419
+
420
+ echo "Suggested fix approach:"
421
+ echo " Root cause: ${cause_file}"
422
+
423
+ if echo "$cause_file" | grep -qiE '(config|\.json$|\.ya?ml$|\.env)'; then
424
+ echo " Type: Configuration issue"
425
+ echo " Action: Check config values, defaults, and schema validation"
426
+ elif echo "$cause_file" | grep -qiE '(test|spec)'; then
427
+ echo " Type: Test issue (test itself may be wrong)"
428
+ echo " Action: Review test assertions and expected values"
429
+ else
430
+ echo " Type: Source code issue"
431
+ echo " Action: Review recent changes to ${cause_file}"
432
+ echo " Hint: Check git diff for this file and verify logic correctness"
433
+ fi
434
+
435
+ # Show the causal chain for context
436
+ echo ""
437
+ echo "Causal chain:"
438
+ jq -r '.chain[] | " \(.edge_type): \(.node)"' "$trace_file" 2>/dev/null
439
+
440
+ return 0
441
+ }
442
+
443
+ # ─── Graph Status ────────────────────────────────────────────────────────────
444
+
445
+ causal_status() {
446
+ if [[ -f "$CAUSAL_GRAPH_FILE" ]]; then
447
+ local node_count edge_count built_at
448
+ node_count=$(jq -r '.node_count // 0' "$CAUSAL_GRAPH_FILE" 2>/dev/null || echo "0")
449
+ edge_count=$(jq -r '.edge_count // 0' "$CAUSAL_GRAPH_FILE" 2>/dev/null || echo "0")
450
+ built_at=$(jq -r '.built_at // "unknown"' "$CAUSAL_GRAPH_FILE" 2>/dev/null || echo "unknown")
451
+ echo "causal_graph=active nodes=${node_count} edges=${edge_count} built=${built_at}"
452
+ else
453
+ echo "causal_graph=none"
454
+ fi
455
+ }
@@ -281,3 +281,129 @@ compute_md5() {
281
281
  md5 -q "$file" 2>/dev/null || md5sum "$file" 2>/dev/null | awk '{print $1}'
282
282
  fi
283
283
  }
284
+
285
+ # ─── Intelligent Model Selection ──────────────────────────────────────────
286
+ # _smart_model <purpose> [default]
287
+ # Returns model name from config chain: env var → daemon-config.json → default
288
+ # Purpose: "classification", "detection", "validation", "commit_quality",
289
+ # "default", or any custom key under model_routing.{purpose}
290
+ _smart_model() {
291
+ local purpose="${1:-default}" default="${2:-haiku}"
292
+
293
+ # 1. Environment override (e.g., SW_MODEL_CLASSIFICATION=sonnet)
294
+ local env_key
295
+ env_key="SW_MODEL_$(echo "$purpose" | tr '[:lower:]' '[:upper:]')"
296
+ local env_val=""
297
+ eval 'env_val="${'"$env_key"':-}"' 2>/dev/null || true
298
+ if [[ -n "$env_val" ]]; then
299
+ echo "$env_val"
300
+ return
301
+ fi
302
+
303
+ # 2. Daemon config: model_routing.{purpose}
304
+ local cfg="${DAEMON_CONFIG:-${WORK_DIR:-.}/.claude/daemon-config.json}"
305
+ if [[ -f "$cfg" ]]; then
306
+ local cfg_val
307
+ cfg_val=$(jq -r --arg p "$purpose" '.model_routing[$p] // empty' "$cfg" 2>/dev/null || true)
308
+ if [[ -n "$cfg_val" && "$cfg_val" != "null" ]]; then
309
+ echo "$cfg_val"
310
+ return
311
+ fi
312
+ fi
313
+
314
+ # 3. User-level config: ~/.shipwright/model-routing.json
315
+ local user_cfg="${HOME}/.shipwright/model-routing.json"
316
+ if [[ -f "$user_cfg" ]]; then
317
+ local user_val
318
+ user_val=$(jq -r --arg p "$purpose" '.[$p] // empty' "$user_cfg" 2>/dev/null || true)
319
+ if [[ -n "$user_val" && "$user_val" != "null" ]]; then
320
+ echo "$user_val"
321
+ return
322
+ fi
323
+ fi
324
+
325
+ # 4. Default
326
+ echo "$default"
327
+ }
328
+
329
+ # _smart_int <config_key> <default>
330
+ # Read int from daemon-config with env override and default fallback
331
+ _smart_int() {
332
+ local key="$1" default="$2"
333
+
334
+ # Env override: config.key.path → SW_KEY_PATH
335
+ local env_key
336
+ env_key="SW_$(echo "$key" | tr '[:lower:].' '[:upper:]_')"
337
+ local env_val=""
338
+ eval 'env_val="${'"$env_key"':-}"' 2>/dev/null || true
339
+ if [[ -n "$env_val" ]]; then
340
+ echo "$env_val"
341
+ return
342
+ fi
343
+
344
+ # Daemon config
345
+ local cfg="${DAEMON_CONFIG:-${WORK_DIR:-.}/.claude/daemon-config.json}"
346
+ if [[ -f "$cfg" ]]; then
347
+ local cfg_val
348
+ cfg_val=$(jq -r --arg k "$key" 'getpath($k | split(".")) // empty' "$cfg" 2>/dev/null || true)
349
+ if [[ -n "$cfg_val" && "$cfg_val" != "null" ]]; then
350
+ echo "$cfg_val"
351
+ return
352
+ fi
353
+ fi
354
+
355
+ echo "$default"
356
+ }
357
+
358
+ # _smart_effort <stage>
359
+ # Read effort level from config, with per-stage defaults
360
+ _smart_effort() {
361
+ local stage="$1"
362
+
363
+ # 1. Explicit override
364
+ if [[ -n "${EFFORT_LEVEL_OVERRIDE:-}" ]]; then
365
+ echo "$EFFORT_LEVEL_OVERRIDE"
366
+ return
367
+ fi
368
+
369
+ # 2. Config: effort_levels.{stage}
370
+ local cfg="${DAEMON_CONFIG:-${WORK_DIR:-.}/.claude/daemon-config.json}"
371
+ if [[ -f "$cfg" ]]; then
372
+ local cfg_val
373
+ cfg_val=$(jq -r --arg s "$stage" '.effort_levels[$s] // empty' "$cfg" 2>/dev/null || true)
374
+ if [[ -n "$cfg_val" && "$cfg_val" != "null" ]]; then
375
+ echo "$cfg_val"
376
+ return
377
+ fi
378
+ fi
379
+
380
+ # 3. Intelligent defaults (same as before, but now overridable)
381
+ case "$stage" in
382
+ intake) echo "low" ;;
383
+ plan|design) echo "high" ;;
384
+ build|test) echo "medium" ;;
385
+ review|compound_quality) echo "high" ;;
386
+ pr|merge) echo "low" ;;
387
+ deploy|validate|monitor) echo "medium" ;;
388
+ *) echo "medium" ;;
389
+ esac
390
+ }
391
+
392
+ # _exponential_backoff <attempt> [base_seconds] [max_seconds]
393
+ # Returns sleep duration with jitter for retry loops
394
+ _exponential_backoff() {
395
+ local attempt="${1:-1}" base="${2:-2}" max="${3:-60}"
396
+ local delay=$base
397
+ local i=1
398
+ while [ "$i" -lt "$attempt" ]; do
399
+ delay=$((delay * 2))
400
+ i=$((i + 1))
401
+ done
402
+ # Cap at max
403
+ [ "$delay" -gt "$max" ] && delay=$max
404
+ # Add jitter: ±25%
405
+ local jitter=$(( (RANDOM % (delay / 2 + 1)) - delay / 4 ))
406
+ delay=$((delay + jitter))
407
+ [ "$delay" -lt 1 ] && delay=1
408
+ echo "$delay"
409
+ }