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,634 @@
1
+ #!/usr/bin/env bash
2
+ # ╔═══════════════════════════════════════════════════════════════════════════╗
3
+ # ║ shipwright cost-optimizer — Dynamic Cost-Performance Pipeline Optimizer ║
4
+ # ║ Real-time budget monitoring · Cost reduction suggestions · Burst mode ║
5
+ # ╚═══════════════════════════════════════════════════════════════════════════╝
6
+
7
+ # Module guard
8
+ [[ -n "${_COST_OPTIMIZER_LOADED:-}" ]] && return 0
9
+ _COST_OPTIMIZER_LOADED=1
10
+
11
+ # ─── Default Paths ──────────────────────────────────────────────────────────
12
+ COST_DIR="${COST_DIR:-${HOME}/.shipwright}"
13
+ BUDGET_FILE="${BUDGET_FILE:-${COST_DIR}/budget.json}"
14
+ COST_FILE="${COST_FILE:-${COST_DIR}/costs.json}"
15
+ ARTIFACTS_DIR="${ARTIFACTS_DIR:-.claude/pipeline-artifacts}"
16
+
17
+ # ─── Safe Defaults (for set -u) ──────────────────────────────────────────────
18
+ SCRIPT_DIR="${SCRIPT_DIR:-.}"
19
+ NOW_ISO="${NOW_ISO:-}"
20
+ NOW_EPOCH="${NOW_EPOCH:-}"
21
+
22
+ # ─── Helper Functions (fallback if not sourced from main script) ─────────────
23
+ [[ "$(type -t info 2>/dev/null)" == "function" ]] || info() { echo -e "\033[38;2;0;212;255m\033[1m▸\033[0m $*"; }
24
+ [[ "$(type -t success 2>/dev/null)" == "function" ]] || success() { echo -e "\033[38;2;74;222;128m\033[1m✓\033[0m $*"; }
25
+ [[ "$(type -t warn 2>/dev/null)" == "function" ]] || warn() { echo -e "\033[38;2;250;204;21m\033[1m⚠\033[0m $*"; }
26
+ [[ "$(type -t error 2>/dev/null)" == "function" ]] || error() { echo -e "\033[38;2;248;113;113m\033[1m✗\033[0m $*" >&2; }
27
+
28
+ if [[ "$(type -t now_iso 2>/dev/null)" != "function" ]]; then
29
+ now_iso() { date -u +"%Y-%m-%dT%H:%M:%SZ"; }
30
+ now_epoch() { date +%s; }
31
+ fi
32
+
33
+ if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
34
+ emit_event() {
35
+ local event_type="$1"; shift
36
+ mkdir -p "${COST_DIR}"
37
+ local payload
38
+ payload="{\"ts\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"type\":\"$event_type\""
39
+ while [[ $# -gt 0 ]]; do
40
+ local key="${1%%=*}" val="${1#*=}"
41
+ payload="${payload},\"${key}\":\"${val}\""
42
+ shift
43
+ done
44
+ echo "${payload}}" >> "${COST_DIR}/events.jsonl"
45
+ }
46
+ fi
47
+
48
+ # ─── Helper to Validate JSON File Existence ──────────────────────────────────
49
+ _ensure_optimizer_files() {
50
+ mkdir -p "$COST_DIR" "$ARTIFACTS_DIR"
51
+ [[ -f "$COST_FILE" ]] || echo '{"entries":[],"summary":{}}' > "$COST_FILE"
52
+ [[ -f "$BUDGET_FILE" ]] || echo '{"daily_budget_usd":0,"enabled":false}' > "$BUDGET_FILE"
53
+ }
54
+
55
+ # ─── Model Pricing (fallback if not loaded from sw-cost.sh) ───────────────────
56
+ if [[ -z "${OPUS_INPUT_PER_M:-}" ]]; then
57
+ OPUS_INPUT_PER_M="15.00"
58
+ OPUS_OUTPUT_PER_M="75.00"
59
+ SONNET_INPUT_PER_M="3.00"
60
+ SONNET_OUTPUT_PER_M="15.00"
61
+ HAIKU_INPUT_PER_M="0.25"
62
+ HAIKU_OUTPUT_PER_M="1.25"
63
+ fi
64
+
65
+ # ─── Calculate Cost for Token Counts ─────────────────────────────────────────
66
+ _costopt_cost_for_tokens() {
67
+ local input_tokens="${1:-0}"
68
+ local output_tokens="${2:-0}"
69
+ local model="${3:-sonnet}"
70
+
71
+ local input_rate output_rate
72
+ case "$model" in
73
+ opus|claude-opus-4*)
74
+ input_rate="${OPUS_INPUT_PER_M}"
75
+ output_rate="${OPUS_OUTPUT_PER_M}"
76
+ ;;
77
+ sonnet|claude-sonnet-4*)
78
+ input_rate="${SONNET_INPUT_PER_M}"
79
+ output_rate="${SONNET_OUTPUT_PER_M}"
80
+ ;;
81
+ haiku|claude-haiku-4*)
82
+ input_rate="${HAIKU_INPUT_PER_M}"
83
+ output_rate="${HAIKU_OUTPUT_PER_M}"
84
+ ;;
85
+ *)
86
+ input_rate="${SONNET_INPUT_PER_M}"
87
+ output_rate="${SONNET_OUTPUT_PER_M}"
88
+ ;;
89
+ esac
90
+
91
+ awk -v it="$input_tokens" -v ot="$output_tokens" \
92
+ -v ir="$input_rate" -v or_="$output_rate" \
93
+ 'BEGIN { printf "%.4f", (it / 1000000.0 * ir) + (ot / 1000000.0 * or_) }'
94
+ }
95
+
96
+ # ─── 1. costopt_init() ───────────────────────────────────────────────────────
97
+ # Initialize cost optimization for a pipeline run.
98
+ # Loads budget and historical cost data, calculates projections.
99
+ costopt_init() {
100
+ _ensure_optimizer_files
101
+
102
+ local budget_enabled budget_usd
103
+ budget_enabled=$(jq -r '.enabled // false' "$BUDGET_FILE" 2>/dev/null || echo "false")
104
+ budget_usd=$(jq -r '.daily_budget_usd // 0' "$BUDGET_FILE" 2>/dev/null || echo "0")
105
+
106
+ # If budget not enabled, gracefully return
107
+ if [[ "$budget_enabled" != "true" || "$budget_usd" == "0" ]]; then
108
+ # Silent no-op: budget not configured
109
+ return 0
110
+ fi
111
+
112
+ # Calculate today's spending
113
+ local today_start
114
+ today_start=$(date -u +"%Y-%m-%dT00:00:00Z")
115
+ local today_epoch
116
+ today_epoch=$(date -u -jf "%Y-%m-%dT%H:%M:%SZ" "$today_start" +%s 2>/dev/null || date -u -d "$today_start" +%s 2>/dev/null || echo "0")
117
+
118
+ local today_spent
119
+ today_spent=$(jq --argjson cutoff "$today_epoch" \
120
+ '[.entries[] | select(.ts_epoch >= $cutoff) | .cost_usd] | add // 0' \
121
+ "$COST_FILE" 2>/dev/null || echo "0")
122
+
123
+ # Calculate historical average cost per stage
124
+ local stage_costs
125
+ stage_costs=$(jq -r '
126
+ [.entries | group_by(.stage) | .[] |
127
+ {
128
+ stage: .[0].stage,
129
+ avg_cost: (map(.cost_usd) | add / length)
130
+ }] | map("\(.stage):\(.avg_cost)") | join(",")
131
+ ' "$COST_FILE" 2>/dev/null || echo "")
132
+
133
+ local remaining_budget
134
+ remaining_budget=$(awk -v budget="$budget_usd" -v spent="$today_spent" 'BEGIN { printf "%.2f", budget - spent }')
135
+
136
+ # Write initial cost optimization state
137
+ local opt_state
138
+ opt_state=$(cat <<EOF
139
+ {
140
+ "initialized_at": "$(now_iso)",
141
+ "daily_budget_usd": $budget_usd,
142
+ "today_spent_usd": $today_spent,
143
+ "remaining_budget_usd": $remaining_budget,
144
+ "avg_pipeline_cost": 0,
145
+ "stage_costs": "$stage_costs",
146
+ "reductions_applied": [],
147
+ "burst_active": false,
148
+ "burst_end_ts": ""
149
+ }
150
+ EOF
151
+ )
152
+
153
+ local tmp_file
154
+ tmp_file=$(mktemp "$ARTIFACTS_DIR/cost-optimization.json.tmp.XXXXXX" 2>/dev/null) || tmp_file="/tmp/costopt-$$.tmp"
155
+ echo "$opt_state" > "$tmp_file"
156
+ mv "$tmp_file" "$ARTIFACTS_DIR/cost-optimization.json" 2>/dev/null || true
157
+
158
+ emit_event "costopt.init" \
159
+ "daily_budget=$budget_usd" \
160
+ "today_spent=$today_spent" \
161
+ "remaining=$remaining_budget"
162
+
163
+ success "Cost optimization initialized: \$$remaining_budget remaining of \$$budget_usd daily budget"
164
+ }
165
+
166
+ # ─── 2. costopt_check_budget() ───────────────────────────────────────────────
167
+ # Real-time budget check with projections.
168
+ # Returns status and writes projection info.
169
+ # Status: under_budget, on_track, over_projecting, budget_exceeded
170
+ costopt_check_budget() {
171
+ local current_pipeline_cost="${1:-0}"
172
+ local remaining_stages="${2:-5}"
173
+
174
+ _ensure_optimizer_files
175
+
176
+ local budget_enabled budget_usd
177
+ budget_enabled=$(jq -r '.enabled // false' "$BUDGET_FILE" 2>/dev/null || echo "false")
178
+ budget_usd=$(jq -r '.daily_budget_usd // 0' "$BUDGET_FILE" 2>/dev/null || echo "0")
179
+
180
+ # If budget not enabled, return under_budget
181
+ if [[ "$budget_enabled" != "true" || "$budget_usd" == "0" ]]; then
182
+ echo "under_budget"
183
+ return 0
184
+ fi
185
+
186
+ # Get today's spending
187
+ local today_start
188
+ today_start=$(date -u +"%Y-%m-%dT00:00:00Z")
189
+ local today_epoch
190
+ today_epoch=$(date -u -jf "%Y-%m-%dT%H:%M:%SZ" "$today_start" +%s 2>/dev/null || date -u -d "$today_start" +%s 2>/dev/null || echo "0")
191
+
192
+ local today_spent
193
+ today_spent=$(jq --argjson cutoff "$today_epoch" \
194
+ '[.entries[] | select(.ts_epoch >= $cutoff) | .cost_usd] | add // 0' \
195
+ "$COST_FILE" 2>/dev/null || echo "0")
196
+
197
+ # Calculate historical average cost per remaining stage
198
+ local avg_per_stage
199
+ avg_per_stage=$(jq -r \
200
+ '[.entries[].cost_usd] | length as $count | if $count > 0 then add / $count / 5 else 0.50 end' \
201
+ "$COST_FILE" 2>/dev/null || echo "0.50")
202
+
203
+ local estimated_remaining
204
+ estimated_remaining=$(awk -v avg="$avg_per_stage" -v stages="$remaining_stages" \
205
+ 'BEGIN { printf "%.2f", avg * stages }')
206
+
207
+ local projected_total
208
+ projected_total=$(awk -v current="$current_pipeline_cost" -v est="$estimated_remaining" \
209
+ 'BEGIN { printf "%.2f", current + est }')
210
+
211
+ local total_projected
212
+ total_projected=$(awk -v spent="$today_spent" -v proj="$projected_total" \
213
+ 'BEGIN { printf "%.2f", spent + proj }')
214
+
215
+ local remaining_budget
216
+ remaining_budget=$(awk -v budget="$budget_usd" -v spent="$today_spent" \
217
+ 'BEGIN { printf "%.2f", budget - spent }')
218
+
219
+ local status
220
+ if awk -v total="$total_projected" -v budget="$budget_usd" 'BEGIN { exit !(total > budget) }'; then
221
+ status="budget_exceeded"
222
+ elif awk -v total="$total_projected" -v budget="$budget_usd" -v threshold="$budget_usd * 0.8" \
223
+ 'BEGIN { exit !(total > threshold) }'; then
224
+ status="over_projecting"
225
+ elif awk -v total="$total_projected" -v budget="$budget_usd" -v threshold="$budget_usd * 0.6" \
226
+ 'BEGIN { exit !(total > threshold) }'; then
227
+ status="on_track"
228
+ else
229
+ status="under_budget"
230
+ fi
231
+
232
+ # Write budget check state
233
+ local check_state
234
+ check_state=$(cat <<EOF
235
+ {
236
+ "checked_at": "$(now_iso)",
237
+ "daily_budget_usd": $budget_usd,
238
+ "today_spent_usd": $today_spent,
239
+ "current_pipeline_usd": $current_pipeline_cost,
240
+ "remaining_stages": $remaining_stages,
241
+ "avg_cost_per_stage": $avg_per_stage,
242
+ "estimated_remaining_usd": $estimated_remaining,
243
+ "projected_pipeline_total_usd": $projected_total,
244
+ "projected_today_total_usd": $total_projected,
245
+ "remaining_budget_usd": $remaining_budget,
246
+ "status": "$status"
247
+ }
248
+ EOF
249
+ )
250
+
251
+ local tmp_file
252
+ tmp_file=$(mktemp "$ARTIFACTS_DIR/cost-check.json.tmp.XXXXXX" 2>/dev/null) || tmp_file="/tmp/costchk-$$.tmp"
253
+ echo "$check_state" > "$tmp_file"
254
+ mv "$tmp_file" "$ARTIFACTS_DIR/cost-check.json" 2>/dev/null || true
255
+
256
+ echo "$status"
257
+ }
258
+
259
+ # ─── 3. costopt_suggest_reductions() ─────────────────────────────────────────
260
+ # Suggest cost reduction actions when over budget.
261
+ # Returns JSON array of suggested reductions with savings estimates.
262
+ costopt_suggest_reductions() {
263
+ local remaining_stages="${1:-5}"
264
+ local current_model="${2:-opus}"
265
+ local current_cost="${3:-0}"
266
+ local budget_usd="${4:-100}"
267
+ local today_spent="${5:-0}"
268
+
269
+ local suggestions="[]"
270
+
271
+ # Calculate total projected cost
272
+ local avg_per_stage
273
+ avg_per_stage=$(jq -r \
274
+ '[.entries[].cost_usd] | length as $count | if $count > 0 then add / $count / 5 else 0.50 end' \
275
+ "$COST_FILE" 2>/dev/null || echo "0.50")
276
+
277
+ local estimated_remaining
278
+ estimated_remaining=$(awk -v avg="$avg_per_stage" -v stages="$remaining_stages" \
279
+ 'BEGIN { printf "%.2f", avg * stages }')
280
+
281
+ local projected_today_total
282
+ projected_today_total=$(awk -v spent="$today_spent" -v current="$current_cost" -v est="$estimated_remaining" \
283
+ 'BEGIN { printf "%.2f", spent + current + est }')
284
+
285
+ # Suggestion 1: Downgrade model (opus -> sonnet -> haiku)
286
+ if [[ "$current_model" == "opus" ]]; then
287
+ local opus_cost
288
+ opus_cost=$(jq -r '[.entries[] | select(.model == "opus") | .cost_usd] | length as $c | if $c > 0 then add / $c else 15 end' "$COST_FILE" 2>/dev/null || echo "15")
289
+ local sonnet_cost
290
+ sonnet_cost=$(jq -r '[.entries[] | select(.model == "sonnet") | .cost_usd] | length as $c | if $c > 0 then add / $c else 5 end' "$COST_FILE" 2>/dev/null || echo "5")
291
+ local savings
292
+ savings=$(awk -v opus="$opus_cost" -v sonnet="$sonnet_cost" -v stages="$remaining_stages" \
293
+ 'BEGIN { printf "%.2f", (opus - sonnet) * stages }')
294
+
295
+ suggestions=$(jq --arg sav "$savings" '.+=[{
296
+ "action": "downgrade_model",
297
+ "from": "opus",
298
+ "to": "sonnet",
299
+ "estimated_savings_usd": ($sav | tonumber),
300
+ "impact": "slower but acceptable for some stages"
301
+ }]' <<< "$suggestions")
302
+ elif [[ "$current_model" == "sonnet" ]]; then
303
+ local sonnet_cost
304
+ sonnet_cost=$(jq -r '[.entries[] | select(.model == "sonnet") | .cost_usd] | length as $c | if $c > 0 then add / $c else 5 end' "$COST_FILE" 2>/dev/null || echo "5")
305
+ local haiku_cost
306
+ haiku_cost=$(jq -r '[.entries[] | select(.model == "haiku") | .cost_usd] | length as $c | if $c > 0 then add / $c else 0.5 end' "$COST_FILE" 2>/dev/null || echo "0.5")
307
+ local savings
308
+ savings=$(awk -v sonnet="$sonnet_cost" -v haiku="$haiku_cost" -v stages="$remaining_stages" \
309
+ 'BEGIN { printf "%.2f", (sonnet - haiku) * stages }')
310
+
311
+ suggestions=$(jq --arg sav "$savings" '.+=[{
312
+ "action": "downgrade_model",
313
+ "from": "sonnet",
314
+ "to": "haiku",
315
+ "estimated_savings_usd": ($sav | tonumber),
316
+ "impact": "lowest cost, suitable for routine tasks"
317
+ }]' <<< "$suggestions")
318
+ fi
319
+
320
+ # Suggestion 2: Skip optional stages
321
+ local optional_stage_cost
322
+ optional_stage_cost=$(jq -r '[.entries[] | select(.stage == "compound_quality" or .stage == "adversarial") | .cost_usd] | length as $c | if $c > 0 then add / $c else 2 end' "$COST_FILE" 2>/dev/null || echo "2")
323
+
324
+ suggestions=$(jq --arg sav "$optional_stage_cost" '.+=[{
325
+ "action": "skip_optional_stages",
326
+ "stages": ["compound_quality", "adversarial"],
327
+ "estimated_savings_usd": ($sav | tonumber),
328
+ "impact": "skips quality stages but keeps test coverage"
329
+ }]' <<< "$suggestions")
330
+
331
+ # Suggestion 3: Reduce loop iterations
332
+ suggestions=$(jq '.+=[{
333
+ "action": "reduce_iterations",
334
+ "max_iterations": 3,
335
+ "from_max": 10,
336
+ "estimated_savings_usd": 5.0,
337
+ "impact": "faster convergence but less exploration"
338
+ }]' <<< "$suggestions")
339
+
340
+ # Suggestion 4: Use fast-test-cmd more frequently
341
+ suggestions=$(jq '.+=[{
342
+ "action": "increase_fast_test_frequency",
343
+ "fast_test_interval": 2,
344
+ "from_interval": 5,
345
+ "estimated_savings_usd": 1.5,
346
+ "impact": "run full test suite less often"
347
+ }]' <<< "$suggestions")
348
+
349
+ echo "$suggestions"
350
+ }
351
+
352
+ # ─── 4. costopt_apply_reduction() ────────────────────────────────────────────
353
+ # Apply a cost reduction action to the pipeline config.
354
+ # $1: reduction action name (downgrade_model, skip_optional_stages, reduce_iterations, etc.)
355
+ # $2: JSON config path (if modifiable in-memory)
356
+ # Returns 0 on success
357
+ costopt_apply_reduction() {
358
+ local action="${1:-}"
359
+ local config_path="${2:-}"
360
+ local estimated_savings="${3:-0}"
361
+
362
+ [[ -z "$action" ]] && { error "costopt_apply_reduction: action required"; return 1; }
363
+
364
+ local opt_state
365
+ opt_state="$ARTIFACTS_DIR/cost-optimization.json"
366
+
367
+ # Record applied reduction
368
+ local reduction_record
369
+ reduction_record=$(cat <<EOF
370
+ {
371
+ "applied_at": "$(now_iso)",
372
+ "action": "$action",
373
+ "estimated_savings_usd": $estimated_savings,
374
+ "config_modified": "$config_path"
375
+ }
376
+ EOF
377
+ )
378
+
379
+ # Update optimization state if file exists
380
+ if [[ -f "$opt_state" ]]; then
381
+ local tmp_file
382
+ tmp_file=$(mktemp "$opt_state.tmp.XXXXXX" 2>/dev/null) || tmp_file="/tmp/costopt-red-$$.tmp"
383
+ jq --argjson red "$reduction_record" '.reductions_applied += [$red]' "$opt_state" > "$tmp_file" 2>/dev/null && \
384
+ mv "$tmp_file" "$opt_state" || rm -f "$tmp_file"
385
+ fi
386
+
387
+ emit_event "costopt.reduction_applied" \
388
+ "action=$action" \
389
+ "estimated_savings=$estimated_savings"
390
+
391
+ info "Cost reduction applied: $action (estimated savings: \$$estimated_savings)"
392
+ return 0
393
+ }
394
+
395
+ # ─── 5. costopt_burst_mode() ────────────────────────────────────────────────
396
+ # Temporarily increase budget for critical work (near completion, high convergence).
397
+ # Returns 0 if burst mode activated, 1 if conditions not met.
398
+ # $1: convergence_score (0-100)
399
+ # $2: iteration_count
400
+ # $3: base_iteration_limit
401
+ # $4: max_burst_multiplier (default: 2)
402
+ costopt_burst_mode() {
403
+ local convergence_score="${1:-0}"
404
+ local iteration_count="${2:-0}"
405
+ local base_limit="${3:-10}"
406
+ local max_multiplier="${4:-2}"
407
+
408
+ _ensure_optimizer_files
409
+
410
+ local budget_enabled budget_usd
411
+ budget_enabled=$(jq -r '.enabled // false' "$BUDGET_FILE" 2>/dev/null || echo "false")
412
+ budget_usd=$(jq -r '.daily_budget_usd // 0' "$BUDGET_FILE" 2>/dev/null || echo "0")
413
+
414
+ # Burst requires budget to be enabled
415
+ if [[ "$budget_enabled" != "true" || "$budget_usd" == "0" ]]; then
416
+ return 1
417
+ fi
418
+
419
+ # Conditions for burst: tests passing, convergence > 60, iterations > base
420
+ if [[ "$convergence_score" -lt 60 || "$iteration_count" -le "$base_limit" ]]; then
421
+ return 1
422
+ fi
423
+
424
+ # Calculate burst extension
425
+ local burst_budget
426
+ burst_budget=$(awk -v budget="$budget_usd" -v mult="$max_multiplier" \
427
+ 'BEGIN { printf "%.2f", budget * mult }')
428
+
429
+ local burst_end_ts
430
+ burst_end_ts=$(date -u -v+2H +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || \
431
+ date -u -d "+2 hours" +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || \
432
+ echo "")
433
+
434
+ # Update cost-optimization.json
435
+ if [[ -f "$ARTIFACTS_DIR/cost-optimization.json" ]]; then
436
+ local tmp_file
437
+ tmp_file=$(mktemp "$ARTIFACTS_DIR/cost-optimization.json.tmp.XXXXXX" 2>/dev/null) || tmp_file="/tmp/costopt-burst-$$.tmp"
438
+ jq --arg burst_ts "$burst_end_ts" --arg burst_bud "$burst_budget" \
439
+ '.burst_active = true | .burst_end_ts = $burst_ts | .burst_budget_usd = $burst_bud' \
440
+ "$ARTIFACTS_DIR/cost-optimization.json" > "$tmp_file" 2>/dev/null && \
441
+ mv "$tmp_file" "$ARTIFACTS_DIR/cost-optimization.json" || rm -f "$tmp_file"
442
+ fi
443
+
444
+ emit_event "costopt.burst_activated" \
445
+ "convergence=$convergence_score" \
446
+ "iteration=$iteration_count" \
447
+ "burst_budget=$burst_budget" \
448
+ "expires=$burst_end_ts"
449
+
450
+ warn "Burst mode activated: temporary budget extension to \$$burst_budget (expires $burst_end_ts)"
451
+ return 0
452
+ }
453
+
454
+ # ─── 6. costopt_efficiency_score() ──────────────────────────────────────────
455
+ # Calculate pipeline cost efficiency (0-100).
456
+ # Factors: cost per stage, cost per test, cost per line changed.
457
+ # Higher score = more efficient (less cost per unit work).
458
+ costopt_efficiency_score() {
459
+ local total_cost="${1:-0}"
460
+ local tests_passed="${2:-0}"
461
+ local lines_changed="${3:-0}"
462
+ local stages_completed="${4:-0}"
463
+
464
+ # Metrics with reasonable defaults
465
+ local cost_per_test=999
466
+ local cost_per_line=999
467
+ local cost_per_stage=999
468
+
469
+ [[ "$tests_passed" -gt 0 ]] && \
470
+ cost_per_test=$(awk -v cost="$total_cost" -v tests="$tests_passed" \
471
+ 'BEGIN { printf "%.4f", cost / tests }')
472
+
473
+ [[ "$lines_changed" -gt 0 ]] && \
474
+ cost_per_line=$(awk -v cost="$total_cost" -v lines="$lines_changed" \
475
+ 'BEGIN { printf "%.6f", cost / lines }')
476
+
477
+ [[ "$stages_completed" -gt 0 ]] && \
478
+ cost_per_stage=$(awk -v cost="$total_cost" -v stages="$stages_completed" \
479
+ 'BEGIN { printf "%.2f", cost / stages }')
480
+
481
+ # Normalize to 0-100 score
482
+ # Lower costs = higher score
483
+ # Benchmarks: cost_per_test < $0.05 is excellent
484
+ # cost_per_line < $0.001 is excellent
485
+ # cost_per_stage < $1.00 is excellent
486
+ local test_score
487
+ test_score=$(awk -v cpt="$cost_per_test" 'BEGIN {
488
+ s = 100 * (1 - cpt / 0.10)
489
+ if (s < 0) s = 0
490
+ if (s > 100) s = 100
491
+ printf "%.0f", s
492
+ }')
493
+
494
+ local line_score
495
+ line_score=$(awk -v cpl="$cost_per_line" 'BEGIN {
496
+ s = 100 * (1 - cpl / 0.002)
497
+ if (s < 0) s = 0
498
+ if (s > 100) s = 100
499
+ printf "%.0f", s
500
+ }')
501
+
502
+ local stage_score
503
+ stage_score=$(awk -v cps="$cost_per_stage" 'BEGIN {
504
+ s = 100 * (1 - cps / 2.00)
505
+ if (s < 0) s = 0
506
+ if (s > 100) s = 100
507
+ printf "%.0f", s
508
+ }')
509
+
510
+ # Average the three scores
511
+ local efficiency
512
+ efficiency=$(awk -v ts="$test_score" -v ls="$line_score" -v ss="$stage_score" \
513
+ 'BEGIN { printf "%.0f", (ts + ls + ss) / 3 }')
514
+
515
+ echo "$efficiency"
516
+ }
517
+
518
+ # ─── 7. costopt_report() ────────────────────────────────────────────────────
519
+ # Generate cost optimization dashboard/report.
520
+ # $1: output format (text|json) - default: text
521
+ costopt_report() {
522
+ local format="${1:-text}"
523
+
524
+ _ensure_optimizer_files
525
+
526
+ local budget_enabled budget_usd
527
+ budget_enabled=$(jq -r '.enabled // false' "$BUDGET_FILE" 2>/dev/null || echo "false")
528
+ budget_usd=$(jq -r '.daily_budget_usd // 0' "$BUDGET_FILE" 2>/dev/null || echo "0")
529
+
530
+ # If budget not enabled, return minimal report
531
+ if [[ "$budget_enabled" != "true" || "$budget_usd" == "0" ]]; then
532
+ if [[ "$format" == "json" ]]; then
533
+ echo '{"status":"budget_not_configured","daily_budget_usd":0}'
534
+ else
535
+ echo "Budget not configured. Use 'shipwright cost budget set <amount>' to enable cost tracking."
536
+ fi
537
+ return 0
538
+ fi
539
+
540
+ # Calculate today's spending
541
+ local today_start
542
+ today_start=$(date -u +"%Y-%m-%dT00:00:00Z")
543
+ local today_epoch
544
+ today_epoch=$(date -u -jf "%Y-%m-%dT%H:%M:%SZ" "$today_start" +%s 2>/dev/null || date -u -d "$today_start" +%s 2>/dev/null || echo "0")
545
+
546
+ local today_spent
547
+ today_spent=$(jq --argjson cutoff "$today_epoch" \
548
+ '[.entries[] | select(.ts_epoch >= $cutoff) | .cost_usd] | add // 0' \
549
+ "$COST_FILE" 2>/dev/null || echo "0")
550
+
551
+ local remaining
552
+ remaining=$(awk -v budget="$budget_usd" -v spent="$today_spent" \
553
+ 'BEGIN { printf "%.2f", budget - spent }')
554
+
555
+ local pct_used
556
+ pct_used=$(awk -v spent="$today_spent" -v budget="$budget_usd" \
557
+ 'BEGIN { printf "%.0f", (spent / budget) * 100 }')
558
+
559
+ # Stage breakdown
560
+ local stage_summary
561
+ stage_summary=$(jq -r '
562
+ [.entries | group_by(.stage) | .[] |
563
+ {
564
+ stage: .[0].stage,
565
+ count: length,
566
+ total_cost: (map(.cost_usd) | add),
567
+ avg_cost: (map(.cost_usd) | add / length)
568
+ }] | sort_by(-.total_cost) | .[:10]
569
+ ' "$COST_FILE" 2>/dev/null || echo "[]")
570
+
571
+ # Model breakdown
572
+ local model_summary
573
+ model_summary=$(jq -r '
574
+ [.entries | group_by(.model) | .[] |
575
+ {
576
+ model: .[0].model,
577
+ count: length,
578
+ total_cost: (map(.cost_usd) | add)
579
+ }] | sort_by(-.total_cost)
580
+ ' "$COST_FILE" 2>/dev/null || echo "[]")
581
+
582
+ if [[ "$format" == "json" ]]; then
583
+ cat <<EOF
584
+ {
585
+ "report_timestamp": "$(now_iso)",
586
+ "budget": {
587
+ "daily_limit_usd": $budget_usd,
588
+ "today_spent_usd": $today_spent,
589
+ "remaining_usd": $remaining,
590
+ "percent_used": $pct_used
591
+ },
592
+ "stage_breakdown": $stage_summary,
593
+ "model_breakdown": $model_summary,
594
+ "burst_enabled": false,
595
+ "optimizations_applied": []
596
+ }
597
+ EOF
598
+ else
599
+ # Text format
600
+ cat <<EOF
601
+
602
+ ╔═══════════════════════════════════════════════════════════════╗
603
+ ║ Cost Optimization Report ║
604
+ ╚═══════════════════════════════════════════════════════════════╝
605
+
606
+ Budget Status:
607
+ Daily limit: \$$budget_usd
608
+ Today spent: \$$today_spent (${pct_used}%)
609
+ Remaining: \$$remaining
610
+
611
+ Top Cost Drivers (by stage):
612
+ $(echo "$stage_summary" | jq -r '.[] | " \(.stage): $\(.total_cost) (\(.count) calls)"' || echo " (no data)")
613
+
614
+ Model Cost Breakdown:
615
+ $(echo "$model_summary" | jq -r '.[] | " \(.model): $\(.total_cost) (\(.count) calls)"' || echo " (no data)")
616
+
617
+ Burst Mode: Disabled (activate when near completion)
618
+ Optimizations Applied: None yet
619
+
620
+ EOF
621
+ fi
622
+ }
623
+
624
+ # ─── Public API ──────────────────────────────────────────────────────────────
625
+ # These functions are the main entry points:
626
+ # costopt_init
627
+ # costopt_check_budget
628
+ # costopt_suggest_reductions
629
+ # costopt_apply_reduction
630
+ # costopt_burst_mode
631
+ # costopt_efficiency_score
632
+ # costopt_report
633
+
634
+ true
@@ -3,6 +3,16 @@
3
3
  [[ -n "${_DAEMON_ADAPTIVE_LOADED:-}" ]] && return 0
4
4
  _DAEMON_ADAPTIVE_LOADED=1
5
5
 
6
+ # Defaults for variables normally set by sw-daemon.sh (safe under set -u).
7
+ DAEMON_DIR="${DAEMON_DIR:-${HOME}/.shipwright}"
8
+ STATE_FILE="${STATE_FILE:-${DAEMON_DIR}/daemon-state.json}"
9
+ POLL_INTERVAL="${POLL_INTERVAL:-60}"
10
+ MAX_PARALLEL="${MAX_PARALLEL:-4}"
11
+ EMPTY_QUEUE_CYCLES="${EMPTY_QUEUE_CYCLES:-0}"
12
+ EST_COST_PER_JOB="${EST_COST_PER_JOB:-5.0}"
13
+ WORKER_MEM_GB="${WORKER_MEM_GB:-4}"
14
+ EVENTS_FILE="${EVENTS_FILE:-${DAEMON_DIR}/events.jsonl}"
15
+
6
16
  # Adapt poll interval based on queue state
7
17
  # Empty queue 5+ cycles → 120s; queue has items → 30s; processing → 60s
8
18
  get_adaptive_poll_interval() {
@@ -223,10 +233,12 @@ daemon_collect_snapshot() {
223
233
  if [[ -d "$worktree/.git" ]] || [[ -f "$worktree/.git" ]]; then
224
234
  diff_lines=$(cd "$worktree" && git diff --stat 2>/dev/null | tail -1 | grep -o '[0-9]* insertion' | grep -o '[0-9]*' || echo "0")
225
235
  [[ -z "$diff_lines" ]] && diff_lines=0
226
- files_changed=$(cd "$worktree" && git diff --name-only 2>/dev/null | wc -l | tr -d ' ' || echo "0")
236
+ files_changed=$(cd "$worktree" && git diff --name-only 2>/dev/null | wc -l | tr -d ' ' || true)
237
+ files_changed="${files_changed:-0}"
227
238
  # Also count untracked files the agent has created
228
239
  local untracked
229
- untracked=$(cd "$worktree" && git ls-files --others --exclude-standard 2>/dev/null | wc -l | tr -d ' ' || echo "0")
240
+ untracked=$(cd "$worktree" && git ls-files --others --exclude-standard 2>/dev/null | wc -l | tr -d ' ' || true)
241
+ untracked="${untracked:-0}"
230
242
  files_changed=$((files_changed + untracked))
231
243
  fi
232
244