shipwright-cli 3.0.0 → 3.2.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 (143) hide show
  1. package/README.md +21 -7
  2. package/completions/_shipwright +247 -93
  3. package/completions/shipwright.bash +69 -15
  4. package/completions/shipwright.fish +309 -41
  5. package/config/decision-tiers.json +55 -0
  6. package/config/defaults.json +25 -2
  7. package/config/event-schema.json +142 -5
  8. package/config/policy.json +8 -0
  9. package/dashboard/public/index.html +6 -0
  10. package/dashboard/public/styles.css +76 -0
  11. package/dashboard/server.ts +51 -0
  12. package/dashboard/src/core/api.ts +5 -0
  13. package/dashboard/src/types/api.ts +10 -0
  14. package/dashboard/src/views/metrics.ts +69 -1
  15. package/package.json +3 -3
  16. package/scripts/lib/architecture.sh +2 -1
  17. package/scripts/lib/bootstrap.sh +0 -0
  18. package/scripts/lib/config.sh +0 -0
  19. package/scripts/lib/daemon-adaptive.sh +4 -2
  20. package/scripts/lib/daemon-dispatch.sh +24 -1
  21. package/scripts/lib/daemon-failure.sh +0 -0
  22. package/scripts/lib/daemon-health.sh +0 -0
  23. package/scripts/lib/daemon-patrol.sh +42 -7
  24. package/scripts/lib/daemon-poll.sh +17 -0
  25. package/scripts/lib/daemon-state.sh +17 -0
  26. package/scripts/lib/daemon-triage.sh +1 -1
  27. package/scripts/lib/decide-autonomy.sh +295 -0
  28. package/scripts/lib/decide-scoring.sh +228 -0
  29. package/scripts/lib/decide-signals.sh +462 -0
  30. package/scripts/lib/fleet-failover.sh +0 -0
  31. package/scripts/lib/helpers.sh +19 -18
  32. package/scripts/lib/pipeline-detection.sh +1 -1
  33. package/scripts/lib/pipeline-github.sh +0 -0
  34. package/scripts/lib/pipeline-intelligence.sh +23 -4
  35. package/scripts/lib/pipeline-quality-checks.sh +11 -6
  36. package/scripts/lib/pipeline-quality.sh +0 -0
  37. package/scripts/lib/pipeline-stages.sh +330 -33
  38. package/scripts/lib/pipeline-state.sh +14 -0
  39. package/scripts/lib/policy.sh +0 -0
  40. package/scripts/lib/test-helpers.sh +0 -0
  41. package/scripts/postinstall.mjs +75 -1
  42. package/scripts/signals/example-collector.sh +36 -0
  43. package/scripts/sw +8 -4
  44. package/scripts/sw-activity.sh +1 -7
  45. package/scripts/sw-adaptive.sh +7 -7
  46. package/scripts/sw-adversarial.sh +1 -1
  47. package/scripts/sw-architecture-enforcer.sh +1 -1
  48. package/scripts/sw-auth.sh +1 -1
  49. package/scripts/sw-autonomous.sh +1 -1
  50. package/scripts/sw-changelog.sh +1 -1
  51. package/scripts/sw-checkpoint.sh +1 -1
  52. package/scripts/sw-ci.sh +11 -6
  53. package/scripts/sw-cleanup.sh +1 -1
  54. package/scripts/sw-code-review.sh +36 -17
  55. package/scripts/sw-connect.sh +1 -1
  56. package/scripts/sw-context.sh +1 -1
  57. package/scripts/sw-cost.sh +71 -5
  58. package/scripts/sw-daemon.sh +6 -3
  59. package/scripts/sw-dashboard.sh +1 -1
  60. package/scripts/sw-db.sh +53 -38
  61. package/scripts/sw-decide.sh +685 -0
  62. package/scripts/sw-decompose.sh +1 -1
  63. package/scripts/sw-deps.sh +1 -1
  64. package/scripts/sw-developer-simulation.sh +1 -1
  65. package/scripts/sw-discovery.sh +80 -4
  66. package/scripts/sw-doc-fleet.sh +1 -1
  67. package/scripts/sw-docs-agent.sh +1 -1
  68. package/scripts/sw-docs.sh +1 -1
  69. package/scripts/sw-doctor.sh +1 -1
  70. package/scripts/sw-dora.sh +1 -1
  71. package/scripts/sw-durable.sh +9 -5
  72. package/scripts/sw-e2e-orchestrator.sh +1 -1
  73. package/scripts/sw-eventbus.sh +7 -4
  74. package/scripts/sw-evidence.sh +1 -1
  75. package/scripts/sw-feedback.sh +1 -1
  76. package/scripts/sw-fix.sh +1 -1
  77. package/scripts/sw-fleet-discover.sh +1 -1
  78. package/scripts/sw-fleet-viz.sh +6 -4
  79. package/scripts/sw-fleet.sh +1 -1
  80. package/scripts/sw-github-app.sh +3 -2
  81. package/scripts/sw-github-checks.sh +1 -1
  82. package/scripts/sw-github-deploy.sh +1 -1
  83. package/scripts/sw-github-graphql.sh +1 -1
  84. package/scripts/sw-guild.sh +1 -1
  85. package/scripts/sw-heartbeat.sh +1 -1
  86. package/scripts/sw-hygiene.sh +5 -3
  87. package/scripts/sw-incident.sh +9 -5
  88. package/scripts/sw-init.sh +1 -1
  89. package/scripts/sw-instrument.sh +1 -1
  90. package/scripts/sw-intelligence.sh +11 -6
  91. package/scripts/sw-jira.sh +1 -1
  92. package/scripts/sw-launchd.sh +1 -1
  93. package/scripts/sw-linear.sh +1 -1
  94. package/scripts/sw-logs.sh +1 -1
  95. package/scripts/sw-loop.sh +338 -32
  96. package/scripts/sw-memory.sh +23 -6
  97. package/scripts/sw-mission-control.sh +1 -1
  98. package/scripts/sw-model-router.sh +3 -2
  99. package/scripts/sw-otel.sh +8 -4
  100. package/scripts/sw-oversight.sh +1 -1
  101. package/scripts/sw-pipeline-composer.sh +3 -1
  102. package/scripts/sw-pipeline-vitals.sh +11 -6
  103. package/scripts/sw-pipeline.sh +92 -8
  104. package/scripts/sw-pm.sh +5 -4
  105. package/scripts/sw-pr-lifecycle.sh +7 -4
  106. package/scripts/sw-predictive.sh +11 -5
  107. package/scripts/sw-prep.sh +1 -1
  108. package/scripts/sw-ps.sh +1 -1
  109. package/scripts/sw-public-dashboard.sh +3 -2
  110. package/scripts/sw-quality.sh +21 -10
  111. package/scripts/sw-reaper.sh +1 -1
  112. package/scripts/sw-recruit.sh +1 -1
  113. package/scripts/sw-regression.sh +1 -1
  114. package/scripts/sw-release-manager.sh +1 -1
  115. package/scripts/sw-release.sh +1 -1
  116. package/scripts/sw-remote.sh +1 -1
  117. package/scripts/sw-replay.sh +1 -1
  118. package/scripts/sw-retro.sh +1 -1
  119. package/scripts/sw-review-rerun.sh +1 -1
  120. package/scripts/sw-scale.sh +69 -11
  121. package/scripts/sw-security-audit.sh +1 -1
  122. package/scripts/sw-self-optimize.sh +168 -4
  123. package/scripts/sw-session.sh +3 -3
  124. package/scripts/sw-setup.sh +1 -1
  125. package/scripts/sw-standup.sh +1 -1
  126. package/scripts/sw-status.sh +1 -1
  127. package/scripts/sw-strategic.sh +11 -6
  128. package/scripts/sw-stream.sh +7 -4
  129. package/scripts/sw-swarm.sh +3 -2
  130. package/scripts/sw-team-stages.sh +1 -1
  131. package/scripts/sw-templates.sh +3 -3
  132. package/scripts/sw-testgen.sh +11 -6
  133. package/scripts/sw-tmux-pipeline.sh +1 -1
  134. package/scripts/sw-tmux.sh +35 -1
  135. package/scripts/sw-trace.sh +1 -1
  136. package/scripts/sw-tracker.sh +1 -1
  137. package/scripts/sw-triage.sh +7 -7
  138. package/scripts/sw-upgrade.sh +1 -1
  139. package/scripts/sw-ux.sh +1 -1
  140. package/scripts/sw-webhook.sh +3 -2
  141. package/scripts/sw-widgets.sh +7 -4
  142. package/scripts/sw-worktree.sh +1 -1
  143. package/scripts/update-homebrew-sha.sh +21 -15
