shipwright-cli 3.2.0 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (279) hide show
  1. package/.claude/agents/code-reviewer.md +2 -0
  2. package/.claude/agents/devops-engineer.md +2 -0
  3. package/.claude/agents/doc-fleet-agent.md +2 -0
  4. package/.claude/agents/pipeline-agent.md +2 -0
  5. package/.claude/agents/shell-script-specialist.md +2 -0
  6. package/.claude/agents/test-specialist.md +2 -0
  7. package/.claude/hooks/agent-crash-capture.sh +32 -0
  8. package/.claude/hooks/post-tool-use.sh +3 -2
  9. package/.claude/hooks/pre-tool-use.sh +35 -3
  10. package/README.md +4 -4
  11. package/claude-code/hooks/config-change.sh +18 -0
  12. package/claude-code/hooks/instructions-reloaded.sh +7 -0
  13. package/claude-code/hooks/worktree-create.sh +25 -0
  14. package/claude-code/hooks/worktree-remove.sh +20 -0
  15. package/config/code-constitution.json +130 -0
  16. package/dashboard/middleware/auth.ts +134 -0
  17. package/dashboard/middleware/constants.ts +21 -0
  18. package/dashboard/public/index.html +2 -6
  19. package/dashboard/public/styles.css +100 -97
  20. package/dashboard/routes/auth.ts +38 -0
  21. package/dashboard/server.ts +66 -25
  22. package/dashboard/services/config.ts +26 -0
  23. package/dashboard/services/db.ts +118 -0
  24. package/dashboard/src/canvas/pixel-agent.ts +298 -0
  25. package/dashboard/src/canvas/pixel-sprites.ts +440 -0
  26. package/dashboard/src/canvas/shipyard-effects.ts +367 -0
  27. package/dashboard/src/canvas/shipyard-scene.ts +616 -0
  28. package/dashboard/src/canvas/submarine-layout.ts +267 -0
  29. package/dashboard/src/components/header.ts +8 -7
  30. package/dashboard/src/core/router.ts +1 -0
  31. package/dashboard/src/design/submarine-theme.ts +253 -0
  32. package/dashboard/src/main.ts +2 -0
  33. package/dashboard/src/types/api.ts +2 -1
  34. package/dashboard/src/views/activity.ts +2 -1
  35. package/dashboard/src/views/shipyard.ts +39 -0
  36. package/dashboard/types/index.ts +166 -0
  37. package/docs/plans/2026-02-28-compound-audit-and-shipyard-design.md +186 -0
  38. package/docs/plans/2026-02-28-skipper-shipwright-implementation-plan.md +1182 -0
  39. package/docs/plans/2026-02-28-skipper-shipwright-integration-design.md +531 -0
  40. package/docs/plans/2026-03-01-ai-powered-skill-injection-design.md +298 -0
  41. package/docs/plans/2026-03-01-ai-powered-skill-injection-plan.md +1109 -0
  42. package/docs/plans/2026-03-01-capabilities-cleanup-plan.md +658 -0
  43. package/docs/plans/2026-03-01-clean-architecture-plan.md +924 -0
  44. package/docs/plans/2026-03-01-compound-audit-cascade-design.md +191 -0
  45. package/docs/plans/2026-03-01-compound-audit-cascade-plan.md +921 -0
  46. package/docs/plans/2026-03-01-deep-integration-plan.md +851 -0
  47. package/docs/plans/2026-03-01-pipeline-audit-trail-design.md +145 -0
  48. package/docs/plans/2026-03-01-pipeline-audit-trail-plan.md +770 -0
  49. package/docs/plans/2026-03-01-refined-depths-brand-design.md +382 -0
  50. package/docs/plans/2026-03-01-refined-depths-implementation.md +599 -0
  51. package/docs/plans/2026-03-01-skipper-kernel-integration-design.md +203 -0
  52. package/docs/plans/2026-03-01-unified-platform-design.md +272 -0
  53. package/docs/plans/2026-03-07-claude-code-feature-integration-design.md +189 -0
  54. package/docs/plans/2026-03-07-claude-code-feature-integration-plan.md +1165 -0
  55. package/docs/research/BACKLOG_QUICK_REFERENCE.md +352 -0
  56. package/docs/research/CUTTING_EDGE_RESEARCH_2026.md +546 -0
  57. package/docs/research/RESEARCH_INDEX.md +439 -0
  58. package/docs/research/RESEARCH_SOURCES.md +440 -0
  59. package/docs/research/RESEARCH_SUMMARY.txt +275 -0
  60. package/docs/superpowers/specs/2026-03-10-pipeline-quality-revolution-design.md +341 -0
  61. package/package.json +2 -2
  62. package/scripts/lib/adaptive-model.sh +427 -0
  63. package/scripts/lib/adaptive-timeout.sh +316 -0
  64. package/scripts/lib/audit-trail.sh +309 -0
  65. package/scripts/lib/auto-recovery.sh +471 -0
  66. package/scripts/lib/bandit-selector.sh +431 -0
  67. package/scripts/lib/bootstrap.sh +104 -2
  68. package/scripts/lib/causal-graph.sh +455 -0
  69. package/scripts/lib/compat.sh +126 -0
  70. package/scripts/lib/compound-audit.sh +337 -0
  71. package/scripts/lib/constitutional.sh +454 -0
  72. package/scripts/lib/context-budget.sh +359 -0
  73. package/scripts/lib/convergence.sh +594 -0
  74. package/scripts/lib/cost-optimizer.sh +634 -0
  75. package/scripts/lib/daemon-adaptive.sh +10 -0
  76. package/scripts/lib/daemon-dispatch.sh +106 -17
  77. package/scripts/lib/daemon-failure.sh +34 -4
  78. package/scripts/lib/daemon-patrol.sh +23 -2
  79. package/scripts/lib/daemon-poll-github.sh +361 -0
  80. package/scripts/lib/daemon-poll-health.sh +299 -0
  81. package/scripts/lib/daemon-poll.sh +27 -611
  82. package/scripts/lib/daemon-state.sh +112 -66
  83. package/scripts/lib/daemon-triage.sh +10 -0
  84. package/scripts/lib/dod-scorecard.sh +442 -0
  85. package/scripts/lib/error-actionability.sh +300 -0
  86. package/scripts/lib/formal-spec.sh +461 -0
  87. package/scripts/lib/helpers.sh +177 -4
  88. package/scripts/lib/intent-analysis.sh +409 -0
  89. package/scripts/lib/loop-convergence.sh +350 -0
  90. package/scripts/lib/loop-iteration.sh +682 -0
  91. package/scripts/lib/loop-progress.sh +48 -0
  92. package/scripts/lib/loop-restart.sh +185 -0
  93. package/scripts/lib/memory-effectiveness.sh +506 -0
  94. package/scripts/lib/mutation-executor.sh +352 -0
  95. package/scripts/lib/outcome-feedback.sh +521 -0
  96. package/scripts/lib/pipeline-cli.sh +336 -0
  97. package/scripts/lib/pipeline-commands.sh +1216 -0
  98. package/scripts/lib/pipeline-detection.sh +100 -2
  99. package/scripts/lib/pipeline-execution.sh +897 -0
  100. package/scripts/lib/pipeline-github.sh +28 -3
  101. package/scripts/lib/pipeline-intelligence-compound.sh +431 -0
  102. package/scripts/lib/pipeline-intelligence-scoring.sh +407 -0
  103. package/scripts/lib/pipeline-intelligence-skip.sh +181 -0
  104. package/scripts/lib/pipeline-intelligence.sh +100 -1136
  105. package/scripts/lib/pipeline-quality-bash-compat.sh +182 -0
  106. package/scripts/lib/pipeline-quality-checks.sh +17 -715
  107. package/scripts/lib/pipeline-quality-gates.sh +563 -0
  108. package/scripts/lib/pipeline-stages-build.sh +730 -0
  109. package/scripts/lib/pipeline-stages-delivery.sh +965 -0
  110. package/scripts/lib/pipeline-stages-intake.sh +1133 -0
  111. package/scripts/lib/pipeline-stages-monitor.sh +407 -0
  112. package/scripts/lib/pipeline-stages-review.sh +1022 -0
  113. package/scripts/lib/pipeline-stages.sh +59 -2929
  114. package/scripts/lib/pipeline-state.sh +36 -5
  115. package/scripts/lib/pipeline-util.sh +487 -0
  116. package/scripts/lib/policy-learner.sh +438 -0
  117. package/scripts/lib/process-reward.sh +493 -0
  118. package/scripts/lib/project-detect.sh +649 -0
  119. package/scripts/lib/quality-profile.sh +334 -0
  120. package/scripts/lib/recruit-commands.sh +885 -0
  121. package/scripts/lib/recruit-learning.sh +739 -0
  122. package/scripts/lib/recruit-roles.sh +648 -0
  123. package/scripts/lib/reward-aggregator.sh +458 -0
  124. package/scripts/lib/rl-optimizer.sh +362 -0
  125. package/scripts/lib/root-cause.sh +427 -0
  126. package/scripts/lib/scope-enforcement.sh +445 -0
  127. package/scripts/lib/session-restart.sh +493 -0
  128. package/scripts/lib/skill-memory.sh +300 -0
  129. package/scripts/lib/skill-registry.sh +775 -0
  130. package/scripts/lib/spec-driven.sh +476 -0
  131. package/scripts/lib/test-helpers.sh +18 -7
  132. package/scripts/lib/test-holdout.sh +429 -0
  133. package/scripts/lib/test-optimizer.sh +511 -0
  134. package/scripts/shipwright-file-suggest.sh +45 -0
  135. package/scripts/skills/adversarial-quality.md +61 -0
  136. package/scripts/skills/api-design.md +44 -0
  137. package/scripts/skills/architecture-design.md +50 -0
  138. package/scripts/skills/brainstorming.md +43 -0
  139. package/scripts/skills/data-pipeline.md +44 -0
  140. package/scripts/skills/deploy-safety.md +64 -0
  141. package/scripts/skills/documentation.md +38 -0
  142. package/scripts/skills/frontend-design.md +45 -0
  143. package/scripts/skills/generated/.gitkeep +0 -0
  144. package/scripts/skills/generated/_refinements/.gitkeep +0 -0
  145. package/scripts/skills/generated/_refinements/adversarial-quality.patch.md +3 -0
  146. package/scripts/skills/generated/_refinements/architecture-design.patch.md +3 -0
  147. package/scripts/skills/generated/_refinements/brainstorming.patch.md +3 -0
  148. package/scripts/skills/generated/cli-version-management.md +29 -0
  149. package/scripts/skills/generated/collection-system-validation.md +99 -0
  150. package/scripts/skills/generated/large-scale-c-refactoring-coordination.md +97 -0
  151. package/scripts/skills/generated/pattern-matching-similarity-scoring.md +195 -0
  152. package/scripts/skills/generated/test-parallelization-detection.md +65 -0
  153. package/scripts/skills/observability.md +79 -0
  154. package/scripts/skills/performance.md +48 -0
  155. package/scripts/skills/pr-quality.md +49 -0
  156. package/scripts/skills/product-thinking.md +43 -0
  157. package/scripts/skills/security-audit.md +49 -0
  158. package/scripts/skills/systematic-debugging.md +40 -0
  159. package/scripts/skills/testing-strategy.md +47 -0
  160. package/scripts/skills/two-stage-review.md +52 -0
  161. package/scripts/skills/validation-thoroughness.md +55 -0
  162. package/scripts/sw +9 -3
  163. package/scripts/sw-activity.sh +9 -2
  164. package/scripts/sw-adaptive.sh +2 -1
  165. package/scripts/sw-adversarial.sh +2 -1
  166. package/scripts/sw-architecture-enforcer.sh +3 -1
  167. package/scripts/sw-auth.sh +12 -2
  168. package/scripts/sw-autonomous.sh +5 -1
  169. package/scripts/sw-changelog.sh +4 -1
  170. package/scripts/sw-checkpoint.sh +2 -1
  171. package/scripts/sw-ci.sh +5 -1
  172. package/scripts/sw-cleanup.sh +4 -26
  173. package/scripts/sw-code-review.sh +10 -4
  174. package/scripts/sw-connect.sh +2 -1
  175. package/scripts/sw-context.sh +2 -1
  176. package/scripts/sw-cost.sh +48 -3
  177. package/scripts/sw-daemon.sh +66 -9
  178. package/scripts/sw-dashboard.sh +3 -1
  179. package/scripts/sw-db.sh +59 -16
  180. package/scripts/sw-decide.sh +8 -2
  181. package/scripts/sw-decompose.sh +360 -17
  182. package/scripts/sw-deps.sh +4 -1
  183. package/scripts/sw-developer-simulation.sh +4 -1
  184. package/scripts/sw-discovery.sh +325 -2
  185. package/scripts/sw-doc-fleet.sh +4 -1
  186. package/scripts/sw-docs-agent.sh +3 -1
  187. package/scripts/sw-docs.sh +2 -1
  188. package/scripts/sw-doctor.sh +453 -2
  189. package/scripts/sw-dora.sh +4 -1
  190. package/scripts/sw-durable.sh +4 -3
  191. package/scripts/sw-e2e-orchestrator.sh +17 -16
  192. package/scripts/sw-eventbus.sh +7 -1
  193. package/scripts/sw-evidence.sh +364 -12
  194. package/scripts/sw-feedback.sh +550 -9
  195. package/scripts/sw-fix.sh +20 -1
  196. package/scripts/sw-fleet-discover.sh +6 -2
  197. package/scripts/sw-fleet-viz.sh +4 -1
  198. package/scripts/sw-fleet.sh +5 -1
  199. package/scripts/sw-github-app.sh +16 -3
  200. package/scripts/sw-github-checks.sh +3 -2
  201. package/scripts/sw-github-deploy.sh +3 -2
  202. package/scripts/sw-github-graphql.sh +18 -7
  203. package/scripts/sw-guild.sh +5 -1
  204. package/scripts/sw-heartbeat.sh +5 -30
  205. package/scripts/sw-hello.sh +67 -0
  206. package/scripts/sw-hygiene.sh +6 -1
  207. package/scripts/sw-incident.sh +265 -1
  208. package/scripts/sw-init.sh +18 -2
  209. package/scripts/sw-instrument.sh +10 -2
  210. package/scripts/sw-intelligence.sh +42 -6
  211. package/scripts/sw-jira.sh +5 -1
  212. package/scripts/sw-launchd.sh +2 -1
  213. package/scripts/sw-linear.sh +4 -1
  214. package/scripts/sw-logs.sh +4 -1
  215. package/scripts/sw-loop.sh +432 -1128
  216. package/scripts/sw-memory.sh +356 -2
  217. package/scripts/sw-mission-control.sh +6 -1
  218. package/scripts/sw-model-router.sh +481 -26
  219. package/scripts/sw-otel.sh +13 -4
  220. package/scripts/sw-oversight.sh +14 -5
  221. package/scripts/sw-patrol-meta.sh +334 -0
  222. package/scripts/sw-pipeline-composer.sh +5 -1
  223. package/scripts/sw-pipeline-vitals.sh +2 -1
  224. package/scripts/sw-pipeline.sh +53 -2664
  225. package/scripts/sw-pm.sh +12 -5
  226. package/scripts/sw-pr-lifecycle.sh +2 -1
  227. package/scripts/sw-predictive.sh +7 -1
  228. package/scripts/sw-prep.sh +185 -2
  229. package/scripts/sw-ps.sh +5 -25
  230. package/scripts/sw-public-dashboard.sh +15 -3
  231. package/scripts/sw-quality.sh +2 -1
  232. package/scripts/sw-reaper.sh +8 -25
  233. package/scripts/sw-recruit.sh +156 -2303
  234. package/scripts/sw-regression.sh +19 -12
  235. package/scripts/sw-release-manager.sh +3 -1
  236. package/scripts/sw-release.sh +4 -1
  237. package/scripts/sw-remote.sh +3 -1
  238. package/scripts/sw-replay.sh +7 -1
  239. package/scripts/sw-retro.sh +158 -1
  240. package/scripts/sw-review-rerun.sh +3 -1
  241. package/scripts/sw-scale.sh +10 -3
  242. package/scripts/sw-security-audit.sh +6 -1
  243. package/scripts/sw-self-optimize.sh +6 -3
  244. package/scripts/sw-session.sh +9 -3
  245. package/scripts/sw-setup.sh +3 -1
  246. package/scripts/sw-stall-detector.sh +406 -0
  247. package/scripts/sw-standup.sh +15 -7
  248. package/scripts/sw-status.sh +3 -1
  249. package/scripts/sw-strategic.sh +4 -1
  250. package/scripts/sw-stream.sh +7 -1
  251. package/scripts/sw-swarm.sh +18 -6
  252. package/scripts/sw-team-stages.sh +13 -6
  253. package/scripts/sw-templates.sh +5 -29
  254. package/scripts/sw-testgen.sh +7 -1
  255. package/scripts/sw-tmux-pipeline.sh +4 -1
  256. package/scripts/sw-tmux-role-color.sh +2 -0
  257. package/scripts/sw-tmux-status.sh +1 -1
  258. package/scripts/sw-tmux.sh +3 -1
  259. package/scripts/sw-trace.sh +3 -1
  260. package/scripts/sw-tracker-github.sh +3 -0
  261. package/scripts/sw-tracker-jira.sh +3 -0
  262. package/scripts/sw-tracker-linear.sh +3 -0
  263. package/scripts/sw-tracker.sh +3 -1
  264. package/scripts/sw-triage.sh +2 -1
  265. package/scripts/sw-upgrade.sh +3 -1
  266. package/scripts/sw-ux.sh +5 -2
  267. package/scripts/sw-webhook.sh +3 -1
  268. package/scripts/sw-widgets.sh +3 -1
  269. package/scripts/sw-worktree.sh +15 -3
  270. package/scripts/test-skill-injection.sh +1233 -0
  271. package/templates/pipelines/autonomous.json +27 -3
  272. package/templates/pipelines/cost-aware.json +34 -8
  273. package/templates/pipelines/deployed.json +12 -0
  274. package/templates/pipelines/enterprise.json +12 -0
  275. package/templates/pipelines/fast.json +6 -0
  276. package/templates/pipelines/full.json +27 -3
  277. package/templates/pipelines/hotfix.json +6 -0
  278. package/templates/pipelines/standard.json +12 -0
  279. package/templates/pipelines/tdd.json +12 -0
