bullswarm 0.28.0 → 0.28.2

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,149 @@
1
1
  # bullswarm changelog
2
2
 
3
+ ## 0.28.2 — terminal glyph fallback, issue watcher, health on a fresh home
4
+
5
+ - `bullswarm workflow` repainted flashing `?` characters on macOS
6
+ Terminal.app. Cause is font coverage, not encoding: parsing the `cmap` of
7
+ every monospace face Terminal.app offers shows **zero** glyphs in the
8
+ Braille block `U+2800-U+28FF` — Andale Mono, Menlo, SF Mono, Monaco and
9
+ Courier New are all 0/256 — and none of the five has `⧖` (`U+29D6`) or `⟡`
10
+ (`U+27E1`). The dashboard's 10-frame Braille spinner advances every 400ms,
11
+ so every repaint drew a question mark that flashed; the static `✓ ✗ ⊘ ◇ ↳
12
+ ⚠ ● ○ ■ ♡ ◆ ↻ ↺ ▶ ⚙` icons beside it were the steady ones. Terminal.app
13
+ does not substitute another face for these ranges.
14
+ - New `src/lib/glyphs.js` holds one table per mode and the detection order:
15
+ `BULLSWARM_ASCII` forces ASCII, `BULLSWARM_UNICODE` forces Unicode and
16
+ overrides detection, then a non-UTF-8 locale, `TERM=dumb`/`TERM=linux`, and
17
+ `TERM_PROGRAM=Apple_Terminal` select ASCII. Every replacement is one column
18
+ of printable ASCII, because panel width maths uses `String#length` and a
19
+ wide substitute would shear the borders. Box drawing, `· › × … ≈ ≥ █` are in
20
+ all five fonts and are left alone, so panels keep their borders.
21
+ - Wired through the three live-refreshing renderers only:
22
+ `src/workflow/dashboard.js`, `src/workflow/watch-cli.js` and
23
+ `src/strategy-dashboard.js`. One-shot `✓`/`✗` message prefixes in
24
+ `src/workflow/cli.js`, `src/help.js`, `src/integrate.js`,
25
+ `src/workflow/runs-cli.js`, `src/strategy-cli.js` and
26
+ `src/lib/cli-flags.js` are untouched — they do not flash and are a separate
27
+ change.
28
+ - `spinnerGlyph` now parks a non-finite frame counter on frame 0. The previous
29
+ expression `SPINNER_FRAMES[Math.abs(Number(f) || 0) % SPINNER_FRAMES.length]`
30
+ returned `undefined` for `Infinity`, so a repaint could have painted the
31
+ string "undefined" into the frame.
32
+ - `tests/glyphs.test.js` covers the detection matrix (including that `''`,
33
+ `'0'` and `'false'` do not switch tables, and that `LC_ALL` outranks
34
+ `LANG`), asserts every Unicode entry has a one-column pure-ASCII twin that
35
+ is never `?`, and renders both dashboards, `renderWatchSnapshot`,
36
+ `renderWatchEvent` over 15 event shapes and `renderAnalysisProgress` at five
37
+ spinner frames, asserting no substituted glyph survives in ASCII mode while
38
+ the Braille spinner still appears in Unicode mode.
39
+ - `tests/strategy-cli.test.js`, `tests/workflow-dashboard.test.js` and
40
+ `tests/workflow-watch.test.js` assert the Unicode presentation, so they now
41
+ pin `BULLSWARM_UNICODE=1` and `delete process.env.BULLSWARM_ASCII`; without
42
+ the pin the suite's own output depended on which terminal the developer ran
43
+ it in, and without the delete an ambient `BULLSWARM_ASCII=1` — the very
44
+ workaround the README hands an affected user — outranked the pin and broke
45
+ 22 tests.
46
+ - The four arrows `↑ ↓ ← →` are deliberately not substituted. They are only
47
+ missing from Monaco, and they live in the frozen `DASHBOARD_KEYS` constant,
48
+ which binds at import time — before any env pin could apply.
49
+ - `scripts/issue-watcher/` — a durable GitHub issue watcher for this
50
+ repository, installed as a launchd agent
51
+ (`node scripts/issue-watcher/install.mjs`). Idle costs ZERO model tokens:
52
+ a pass is one `gh issue list` and nothing else. Only when an issue appears
53
+ that is new since install does it delegate — one
54
+ `bullswarm run --lane analyze` to triage (label + plain-words comment),
55
+ and for a bug it judged fixable at confidence >= 0.7 one
56
+ `bullswarm run --lane build` to fix, which the watcher then verifies
57
+ itself (test suite green in its own clone, diff non-empty) before pushing
58
+ `fix/issue-<n>` and opening a pull request. Both delegations run on the
59
+ owner's subscriptions through normal bullswarm routing, never API tokens.
60
+ Capped at 6 triages and 2 fix attempts per UTC day, one fix attempt per
61
+ issue; guarded by a pass lock, a `paused` file, and a 3-attempt triage
62
+ retry cap. Delegates get no GitHub credentials and issue text is quoted as
63
+ untrusted data. It never merges, closes, or releases — a maintainer does.
64
+
65
+ ## 0.28.1 — summary bytes on the wire, monthly pacing
66
+
67
+ - `workflow runs result <id> --summary` was budgeted by `fitResultSummary`
68
+ against compact `JSON.stringify(summary)` (`RESULT_SUMMARY_BYTE_BUDGET =
69
+ 4096` in `src/workflow/v2-outcome.js`) but `jsonOut` printed
70
+ `JSON.stringify(obj, null, 2)`, so the bytes on the wire exceeded the
71
+ budget. `--summary` now prints compact single-line JSON
72
+ (`JSON.stringify(obj)` in `src/workflow/runs-cli.js`); `--json` without
73
+ `--summary` still pretty-prints the full envelope, and `--summary --json`
74
+ stays identical to `--summary`. Measured on
75
+ `tests/fixtures/real-result-ze5xz2.json` through the summariser / CLI:
76
+ compact `--summary` is 3,786 bytes
77
+ (`tests/workflow-result-summary.test.js` prints `result-summary size:
78
+ full=57141 summary=3786`; same figure as
79
+ `Buffer.byteLength(JSON.stringify(summarizeV2Result(fixture)))`); the
80
+ full envelope as `--json` prints it is 60,709 bytes
81
+ (`JSON.stringify(envelope, null, 2)` plus the trailing newline
82
+ `console.log` adds — `tests/workflow-result-summary.test.js` prints
83
+ `result-summary cli: prettyFull=60709`).
84
+
85
+ - Routing paced every pool by `windows.seven_day ?? windows.monthly`
86
+ (`paceSnapshot` in `src/meters/framework.js`), so command-code — whose real
87
+ budget is a monthly credit allocation — was paced by its weekly rate-limit
88
+ window. Live meter, captured 2026-09-09T10:45:28Z and evaluated at
89
+ 2026-09-09T11:09:44.982Z: weekly `used 73.1% elapsed 91.8% surplus +18.7`
90
+ against monthly `used 79.4% elapsed 75.3% surplus -4.1`, with 14.43 of 70
91
+ credits left for the 7.66 days to the 2026-09-17T03:06:55Z reset. Routing
92
+ therefore called it "the most-behind capable pool" and kept sending it work
93
+ while its monthly budget was already 4.1 points overspent. Pacing is now per
94
+ pool: `pacingWindowFor({connector, subscription})` resolves
95
+ `state.strategy.subscriptions[pool].quotaWindow`, then
96
+ `connector.subscription.quotaWindow`, normalised to `weekly` | `monthly` |
97
+ `null` (any other label — including a pre-0.28.1 free-text one — is ignored
98
+ for pacing and keeps the old weekly-first order). `paceSnapshot(snapshot,
99
+ nowMs, {pacingWindow})` takes `monthly` → `windows.monthly ??
100
+ windows.seven_day`, `weekly`/`null` → `windows.seven_day ??
101
+ windows.monthly`, and returns `pacingWindow` naming the window actually
102
+ used. `src/lib/config.js` `buildPools` resolves the choice once per pool
103
+ (where the connector and the state both are) and re-paces `usedPct`,
104
+ `elapsedPct`, `pace` and `paceResetsAt` off `reading.windows`, so cache,
105
+ stale and live readings are paced identically; the pool view carries
106
+ `pacingWindow`. The 5h gate is untouched: `command-code` still gates on 5h
107
+ `25.2%`. Connectors already declared this — `command-code` and the kaihk
108
+ pools `monthly`, `claude-code`/`codex`/`grok` `weekly`; nothing read it for
109
+ pacing before.
110
+ - The spend model follows the pacing window. `WINDOW_KEYS` (framework.js)
111
+ gains `monthly: {snapshot: 'monthly', history: 'monthly', windowMs: null}`,
112
+ and `rateForWindow` (`src/lib/spend.js`) derives the bootstrap window start
113
+ with `meta.windowMs ?? monthlyWindowMs(resetsAtMs)` — the calendar month
114
+ ending at the provider's `resets_at` (M2), never an assumed 30 days.
115
+ `attachSpend` now writes `pool.spend.monthly` and `pool.projectedMonthlyPct`
116
+ next to the fiveHour/weekly fields, plus `pool.spend.pacing = {window,
117
+ ratePerMinute, source, samples}` and `pool.projectedPacingPct` for the
118
+ window that paces the pool (default `weekly`, so a pool that declares
119
+ nothing keeps its old numbers). `inflightLoad` (`src/lib/route.js`) charges
120
+ the in-flight penalty from `spend.pacing?.ratePerMinute ??
121
+ spend.weekly?.ratePerMinute` with that rate's own source label, so the
122
+ surplus and the penalty are measured in the same window; candidate rows gain
123
+ `pacingWindow` and `projectedPacingPct` beside the unchanged
124
+ `projectedWeeklyPct`.
125
+ - Operator control and display. `bullswarm strategy set-subscription <pool>
126
+ --quota-window <weekly|monthly>` now selects the window that paces routing
127
+ (help text in `src/help.js`) and validates it: anything else exits 2 with
128
+ `--quota-window must be weekly or monthly (or unknown to clear)`, and
129
+ `unknown` clears the override back to the connector's declaration. Labels
130
+ already stored are ignored for pacing, never rejected on read. `bullswarm
131
+ pools` names the window in the meter column — `cmd-fixture cost=5
132
+ lanes=chore monthly used 79.4% elapsed 75.3% [cache] surplus=-4.1
133
+ inflight=0 5h=25.2% ready` — and `pools --json` entries carry
134
+ `pacingWindow`. `strategy refresh`/`show` print the window on each
135
+ subscription line (`command-code: GOAT · ... · monthly 79.4% used · surplus
136
+ -4.1`) and carry `pacingWindow` next to the free-text `quotaWindow` label in
137
+ `--json`; `strategy inventory --json` carries it per provider.
138
+ - Tests: 738 -> 750, 0 failures. The new behaviour is covered in
139
+ `tests/meters.test.js` (the live command-code snapshot as a fixture, the
140
+ helper's precedence, `buildPools` pacing), `tests/spend.test.js` (monthly
141
+ bootstrap window start, `spend.pacing`/`projectedPacingPct`),
142
+ `tests/route.test.js` (the penalty on the pacing window; weekly-only pools
143
+ unchanged), `tests/strategy-cli.test.js` (`--quota-window` validation) and
144
+ `tests/assignments.test.js` (the `pools` meter column and `--json`
145
+ `pacingWindow`).
146
+
3
147
  ## 0.28.0 — context diet