@@ -0,0 +1,55 @@
1
+ {
2
+ "$schema": "https://shipwright.dev/schemas/decision-tiers-v1.json",
3
+ "description": "Autonomy tiers for Shipwright decision engine. Controls which actions the engine takes automatically vs proposing for human review.",
4
+ "version": "1",
5
+ "tiers": {
6
+ "auto": {
7
+ "description": "Create issue and auto-spawn pipeline — no human approval needed",
8
+ "labels": ["shipwright", "shipwright:auto", "ready-to-build"],
9
+ "pipeline_template": "fast"
10
+ },
11
+ "propose": {
12
+ "description": "Create issue with proposed label — human approves before build",
13
+ "labels": ["shipwright", "shipwright:proposed"],
14
+ "pipeline_template": "standard"
15
+ },
16
+ "draft": {
17
+ "description": "Write to decision-drafts directory only — human reviews offline",
18
+ "labels": [],
19
+ "pipeline_template": null
20
+ }
21
+ },
22
+ "category_rules": {
23
+ "deps_patch": { "tier": "auto", "risk_ceiling": 30 },
24
+ "deps_minor": { "tier": "auto", "risk_ceiling": 40 },
25
+ "deps_major": { "tier": "propose", "risk_ceiling": 60 },
26
+ "security_patch": { "tier": "auto", "risk_ceiling": 50 },
27
+ "security_critical": { "tier": "propose", "risk_ceiling": 80 },
28
+ "test_coverage": { "tier": "auto", "risk_ceiling": 30 },
29
+ "doc_sync": { "tier": "auto", "risk_ceiling": 20 },
30
+ "dead_code": { "tier": "auto", "risk_ceiling": 35 },
31
+ "refactor_hotspot": { "tier": "propose", "risk_ceiling": 60 },
32
+ "architecture_drift": { "tier": "propose", "risk_ceiling": 70 },
33
+ "performance_regression": { "tier": "propose", "risk_ceiling": 60 },
34
+ "recurring_failure": { "tier": "propose", "risk_ceiling": 50 },
35
+ "dora_regression": { "tier": "propose", "risk_ceiling": 55 },
36
+ "new_feature": { "tier": "draft", "risk_ceiling": 90 },
37
+ "breaking_change": { "tier": "draft", "risk_ceiling": 95 },
38
+ "business_logic": { "tier": "draft", "risk_ceiling": 90 },
39
+ "api_change": { "tier": "draft", "risk_ceiling": 85 },
40
+ "data_model_change": { "tier": "draft", "risk_ceiling": 90 }
41
+ },
42
+ "limits": {
43
+ "max_issues_per_day": 15,
44
+ "max_cost_per_day_usd": 25,
45
+ "cooldown_seconds": 300,
46
+ "halt_after_consecutive_failures": 3
47
+ },
48
+ "scoring_weights": {
49
+ "impact": 0.3,
50
+ "urgency": 0.25,
51
+ "effort": 0.2,
52
+ "confidence": 0.15,
53
+ "risk": 0.1
54
+ }
55
+ }
@@ -25,6 +25,7 @@
25
25
  "build_test_retries": 3,
