bullswarm 0.23.0 → 0.23.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/README.md CHANGED
@@ -256,6 +256,17 @@ updates the requirement ledger from schema-valid evidence. Only real
256
256
  consolidated gaps re-enter the planner. There are no formal reviewer or repair
257
257
  roles and no automatic semantic repair/reverify loop.
258
258
 
259
+ Lane and effort are separate decisions for every proposed action. `analyze` is
260
+ read-only investigation, judgment, or evidence; `build` is contextual product,
261
+ test, or documentation mutation; `chore` is deterministic mechanical mutation.
262
+ The kernel rejects evidence outside `analyze`, file ownership inside `analyze`,
263
+ and any `chore` above low effort. Low is for fixed-procedure checks and edits,
264
+ medium is the default for ordinary bounded work, and high is reserved for
265
+ architecture, ambiguous tradeoffs, cross-cutting integration, or genuinely
266
+ adversarial acceptance judgment. Merely being an analysis/evidence action or
267
+ part of a difficult goal never promotes an action to high. The selected effort
268
+ then resolves through the High/Medium/Low routes configured by `bullswarm setup`.
269
+
259
270
  The planner does not author phases or declare success/failure. The kernel
260
271
  derives stable presentation stages for the TUI and computes the final V2
261
272
  result. Old autonomous run directories are not migrated or resumed;
@@ -46,13 +46,16 @@
46
46
  },
47
47
  "costRank": 1,
48
48
  "lanes": [
49
+ "analyze",
49
50
  "build",
50
51
  "chore"
51
52
  ],
52
- "capabilities": ["code-reading", "file-editing"],
53
- "modelDiscovery": { "cmd": ["command-code", "--list-models"], "parse": "columns", "ignorePattern": "^(Available|Open Source|Anthropic|OpenAI|Google|Sakana|Meta|xAI|Pass|Docs)", "timeoutMs": 20000, "maxModels": 150 },
53
+ "capabilities": ["code-reading", "file-editing", "strong-analysis", "workflow-planning"],
54
+ "modelDiscovery": { "cmd": ["command-code", "--list-models"], "parse": "columns", "ignorePattern": "^(Available|Open Source$|Anthropic$|OpenAI$|Google$|Sakana$|Meta$|xAI$|Pass|cmd|Docs)", "timeoutMs": 20000, "maxModels": 150 },
54
55
  "modelSelection": { "flag": "--model", "mode": "replace-or-append" },
55
56
  "modelProfiles": [
57
+ { "match": "^meta/muse-spark-1\\.3-contributor$", "tier": "high", "qualityRank": 5, "pricing": { "inputUsdPerMillion": 0.1, "cacheReadUsdPerMillion": 0.002, "outputUsdPerMillion": 0.2 }, "pricingSource": "https://commandcode.ai/docs/resources/pricing-limits", "pricingUpdatedAt": "2026-09-03" },
58
+ { "match": "^meta/muse-spark-", "tier": "high", "qualityRank": 5, "pricing": { "inputUsdPerMillion": 1.25, "cacheReadUsdPerMillion": 0.15, "outputUsdPerMillion": 4.25 }, "pricingSource": "https://commandcode.ai/docs/resources/pricing-limits", "pricingUpdatedAt": "2026-09-03" },
56
59
  { "match": "^gpt-5\\.6-sol$", "tier": "high", "qualityRank": 6, "pricing": { "inputUsdPerMillion": 4, "cacheReadUsdPerMillion": 0.4, "outputUsdPerMillion": 20 }, "pricingSource": "https://help.openai.com/en/articles/20001415-chatgpt-rate-card-enterprise-token-based-pricing", "pricingUpdatedAt": "2026-08-27" },
57
60
  { "match": "^gpt-5\\.6-terra$", "tier": "medium", "qualityRank": 4, "pricing": { "inputUsdPerMillion": 2, "cacheReadUsdPerMillion": 0.2, "outputUsdPerMillion": 12 }, "pricingSource": "https://developers.openai.com/api/docs/models/gpt-5.6-terra", "pricingUpdatedAt": "2026-08-27" },
58
61
  { "match": "^gpt-5\\.6-luna$", "tier": "low", "qualityRank": 3, "pricing": { "inputUsdPerMillion": 0.2, "cacheReadUsdPerMillion": 0.02, "outputUsdPerMillion": 1.2 }, "pricingSource": "https://developers.openai.com/api/docs/models", "pricingUpdatedAt": "2026-08-27" },
@@ -65,7 +68,6 @@
65
68
  { "match": "(?:fable|opus|sol|grok-4\\.6|minimax-m3)", "tier": "high", "qualityRank": 5 }
66
69
  ],