@@ -0,0 +1,682 @@
1
+ #!/usr/bin/env bash
2
+ # Module guard - prevent double-sourcing
3
+ [[ -n "${_LOOP_ITERATION_LOADED:-}" ]] && return 0
4
+ _LOOP_ITERATION_LOADED=1
5
+
6
+ # ─── Prompt Composition ──────────────────────────────────────────────────────
7
+
8
+ manage_context_window() {
9
+ local prompt="$1"
10
+ local budget="${CONTEXT_BUDGET_CHARS:-200000}"
11
+ local current_len=${#prompt}
12
+
13
+ # Read trimming tunables from config (env > daemon-config > policy > defaults.json)
14
+ local trim_memory_chars trim_git_entries trim_hotspot_files trim_test_lines
15
+ trim_memory_chars=$(_config_get_int "loop.context_trim_memory_chars" 20000 2>/dev/null || echo 20000)
16
+ trim_git_entries=$(_config_get_int "loop.context_trim_git_entries" 10 2>/dev/null || echo 10)
17
+ trim_hotspot_files=$(_config_get_int "loop.context_trim_hotspot_files" 5 2>/dev/null || echo 5)
18
+ trim_test_lines=$(_config_get_int "loop.context_trim_test_lines" 50 2>/dev/null || echo 50)
19
+
20
+ if [[ "$current_len" -le "$budget" ]]; then
21
+ echo "$prompt"
22
+ return
23
+ fi
24
+
25
+ # Over budget — progressively trim sections (least important first)
26
+ local trimmed="$prompt"
27
+
28
+ # 1. Trim DORA/Performance baselines (least critical for code generation)
29
+ if [[ "${#trimmed}" -gt "$budget" ]]; then
30
+ trimmed=$(echo "$trimmed" | awk '/^## Performance Baselines/{skip=1; next} skip && /^## [^#]/{skip=0} !skip{print}')
31
+ fi
32
+
33
+ # 2. Trim file hotspots to top N
34
+ if [[ "${#trimmed}" -gt "$budget" ]]; then
35
+ trimmed=$(echo "$trimmed" | awk -v max="$trim_hotspot_files" '/## File Hotspots/{p=1; c=0} p && /^- /{c++; if(c>max) next} {print}')
36
+ fi
37
+
38
+ # 3. Trim git log to last N entries
39
+ if [[ "${#trimmed}" -gt "$budget" ]]; then
40
+ trimmed=$(echo "$trimmed" | awk -v max="$trim_git_entries" '/## Recent Git Activity/{p=1; c=0} p && /^[a-f0-9]/{c++; if(c>max) next} {print}')
41
+ fi
42
+
43
+ # 4. Truncate memory context to first N chars
44
+ if [[ "${#trimmed}" -gt "$budget" ]]; then
45
+ trimmed=$(echo "$trimmed" | awk -v max="$trim_memory_chars" '
46
+ /## Memory Context/{mem=1; skip_rest=0; chars=0; print; next}
47
+ mem && /^## [^#]/{mem=0; print; next}
48
+ mem{chars+=length($0)+1; if(chars>max){print "... (memory truncated for context budget)"; skip_rest=1; mem=0; next}}
49
+ skip_rest && /^## [^#]/{skip_rest=0; print; next}
50
+ skip_rest{next}
51
+ {print}
52
+ ')
53
+ fi
54
+
55
+ # 5. Truncate test output to last N lines
56
+ if [[ "${#trimmed}" -gt "$budget" ]]; then
57
+ trimmed=$(echo "$trimmed" | awk -v max="$trim_test_lines" '
58
+ /## Test Results/{found=1; buf=""; print; next}
59
+ found && /^## [^#]/{found=0; n=split(buf,arr,"\n"); start=(n>max)?(n-max+1):1; for(i=start;i<=n;i++) if(arr[i]!="") print arr[i]; print; next}
60
+ found{buf=buf $0 "\n"; next}
61
+ {print}
62
+ ')
63
+ fi
64
+
65
+ # 6. Last resort: hard truncate with notice
66
+ if [[ "${#trimmed}" -gt "$budget" ]]; then
67
+ trimmed="${trimmed:0:$budget}
68
+
69
+ ... [CONTEXT TRUNCATED: prompt exceeded ${budget} char budget. Focus on the goal and most recent errors.]"
70
+ fi
71
+
72
+ # Log the trimming
73
+ local final_len=${#trimmed}
74
+ if [[ "$final_len" -lt "$current_len" ]]; then
75
+ warn "Context trimmed from ${current_len} to ${final_len} chars (budget: ${budget})"
76
+ emit_event "loop.context_trimmed" "original=$current_len" "trimmed=$final_len" "budget=$budget" 2>/dev/null || true
77
+ fi
78
+
79
+ echo "$trimmed"
80
+ }
81
+
82
+ compose_prompt() {
83
+ local recent_log
84
+ # Get last 3 iteration summaries from log entries
85
+ recent_log="$(echo "$LOG_ENTRIES" | tail -15)"
86
+ if [[ -z "$recent_log" ]]; then
87
+ recent_log="(first iteration — no previous progress)"
88
+ fi
89
+
90
+ local git_log
91
+ git_log="$(git_recent_log)"
92
+
93
+ local test_section
94
+ if [[ -z "$TEST_CMD" ]]; then
95
+ test_section="No test command configured."
96
+ elif [[ -z "$TEST_PASSED" ]]; then
97
+ test_section="No test results yet (first iteration). Test command: $TEST_CMD"
98
+ elif $TEST_PASSED; then
99
+ test_section="$TEST_OUTPUT"
100
+ else
101
+ test_section="TESTS FAILED — fix these before proceeding:
102
+ $TEST_OUTPUT"
103
+ fi
104
+
105
+ # Structured error context (machine-readable)
106
+ local error_summary_section=""
107
+ local error_json="$LOG_DIR/error-summary.json"
108
+ if [[ -f "$error_json" ]]; then
109
+ local err_count err_lines
110
+ err_count=$(jq -r '.error_count // 0' "$error_json" 2>/dev/null || echo "0")
111
+ err_lines=$(jq -r '.error_lines[]? // empty' "$error_json" 2>/dev/null | head -10 || true)
112
+ if [[ "$err_count" -gt 0 ]] && [[ -n "$err_lines" ]]; then
113
+ error_summary_section="## Structured Error Summary (${err_count} errors detected)
114
+ ${err_lines}
115
+
116
+ Fix these specific errors. Each line above is one distinct error from the test output."
117
+ fi
118
+ fi
119
+
120
+ # Build audit sections (captured before heredoc to avoid nested heredoc issues)
121
+ local audit_section
122
+ audit_section="$(compose_audit_section)"
123
+ local audit_feedback_section
124
+ audit_feedback_section="$(compose_audit_feedback_section)"
125
+ local rejection_notice_section
126
+ rejection_notice_section="$(compose_rejection_notice_section)"
127
+
128
+ # Memory context injection (failure patterns + past learnings)
129
+ local memory_section=""
130
+ if type memory_inject_context >/dev/null 2>&1; then
131
+ memory_section="$(memory_inject_context "build" 2>/dev/null || true)"
132
+ elif [[ -f "$SCRIPT_DIR/sw-memory.sh" ]]; then
133
+ memory_section="$("$SCRIPT_DIR/sw-memory.sh" inject build 2>/dev/null || true)"
134
+ fi
135
+
136
+ # Cross-pipeline discovery injection (learnings from other pipeline runs)
137
+ local discovery_section=""
138
+ if type inject_discoveries >/dev/null 2>&1; then
139
+ local disc_output
140
+ disc_output="$(inject_discoveries "${GOAL:-}" 2>/dev/null || true)"
141
+ if [[ -n "$disc_output" ]]; then
142
+ discovery_section="$disc_output"
143
+ fi
144
+ fi
145
+
146
+ # DORA baselines for context
147
+ local dora_section=""
148
+ if type memory_get_dora_baseline >/dev/null 2>&1; then
149
+ local dora_json
150
+ dora_json="$(memory_get_dora_baseline 7 2>/dev/null || echo "{}")"
151
+ local dora_total
152
+ dora_total=$(echo "$dora_json" | jq -r '.total // 0' 2>/dev/null || echo "0")
153
+ if [[ "$dora_total" -gt 0 ]]; then
154
+ local dora_df dora_cfr
155
+ dora_df=$(echo "$dora_json" | jq -r '.deploy_freq // 0' 2>/dev/null || echo "0")
156
+ dora_cfr=$(echo "$dora_json" | jq -r '.cfr // 0' 2>/dev/null || echo "0")
157
+ dora_section="## Performance Baselines (Last 7 Days)
158
+ - Deploy frequency: ${dora_df}/week
159
+ - Change failure rate: ${dora_cfr}%
160
+ - Total pipeline runs: ${dora_total}"
161
+ fi
162
+ fi
163
+
164
+ # Append mid-loop memory refresh if available
165
+ local memory_refresh_file="$LOG_DIR/memory-refresh-$(( ITERATION - 1 )).txt"
166
+ if [[ -f "$memory_refresh_file" ]]; then
167
+ memory_section="${memory_section}
168
+
169
+ ## Fresh Context (from iteration $(( ITERATION - 1 )) analysis)
170
+ $(cat "$memory_refresh_file")"
171
+ fi
172
+
173
+ # GitHub intelligence context (gated by availability)
174
+ local intelligence_section=""
175
+ if [[ "${NO_GITHUB:-}" != "true" ]]; then
176
+ # File hotspots — top 5 most-changed files
177
+ if type gh_file_change_frequency >/dev/null 2>&1; then
178
+ local hotspots
179
+ hotspots=$(gh_file_change_frequency 2>/dev/null | head -5 || true)
180
+ if [[ -n "$hotspots" ]]; then
181
+ intelligence_section="${intelligence_section}
182
+ ## File Hotspots (most frequently changed)
183
+ ${hotspots}"
184
+ fi
185
+ fi
186
+
187
+ # CODEOWNERS context
188
+ if type gh_codeowners >/dev/null 2>&1; then
189
+ local owners
190
+ owners=$(gh_codeowners 2>/dev/null | head -10 || true)
191
+ if [[ -n "$owners" ]]; then
192
+ intelligence_section="${intelligence_section}
193
+ ## Code Owners
194
+ ${owners}"
195
+ fi
196
+ fi
197
+
198
+ # Active security alerts
199
+ if type gh_security_alerts >/dev/null 2>&1; then
200
+ local alerts
201
+ alerts=$(gh_security_alerts 2>/dev/null | head -5 || true)
202
+ if [[ -n "$alerts" ]]; then
203
+ intelligence_section="${intelligence_section}
204
+ ## Active Security Alerts
205
+ ${alerts}"
206
+ fi
207
+ fi
208
+ fi
209
+
210
+ # Architecture rules (from intelligence layer)
211
+ local repo_hash
212
+ repo_hash=$(echo -n "$(pwd)" | shasum -a 256 2>/dev/null | cut -c1-12 || echo "unknown")
213
+ local arch_file="${HOME}/.shipwright/memory/${repo_hash}/architecture.json"
214
+ if [[ -f "$arch_file" ]]; then
215
+ local arch_rules
216
+ arch_rules=$(jq -r '.rules[]? // empty' "$arch_file" 2>/dev/null | head -10 || true)
217
+ if [[ -n "$arch_rules" ]]; then
218
+ intelligence_section="${intelligence_section}
219
+ ## Architecture Rules
220
+ ${arch_rules}"
221
+ fi
222
+ fi
223
+
224
+ # Coverage baseline
225
+ local coverage_file="${HOME}/.shipwright/baselines/${repo_hash}/coverage.json"
226
+ if [[ -f "$coverage_file" ]]; then
227
+ local coverage_pct
228
+ coverage_pct=$(jq -r '.coverage_percent // empty' "$coverage_file" 2>/dev/null || true)
229
+ if [[ -n "$coverage_pct" ]]; then
230
+ intelligence_section="${intelligence_section}
231
+ ## Coverage Baseline
232
+ Current coverage: ${coverage_pct}% — do not decrease this."
233
+ fi
234
+ fi
235
+
236
+ # Error classification from last failure
237
+ local error_log=".claude/pipeline-artifacts/error-log.jsonl"
238
+ if [[ -f "$error_log" ]]; then
239
+ local last_error
240
+ last_error=$(tail -1 "$error_log" 2>/dev/null | jq -r '"Type: \(.type), Exit: \(.exit_code), Error: \(.error | split("\n") | first)"' 2>/dev/null || true)
241
+ if [[ -n "$last_error" ]]; then
242
+ intelligence_section="${intelligence_section}
243
+ ## Last Error Context
244
+ ${last_error}"
245
+ fi
246
+ fi
247
+
248
+ # Stuckness detection — compare last 3 iteration outputs
249
+ local stuckness_section=""
250
+ stuckness_section="$(detect_stuckness)"
251
+ local _stuck_ret=$?
252
+ local stuckness_detected=false
253
+ [[ "$_stuck_ret" -eq 0 ]] && stuckness_detected=true
254
+
255
+ # Strategy exploration when stuck — append alternative strategy to GOAL
256
+ if [[ "$stuckness_detected" == "true" ]]; then
257
+ local last_error diagnosis
258
+ last_error=$(tail -1 "${ARTIFACTS_DIR:-${PROJECT_ROOT:-.}/.claude/pipeline-artifacts}/error-log.jsonl" 2>/dev/null | jq -r '"Type: \(.type), Exit: \(.exit_code), Error: \(.error | split("\n") | first)"' 2>/dev/null || true)
259
+ [[ -z "$last_error" || "$last_error" == "null" ]] && last_error="unknown"
260
+ diagnosis="${STUCKNESS_DIAGNOSIS:-}"
261
+ local alt_strategy
262
+ alt_strategy=$(explore_alternative_strategy "$last_error" "${ITERATION:-0}" "$diagnosis")
263
+ GOAL="${GOAL}
264
+
265
+ ${alt_strategy}"
266
+
267
+ # Handle model escalation
268
+ if [[ "${ESCALATE_MODEL:-}" == "true" ]]; then
269
+ if [[ -f "$SCRIPT_DIR/sw-model-router.sh" ]]; then
270
+ source "$SCRIPT_DIR/sw-model-router.sh" 2>/dev/null || true
271
+ fi
272
+ if type escalate_model &>/dev/null; then
273
+ MODEL=$(escalate_model "${MODEL:-sonnet}")
274
+ info "Escalated to model: $MODEL"
275
+ fi
276
+ unset ESCALATE_MODEL
277
+ fi
278
+ fi
279
+
280
+ # Session restart context — inject intelligent briefing or fallback to progress
281
+ local restart_section=""
282
+ if [[ "$SESSION_RESTART" == "true" ]]; then
283
+ local briefing_file="${ARTIFACTS_DIR:-${LOG_DIR}}/restart-briefing.md"
284
+ if [[ -f "$briefing_file" ]]; then
285
+ # Inject intelligent briefing from session-restart.sh
286
+ restart_section="## Session Restart Briefing
287
+ $(cat "$briefing_file")
288
+
289
+ You are starting a FRESH session after the previous one exhausted its context.
290
+ Read the briefing above carefully and continue from where the previous session left off.
291
+ Do NOT repeat work already done. Focus on what's failing and what to try next."
292
+ elif [[ -f "$LOG_DIR/progress.md" ]]; then
293
+ # Fallback to basic progress.md
294
+ restart_section="## Previous Session Progress
295
+ $(cat "$LOG_DIR/progress.md")
296
+
297
+ You are starting a FRESH session after the previous one exhausted its iterations.
298
+ Read the progress above and continue from where it left off. Do NOT repeat work already done."
299
+ fi
300
+ fi
301
+
302
+ # Resume-from-checkpoint context — reconstruct Claude context for meaningful resume
303
+ local resume_section=""
304
+ if [[ -n "${RESUMED_FROM_ITERATION:-}" && "${RESUMED_FROM_ITERATION:-0}" -gt 0 ]]; then
305
+ local _test_tail=" (none recorded)"
306
+ [[ -n "${RESUMED_TEST_OUTPUT:-}" ]] && _test_tail="$(echo "$RESUMED_TEST_OUTPUT" | tail -20)"
307
+ resume_section="## RESUMING FROM ITERATION ${RESUMED_FROM_ITERATION}
308
+
309
+ Continue from where you left off. Do NOT repeat work already done.
310
+
311
+ Previous work modified these files:
312
+ ${RESUMED_MODIFIED:- (none recorded)}
313
+
314
+ Previous findings/errors from earlier iterations:
315
+ ${RESUMED_FINDINGS:- (none recorded)}
316
+
317
+ Last test output (fix any failures, tail):
318
+ ${_test_tail}
319
+
320
+ ---
321
+ "
322
+ # Clear after first use so we don't keep injecting on every iteration
323
+ RESUMED_FROM_ITERATION=""
324
+ RESUMED_MODIFIED=""
325
+ RESUMED_FINDINGS=""
326
+ RESUMED_TEST_OUTPUT=""
327
+ fi
328
+
329
+ # Build cumulative progress summary showing all iterations' work
330
+ local cumulative_section=""
331
+ if [[ -n "${LOOP_START_COMMIT:-}" ]] && [[ "$ITERATION" -gt 1 ]]; then
332
+ local cum_stat
333
+ cum_stat="$(git -C "$PROJECT_ROOT" diff --stat "${LOOP_START_COMMIT}..HEAD" 2>/dev/null | tail -1 || true)"
334
+ if [[ -n "$cum_stat" ]]; then
335
+ cumulative_section="## Cumulative Progress (all iterations combined)
336
+ ${cum_stat}
337
+ "
338
+ fi
339
+ fi
340
+
341
+ # Process reward context injection (from process-reward.sh)
342
+ local reward_section=""
343
+ if type process_reward_inject_context >/dev/null 2>&1; then
344
+ reward_section="$(process_reward_inject_context 2>/dev/null || true)"
345
+ fi
346
+
347
+ # RL optimizer context injection (from rl-optimizer.sh, Phase 7)
348
+ local rl_section=""
349
+ if type rl_compose_prompt_section >/dev/null 2>&1; then
350
+ rl_section="$(rl_compose_prompt_section 2>/dev/null || true)"
351
+ fi
352
+
353
+ # Autoresearch RL Phase 8: policy and reward feedback injection
354
+ local policy_section=""
355
+ if type policy_inject_into_prompt >/dev/null 2>&1; then
356
+ policy_section="$(policy_inject_into_prompt 2>/dev/null || true)"
357
+ fi
358
+ local reward_feedback=""
359
+ if type reward_inject_feedback >/dev/null 2>&1; then
360
+ reward_feedback="$(reward_inject_feedback 2>/dev/null || true)"
361
+ fi
362
+
363
+ # Auto-recovery hint injection (from auto-recovery.sh)
364
+ local recovery_section=""
365
+ if [[ -n "${RECOVERY_HINT:-}" ]]; then
366
+ recovery_section="## Auto-Recovery Guidance
367
+ ${RECOVERY_HINT}
368
+ "
369
+ fi
370
+ if [[ -n "${RECOVERY_ESCALATED_MODEL:-}" ]]; then
371
+ MODEL="${RECOVERY_ESCALATED_MODEL}"
372
+ info "Model escalated to: ${MODEL} (auto-recovery)"
373
+ fi
374
+
375
+ cat <<PROMPT
376
+ You are an autonomous coding agent on iteration ${ITERATION}/${MAX_ITERATIONS} of a continuous loop.
377
+ ${resume_section}
378
+ ${recovery_section}
379
+ ## Your Goal
380
+ ${GOAL}
381
+
382
+ ${cumulative_section}
383
+ ## Current Progress
384
+ ${recent_log}
385
+
386
+ ## Recent Git Activity
387
+ ${git_log}
388
+
389
+ ## Test Results (Previous Iteration)
390
+ ${test_section}
391
+
392
+ ${error_summary_section:+$error_summary_section
393
+ }
394
+ ${memory_section:+## Memory Context
395
+ $memory_section
396
+ }
397
+ ${discovery_section:+## Cross-Pipeline Learnings
398
+ $discovery_section
399
+ }
400
+ ${reward_section:+$reward_section
401
+ }
402
+ ${rl_section:+$rl_section
403
+ }
404
+ ${policy_section:+$policy_section
405
+ }
406
+ ${reward_feedback:+$reward_feedback
407
+ }
408
+ ${dora_section:+$dora_section
409
+ }
410
+ ${intelligence_section:+$intelligence_section
411
+ }
412
+ ${restart_section:+$restart_section
413
+ }
414
+ ## Instructions
415
+ 1. Read the codebase and understand the current state
416
+ 2. Identify the highest-priority remaining work toward the goal
417
+ 3. Implement ONE meaningful chunk of progress
418
+ 4. Run tests if a test command exists: ${TEST_CMD:-"(none)"}
419
+ 5. Commit your work with a descriptive message
420
+ 6. When the goal is FULLY achieved, output exactly: LOOP_COMPLETE
421
+
422
+ ## Context Efficiency
423
+ - Batch independent tool calls in parallel — avoid sequential round-trips
424
+ - Use targeted file reads (offset/limit) instead of reading entire large files
425
+ - Delegate large searches to subagents — only import the summary
426
+ - Filter tool results with grep/jq before reasoning over them
427
+ - Keep working memory lean — summarize completed steps, don't preserve full outputs
428
+
429
+ ${audit_section}
430
+
431
+ ${audit_feedback_section}
432
+
433
+ ${rejection_notice_section}
434
+
435
+ ${stuckness_section}
436
+
437
+ ## Rules
438
+ - Focus on ONE task per iteration — do it well
439
+ - Always commit with descriptive messages
440
+ - If tests fail, fix them before ending
441
+ - If stuck on the same issue for 2+ iterations, try a different approach
442
+ - Do NOT output LOOP_COMPLETE unless the goal is genuinely achieved
443
+ PROMPT
444
+ }
445
+
446
+ # ─── Alternative Strategy Exploration ─────────────────────────────────────────
447
+
448
+ explore_alternative_strategy() {
449
+ local last_error="${1:-unknown}"
450
+ local iteration="${2:-0}"
451
+ local diagnosis="${3:-}"
452
+
453
+ # Track attempted strategies to avoid repeating them
454
+ local strategy_file="${LOG_DIR:-/tmp}/strategy-attempts.txt"
455
+ local attempted
456
+ attempted=$(cat "$strategy_file" 2>/dev/null || true)
457
+
458
+ local strategy=""
459
+
460
+ # If quality gates are passing but evaluators disagree, suggest focusing on evaluator alignment
461
+ if [[ "${TEST_PASSED:-}" == "true" ]] && [[ "${QUALITY_GATE_PASSED:-}" == "true" || "${AUDIT_RESULT:-}" == "pass" ]]; then
462
+ if ! echo "$attempted" | grep -q "evaluator_alignment"; then
463
+ echo "evaluator_alignment" >> "$strategy_file"
464
+ strategy="## Alternative Strategy: Evaluator Alignment
465
+ The code appears functionally complete (tests pass). Focus on satisfying the remaining
466
+ quality gate evaluators. Check the DoD log and audit log for specific complaints, then
467
+ address those exact points rather than adding new features."
468
+ fi
469
+ fi
470
+
471
+ # If no code changes in last iteration, suggest verifying existing work
472
+ if echo "$last_error" | grep -qi "no code changes" || [[ "$diagnosis" == *"no code"* ]]; then
473
+ if ! echo "$attempted" | grep -q "verify_existing"; then
474
+ echo "verify_existing" >> "$strategy_file"
475
+ strategy="## Alternative Strategy: Verify Existing Work
476
+ Recent iterations made no code changes. The work may already be complete.
477
+ Run the full test suite, verify all features work, and if everything passes,
478
+ commit a verification message and declare LOOP_COMPLETE with evidence."
479
+ fi
480
+ fi
481
+
482
+ # Generic fallback: break the problem down
483
+ if [[ -z "$strategy" ]]; then
484
+ if ! echo "$attempted" | grep -q "decompose"; then
485
+ echo "decompose" >> "$strategy_file"
486
+ strategy="## Alternative Strategy: Decompose
487
+ Break the remaining work into smaller, independent steps. Focus on one specific
488
+ file or function at a time. Read error messages literally — the root cause may
489
+ differ from your assumption."
490
+ fi
491
+ fi
492
+
493
+ echo "$strategy"
494
+ }
495
+
496
+ # ─── Claude Execution ────────────────────────────────────────────────────────
497
+
498
+ build_claude_flags() {
499
+ local flags=()
500
+ flags+=("--model" "$MODEL")
501
+ flags+=("--output-format" "json")
502
+
503
+ if $SKIP_PERMISSIONS; then
504
+ flags+=("--dangerously-skip-permissions")
505
+ fi
506
+
507
+ if [[ -n "$MAX_TURNS" ]]; then
508
+ flags+=("--max-turns" "$MAX_TURNS")
509
+ fi
510
+
511
+ if [[ -n "${EFFORT_LEVEL:-}" ]]; then
512
+ flags+=("--effort" "$EFFORT_LEVEL")
513
+ fi
514
+
515
+ # Only add fallback-model if it differs from primary (Claude CLI rejects same model)
516
+ if [[ -n "${FALLBACK_MODEL:-}" ]] && [[ "${FALLBACK_MODEL}" != "$MODEL" ]]; then
517
+ flags+=("--fallback-model" "$FALLBACK_MODEL")
518
+ fi
519
+
520
+ echo "${flags[*]}"
521
+ }
522
+
523
+ run_claude_iteration() {
524
+ local log_file="$LOG_DIR/iteration-${ITERATION}.log"
525
+ local json_file="$LOG_DIR/iteration-${ITERATION}.json"
526
+ local prompt
527
+ prompt="$(compose_prompt)"
528
+
529
+ # Context budget monitoring and proactive trimming (issue #209)
530
+ local budget_estimate=""
531
+ local budget_status=""
532
+ if type context_budget_estimate >/dev/null 2>&1; then
533
+ budget_estimate=$(context_budget_estimate "$prompt" "${ARTIFACTS_DIR:-./.claude/pipeline-artifacts}" 2>/dev/null || echo "{}")
534
+ if [[ -n "$budget_estimate" ]]; then
535
+ budget_status=$(context_budget_check "$budget_estimate" 2>/dev/null || echo "{}")
536
+ local status_val=$(echo "$budget_status" | jq -r '.status // "unknown"' 2>/dev/null || echo "unknown")
537
+
538
+ # Log budget state
539
+ if type context_budget_log_state >/dev/null 2>&1; then
540
+ context_budget_log_state "$budget_estimate" "$budget_status" "${ARTIFACTS_DIR:-./.claude/pipeline-artifacts}" 2>/dev/null || true
541
+ fi
542
+
543
+ # Warn if approaching limits
544
+ if [[ "$status_val" == "yellow" ]] || [[ "$status_val" == "red" ]] || [[ "$status_val" == "critical" ]]; then
545
+ local msg=$(echo "$budget_status" | jq -r '.message // "context budget alert"' 2>/dev/null || echo "")
546
+ if [[ -n "$msg" ]]; then
547
+ warn "$msg"
548
+ fi
549
+ fi
550
+ fi
551
+ fi
552
+
553
+ local final_prompt
554
+ final_prompt=$(manage_context_window "$prompt")
555
+
556
+ # Apply proactive trimming if budget is tight
557
+ if [[ -n "$budget_status" ]]; then
558
+ local action=$(echo "$budget_status" | jq -r '.action // "continue"' 2>/dev/null || echo "continue")
559
+ if [[ "$action" != "continue" ]] && type context_budget_trim >/dev/null 2>&1; then
560
+ local trim_status=$(echo "$budget_status" | jq -r '.status // "green"' 2>/dev/null || echo "green")
561
+ final_prompt=$(context_budget_trim "$final_prompt" "$trim_status" 200000 2>/dev/null || echo "$final_prompt")
562
+ fi
563
+ fi
564
+
565
+ local raw_prompt_chars=${#prompt}
566
+ local prompt_chars=${#final_prompt}
567
+ local approx_tokens=$((prompt_chars / 4))
568
+ info "Prompt: ~${approx_tokens} tokens (${prompt_chars} chars)"
569
+
570
+ # Audit: save full prompt to disk for traceability
571
+ if type audit_save_prompt >/dev/null 2>&1; then
572
+ audit_save_prompt "$final_prompt" "$ITERATION" || true
573
+ fi
574
+ if type audit_emit >/dev/null 2>&1; then
575
+ audit_emit "loop.prompt" "iteration=$ITERATION" "chars=$prompt_chars" \
576
+ "raw_chars=$raw_prompt_chars" "path=iteration-${ITERATION}.prompt.txt" || true
577
+ fi
578
+
579
+ # Emit context efficiency metrics
580
+ if type emit_event >/dev/null 2>&1; then
581
+ local trim_ratio=0
582
+ local budget_utilization=0
583
+ if [[ "$raw_prompt_chars" -gt 0 ]]; then
584
+ trim_ratio=$(awk -v raw="$raw_prompt_chars" -v trimmed="$prompt_chars" \
585
+ 'BEGIN { printf "%.1f", ((raw - trimmed) / raw) * 100 }')
586
+ fi
587
+ if [[ "${CONTEXT_BUDGET_CHARS:-0}" -gt 0 ]]; then
588
+ budget_utilization=$(awk -v used="$prompt_chars" -v budget="${CONTEXT_BUDGET_CHARS}" \
589
+ 'BEGIN { printf "%.1f", (used / budget) * 100 }')
590
+ fi
591
+ emit_event "loop.context_efficiency" \
592
+ "iteration=$ITERATION" \
593
+ "raw_prompt_chars=$raw_prompt_chars" \
594
+ "trimmed_prompt_chars=$prompt_chars" \
595
+ "trim_ratio=$trim_ratio" \
596
+ "budget_utilization=$budget_utilization" \
597
+ "budget_chars=${CONTEXT_BUDGET_CHARS:-0}" \
598
+ "job_id=${PIPELINE_JOB_ID:-loop-$$}" 2>/dev/null || true
599
+ fi
600
+
601
+ local flags
602
+ flags="$(build_claude_flags)"
603
+
604
+ local iter_start
605
+ iter_start="$(now_epoch)"
606
+
607
+ echo -e "\n${CYAN}${BOLD}▸${RESET} ${BOLD}Iteration ${ITERATION}/${MAX_ITERATIONS}${RESET} — Starting..."
608
+
609
+ # Run Claude headless (with timeout + PID capture for signal handling)
610
+ # Output goes to .json first, then we extract text into .log for compat
611
+ local exit_code=0
612
+ # shellcheck disable=SC2086
613
+ local err_file="${json_file%.json}.stderr"
614
+ if [[ -n "$TIMEOUT_CMD" ]]; then
615
+ $TIMEOUT_CMD "$CLAUDE_TIMEOUT" claude -p "$final_prompt" $flags > "$json_file" 2>"$err_file" &
616
+ else
617
+ claude -p "$final_prompt" $flags > "$json_file" 2>"$err_file" &
618
+ fi
619
+ CHILD_PID=$!
620
+ wait "$CHILD_PID" 2>/dev/null || exit_code=$?
621
+ CHILD_PID=""
622
+ if [[ "$exit_code" -eq 124 ]]; then
623
+ warn "Claude CLI timed out after ${CLAUDE_TIMEOUT}s"
624
+ fi
625
+
626
+ # Extract text result from JSON into .log for backwards compatibility
627
+ # With --output-format json, stdout is a JSON array; .[-1].result has the text
628
+ _extract_text_from_json "$json_file" "$log_file" "$err_file"
629
+
630
+ local iter_end
631
+ iter_end="$(now_epoch)"
632
+ local iter_duration=$(( iter_end - iter_start ))
633
+
634
+ echo -e " ${GREEN}✓${RESET} Claude session completed ($(format_duration "$iter_duration"), exit $exit_code)"
635
+
636
+ # Accumulate token usage from this iteration's JSON output
637
+ accumulate_loop_tokens "$json_file"
638
+
639
+ # Audit: record response metadata
640
+ if type audit_emit >/dev/null 2>&1; then
641
+ local response_chars=0
642
+ [[ -f "$log_file" ]] && response_chars=$(wc -c < "$log_file" | tr -d ' ')
643
+ audit_emit "loop.response" "iteration=$ITERATION" "chars=$response_chars" \
644
+ "exit_code=$exit_code" "duration_s=$iter_duration" \
645
+ "path=iteration-${ITERATION}.json" || true
646
+ fi
647
+
648
+ # Context budget: record iteration summary for context compression (issue #209)
649
+ if type context_budget_summarize_iteration >/dev/null 2>&1; then
650
+ # Extract test result from log or TEST_PASSED variable
651
+ local test_result="${TEST_OUTPUT:-}"
652
+ [[ -z "$test_result" && -n "${TEST_PASSED:-}" ]] && test_result=$([ "$TEST_PASSED" = true ] && echo "PASSED" || echo "FAILED")
653
+ context_budget_summarize_iteration "$ITERATION" "$log_file" "$test_result" "${ARTIFACTS_DIR:-./.claude/pipeline-artifacts}" 2>/dev/null || true
654
+ fi
655
+
656
+ # Show verbose output if requested
657
+ if $VERBOSE; then
658
+ echo -e " ${DIM}─── Claude Output ───${RESET}"
659
+ sed 's/^/ /' "$log_file" | head -100
660
+ echo -e " ${DIM}─────────────────────${RESET}"
661
+ fi
662
+
663
+ return $exit_code
664
+ }
665
+
666
+ # ─── Iteration Summary Extraction ────────────────────────────────────────────
667
+
668
+ extract_summary() {
669
+ local log_file="$1"
670
+ # Grab last meaningful lines from Claude output, skipping empty lines
671
+ local summary
672
+ summary="$(grep -v '^$' "$log_file" | tail -5 | head -3 2>/dev/null || echo "(no output)")"
673
+ # Truncate long lines
674
+ summary="$(echo "$summary" | cut -c1-120)"
675
+
676
+ # Sanitize: if summary is just a CLI/API error, replace with generic text
677
+ if echo "$summary" | grep -qiE 'Invalid API key|authentication_error|rate_limit|API key expired|ANTHROPIC_API_KEY'; then
678
+ summary="(CLI error — no useful output this iteration)"
679
+ fi
680
+
681
+ echo "$summary"
682
+ }