26
26
  "claude_timeout": 1800,
27
27
  "heartbeat_interval": 30,
28
+ "composed_cache_ttl": 3600,
28
29
  "branch_pattern": "shipwright/issue-{issue}",
29
30
  "stage_order": [
30
31
  "intake",
@@ -47,7 +48,12 @@
47
48
  "max_restarts": 0,
48
49
  "fast_test_interval": 5,
49
50
  "convergence_threshold": 3,
50
- "multi_agent_sleep": 5
51
+ "multi_agent_sleep": 5,
52
+ "context_budget_chars": 180000,
53
+ "context_trim_memory_chars": 20000,
54
+ "context_trim_git_entries": 10,
55
+ "context_trim_hotspot_files": 5,
56
+ "context_trim_test_lines": 50
51
57
  },
52
58
  "dashboard": {
53
59
  "port": 8767,
@@ -77,12 +83,29 @@
77
83
  "ab_test_ratio": 0.2,
78
84
  "claude_timeout": 60
79
85
  },
86
+ "predictive": {
87
+ "default_risk_score": 50,
88
+ "keyword_risk_score": 70
89
+ },
90
+ "api_optimization": {
91
+ "programmatic_tool_calling": true,
92
+ "tool_search_enabled": true,
93
+ "tool_search_type": "bm25",
94
+ "defer_unused_tools": true,
95
+ "web_search_version": "web_search_20260209",
96
+ "web_fetch_version": "web_fetch_20260209",
97
+ "dynamic_filtering": true,
98
+ "code_execution_sandbox": true,
99
+ "beta_header": "code-execution-web-tools-2026-02-09"
100
+ },
80
101
  "quality": {
81
102
  "gate_score_threshold": 70,
82
103
  "secret_threshold": 3,
83
104
  "min_file_count": 10,
84
105
  "score_weight_per_file": 25,
85
- "pass_rate_threshold": 5.0
106
+ "pass_rate_threshold": 5.0,
107
+ "bundle_growth_legacy_pct": 20,
108
+ "perf_regression_legacy_pct": 30
86
109
  },
87
110
  "cleanup": {
88
111
  "artifact_age_days": 7,
@@ -15,12 +15,15 @@
15
15
  "required": ["job_id"],
16
16
  "optional": ["error", "stage"]
17
17
  },
18
- "stage.started": { "required": ["job_id", "stage"], "optional": [] },
18
+ "stage.started": { "required": ["stage"], "optional": ["issue", "job_id"] },
19
19
  "stage.completed": {
20
- "required": ["job_id", "stage"],
21
- "optional": ["duration_s"]
20
+ "required": ["stage"],
21
+ "optional": ["issue", "job_id", "duration_s", "result"]
22
+ },
23
+ "stage.failed": {
24
+ "required": ["stage"],
25
+ "optional": ["issue", "job_id", "error", "error_class", "duration_s"]
22
26
  },
23
- "stage.failed": { "required": ["job_id", "stage"], "optional": ["error"] },
24
27
  "daemon.started": { "required": [], "optional": ["pid", "mode"] },
25
28
  "daemon.stopped": { "required": [], "optional": ["reason"] },
26
29
  "daemon.claimed": { "required": ["issue"], "optional": ["repo", "title"] },
@@ -76,6 +79,140 @@
76
79
  "required": ["session_id"],
77
80
  "optional": ["template", "agents"]
78
81
  },