67
70
  "subscription": { "plan": null, "monthlyPriceUsd": null, "includedValueUsd": null, "quotaWindow": "monthly" },
68
- "$comment-lanes": "cannot serve analyze (upstream 403 on the frontier model)",
69
71
  "flags": {
70
72
  "stealth": false
71
73
  },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schemaVersion": "bullswarm.openrouter.benchmarks.v1",
3
- "capturedAt": "2026-09-02T01:02:39.330Z",
3
+ "capturedAt": "2026-09-02T07:52:24.004Z",
4
4
  "upstream": {
5
5
  "benchmarks": "https://openrouter.ai/api/v1/benchmarks",
6
6
  "models": "https://openrouter.ai/api/v1/models",
@@ -170,6 +170,20 @@
170
170
  },
171
171
  "pricingSource": "https://openrouter.ai/api/v1/models"
172
172
  },
173
+ "~z-ai/glm-flash-latest": {
174
+ "id": "~z-ai/glm-flash-latest",
175
+ "name": "Z.ai: GLM Flash Latest",
176
+ "created": 1787817633,
177
+ "indices": {},
178
+ "ranks": {},
179
+ "pricing": {
180
+ "inputUsdPerMillion": 0.075,
181
+ "cacheReadUsdPerMillion": 0.015,
182
+ "cacheWriteUsdPerMillion": null,
183
+ "outputUsdPerMillion": 0.25
184
+ },
185
+ "pricingSource": "https://openrouter.ai/api/v1/models"
186
+ },
173
187
  "~z-ai/glm-latest": {
174
188
  "id": "~z-ai/glm-latest",
175
189
  "name": "Z.ai: GLM Latest",
@@ -744,6 +758,20 @@
744
758
  },
745
759
  "pricingSource": "https://openrouter.ai/api/v1/models"
746
760
  },