4
148
 
5
149
  - `workflow runs result <id> --summary` prints a compact status-loop
@@ -77,9 +221,10 @@
77
221
  39,288; compact `JSON.stringify` of the parsed envelope is 57,141.
78
222
  `summarizeV2Result` of that fixture is 3,786 bytes —
79
223
  `tests/workflow-result-summary.test.js` prints `result-summary size:
80
- full=57141 summary=3786`. The 0.28.0 goal recorded the integrator's
81
- inputs as 60,790 bytes; `wc -c` of the seven dependency out-files under
82
- `.diet-inputs/` sums to 46,022 (out-surface 18,659, out-routing-cleanup
224
+ full=57141 summary=3786` (numbers re-measured in 0.28.1). The 0.28.0
225
+ goal recorded the integrator's inputs as 60,790 bytes; `wc -c` of the
226
+ seven dependency out-files under `.diet-inputs/` sums to 46,022
227
+ (out-surface 18,659, out-routing-cleanup
83
228
  10,202, out-state-bugs 7,052, out-docs 6,831, out-dead-kernel 1,377,
84
229
  out-verify-gate 974, out-dead-code 927) and the integrator task file is
85
230
  14,768 (`wc -c .diet-inputs/task-integrate-attempt-1.md`), which
package/README.md CHANGED
@@ -51,7 +51,12 @@ detaches safely.
51
51
  passing verification.
52
52
  2. **Pace by meter.** The scheduling resource is the subscription window:
53
53
  elapsed% minus used%, most-behind pool wins. Pace may only promote a
54
- *cheaper* pool. Lanes are work-nature, never hard-coded to pools. The
54
+ *cheaper* pool. Lanes are work-nature, never hard-coded to pools. Which
55
+ window paces one pool is the subscription window that pool's connector
56
+ declares (`quotaWindow`: weekly for claude-code, codex and grok; monthly
57
+ for command-code and the kaihk pools), overridable per pool with
58
+ `bullswarm strategy set-subscription <pool> --quota-window <weekly|monthly>`
59
+ — `bullswarm pools` names it in the meter column. The
55
60
  5-hour window never paces — it gates: a pool at or above 75% of it is
56
61
  chosen only when no eligible pool below that line exists, and one at or
57
62
  above 90% is not dispatched at all.
@@ -656,6 +661,10 @@ These are UTF-8 byte counts, never tokens.
656
661
  requirement as `{ id, status, mandatory, evidenceCount, why }`, each action as
657
662
  `{ id, kind, lane, effort, status, pool, model, reasoning, wallSec, outFile,
658
663
  bytes }`, `concerns: { count, first }`, `usage`, and `next: { full, runDir, outputs }` — every output name is a basename inside `next.runDir`.
664
+ `--summary` is single-line JSON (`JSON.stringify`), so the bytes on the wire
665
+ match the 4,096-byte fitter budget. As printed by the CLI on
666
+ `tests/fixtures/real-result-ze5xz2.json`, the compact summary is 3,786 bytes
667
+ and the pretty full envelope (`--json` alone) is 60,709 bytes.
659
668
  The full `bullswarm.workflow.result.v2` envelope is unchanged and remains the
660
669
  default. Read it (`--json` alone) on a failed or partial run, or before judging
661
670
  evidence. A terminal `workflow watch` prints the same compact command as
@@ -791,6 +800,25 @@ to toggle Timeline and Phases, then use Enter/Esc for agents and activity.
791
800
  bullswarm workflow tui
792
801
  ```
793
802
 
803
+ #### Terminal glyphs
804
+
805
+ The live views draw a Braille spinner and symbol status icons. macOS
806
+ Terminal.app cannot render them: Andale Mono, Menlo, SF Mono, Monaco and
807
+ Courier New all have zero glyphs in `U+2800-U+28FF`, and none has `⧖`, so the
808
+ dashboard repaints a flashing `?` where each one should be. Apple Terminal is
809
+ detected and given a one-column ASCII table instead (`|/-\` spinner, `+`
810
+ succeeded, `x` failed, `:` waiting, `#` blocked). Panel borders are unchanged —
811
+ box drawing is present in every one of those fonts.
812
+
813
+ Override the detection either way:
814
+
815
+ ```bash
816
+ BULLSWARM_ASCII=1 bullswarm workflow # force ascii (any terminal showing ?)
817
+ BULLSWARM_UNICODE=1 bullswarm workflow # force unicode (font does have them)
818
+ ```
819
+
820
+ A non-UTF-8 locale, `TERM=dumb` and `TERM=linux` also select ASCII.
821
+
794
822
  The same dashboard is agent-friendly and never requires a TTY when used with
795
823
  JSON/control flags:
796
824
 
@@ -879,6 +907,19 @@ runtime value and uses its matching connector rate metadata for the attempt's
879
907
  cost estimate. Unknown or provider-hidden model identity remains explicitly
880
908
  unknown.
881
909
 
910
+ ## Repository operations
911
+
912
+ `scripts/issue-watcher/` is a launchd agent that watches this repository's
913
+ GitHub issues. Idle it costs zero model tokens — a pass is one `gh issue
914
+ list` and nothing else. When an issue arrives that is new since install, it
915
+ delegates one analyze run to triage it (label plus a plain-words comment),
916
+ and for a bug it judged fixable at confidence >= 0.7 one build run to fix
917
+ it, verifying the result itself (suite green in its own clone, diff
918
+ non-empty) before pushing `fix/issue-<n>` and opening a pull request. Both
919
+ delegations route through `bullswarm run` on the owner's subscriptions. It
920
+ never merges, closes, or releases. Install, guards, cost model and paths:
921
+ [`scripts/issue-watcher/README.md`](scripts/issue-watcher/README.md).
922
+
882
923
  ## The verdict
883
924
 
884
925
  ```json