79
- "session.ended": { "required": ["session_id"], "optional": ["duration_s"] }
82
+ "session.ended": { "required": ["session_id"], "optional": ["duration_s"] },
83
+ "memory.fix_outcome": {
84
+ "required": ["pattern"],
85
+ "optional": ["outcome", "repo"]
86
+ },
87
+ "memory.global_aggregated": {
88
+ "required": [],
89
+ "optional": ["repo", "patterns_promoted"]
90
+ },
91
+ "memory.not_available": {
92
+ "required": ["path"],
93
+ "optional": ["action"]
94
+ },
95
+ "memory.export": {
96
+ "required": [],
97
+ "optional": ["repo"]
98
+ },
99
+ "cost.budget_unconfigured": {
100
+ "required": ["status"],
101
+ "optional": []
102
+ },
103
+ "scale.recommendation": {
104
+ "required": [],
105
+ "optional": ["iterations", "coverage", "modules", "has_recommendations"]
106
+ },
107
+ "pr.skipped": {
108
+ "required": ["issue"],
109
+ "optional": ["reason"]
110
+ },
111
+ "pr.rejected": {
112
+ "required": ["issue"],
113
+ "optional": ["reason"]
114
+ },
115
+ "intelligence.compose": {
116
+ "required": [],
117
+ "optional": ["stage_count", "complexity"]
118
+ },
119
+ "intelligence.synthesize": {
120
+ "required": [],
121
+ "optional": ["fix_count", "cause_count"]
122
+ },
123
+ "simulation.complete": {
124
+ "required": [],
125
+ "optional": ["issue", "concerns"]
126
+ },
127
+ "daemon.hard_limit": {
128
+ "required": ["issue"],
129
+ "optional": ["elapsed_s", "limit_s", "pid"]
130
+ },
131
+ "daemon.stalled": {
132
+ "required": ["issue"],
133
+ "optional": ["no_progress", "elapsed_s", "pid"]
134
+ },
135
+ "daemon.nudge": {
136
+ "required": ["issue"],
137
+ "optional": ["no_progress", "stage", "elapsed_s"]
138
+ },
139
+ "daemon.stuck_kill": {
140
+ "required": ["issue"],
141
+ "optional": [
142
+ "no_progress",
143
+ "repeated_errors",
144
+ "stage",
145
+ "elapsed_s",
146
+ "pid",
147
+ "reason"
148
+ ]
149
+ },
150
+ "convergence.tests_passed": {
151
+ "required": [],
152
+ "optional": ["issue", "cycle"]
153
+ },
154
+ "vitals.snapshot": {
155
+ "required": [],
156
+ "optional": [
157
+ "issue",
158
+ "stage",
159
+ "iteration",
160
+ "diff_count",
161
+ "files_changed",
162
+ "error"
163
+ ]
164
+ },
165
+ "retry.classified": {
166
+ "required": ["stage"],
167
+ "optional": ["issue", "attempt", "error_class"]
168
+ },
169
+ "retry.escalated": {
170
+ "required": ["stage"],
171
+ "optional": ["issue", "reason"]
172
+ },
173
+ "retry.skipped": {
174
+ "required": ["stage"],
175
+ "optional": ["issue", "reason"]
176
+ },
177
+ "pipeline.anomaly": {
178
+ "required": ["stage"],
179
+ "optional": ["metric", "value", "severity"]
180
+ },
181
+ "pipeline.cost": {
182
+ "required": [],
183
+ "optional": ["issue", "stage", "cost_usd", "model"]
184
+ },
185
+ "pipeline.cleanup": {
186
+ "required": [],
187
+ "optional": ["issue", "reason"]
188
+ },
189
+ "pipeline.comment_failed": {
190
+ "required": [],
191
+ "optional": ["issue"]
192
+ },
193
+ "loop.iteration_start": {
194
+ "required": ["iteration"],
195
+ "optional": ["issue", "goal"]
196
+ },
197
+ "loop.iteration_complete": {
198
+ "required": ["iteration"],
199
+ "optional": ["issue", "test_result", "duration_s"]
200
+ },
201
+ "loop.restart": {
202
+ "required": [],
203
+ "optional": ["issue", "restart_count", "reason"]
204
+ },
205
+ "loop.stuckness_detected": {
206
+ "required": [],
207
+ "optional": ["issue", "iteration", "reason"]
208
+ },
209
+ "stage.skipped": {
210
+ "required": ["stage"],
211
+ "optional": ["issue", "reason"]
212
+ },
213
+ "stage.artifact_miss": {
214
+ "required": ["stage"],
215
+ "optional": ["issue"]
216
+ }
80
217
  }
81
218
  }
@@ -222,5 +222,13 @@
222
222
  "default_model": "sonnet",
223
223
  "promote_threshold_tasks": 10,
224
224
  "promote_threshold_success_rate": 85
225
+ },
226
+ "decision": {
227
+ "enabled": true,
228
+ "cycle_interval_seconds": 1800,
229
+ "tiers_file": "config/decision-tiers.json",
230
+ "outcome_learning_enabled": true,
231
+ "outcome_min_samples": 10,
232
+ "dedup_window_days": 7
225
233
  }
226
234
  }
@@ -516,6 +516,12 @@
516
516
  id="cost-trend-container"
517
517
  ></div>
518
518
 
519
+ <!-- Context efficiency -->
520
+ <div
521
+ class="metric-card metric-card-wide"
522
+ id="context-efficiency-container"
523
+ ></div>
524
+
519
525
  <!-- DORA trend -->
520
526
  <div
521
527
  class="metric-card metric-card-wide"
@@ -3634,6 +3634,82 @@ body::-webkit-scrollbar-thumb {
3634
3634
  text-align: center;
3635
3635
  }
3636
3636
 
3637
+ /* Context efficiency widget */
3638
+ .ctx-eff-grid {
3639
+ display: grid;
3640
+ grid-template-columns: repeat(4, 1fr);
3641
+ gap: 16px;
3642
+ }
3643
+
3644
+ .ctx-eff-card {
3645
+ background: var(--glass-bg);
3646
+ border: 1px solid var(--glass-border);
3647
+ border-radius: 10px;
3648
+ padding: 12px;
3649
+ display: flex;
3650
+ flex-direction: column;
3651
+ gap: 6px;
3652
+ }
3653
+
3654
+ .ctx-eff-card-label {
3655
+ font-family: var(--font-mono);
3656
+ font-size: 0.65rem;
3657
+ font-weight: 600;
3658
+ letter-spacing: 0.08em;
3659
+ color: var(--text-muted);
3660
+ text-transform: uppercase;
3661
+ }
3662
+
3663
+ .ctx-eff-gauge {
3664
+ height: 8px;
3665
+ border-radius: 4px;
3666
+ background: var(--glass-border);
3667
+ overflow: hidden;
3668
+ }
3669
+
3670
+ .ctx-eff-gauge-fill {
3671
+ height: 100%;
3672
+ border-radius: 4px;
3673
+ transition: width 0.5s ease;
3674
+ }
3675
+
3676
+ .ctx-eff-gauge-fill.ctx-eff-high {
3677
+ background: #4ade80;
3678
+ }
3679
+
3680
+ .ctx-eff-gauge-fill.ctx-eff-mid {
3681
+ background: #00d4ff;
3682
+ }
3683
+
3684
+ .ctx-eff-gauge-fill.ctx-eff-low {
3685
+ background: #f43f5e;
3686
+ }
3687
+
3688
+ .ctx-eff-gauge-fill.ctx-eff-trim {
3689
+ background: #7c3aed;
3690
+ }
3691
+
3692
+ .ctx-eff-value {
3693
+ font-family: var(--font-mono);
3694
+ font-size: 0.85rem;
3695
+ font-weight: 700;
3696
+ color: var(--text-primary);
3697
+ }
3698
+
3699
+ .ctx-eff-big {
3700
+ font-family: var(--font-mono);
3701
+ font-size: 1.4rem;
3702
+ font-weight: 700;
3703
+ color: var(--text-primary);
3704
+ line-height: 1;
3705
+ }
3706
+
3707
+ .ctx-eff-sub {
3708
+ font-family: var(--font-mono);
3709
+ font-size: 0.65rem;
3710
+ color: var(--text-muted);
3711
+ }
3712
+
3637
3713
  .dora-trend-grid {
3638
3714
  display: grid;
3639
3715
  grid-template-columns: repeat(4, 1fr);
@@ -3690,6 +3690,57 @@ const server = Bun.serve({
3690
3690
  });
3691
3691
  }