761
+ "anthropic/claude-fable-5.1:batch": {
762
+ "id": "anthropic/claude-fable-5.1:batch",
763
+ "name": "Anthropic: Claude Fable 5.1 (batch)",
764
+ "created": 1788285838,
765
+ "indices": {},
766
+ "ranks": {},
767
+ "pricing": {
768
+ "inputUsdPerMillion": 5,
769
+ "cacheReadUsdPerMillion": 0.125,
770
+ "cacheWriteUsdPerMillion": 6.25,
771
+ "outputUsdPerMillion": 25
772
+ },
773
+ "pricingSource": "https://openrouter.ai/api/v1/models"
774
+ },
747
775
  "anthropic/claude-haiku-4.5": {
748
776
  "id": "anthropic/claude-haiku-4.5",
749
777
  "name": "Anthropic: Claude Haiku 4.5",
@@ -1603,10 +1631,10 @@
1603
1631
  "indices": {},
1604
1632
  "ranks": {},
1605
1633
  "pricing": {
1606
- "inputUsdPerMillion": 0.22,
1607
- "cacheReadUsdPerMillion": 0.007,
1634
+ "inputUsdPerMillion": 0.44,
1635
+ "cacheReadUsdPerMillion": 0.014,
1608
1636
  "cacheWriteUsdPerMillion": null,
1609
- "outputUsdPerMillion": 0.66
1637
+ "outputUsdPerMillion": 1.32
1610
1638
  },
1611
1639
  "pricingSource": "https://openrouter.ai/api/v1/models"
1612
1640
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bullswarm",
3
- "version": "0.23.0",
3
+ "version": "0.23.2",
4
4
  "description": "Route work across coding-agent CLI subscriptions — paced by live quota meters, verified by content, never trusting exit codes.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -5,6 +5,11 @@ export const ACTION_PROGRAM_SCHEMA_VERSION = 'bullswarm.workflow.program.v2';
5
5
  const ID_RE = /^[a-z0-9][a-z0-9-]*$/;
6
6
  const LANES = new Set(['analyze', 'build', 'chore']);
7
7
  const EFFORTS = new Set(['high', 'medium', 'low']);
8
+ export const DEFAULT_EFFORT_BY_LANE = Object.freeze({
9
+ analyze: 'medium',
10
+ build: 'medium',
11
+ chore: 'low',
12
+ });
8
13
  const PROGRAM_FIELDS = new Set(['schemaVersion', 'actions']);
9
14
  const ACTION_FIELDS = new Set([
10
15
  'id', 'purpose', 'dependsOn', 'affects', 'ownedFiles', 'prompt',
@@ -241,6 +246,7 @@ function maxParallelism(actions, byId) {
241
246
  */
242
247
  export function validateActionProgram(program, runtime = {}) {
243
248
  const issues = [];
249
+ const enforceRoutingPolicy = runtime.enforceRoutingPolicy !== false;
244
250
  if (!isObject(program)) throw new ActionValidationError(['program must be an object']);
245
251
  for (const key of Object.keys(program)) if (!PROGRAM_FIELDS.has(key)) issues.push(`program.${key} is not allowed`);
246
252
  if (program.schemaVersion !== ACTION_PROGRAM_SCHEMA_VERSION) {
@@ -284,6 +290,15 @@ export function validateActionProgram(program, runtime = {}) {
284
290
  const ownedFiles = normalizeOwnedFiles(action.ownedFiles, `${at}.ownedFiles`, issues);
285
291
  if (!LANES.has(action.lane)) issues.push(`${at}.lane must be analyze|build|chore`);
286
292
  if (!EFFORTS.has(action.effort)) issues.push(`${at}.effort must be high|medium|low`);
293
+ if (enforceRoutingPolicy && evidenceFor.length && action.lane !== 'analyze') {
294
+ issues.push(`${at} evidence actions must use lane analyze`);
295
+ }
296
+ if (enforceRoutingPolicy && ownedFiles.length && action.lane === 'analyze') {
297
+ issues.push(`${at} analyze actions must not own workspace files; use build or chore for mutations`);
298
+ }
299
+ if (enforceRoutingPolicy && action.lane === 'chore' && action.effort !== 'low') {
300
+ issues.push(`${at} chore actions are deterministic mechanical work and must use low effort`);
301
+ }
287
302
  if (evidenceFor.length && (affects.length || ownedFiles.length)) {
288
303
  issues.push(`${at} evidence actions must have empty affects and ownedFiles`);
289
304
  }
@@ -4,8 +4,8 @@ import { pickPool, isQuarantined } from '../lib/route.js';
4
4
  import { assertDepthAllowed, childDepthEnv, loadState, quarantinePool, saveState } from '../lib/state.js';
5
5
  import { disabledModelsForPool, resolveDispatchModel, selectedModelsForTier } from '../lib/strategy.js';
6
6
  import { watchOnce } from '../lib/watch.js';
7
+ import { DEFAULT_EFFORT_BY_LANE } from './action-validator.js';
7
8
 
8
- const EFFORT_BY_LANE = Object.freeze({ analyze: 'high', build: 'medium', chore: 'low' });
9
9
  const MECHANICAL_KINDS = new Set(['auth', 'provider', 'process', 'interrupted', 'schema']);
10
10
 
11
11
  function clone(value) {
@@ -154,7 +154,7 @@ export async function dispatchV2Action({
154
154
  const saveCoreState = dependencies.saveState ?? saveState;
155
155
  const now = dependencies.now ?? Date.now;
156
156
  const uuid = dependencies.uuid ?? randomUUID;
157
- const effort = action.effort ?? EFFORT_BY_LANE[action.lane] ?? 'low';
157
+ const effort = action.effort ?? DEFAULT_EFFORT_BY_LANE[action.lane] ?? 'medium';
158
158
  let candidates = preparePools(pools, action, effort, {
159
159
  avoidPools, preferredModel, strictPool, now: now(),
160
160
  });
@@ -170,6 +170,9 @@ export function buildV2PlannerPrompt(context) {
170
170
  'The numeric values in context.targets are advisory planning targets, never execution ceilings. Prefer to stay within them by consolidating optional work, but exceed them whenever the smallest essential program needs more actions, agent dispatches, or gap rounds. Reaching or crossing a target is not a reason to return exhausted.',
171
171
  'context.execution.concurrency limits only how many dependency-ready actions run at once. It does not limit the total number of independent actions in the program; the scheduler will batch wider programs safely.',
172
172
  'Use only generic actions. A work action declares affects and any exact ownedFiles. affects means the action directly owns and delivers a bounded acceptance slice of that requirement; merely editing a supporting test or sharing a file does not make an action affect every requirement associated with that file. An evidence action declares evidenceFor, has empty affects/ownedFiles, and independently inspects the work it judges.',
173
+ 'Choose lane from the action itself, not from the overall goal: analyze is read-only investigation, judgment, or evidence; build changes behavior, documentation, or tests and requires contextual implementation; chore is only deterministic mechanical mutation with no design choice. Evidence actions must use analyze. Analyze actions cannot own files. Chore actions must use low effort.',
174
+ 'Choose effort independently from lane, using the cheapest tier sufficient for this one action. Low is for fixed-procedure checks or mechanical edits whose success is objectively decidable. Medium is the default for normal bounded analysis or implementation with local decisions. High is exceptional: use it only when architecture, ambiguous tradeoffs, cross-cutting integration, or adversarial acceptance judgment materially determines correctness. If uncertain, choose medium.',
175
+ 'Do not choose high merely because an action uses analyze, supplies evidence, affects an important requirement, mentions many files, or belongs to a difficult overall goal. Do not choose low merely because an action is short. Examples: exact file comparison or formatting update = low; ordinary scoped feature plus focused test = medium; choosing an architecture across subsystems = high; running deterministic acceptance commands = low; interpreting ambiguous cross-cutting acceptance evidence = high.',
173
176
  'Dependencies represent required data or exact-file ordering only. Do not serialize unrelated work. Do not add reviewer, verify, repair, phase, completion, pool, model, timeout, or retry fields.',
174
177
  'Every mandatory unresolved requirement needs an evidence action. Parallel actions must be both file-disjoint and acceptance-independent. Isolated parallel siblings cannot see each other\'s unintegrated changes. If one action writes tests for behavior introduced by another action, combine code and tests under one owner or make the test action depend on and consume an artifact from the implementation action; never run new behavioral tests against the unchanged baseline in parallel. Prompts must be self-contained and include exact scope plus acceptance evidence.',
175
178
  'For mutating behavioral work, keep implementation and its focused regression test under one coherent owner. The action must prove the regression on the untouched baseline, then exercise the real production entry point or state transition after the change; disconnected helpers, no-op assertions, and test-only behavior do not satisfy acceptance.',
@@ -380,6 +380,10 @@ function validateProgram(program, state) {
380
380
  maxParallel: state.config.settings.concurrency ?? state.config.settings.maxParallel ?? 100,
381
381
  enforceMaxActions: false,
382
382
  enforceMaxParallel: false,
383
+ // Durable runs predate the current routing policy. Replay their exact
384
+ // accepted program; strict lane/effort policy applies to new planner
385
+ // candidates before dispatch, not historical display or resume.
386
+ enforceRoutingPolicy: false,
383
387
  },
384
388
  );
385
389
  for (const action of actions) {