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,594 @@
1
+ #!/usr/bin/env bash
2
+ # ╔═══════════════════════════════════════════════════════════════════════════╗
3
+ # ║ convergence.sh — Build Loop Iteration Progress Quality Scorer ║
4
+ # ║ ║
5
+ # ║ Scores each build loop iteration's progress (0-100) and detects when ║
6
+ # ║ the loop has converged, diverging, oscillating, or needs escalation. ║
7
+ # ║ ║
8
+ # ║ Usage: Source from sw-loop.sh, call convergence_integrate() after ║
9
+ # ║ test evaluation. Returns exit codes: 0=continue, 1=stop-success, ║
10
+ # ║ 2=stop-fail, 3=escalate. ║
11
+ # ╚═══════════════════════════════════════════════════════════════════════════╝
12
+
13
+ [[ -n "${_CONVERGENCE_LOADED:-}" ]] && return 0
14
+ _CONVERGENCE_LOADED=1
15
+
16
+ # ─── Defaults ────────────────────────────────────────────────────────────────
17
+ ARTIFACTS_DIR="${ARTIFACTS_DIR:-.claude/pipeline-artifacts}"
18
+ SCRIPT_DIR="${SCRIPT_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}"
19
+
20
+ # ─── Score an iteration's progress (0-100) ───────────────────────────────────
21
+ #
22
+ # Evaluates:
23
+ # - Tests newly passing (+25 points)
24
+ # - Tests newly failing (-15 points)
25
+ # - Meaningful code changes (+20 points)
26
+ # - Same error repeated (-10 points per repeat)
27
+ # - New files created (+5 points each, capped at +15)
28
+ #
29
+ convergence_score_iteration() {
30
+ local iteration="${1:-0}"
31
+ local test_passed="${2:-false}"
32
+ local test_changed="${3:-false}"
33
+ local git_diff_lines="${4:-0}"
34
+ local error_count="${5:-0}"
35
+ local prev_score="${6:-50}"
36
+
37
+ local score=50 # Baseline
38
+ local signals=()
39
+
40
+ # Signal 1: Test transitions
41
+ if [[ "$test_passed" == "true" ]]; then
42
+ if [[ "$test_changed" == "true" ]]; then
43
+ # Newly passing tests — major progress
44
+ score=$((score + 25))
45
+ signals+=("newly_passing_tests:+25")
46
+ else
47
+ # Already passing — maintain score
48
+ signals+=("tests_already_passing:0")
49
+ fi
50
+ else
51
+ # Tests failing
52
+ if [[ "$test_changed" == "true" ]]; then
53
+ # Newly failing tests — regression
54
+ score=$((score - 15))
55
+ signals+=("newly_failing_tests:-15")
56
+ else
57
+ # Already failing — decay
58
+ score=$((score - 5))
59
+ signals+=("tests_still_failing:-5")
60
+ fi
61
+ fi
62
+
63
+ # Signal 2: Code changes (meaningful = >5 lines)
64
+ if [[ "$git_diff_lines" -gt 5 ]]; then
65
+ local change_bonus=20
66
+ # Scaling: cap at 50 line changes for max bonus
67
+ if [[ "$git_diff_lines" -gt 50 ]]; then
68
+ change_bonus=$((20 - (git_diff_lines - 50) / 10))
69
+ [[ "$change_bonus" -lt 5 ]] && change_bonus=5
70
+ fi
71
+ score=$((score + change_bonus))
72
+ signals+=("code_changes:+${change_bonus}")
73
+ elif [[ "$git_diff_lines" -gt 0 ]]; then
74
+ # Minimal changes — slight penalty
75
+ score=$((score - 5))
76
+ signals+=("minimal_changes:-5")
77
+ else
78
+ # No changes — stalled
79
+ score=$((score - 10))
80
+ signals+=("no_code_changes:-10")
81
+ fi
82
+
83
+ # Signal 3: Error repetition (from error-log.jsonl)
84
+ if [[ "$error_count" -gt 2 ]]; then
85
+ local err_penalty
86
+ err_penalty=$((error_count - 2))
87
+ err_penalty=$((err_penalty * 5))
88
+ [[ "$err_penalty" -gt 20 ]] && err_penalty=20
89
+ score=$((score - err_penalty))
90
+ signals+=("repeated_errors:-${err_penalty}")
91
+ fi
92
+
93
+ # Clamp score to 0-100
94
+ [[ "$score" -lt 0 ]] && score=0
95
+ [[ "$score" -gt 100 ]] && score=100
96
+
97
+ # Determine trend vs previous score
98
+ local trend="stable"
99
+ local trend_delta=$((score - prev_score))
100
+ if [[ "$trend_delta" -gt 5 ]]; then
101
+ trend="improving"
102
+ elif [[ "$trend_delta" -lt -5 ]]; then
103
+ trend="declining"
104
+ fi
105
+
106
+ # Output JSON
107
+ cat <<JSON
108
+ {
109
+ "score": $score,
110
+ "trend": "$trend",
111
+ "trend_delta": $trend_delta,
112
+ "signals": [$(printf '"%s"' "${signals[@]}" | sed 's/" *"/, /g')],
113
+ "iteration": $iteration,
114
+ "test_passed": $test_passed,
115
+ "code_changed": $([ "$git_diff_lines" -gt 0 ] && echo "true" || echo "false"),
116
+ "code_lines": $git_diff_lines,
117
+ "errors": $error_count
118
+ }
119
+ JSON
120
+ }
121
+
122
+ # ─── Detect convergence status ────────────────────────────────────────────────
123
+ #
124
+ # Reads convergence-history.json and determines the loop's state:
125
+ # - converged: Score >80 for 2+ consecutive iterations
126
+ # - diverging: Score dropping for 3+ consecutive iterations
127
+ # - oscillating: Score bouncing up/down for 4+ iterations
128
+ # - progressing: Score improving overall
129
+ # - stalled: Score unchanged for 3+ iterations
130
+ #
131
+ convergence_detect() {
132
+ local history_file="${ARTIFACTS_DIR}/convergence-history.json"
133
+
134
+ # If no history yet, can't detect
135
+ if [[ ! -f "$history_file" ]]; then
136
+ cat <<JSON
137
+ {
138
+ "status": "progressing",
139
+ "recommendation": "continue",
140
+ "reason": "first_iteration",
141
+ "confidence": 0.5
142
+ }
143
+ JSON
144
+ return 0
145
+ fi
146
+
147
+ # Parse history (array of {score, trend, iteration})
148
+ local scores=()
149
+ local trends=()
150
+ local last_4=()
151
+
152
+ # Extract last 6 scores and trends
153
+ local history
154
+ history=$(jq -r '.[] | "\(.score),\(.trend)"' "$history_file" 2>/dev/null | tail -6)
155
+
156
+ if [[ -z "$history" ]]; then
157
+ cat <<JSON
158
+ {
159
+ "status": "progressing",
160
+ "recommendation": "continue",
161
+ "reason": "malformed_history",
162
+ "confidence": 0.5
163
+ }
164
+ JSON
165
+ return 0
166
+ fi
167
+
168
+ while IFS=',' read -r s t; do
169
+ [[ -z "$s" ]] && continue
170
+ scores+=("$s")
171
+ trends+=("$t")
172
+ last_4+=("$s")
173
+ # Keep only last 4
174
+ [[ "${#last_4[@]}" -gt 4 ]] && last_4=("${last_4[@]:1}")
175
+ done <<< "$history"
176
+
177
+ local num_scores=${#scores[@]}
178
+ [[ "$num_scores" -lt 2 ]] && {
179
+ cat <<JSON
180
+ {
181
+ "status": "progressing",
182
+ "recommendation": "continue",
183
+ "reason": "insufficient_history",
184
+ "confidence": 0.5,
185
+ "scores": $(printf '[%s]' "$(printf '%s,' "${scores[@]}" | sed 's/,$//')")
186
+ }
187
+ JSON
188
+ return 0
189
+ }
190
+
191
+ local first_score="${scores[0]}"
192
+ local last_idx=$((num_scores - 1))
193
+ local last_score="${scores[$last_idx]}"
194
+
195
+ # Detection logic
196
+
197
+ # 1. Converged: Last 2 scores >= 80 and stable
198
+ if [[ "$num_scores" -ge 2 ]]; then
199
+ local last1_idx=$((num_scores - 1))
200
+ local last2_idx=$((num_scores - 2))
201
+ local last1="${scores[$last1_idx]}"
202
+ local last2="${scores[$last2_idx]}"
203
+ if [[ "$last1" -ge 80 && "$last2" -ge 80 ]]; then
204
+ local delta=$((last1 - last2))
205
+ [[ "$delta" -lt -5 ]] && delta=$((0 - delta))
206
+ if [[ "$delta" -le 5 ]]; then
207
+ cat <<JSON
208
+ {
209
+ "status": "converged",
210
+ "recommendation": "stop",
211
+ "reason": "high_score_stable",
212
+ "confidence": 0.95,
213
+ "last_scores": [$(printf '%s' "${last_4[@]}" | sed 's/ /, /g')],
214
+ "latest_score": $last1
215
+ }
216
+ JSON
217
+ return 0
218
+ fi
219
+ fi
220
+ fi
221
+
222
+ # 2. Diverging: 3+ consecutive declining iterations
223
+ if [[ "$num_scores" -ge 3 ]]; then
224
+ local diverging_count=0
225
+ for i in $(seq 1 $((num_scores - 1))); do
226
+ local prev="${scores[$((i - 1))]}"
227
+ local curr="${scores[$i]}"
228
+ [[ "$curr" -lt "$prev" ]] && diverging_count=$((diverging_count + 1))
229
+ done
230
+ if [[ "$diverging_count" -ge 3 ]]; then
231
+ cat <<JSON
232
+ {
233
+ "status": "diverging",
234
+ "recommendation": "stop",
235
+ "reason": "consistent_decline",
236
+ "confidence": 0.85,
237
+ "last_scores": [$(printf '%s' "${last_4[@]}" | sed 's/ /, /g')],
238
+ "declining_iterations": $diverging_count
239
+ }
240
+ JSON
241
+ return 0
242
+ fi
243
+ fi
244
+
245
+ # 3. Oscillating: 4+ iterations with alternating up/down
246
+ if [[ "$num_scores" -ge 4 ]]; then
247
+ local oscillations=0
248
+ local last_dir=""
249
+ for i in $(seq 1 $((num_scores - 1))); do
250
+ local prev="${scores[$((i - 1))]}"
251
+ local curr="${scores[$i]}"
252
+ local direction=""
253
+ [[ "$curr" -gt "$prev" ]] && direction="up"
254
+ [[ "$curr" -lt "$prev" ]] && direction="down"
255
+
256
+ if [[ -n "$last_dir" && "$direction" != "$last_dir" ]]; then
257
+ oscillations=$((oscillations + 1))
258
+ fi
259
+ last_dir="$direction"
260
+ done
261
+
262
+ if [[ "$oscillations" -ge 3 ]]; then
263
+ cat <<JSON
264
+ {
265
+ "status": "oscillating",
266
+ "recommendation": "escalate",
267
+ "reason": "bouncing_scores",
268
+ "confidence": 0.80,
269
+ "last_scores": [$(printf '%s' "${last_4[@]}" | sed 's/ /, /g')],
270
+ "oscillations": $oscillations
271
+ }
272
+ JSON
273
+ return 0
274
+ fi
275
+ fi
276
+
277
+ # 4. Stalled: Score unchanged for 3+ iterations
278
+ if [[ "$num_scores" -ge 3 ]]; then
279
+ local stalled_count=0
280
+ local start_idx=$((num_scores - 3))
281
+ local end_idx=$((num_scores - 1))
282
+ for i in $(seq "$start_idx" "$end_idx"); do
283
+ [[ "$i" -lt 0 ]] && continue
284
+ local prev_idx=$((i - 1))
285
+ [[ "$prev_idx" -lt 0 ]] && continue
286
+ local prev="${scores[$prev_idx]:-0}"
287
+ local curr="${scores[$i]}"
288
+ local delta=$((curr - prev))
289
+ [[ "$delta" -lt 0 ]] && delta=$((0 - delta))
290
+ [[ "$delta" -le 3 ]] && stalled_count=$((stalled_count + 1))
291
+ done
292
+ if [[ "$stalled_count" -ge 2 ]]; then
293
+ cat <<JSON
294
+ {
295
+ "status": "stalled",
296
+ "recommendation": "change_strategy",
297
+ "reason": "no_progress",
298
+ "confidence": 0.75,
299
+ "last_scores": [$(printf '%s' "${last_4[@]}" | sed 's/ /, /g')]
300
+ }
301
+ JSON
302
+ return 0
303
+ fi
304
+ fi
305
+
306
+ # 5. Default: progressing
307
+ cat <<JSON
308
+ {
309
+ "status": "progressing",
310
+ "recommendation": "continue",
311
+ "reason": "improving_or_stable",
312
+ "confidence": 0.70,
313
+ "last_scores": [$(printf '%s' "${last_4[@]}" | sed 's/ /, /g')],
314
+ "trend": "$([ "$last_score" -gt "$first_score" ] && echo "up" || echo "down")"
315
+ }
316
+ JSON
317
+ }
318
+
319
+ # ─── Track history of scores across iterations ────────────────────────────────
320
+ #
321
+ convergence_history() {
322
+ local iteration="${1:-0}"
323
+ local score="${2:-50}"
324
+ local trend="${3:-stable}"
325
+
326
+ local history_file="${ARTIFACTS_DIR}/convergence-history.json"
327
+ mkdir -p "$ARTIFACTS_DIR"
328
+
329
+ # Initialize if doesn't exist
330
+ if [[ ! -f "$history_file" ]]; then
331
+ echo "[]" > "$history_file.tmp.$$"
332
+ mv "$history_file.tmp.$$" "$history_file"
333
+ fi
334
+
335
+ # Append new entry
336
+ local entry="{\"iteration\": $iteration, \"score\": $score, \"trend\": \"$trend\", \"ts\": \"$(date -u +"%Y-%m-%dT%H:%M:%SZ")\"}"
337
+ local tmp_hist="${history_file}.tmp.$$"
338
+
339
+ # Use jq to append safely
340
+ if jq ". += [$(echo "$entry" | jq '.') ]" "$history_file" > "$tmp_hist" 2>/dev/null; then
341
+ mv "$tmp_hist" "$history_file"
342
+ else
343
+ # Fallback: if jq fails, manually append JSON
344
+ {
345
+ cat "$history_file" | jq '.' 2>/dev/null | head -n -1
346
+ echo " $entry"
347
+ echo "]"
348
+ } > "$tmp_hist" 2>/dev/null
349
+ mv "$tmp_hist" "$history_file" || rm -f "$tmp_hist"
350
+ fi
351
+ }
352
+
353
+ # ─── Suggest action when stalled/diverging ────────────────────────────────────
354
+ #
355
+ convergence_suggest_action() {
356
+ local status="${1:-progressing}"
357
+ local iteration="${2:-0}"
358
+ local context="${3:-}"
359
+
360
+ case "$status" in
361
+ stalled)
362
+ cat <<ACTION
363
+ {
364
+ "action": "change_approach",
365
+ "reason": "score_unchanged_for_multiple_iterations",
366
+ "suggestions": [
367
+ "Break the remaining work into smaller, more granular steps",
368
+ "Try a completely different implementation strategy",
369
+ "Verify that dependencies are installed and working",
370
+ "Read error messages more carefully — focus on root cause, not symptoms"
371
+ ],
372
+ "escalation": "if_still_stuck_after_next_iteration"
373
+ }
374
+ ACTION
375
+ ;;
376
+ diverging)
377
+ cat <<ACTION
378
+ {
379
+ "action": "escalate_model",
380
+ "reason": "score_declining_consistently",
381
+ "suggestions": [
382
+ "Problem may be too complex for current model",
383
+ "Try a fundamentally different approach",
384
+ "Consider splitting into independent subproblems"
385
+ ],
386
+ "escalation": "use_opus_model_or_manual_review"
387
+ }
388
+ ACTION
389
+ ;;
390
+ oscillating)
391
+ cat <<ACTION
392
+ {
393
+ "action": "restart_session",
394
+ "reason": "context_pollution_or_conflicting_approaches",
395
+ "suggestions": [
396
+ "Context may be polluted from previous attempts",
397
+ "Previous approaches may be conflicting with new ones",
398
+ "Start fresh session with progress.md carrying over key learnings"
399
+ ],
400
+ "escalation": "manual_review_recommended"
401
+ }
402
+ ACTION
403
+ ;;
404
+ *)
405
+ cat <<ACTION
406
+ {
407
+ "action": "continue",
408
+ "reason": "progressing_normally",
409
+ "suggestions": []
410
+ }
411
+ ACTION
412
+ ;;
413
+ esac
414
+ }
415
+
416
+ # ─── Integration hook for sw-loop.sh ──────────────────────────────────────────
417
+ #
418
+ # Called after test gate + quality gates. Scores iteration, detects convergence,
419
+ # and returns:
420
+ # 0 = continue
421
+ # 1 = stop-success (converged)
422
+ # 2 = stop-fail (diverging)
423
+ # 3 = escalate (oscillating)
424
+ #
425
+ convergence_integrate() {
426
+ local iteration="${ITERATION:-0}"
427
+ local test_passed="${TEST_PASSED:-false}"
428
+ local quality_passed="${QUALITY_GATE_PASSED:-false}"
429
+
430
+ # For first iteration, can't compute trend
431
+ if [[ "$iteration" -lt 1 ]]; then
432
+ return 0
433
+ fi
434
+
435
+ # ── LOOP_COMPLETE signal check ─────────────────────────────────────────
436
+ # If the agent explicitly signaled completion and tests pass, stop immediately.
437
+ # This prevents wasting iterations when the work is done but scores stay ~50.
438
+ local _log_file="${LOG_DIR:-}/iteration-${iteration}.log"
439
+ if [[ "$test_passed" == "true" ]] && grep -q "LOOP_COMPLETE" "$_log_file" 2>/dev/null; then
440
+ # Write a converged recommendation so the pipeline knows why we stopped
441
+ local rec_file="${ARTIFACTS_DIR}/convergence-recommendation.json"
442
+ mkdir -p "$ARTIFACTS_DIR"
443
+ cat > "$rec_file.tmp.$$" <<CONV_JSON
444
+ {
445
+ "status": "converged",
446
+ "recommendation": "stop",
447
+ "reason": "agent_signaled_complete_tests_pass",
448
+ "confidence": 0.95
449
+ }
450
+ CONV_JSON
451
+ mv "$rec_file.tmp.$$" "$rec_file"
452
+ if type emit_event >/dev/null 2>&1; then
453
+ emit_event "convergence.agent_complete" \
454
+ "iteration=$iteration" \
455
+ "test_passed=$test_passed" 2>/dev/null || true
456
+ fi
457
+ return 1 # Stop success
458
+ fi
459
+
460
+ # Compute test transition (newly passing/failing)
461
+ local test_changed="false"
462
+ if [[ -f "$LOG_DIR/iteration-$((iteration - 1)).log" ]]; then
463
+ local prev_test=""
464
+ # Try to extract previous test status from state
465
+ if [[ -f "${STATE_FILE:-}" ]]; then
466
+ prev_test=$(grep "TEST_PASSED=" "${STATE_FILE}" | tail -1 | cut -d'=' -f2 || echo "")
467
+ fi
468
+ if [[ -n "$prev_test" && "$prev_test" != "$test_passed" ]]; then
469
+ test_changed="true"
470
+ fi
471
+ fi
472
+
473
+ # Git diff stats
474
+ local diff_lines=0
475
+ if [[ "$iteration" -gt 0 ]]; then
476
+ diff_lines=$(git diff HEAD~1 --stat 2>/dev/null | tail -1 | grep -oE '[0-9]+ insertion|0 file' | grep -oE '[0-9]+' | head -1 || echo "0")
477
+ diff_lines="${diff_lines:-0}"
478
+ fi
479
+
480
+ # Count repeated errors from error-log.jsonl
481
+ local error_count=0
482
+ local error_log="${ARTIFACTS_DIR}/error-log.jsonl"
483
+ if [[ -f "$error_log" ]]; then
484
+ # Count unique errors in last 5 entries
485
+ error_count=$(tail -5 "$error_log" 2>/dev/null | jq -r '.error // .message // empty' 2>/dev/null | sort | uniq -c | sort -rn | head -1 | awk '{print $1}' || echo "0")
486
+ error_count="${error_count:-0}"
487
+ fi
488
+
489
+ # Get previous score (default 50)
490
+ local prev_score=50
491
+ if [[ -f "${ARTIFACTS_DIR}/convergence-history.json" ]]; then
492
+ prev_score=$(jq -r '.[-1].score // 50' "${ARTIFACTS_DIR}/convergence-history.json" 2>/dev/null || echo "50")
493
+ prev_score="${prev_score:-50}"
494
+ fi
495
+
496
+ # Score this iteration
497
+ local score_json
498
+ score_json=$(convergence_score_iteration "$iteration" "$test_passed" "$test_changed" "$diff_lines" "$error_count" "$prev_score")
499
+ local score
500
+ score=$(echo "$score_json" | jq -r '.score // 50' 2>/dev/null || echo "50")
501
+ local trend
502
+ trend=$(echo "$score_json" | jq -r '.trend // "stable"' 2>/dev/null || echo "stable")
503
+
504
+ # Track history
505
+ convergence_history "$iteration" "$score" "$trend"
506
+
507
+ # Detect convergence
508
+ local detect_json
509
+ detect_json=$(convergence_detect)
510
+ local status
511
+ status=$(echo "$detect_json" | jq -r '.status // "progressing"' 2>/dev/null || echo "progressing")
512
+ local recommendation
513
+ recommendation=$(echo "$detect_json" | jq -r '.recommendation // "continue"' 2>/dev/null || echo "continue")
514
+
515
+ # Write recommendation to artifact
516
+ local rec_file="${ARTIFACTS_DIR}/convergence-recommendation.json"
517
+ mkdir -p "$ARTIFACTS_DIR"
518
+ echo "$detect_json" > "$rec_file.tmp.$$"
519
+ mv "$rec_file.tmp.$$" "$rec_file"
520
+
521
+ # Log via emit_event if available
522
+ if type emit_event >/dev/null 2>&1; then
523
+ emit_event "convergence.scored" \
524
+ "iteration=$iteration" \
525
+ "score=$score" \
526
+ "trend=$trend" \
527
+ "status=$status" \
528
+ "recommendation=$recommendation" \
529
+ "test_passed=$test_passed" \
530
+ "code_lines=$diff_lines" 2>/dev/null || true
531
+ fi
532
+
533
+ # Return based on recommendation
534
+ case "$recommendation" in
535
+ stop)
536
+ return 1 # Stop success
537
+ ;;
538
+ escalate)
539
+ return 3 # Escalate
540
+ ;;
541
+ change_strategy)
542
+ # Suggest action but continue for one more iteration
543
+ local action_json
544
+ action_json=$(convergence_suggest_action "$status" "$iteration")
545
+ local action_file="${ARTIFACTS_DIR}/convergence-action.json"
546
+ echo "$action_json" > "$action_file.tmp.$$"
547
+ mv "$action_file.tmp.$$" "$action_file"
548
+
549
+ # Log suggestion
550
+ if type warn >/dev/null 2>&1; then
551
+ warn "Convergence stalled — $(echo "$action_json" | jq -r '.reason' 2>/dev/null || echo 'no progress')"
552
+ fi
553
+ return 0 # Continue but with warning
554
+ ;;
555
+ *)
556
+ return 0 # Continue
557
+ ;;
558
+ esac
559
+ }
560
+
561
+ # ─── Utilities ────────────────────────────────────────────────────────────────
562
+
563
+ # Force history flush (for testing)
564
+ convergence_clear_history() {
565
+ rm -f "${ARTIFACTS_DIR}/convergence-history.json" "${ARTIFACTS_DIR}/convergence-recommendation.json" "${ARTIFACTS_DIR}/convergence-action.json"
566
+ }
567
+
568
+ # Read current convergence status
569
+ convergence_read_status() {
570
+ local rec_file="${ARTIFACTS_DIR}/convergence-recommendation.json"
571
+ if [[ -f "$rec_file" ]]; then
572
+ local status
573
+ status=$(jq -r '.status // "unknown"' "$rec_file" 2>/dev/null || echo "unknown")
574
+ echo "$status"
575
+ else
576
+ # If no recommendation file, check history and detect
577
+ if [[ -f "${ARTIFACTS_DIR}/convergence-history.json" ]]; then
578
+ local detect_out
579
+ detect_out=$(convergence_detect 2>/dev/null || echo '{"status":"unknown"}')
580
+ echo "$detect_out" | jq -r '.status // "unknown"' 2>/dev/null || echo "unknown"
581
+ else
582
+ echo "unknown"
583
+ fi
584
+ fi
585
+ }
586
+
587
+ # Export for use by sw-loop.sh
588
+ export -f convergence_score_iteration
589
+ export -f convergence_detect
590
+ export -f convergence_history
591
+ export -f convergence_suggest_action
592
+ export -f convergence_integrate
593
+ export -f convergence_clear_history
594
+ export -f convergence_read_status