3692
3692
 
3693
+ // REST: Context efficiency metrics (from loop.context_efficiency events)
3694
+ if (pathname === "/api/context-efficiency") {
3695
+ const period = parseInt(url.searchParams.get("period") || "7");
3696
+ const events = readEvents();
3697
+ const now = Math.floor(Date.now() / 1000);
3698
+ const cutoff = now - period * 86400;
3699
+
3700
+ let totalUtil = 0;
3701
+ let totalRatio = 0;
3702
+ let totalRaw = 0;
3703
+ let totalTrimmed = 0;
3704
+ let trimEvents = 0;
3705
+ let count = 0;
3706
+
3707
+ for (const e of events) {
3708
+ if ((e.ts_epoch || 0) < cutoff) continue;
3709
+ if (e.type !== "loop.context_efficiency") continue;
3710
+
3711
+ const util = parseFloat(String(e.budget_utilization || 0));
3712
+ const ratio = parseFloat(String(e.trim_ratio || 0));
3713
+ const raw = parseInt(String(e.raw_prompt_chars || 0), 10);
3714
+ const trimmed = parseInt(String(e.trimmed_prompt_chars || 0), 10);
3715
+
3716
+ totalUtil += util;
3717
+ totalRatio += ratio;
3718
+ totalRaw += raw;
3719
+ totalTrimmed += trimmed;
3720
+ if (ratio > 0) trimEvents++;
3721
+ count++;
3722
+ }
3723
+
3724
+ const avgUtilization =
3725
+ count > 0 ? Math.round((totalUtil / count) * 10) / 10 : 0;
3726
+ const avgTrimRatio =
3727
+ count > 0 ? Math.round((totalRatio / count) * 10) / 10 : 0;
3728
+ const totalDiscarded = totalRaw - totalTrimmed;
3729
+
3730
+ return new Response(
3731
+ JSON.stringify({
3732
+ avg_utilization: avgUtilization,
3733
+ avg_trim_ratio: avgTrimRatio,
3734
+ total_raw_chars: totalRaw,
3735
+ total_trimmed_chars: totalTrimmed,
3736
+ total_discarded_chars: totalDiscarded,
3737
+ trim_events: trimEvents,
3738
+ total_iterations: count,
3739
+ }),
3740
+ { headers: { "Content-Type": "application/json", ...CORS_HEADERS } },
3741
+ );
3742
+ }
3743
+
3693
3744
  // REST: DORA trend (weekly sliding windows)
3694
3745
  if (pathname === "/api/metrics/dora-trend") {
3695
3746
  const period = parseInt(url.searchParams.get("period") || "30");
@@ -7,6 +7,7 @@ import type {
7
7
  MachineInfo,
8
8
  JoinToken,
9
9
  CostBreakdown,
10
+ ContextEfficiency,
10
11
  DaemonConfig,
11
12
  AlertInfo,
12
13
  InsightsData,
@@ -113,6 +114,10 @@ export const fetchCostTrend = (period = 30) =>
113
114
  `/api/costs/trend?period=${period}`,
114
115
  );
115
116
 
117
+ // Context efficiency
118
+ export const fetchContextEfficiency = (period = 7) =>
119
+ request<ContextEfficiency>(`/api/context-efficiency?period=${period}`);
120
+
116
121
  // Daemon
117
122
  export const fetchDaemonConfig = () =>
118
123
  request<DaemonConfig>("/api/daemon/config");
@@ -255,6 +255,16 @@ export interface HeatmapData {
255
255
  heatmap: Record<string, Record<string, number>>;
256
256
  }
257
257
 
258
+ export interface ContextEfficiency {
259
+ avg_utilization: number;
260
+ avg_trim_ratio: number;
261
+ total_raw_chars: number;
262
+ total_trimmed_chars: number;
263
+ total_discarded_chars: number;
264
+ trim_events: number;
265
+ total_iterations: number;
266
+ }
267
+
258
268
  export interface DaemonConfig {
259
269
  paused?: boolean;
260
270
  config?: Record<string, unknown>;
@@ -82,10 +82,12 @@ function renderMetrics(data: MetricsData): void {
82
82
  doraContainer.style.display = "none";
83
83
  }
84
84
 
85
- // Cost breakdown/trend
85
+ // Cost breakdown/trend/context efficiency
86
86
  if (document.getElementById("cost-breakdown-container"))
87
87
  renderCostBreakdown();
88
88
  if (document.getElementById("cost-trend-container")) renderCostTrend();
89
+ if (document.getElementById("context-efficiency-container"))
90
+ renderContextEfficiency();
89
91
  if (document.getElementById("dora-trend-container")) renderDoraTrend();
90
92
  if (document.getElementById("stage-performance-container"))
91
93
  renderStagePerformance();
@@ -256,6 +258,72 @@ function renderCostTrend(): void {
256
258
  });
257
259
  }
258
260
 
261
+ function renderContextEfficiency(): void {
262
+ const container = document.getElementById("context-efficiency-container");
263
+ if (!container) return;
264
+ api
265
+ .fetchContextEfficiency()
266
+ .then((data) => {
267
+ if (!data.total_iterations) {
268
+ container.innerHTML =
269
+ '<div class="empty-state"><p>No context efficiency data</p></div>';
270
+ return;
271
+ }
272
+ const utilPct = Math.min(data.avg_utilization, 100);
273
+ const utilClass =
274
+ utilPct >= 90
275
+ ? "ctx-eff-high"
276
+ : utilPct >= 60
277
+ ? "ctx-eff-mid"
278
+ : "ctx-eff-low";
279
+ const trimPct = Math.min(data.avg_trim_ratio, 100);
280
+
281
+ let html =
282
+ '<span class="metric-label">CONTEXT EFFICIENCY</span>' +
283
+ '<div class="ctx-eff-grid">';
284
+
285
+ // Budget utilization gauge
286
+ html +=
287
+ '<div class="ctx-eff-card">' +
288
+ '<span class="ctx-eff-card-label">Budget Utilization</span>' +
289
+ `<div class="ctx-eff-gauge"><div class="ctx-eff-gauge-fill ${utilClass}" style="width:${utilPct.toFixed(0)}%"></div></div>` +
290
+ `<span class="ctx-eff-value">${data.avg_utilization.toFixed(1)}%</span>` +
291
+ "</div>";
292
+
293
+ // Trim ratio
294
+ html +=
295
+ '<div class="ctx-eff-card">' +
296
+ '<span class="ctx-eff-card-label">Avg Trim Ratio</span>' +
297
+ `<div class="ctx-eff-gauge"><div class="ctx-eff-gauge-fill ctx-eff-trim" style="width:${trimPct.toFixed(0)}%"></div></div>` +
298
+ `<span class="ctx-eff-value">${data.avg_trim_ratio.toFixed(1)}%</span>` +
299
+ "</div>";
300
+
301
+ // Chars saved
302
+ const savedK = Math.round(data.total_discarded_chars / 1000);
303
+ const totalK = Math.round(data.total_raw_chars / 1000);
304
+ html +=
305
+ '<div class="ctx-eff-card">' +
306
+ '<span class="ctx-eff-card-label">Chars Discarded</span>' +
307
+ `<span class="ctx-eff-big">${fmtNum(savedK)}K</span>` +
308
+ `<span class="ctx-eff-sub">of ${fmtNum(totalK)}K generated</span>` +
309
+ "</div>";
310
+
311
+ // Trim events
312
+ html +=
313
+ '<div class="ctx-eff-card">' +
314
+ '<span class="ctx-eff-card-label">Trim Events</span>' +
315
+ `<span class="ctx-eff-big">${data.trim_events}</span>` +
316
+ `<span class="ctx-eff-sub">of ${data.total_iterations} iterations</span>` +
317
+ "</div>";
318
+
319
+ html += "</div>";
320
+ container.innerHTML = html;
321
+ })
322
+ .catch((err) => {
323
+ container.innerHTML = `<div class="empty-state"><p>Failed to load: ${escapeHtml(String(err))}</p></div>`;
324
+ });
325
+ }
326
+
259
327
  function renderDoraTrend(): void {
260
328
  const container = document.getElementById("dora-trend-container");
261
329
  if (!container) return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shipwright-cli",
3
- "version": "3.0.0",
3
+ "version": "3.2.0",
4
4
  "description": "Orchestrate autonomous Claude Code agent teams in tmux",
5
5
  "bin": {
6
6
  "shipwright": "scripts/sw",
@@ -36,9 +36,9 @@
36
36
  "dashboard:test": "vitest run --config dashboard/vitest.config.ts",
37
37
  "dashboard:test:watch": "vitest --config dashboard/vitest.config.ts",
38
38
  "dashboard:test:coverage": "vitest run --config dashboard/vitest.config.ts --coverage",
39
- "test": "bash scripts/sw-agi-roadmap-test.sh && bash scripts/sw-activity-test.sh && bash scripts/sw-adaptive-test.sh && bash scripts/sw-adversarial-test.sh && bash scripts/sw-architecture-enforcer-test.sh && bash scripts/sw-auth-test.sh && bash scripts/sw-autonomous-test.sh && bash scripts/sw-changelog-test.sh && bash scripts/sw-checkpoint-test.sh && bash scripts/sw-ci-test.sh && bash scripts/sw-cleanup-test.sh && bash scripts/sw-code-review-test.sh && bash scripts/sw-connect-test.sh && bash scripts/sw-context-test.sh && bash scripts/sw-cost-test.sh && bash scripts/sw-daemon-test.sh && bash scripts/sw-dashboard-test.sh && bash scripts/sw-db-test.sh && bash scripts/sw-decompose-test.sh && bash scripts/sw-deps-test.sh && bash scripts/sw-developer-simulation-test.sh && bash scripts/sw-discovery-test.sh && bash scripts/sw-doc-fleet-test.sh && bash scripts/sw-docs-agent-test.sh && bash scripts/sw-docs-test.sh && bash scripts/sw-doctor-test.sh && bash scripts/sw-dora-test.sh && bash scripts/sw-durable-test.sh && bash scripts/sw-e2e-orchestrator-test.sh && bash scripts/sw-eventbus-test.sh && bash scripts/sw-feedback-test.sh && bash scripts/sw-fix-test.sh && bash scripts/sw-fleet-discover-test.sh && bash scripts/sw-fleet-test.sh && bash scripts/sw-fleet-viz-test.sh && bash scripts/sw-frontier-test.sh && bash scripts/sw-github-app-test.sh && bash scripts/sw-github-checks-test.sh && bash scripts/sw-github-deploy-test.sh && bash scripts/sw-github-graphql-test.sh && bash scripts/sw-guild-test.sh && bash scripts/sw-heartbeat-test.sh && bash scripts/sw-hygiene-test.sh && bash scripts/sw-incident-test.sh && bash scripts/sw-init-test.sh && bash scripts/sw-instrument-test.sh && bash scripts/sw-intelligence-test.sh && bash scripts/sw-jira-test.sh && bash scripts/sw-launchd-test.sh && bash scripts/sw-linear-test.sh && bash scripts/sw-logs-test.sh && bash scripts/sw-loop-test.sh && bash scripts/sw-memory-test.sh && bash scripts/sw-mission-control-test.sh && bash scripts/sw-model-router-test.sh && bash scripts/sw-otel-test.sh && bash scripts/sw-oversight-test.sh && bash scripts/sw-patrol-meta-test.sh && bash scripts/sw-pipeline-composer-test.sh && bash scripts/sw-pipeline-test.sh && bash scripts/sw-pipeline-vitals-test.sh && bash scripts/sw-pm-test.sh && bash scripts/sw-pr-lifecycle-test.sh && bash scripts/sw-predictive-test.sh && bash scripts/sw-prep-test.sh && bash scripts/sw-ps-test.sh && bash scripts/sw-public-dashboard-test.sh && bash scripts/sw-quality-test.sh && bash scripts/sw-reaper-test.sh && bash scripts/sw-recruit-test.sh && bash scripts/sw-regression-test.sh && bash scripts/sw-release-manager-test.sh && bash scripts/sw-release-test.sh && bash scripts/sw-remote-test.sh && bash scripts/sw-replay-test.sh && bash scripts/sw-retro-test.sh && bash scripts/sw-scale-test.sh && bash scripts/sw-security-audit-test.sh && bash scripts/sw-self-optimize-test.sh && bash scripts/sw-session-test.sh && bash scripts/sw-setup-test.sh && bash scripts/sw-standup-test.sh && bash scripts/sw-status-test.sh && bash scripts/sw-strategic-test.sh && bash scripts/sw-stream-test.sh && bash scripts/sw-swarm-test.sh && bash scripts/sw-team-stages-test.sh && bash scripts/sw-templates-test.sh && bash scripts/sw-testgen-test.sh && bash scripts/sw-tmux-pipeline-test.sh && bash scripts/sw-tmux-test.sh && bash scripts/sw-trace-test.sh && bash scripts/sw-tracker-test.sh && bash scripts/sw-triage-test.sh && bash scripts/sw-upgrade-test.sh && bash scripts/sw-ux-test.sh && bash scripts/sw-webhook-test.sh && bash scripts/sw-widgets-test.sh && bash scripts/sw-worktree-test.sh && bash scripts/sw-policy-e2e-test.sh && bash scripts/sw-e2e-smoke-test.sh && bash scripts/sw-dashboard-e2e-test.sh",
39
+ "test": "bash scripts/sw-agi-roadmap-test.sh && bash scripts/sw-activity-test.sh && bash scripts/sw-adaptive-test.sh && bash scripts/sw-adversarial-test.sh && bash scripts/sw-architecture-enforcer-test.sh && bash scripts/sw-auth-test.sh && bash scripts/sw-autonomous-test.sh && bash scripts/sw-changelog-test.sh && bash scripts/sw-checkpoint-test.sh && bash scripts/sw-ci-test.sh && bash scripts/sw-cleanup-test.sh && bash scripts/sw-code-review-test.sh && bash scripts/sw-connect-test.sh && bash scripts/sw-context-test.sh && bash scripts/sw-cost-test.sh && bash scripts/sw-daemon-test.sh && bash scripts/sw-dashboard-test.sh && bash scripts/sw-db-test.sh && bash scripts/sw-decompose-test.sh && bash scripts/sw-decide-test.sh && bash scripts/sw-deps-test.sh && bash scripts/sw-developer-simulation-test.sh && bash scripts/sw-discovery-test.sh && bash scripts/sw-doc-fleet-test.sh && bash scripts/sw-docs-agent-test.sh && bash scripts/sw-docs-test.sh && bash scripts/sw-doctor-test.sh && bash scripts/sw-dora-test.sh && bash scripts/sw-durable-test.sh && bash scripts/sw-e2e-orchestrator-test.sh && bash scripts/sw-eventbus-test.sh && bash scripts/sw-feedback-test.sh && bash scripts/sw-fix-test.sh && bash scripts/sw-fleet-discover-test.sh && bash scripts/sw-fleet-test.sh && bash scripts/sw-fleet-viz-test.sh && bash scripts/sw-frontier-test.sh && bash scripts/sw-github-app-test.sh && bash scripts/sw-github-checks-test.sh && bash scripts/sw-github-deploy-test.sh && bash scripts/sw-github-graphql-test.sh && bash scripts/sw-guild-test.sh && bash scripts/sw-heartbeat-test.sh && bash scripts/sw-hygiene-test.sh && bash scripts/sw-incident-test.sh && bash scripts/sw-init-test.sh && bash scripts/sw-instrument-test.sh && bash scripts/sw-intelligence-test.sh && bash scripts/sw-jira-test.sh && bash scripts/sw-launchd-test.sh && bash scripts/sw-linear-test.sh && bash scripts/sw-logs-test.sh && bash scripts/sw-loop-test.sh && bash scripts/sw-memory-test.sh && bash scripts/sw-mission-control-test.sh && bash scripts/sw-model-router-test.sh && bash scripts/sw-otel-test.sh && bash scripts/sw-oversight-test.sh && bash scripts/sw-patrol-meta-test.sh && bash scripts/sw-pipeline-composer-test.sh && bash scripts/sw-pipeline-test.sh && bash scripts/sw-pipeline-vitals-test.sh && bash scripts/sw-pm-test.sh && bash scripts/sw-pr-lifecycle-test.sh && bash scripts/sw-predictive-test.sh && bash scripts/sw-prep-test.sh && bash scripts/sw-ps-test.sh && bash scripts/sw-public-dashboard-test.sh && bash scripts/sw-quality-test.sh && bash scripts/sw-reaper-test.sh && bash scripts/sw-recruit-test.sh && bash scripts/sw-regression-test.sh && bash scripts/sw-release-manager-test.sh && bash scripts/sw-release-test.sh && bash scripts/sw-remote-test.sh && bash scripts/sw-replay-test.sh && bash scripts/sw-retro-test.sh && bash scripts/sw-scale-test.sh && bash scripts/sw-security-audit-test.sh && bash scripts/sw-self-optimize-test.sh && bash scripts/sw-session-test.sh && bash scripts/sw-setup-test.sh && bash scripts/sw-standup-test.sh && bash scripts/sw-status-test.sh && bash scripts/sw-strategic-test.sh && bash scripts/sw-stream-test.sh && bash scripts/sw-swarm-test.sh && bash scripts/sw-team-stages-test.sh && bash scripts/sw-templates-test.sh && bash scripts/sw-testgen-test.sh && bash scripts/sw-tmux-pipeline-test.sh && bash scripts/sw-tmux-test.sh && bash scripts/sw-trace-test.sh && bash scripts/sw-tracker-test.sh && bash scripts/sw-triage-test.sh && bash scripts/sw-upgrade-test.sh && bash scripts/sw-ux-test.sh && bash scripts/sw-webhook-test.sh && bash scripts/sw-widgets-test.sh && bash scripts/sw-worktree-test.sh && bash scripts/sw-lib-compat-test.sh && bash scripts/sw-lib-helpers-test.sh && bash scripts/sw-lib-daemon-dispatch-test.sh && bash scripts/sw-lib-daemon-failure-test.sh && bash scripts/sw-lib-daemon-poll-test.sh && bash scripts/sw-lib-daemon-state-test.sh && bash scripts/sw-lib-daemon-triage-test.sh && bash scripts/sw-lib-pipeline-detection-test.sh && bash scripts/sw-lib-pipeline-intelligence-test.sh && bash scripts/sw-lib-pipeline-quality-checks-test.sh && bash scripts/sw-lib-pipeline-stages-test.sh && bash scripts/sw-lib-pipeline-state-test.sh && bash scripts/sw-adapters-test.sh && bash scripts/sw-evidence-test.sh && bash scripts/sw-review-rerun-test.sh && bash scripts/sw-tracker-providers-test.sh && bash scripts/sw-budget-chaos-test.sh && bash scripts/sw-autonomous-e2e-test.sh && bash scripts/sw-memory-discovery-e2e-test.sh && bash scripts/sw-policy-e2e-test.sh && bash scripts/sw-e2e-smoke-test.sh && bash scripts/sw-dashboard-e2e-test.sh",
40
40
  "test:smoke": "bash scripts/sw-e2e-smoke-test.sh",
41
- "test:integration": "bash scripts/sw-e2e-integration-test.sh",
41
+ "test:integration": "bash scripts/sw-e2e-integration-test.sh && bash scripts/sw-e2e-system-test.sh && bash scripts/sw-server-api-test.sh && bash scripts/sw-integration-claude-test.sh",
42
42
  "harness:evidence:capture": "bash scripts/sw-evidence.sh capture",
43
43
  "harness:evidence:verify": "bash scripts/sw-evidence.sh verify",
44
44
  "harness:evidence:pre-pr": "bash scripts/sw-evidence.sh pre-pr",
@@ -45,7 +45,8 @@ $(find "$repo_root" -type f \( -name '*.ts' -o -name '*.js' -o -name '*.py' -o -
45
45
  while IFS= read -r f; do
46
46
  [[ -f "$f" ]] || continue
47
47
  local exports=0
48
- exports=$(grep -c "^export " "$f" 2>/dev/null || echo "0")
48
+ exports=$(grep -c "^export " "$f" 2>/dev/null || true)
49
+ exports="${exports:-0}"
49
50
  [[ "$exports" -gt 2 ]] 2>/dev/null && context="${context} $(basename "$f"): $exports exports
50
51
  "
51
52
  done < <(find "$repo_root/src" "$repo_root/lib" -name "*.ts" -o -name "*.js" 2>/dev/null | head -30)
File without changes
File without changes
@@ -223,10 +223,12 @@ daemon_collect_snapshot() {
223
223
  if [[ -d "$worktree/.git" ]] || [[ -f "$worktree/.git" ]]; then
224
224
  diff_lines=$(cd "$worktree" && git diff --stat 2>/dev/null | tail -1 | grep -o '[0-9]* insertion' | grep -o '[0-9]*' || echo "0")
225
225
  [[ -z "$diff_lines" ]] && diff_lines=0
226
- files_changed=$(cd "$worktree" && git diff --name-only 2>/dev/null | wc -l | tr -d ' ' || echo "0")
226
+ files_changed=$(cd "$worktree" && git diff --name-only 2>/dev/null | wc -l | tr -d ' ' || true)
227
+ files_changed="${files_changed:-0}"
227
228
  # Also count untracked files the agent has created
228
229
  local untracked
229
- untracked=$(cd "$worktree" && git ls-files --others --exclude-standard 2>/dev/null | wc -l | tr -d ' ' || echo "0")
230
+ untracked=$(cd "$worktree" && git ls-files --others --exclude-standard 2>/dev/null | wc -l | tr -d ' ' || true)
231
+ untracked="${untracked:-0}"
230
232
  files_changed=$((files_changed + untracked))
231
233
  fi
232
234