bullswarm 0.10.1 → 0.10.3
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/AGENTS.md +1 -1
- package/CHANGELOG.md +27 -0
- package/README.md +31 -4
- package/package.json +1 -1
- package/skill/SKILL.md +19 -1
- package/src/cli.js +22 -3
- package/src/help.js +150 -0
- package/src/integrate.js +265 -0
- package/src/setup.js +29 -54
package/AGENTS.md
CHANGED
|
@@ -37,7 +37,7 @@ content. Published as `bullswarm` on npm.
|
|
|
37
37
|
## Development
|
|
38
38
|
|
|
39
39
|
```bash
|
|
40
|
-
npm test #
|
|
40
|
+
npm test # 235 tests, no network needed (meters read from cache)
|
|
41
41
|
node bin/bullswarm.js doctor --json # readiness report
|
|
42
42
|
node bin/bullswarm.js workflow list # discover workflows
|
|
43
43
|
node bin/bullswarm.js workflow runs # ongoing workflow instances
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
# bullswarm changelog
|
|
2
2
|
|
|
3
|
+
## 0.10.3 — contextual help everywhere
|
|
4
|
+
|
|
5
|
+
- Added side-effect-free `-h` / `--help` handling for the top-level CLI and
|
|
6
|
+
every command and nested subcommand, including workflow drafts, run history,
|
|
7
|
+
approvals, actions, integrations, and strategy policy controls.
|
|
8
|
+
- Added a centralized command help tree so contextual help is consistent and
|
|
9
|
+
intercepted before setup, provider discovery, state writes, or destructive
|
|
10
|
+
command execution.
|
|
11
|
+
|
|
12
|
+
## 0.10.2 — cross-agent skill integration
|
|
13
|
+
|
|
14
|
+
- Added explicit `bullswarm integrate status|install|remove` support for Codex,
|
|
15
|
+
Claude, and Grok. Installation registers one packaged `bullswarm` skill with
|
|
16
|
+
all selected agents and writes concise marker-delimited global awareness
|
|
17
|
+
rules; removal touches only Bullswarm-managed links and blocks.
|
|
18
|
+
- Added recoverable `integrate retire-legacy --yes` migration for the retired
|
|
19
|
+
Claude `offload` skill. Detection is read-only and retirement always moves the
|
|
20
|
+
old skill into `~/.claude/skills-archive/`.
|
|
21
|
+
- Added recursion-aware global guidance: a worker with `BULLSWARM_DEPTH` set
|
|
22
|
+
performs its assigned task directly instead of casually spawning another
|
|
23
|
+
swarm.
|
|
24
|
+
- Renamed the published agent skill from `bullswarm-setup` to `bullswarm` and
|
|
25
|
+
documented single-task, zero-graph goal, fixed-workflow, observation, and
|
|
26
|
+
integration paths together.
|
|
27
|
+
- Corrected README language so agent/time targets are advisory while graph
|
|
28
|
+
expansion limits remain hard safeguards.
|
|
29
|
+
|
|
3
30
|
## 0.10.1 — initiated-time workflow history search
|
|
4
31
|
|
|
5
32
|
- Added `workflow runs --since <time> --until <time>` filtering against the
|
package/README.md
CHANGED
|
@@ -5,6 +5,15 @@ orchestrator, build and expand the plan, route bounded worker actions by quota,
|
|
|
5
5
|
verify the result, and finish without an initiating agent authoring a graph.
|
|
6
6
|
Every delegate output is judged by content before it counts.
|
|
7
7
|
|
|
8
|
+
Every command and nested subcommand supports contextual `-h` / `--help`
|
|
9
|
+
without initializing state or executing the command:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
bullswarm --help
|
|
13
|
+
bullswarm workflow run --help
|
|
14
|
+
bullswarm workflow draft step add --help
|
|
15
|
+
```
|
|
16
|
+
|
|
8
17
|
## The doctrine (non-negotiable)
|
|
9
18
|
|
|
10
19
|
1. **Judge by CONTENT, not exit code.** Every delegate CLI can exit 0 while
|
|
@@ -23,8 +32,24 @@ Every delegate output is judged by content before it counts.
|
|
|
23
32
|
|
|
24
33
|
```bash
|
|
25
34
|
npm install -g bullswarm # or: node bin/bullswarm.js directly from a checkout
|
|
35
|
+
bullswarm integrate install --agents codex,claude,grok --yes
|
|
26
36
|
```
|
|
27
37
|
|
|
38
|
+
The integration command registers Bullswarm's packaged `bullswarm` skill with
|
|
39
|
+
Codex, Claude, and Grok and appends a concise, marker-delimited awareness rule
|
|
40
|
+
to each agent's global instructions. It is explicit, idempotent, and reversible:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
bullswarm integrate status --json
|
|
44
|
+
bullswarm integrate remove --agents codex,claude,grok --yes
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
If the retired pre-Bullswarm Claude `offload` skill is detected, status reports
|
|
48
|
+
it without changing it. Archive it recoverably with
|
|
49
|
+
`bullswarm integrate retire-legacy --yes`. The awareness rule prevents workers
|
|
50
|
+
already launched by Bullswarm (`BULLSWARM_DEPTH` is set) from casually
|
|
51
|
+
re-delegating and creating recursive swarms.
|
|
52
|
+
|
|
28
53
|
## Quick start
|
|
29
54
|
|
|
30
55
|
```bash
|
|
@@ -42,6 +67,7 @@ bullswarm health # re-judge saved outputs; catch gate failures
|
|
|
42
67
|
| Verb | Purpose |
|
|
43
68
|
|---|---|
|
|
44
69
|
| `setup` | Discover installed agent CLIs, show quota state, toggle pools, suggest a routing table, write config. Approval-gated, idempotent. |
|
|
70
|
+
| `integrate` | Register or remove the canonical Bullswarm skill and global awareness rules for Codex, Claude, and Grok. |
|
|
45
71
|
| `run` | route → dispatch → watch → verify → one JSON verdict |
|
|
46
72
|
| `health` | Re-judge saved outputs against their verdicts; surface verify-gate failures and quarantine clusters |
|
|
47
73
|
| `pools` | Show each pool's meter state, pace position, quarantine status |
|
|
@@ -107,7 +133,7 @@ orchestrator by live quota surplus. The orchestrator observes durable evidence,
|
|
|
107
133
|
and decides when another expansion or verification is necessary. Bullswarm
|
|
108
134
|
validates the proposal, owns agent/process selection, routes workers, and calls
|
|
109
135
|
the orchestrator again until completion, cancellation, failure, approval, or a
|
|
110
|
-
|
|
136
|
+
hard graph-growth safeguard. No initial phases, prompts, JSON schema, or agent choice are
|
|
111
137
|
required from the user.
|
|
112
138
|
|
|
113
139
|
The detached response includes a short ID and exact observation commands:
|
|
@@ -128,9 +154,10 @@ bullswarm workflow goal --resume <shortId> --json
|
|
|
128
154
|
```
|
|
129
155
|
|
|
130
156
|
`--orchestrator <pool>` exists for controlled testing; ordinary use should
|
|
131
|
-
leave selection on `auto`.
|
|
132
|
-
|
|
133
|
-
`--max-
|
|
157
|
+
leave selection on `auto`. `--max-agents` and `--max-workflow-seconds` are
|
|
158
|
+
advisory planning targets; hard graph-growth safeguards are adjusted with
|
|
159
|
+
`--max-expansion-rounds`, `--max-actions`, and `--max-items-per-expansion`.
|
|
160
|
+
Interactive setup also records a worktree-isolation
|
|
134
161
|
preference (`agent-decides`, `off`, or `required`); Bullswarm communicates that
|
|
135
162
|
policy to the orchestrator without imposing repository topology itself.
|
|
136
163
|
|
package/package.json
CHANGED
package/skill/SKILL.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: bullswarm
|
|
2
|
+
name: bullswarm
|
|
3
3
|
description: Use when you want to offload work to coding-agent subscriptions or run a self-contained goal across heterogeneous providers. bullswarm can accept one goal, select an orchestrator, expand and execute a bounded plan, verify the outcome, and expose the whole run through CLI state and events. Use `bullswarm workflow goal` for ordinary multi-step work, an explicit workflow draft when the graph itself is the contract, and `bullswarm run` for one bounded task. Every verb self-initializes.
|
|
4
4
|
---
|
|
5
5
|
|
|
@@ -10,6 +10,20 @@ CLI subscription has the most quota headroom. Every delegate output is
|
|
|
10
10
|
judged by **content**, not exit code. A non-zero exit is never a success; a
|
|
11
11
|
`verified` output is.
|
|
12
12
|
|
|
13
|
+
Every command and nested subcommand supports side-effect-free `-h` / `--help`.
|
|
14
|
+
When a flag or argument is uncertain, inspect the exact surface before acting,
|
|
15
|
+
for example `bullswarm workflow draft step add --help`.
|
|
16
|
+
|
|
17
|
+
This skill is registered globally by:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
bullswarm integrate install --agents codex,claude,grok --yes
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
If `BULLSWARM_DEPTH` is already set, you are a Bullswarm delegate. Complete the
|
|
24
|
+
assigned task directly; do not recursively invoke Bullswarm unless the task
|
|
25
|
+
explicitly requires another bounded delegation.
|
|
26
|
+
|
|
13
27
|
## When to reach for it
|
|
14
28
|
|
|
15
29
|
You should consider bullswarm when **any** of the following apply:
|
|
@@ -306,6 +320,10 @@ You do NOT need to set up bullswarm. Every verb self-initializes:
|
|
|
306
320
|
bullswarm doctor --json
|
|
307
321
|
```
|
|
308
322
|
|
|
323
|
+
Self-initialization prepares routing and connectors; it does not silently edit
|
|
324
|
+
global agent instructions. Inspect or explicitly install agent awareness with
|
|
325
|
+
`bullswarm integrate status --json` and `bullswarm integrate install --yes`.
|
|
326
|
+
|
|
309
327
|
The output has a `checks[]` array with one entry per readiness concern
|
|
310
328
|
(config, connectors, meters, offload-capable) and a `nextActions[]` list
|
|
311
329
|
of exact commands to fix anything missing.
|
package/src/cli.js
CHANGED
|
@@ -16,6 +16,8 @@ import { getVersion } from './lib/version.js';
|
|
|
16
16
|
import { release } from './lib/release.js';
|
|
17
17
|
import { cmdWorkflow } from './workflow/cli.js';
|
|
18
18
|
import { cmdStrategy, maybeRefreshStrategy } from './strategy-cli.js';
|
|
19
|
+
import { cmdIntegrate, installIntegration } from './integrate.js';
|
|
20
|
+
import { helpForArgs } from './help.js';
|
|
19
21
|
|
|
20
22
|
export function getBullswarmDir() {
|
|
21
23
|
const h = process.env.BULLSWARM_HOME?.trim();
|
|
@@ -31,8 +33,10 @@ function parseArgs(argv) {
|
|
|
31
33
|
const rest = [];
|
|
32
34
|
for (let i = 0; i < argv.length; i++) {
|
|
33
35
|
if (argv[i].startsWith('--')) {
|
|
34
|
-
const
|
|
36
|
+
const eq = argv[i].indexOf('=');
|
|
37
|
+
const key = argv[i].slice(2, eq > 0 ? eq : undefined);
|
|
35
38
|
if (key === 'json') args.json = true;
|
|
39
|
+
else if (eq > 0) args[key] = argv[i].slice(eq + 1);
|
|
36
40
|
else if (i + 1 < argv.length && !argv[i + 1].startsWith('--')) args[key] = argv[++i];
|
|
37
41
|
else args[key] = true;
|
|
38
42
|
} else rest.push(argv[i]);
|
|
@@ -295,12 +299,20 @@ async function cmdSetup(opts) {
|
|
|
295
299
|
const report = await refreshStrategy(getBullswarmDir());
|
|
296
300
|
strategy = applyStrategyRecommendations(getBullswarmDir(), report);
|
|
297
301
|
}
|
|
298
|
-
|
|
302
|
+
let integration = null;
|
|
303
|
+
if (opts.yes && opts.integrate) {
|
|
304
|
+
integration = installIntegration({
|
|
305
|
+
agents: opts.agents,
|
|
306
|
+
approved: true,
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
if (opts.json) console.log(JSON.stringify({ ok: true, mode: 'auto', ...r, strategy, integration }, null, 2));
|
|
299
310
|
else {
|
|
300
311
|
console.log(`setup complete (${r.reason}): enabled ${r.enabledPools.join(', ')}`);
|
|
301
312
|
if (r.repaired.length) console.log(`repaired connector files: ${r.repaired.join(', ')}`);
|
|
302
313
|
console.log(`model strategy: ${r.strategyCommand} (discovers models and refreshes tier suggestions)`);
|
|
303
314
|
if (strategy) console.log(`strategy autopilot: applied ${Object.keys(strategy.applied).join(', ')} tiers; refresh every ${strategy.policy.refreshHours}h`);
|
|
315
|
+
if (integration) console.log('agent integration: installed (inspect with bullswarm integrate status)');
|
|
304
316
|
}
|
|
305
317
|
return 0;
|
|
306
318
|
}
|
|
@@ -383,6 +395,11 @@ async function cmdDoctor(opts) {
|
|
|
383
395
|
// --- main ---------------------------------------------------------------------
|
|
384
396
|
|
|
385
397
|
export async function main(argv) {
|
|
398
|
+
const help = helpForArgs(argv);
|
|
399
|
+
if (help) {
|
|
400
|
+
console.log(help);
|
|
401
|
+
return 0;
|
|
402
|
+
}
|
|
386
403
|
const [verb, ...rest] = argv;
|
|
387
404
|
const opts = parseArgs(rest);
|
|
388
405
|
const { ensureSetup } = await import('./setup.js');
|
|
@@ -413,6 +430,8 @@ export async function main(argv) {
|
|
|
413
430
|
return cmdWorkflow(['runs', ...rest]);
|
|
414
431
|
case 'strategy':
|
|
415
432
|
return cmdStrategy(rest, { bullswarmDir: getBullswarmDir() });
|
|
433
|
+
case 'integrate':
|
|
434
|
+
return cmdIntegrate(opts);
|
|
416
435
|
case 'version':
|
|
417
436
|
case '--version':
|
|
418
437
|
console.log(getVersion());
|
|
@@ -421,7 +440,7 @@ export async function main(argv) {
|
|
|
421
440
|
return cmdRelease(opts);
|
|
422
441
|
default:
|
|
423
442
|
console.error(
|
|
424
|
-
`unknown verb "${verb}". try: setup | run | health | pools | strategy | doctor | workflow | runs | version | release`,
|
|
443
|
+
`unknown verb "${verb}". try: setup | integrate | run | health | pools | strategy | doctor | workflow | runs | version | release`,
|
|
425
444
|
);
|
|
426
445
|
return 2;
|
|
427
446
|
}
|
package/src/help.js
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
// Central CLI help tree. Help is resolved before setup or command dispatch so
|
|
2
|
+
// `--help` is side-effect free even for commands that normally touch state.
|
|
3
|
+
|
|
4
|
+
const top = `Usage: bullswarm <command> [options]
|
|
5
|
+
|
|
6
|
+
Route bounded work across coding-agent subscriptions and verify the result.
|
|
7
|
+
|
|
8
|
+
Commands:
|
|
9
|
+
setup discover and configure installed coding agents
|
|
10
|
+
integrate register Bullswarm guidance with Codex, Claude, and Grok
|
|
11
|
+
run dispatch one bounded task
|
|
12
|
+
health re-judge saved delegate outputs
|
|
13
|
+
pools show routing pools, meters, and quarantine state
|
|
14
|
+
strategy discover models and manage tier assignments
|
|
15
|
+
doctor report installation readiness
|
|
16
|
+
workflow create, execute, observe, and audit workflows
|
|
17
|
+
runs alias for workflow runs
|
|
18
|
+
version print the installed version
|
|
19
|
+
release create a version commit and tag
|
|
20
|
+
|
|
21
|
+
Run bullswarm <command> --help for command-specific help.`;
|
|
22
|
+
|
|
23
|
+
const leaf = (usage, body = '') => `Usage: ${usage}${body ? `\n\n${body}` : ''}`;
|
|
24
|
+
|
|
25
|
+
const HELP = {
|
|
26
|
+
_text: top,
|
|
27
|
+
setup: { _text: leaf('bullswarm setup [--yes] [--integrate] [--agents <list>] [--json]',
|
|
28
|
+
'Discover installed agent CLIs and initialize routing. Without --yes on a TTY, opens the interactive wizard.') },
|
|
29
|
+
integrate: {
|
|
30
|
+
_text: leaf('bullswarm integrate <status|install|remove|retire-legacy> [options]',
|
|
31
|
+
'Manage the canonical Bullswarm skill and recursion-safe global awareness rules.'),
|
|
32
|
+
status: { _text: leaf('bullswarm integrate status [--agents codex,claude,grok] [--json]') },
|
|
33
|
+
install: { _text: leaf('bullswarm integrate install [--agents codex,claude,grok] --yes [--json]') },
|
|
34
|
+
remove: { _text: leaf('bullswarm integrate remove [--agents codex,claude,grok] --yes [--json]') },
|
|
35
|
+
'retire-legacy': { _text: leaf('bullswarm integrate retire-legacy --yes [--json]',
|
|
36
|
+
'Recoverably archive the retired Claude offload skill.') },
|
|
37
|
+
},
|
|
38
|
+
run: { _text: leaf('bullswarm run --lane <analyze|build|chore> --add-dir <dir> (--task-file <file> | --prompt <text>) [options]',
|
|
39
|
+
'Options: --effort <high|medium|low>, --timeout <seconds>, --json. Routes one task and verifies its content.') },
|
|
40
|
+
health: { _text: leaf('bullswarm health [--json]', 'Re-judge saved outputs and report failed gates or quarantine clusters.') },
|
|
41
|
+
pools: { _text: leaf('bullswarm pools [--force] [--json]', 'Show live meter, quota-surplus, burst-gate, and quarantine state.') },
|
|
42
|
+
doctor: { _text: leaf('bullswarm doctor [--json]', 'Self-initialize if needed and report readiness without dispatching work.') },
|
|
43
|
+
version: { _text: leaf('bullswarm version') },
|
|
44
|
+
release: { _text: leaf('bullswarm release <patch|minor|major> [--dry-run]') },
|
|
45
|
+
strategy: {
|
|
46
|
+
_text: leaf('bullswarm strategy <command> [options]',
|
|
47
|
+
'Commands: refresh, apply, show, assign, clear-assignment, set-subscription, auto.'),
|
|
48
|
+
refresh: { _text: leaf('bullswarm strategy refresh [--json] [--apply --yes] [--refresh-hours <n>]') },
|
|
49
|
+
recommend: { _text: leaf('bullswarm strategy recommend [--json] [--apply --yes]', 'Alias for strategy refresh.') },
|
|
50
|
+
apply: { _text: leaf('bullswarm strategy apply --yes [--refresh-hours <n>]') },
|
|
51
|
+
show: { _text: leaf('bullswarm strategy show [--json]') },
|
|
52
|
+
assign: { _text: leaf('bullswarm strategy assign <high|medium|low> --pool <pool> --model <model>') },
|
|
53
|
+
'clear-assignment': { _text: leaf('bullswarm strategy clear-assignment <high|medium|low>') },
|
|
54
|
+
'set-subscription': { _text: leaf('bullswarm strategy set-subscription <pool> [--plan <name>] [--monthly-usd <n|unknown>] [--included-usd <n|unknown>] [--quota-window <name>]') },
|
|
55
|
+
auto: {
|
|
56
|
+
_text: leaf('bullswarm strategy auto <status|off> [--yes]'),
|
|
57
|
+
status: { _text: leaf('bullswarm strategy auto status') },
|
|
58
|
+
off: { _text: leaf('bullswarm strategy auto off --yes') },
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
workflow: {
|
|
62
|
+
_text: leaf('bullswarm workflow <command> [options]',
|
|
63
|
+
'Commands: goal, run, validate, list, draft, runs, capabilities, inspect, tui, watch, events, steer, action, approval.'),
|
|
64
|
+
goal: { _text: leaf('bullswarm workflow goal "<goal>" [--cwd <dir>] [--detach] [--json] [planning options]',
|
|
65
|
+
'Use --resume <shortId|runId> to resume. Planning options include --orchestrator, --max-agents, --max-expansion-rounds, --max-actions, --max-items-per-expansion, --max-workflow-seconds, --concurrency, and --retry-attempts.') },
|
|
66
|
+
run: { _text: leaf('bullswarm workflow run <file-or-name> [--input k=v]... [--resume <shortId|runId>] [--json] [--quiet]') },
|
|
67
|
+
validate: { _text: leaf('bullswarm workflow validate <file-or-name>') },
|
|
68
|
+
list: { _text: leaf('bullswarm workflow list [--json]') },
|
|
69
|
+
capabilities: { _text: leaf('bullswarm workflow capabilities [--json]') },
|
|
70
|
+
inspect: { _text: leaf('bullswarm workflow inspect <file-or-name>') },
|
|
71
|
+
tui: { _text: leaf('bullswarm workflow tui [<runId>] [--json] [--all] [--show <runId>] [--cancel <runId>]') },
|
|
72
|
+
watch: { _text: leaf('bullswarm workflow watch <runId> [--interval <seconds>] [--heartbeat <seconds>] [--jsonl] [--once]') },
|
|
73
|
+
events: { _text: leaf('bullswarm workflow events <runId> [--after <sequence>] [--json]') },
|
|
74
|
+
steer: { _text: leaf('bullswarm workflow steer <runId> --message <guidance> [--json]') },
|
|
75
|
+
action: {
|
|
76
|
+
_text: leaf('bullswarm workflow action <command> ...', 'Commands: show.'),
|
|
77
|
+
show: { _text: leaf('bullswarm workflow action show <runId> <actionId> [--json]') },
|
|
78
|
+
},
|
|
79
|
+
approval: {
|
|
80
|
+
_text: leaf('bullswarm workflow approval <approve|reject> <runId> [--json]'),
|
|
81
|
+
approve: { _text: leaf('bullswarm workflow approval approve <runId> [--json]') },
|
|
82
|
+
reject: { _text: leaf('bullswarm workflow approval reject <runId> [--json]') },
|
|
83
|
+
},
|
|
84
|
+
runs: runsHelp(),
|
|
85
|
+
draft: draftHelp(),
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
// Top-level `runs` is a documented alias and gets the same nested help.
|
|
90
|
+
HELP.runs = HELP.workflow.runs;
|
|
91
|
+
|
|
92
|
+
export const HELP_PATHS = Object.freeze(collectPaths(HELP));
|
|
93
|
+
|
|
94
|
+
export function helpForArgs(argv) {
|
|
95
|
+
const wantsHelp = argv.includes('--help') || argv.includes('-h') || argv[0] === 'help';
|
|
96
|
+
if (!wantsHelp) return null;
|
|
97
|
+
const tokens = argv[0] === 'help' ? argv.slice(1) : argv;
|
|
98
|
+
let node = HELP;
|
|
99
|
+
for (const token of tokens) {
|
|
100
|
+
if (token === '--help' || token === '-h' || token.startsWith('-')) continue;
|
|
101
|
+
if (!node[token]) break;
|
|
102
|
+
node = node[token];
|
|
103
|
+
}
|
|
104
|
+
return node._text ?? HELP._text;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function runsHelp() {
|
|
108
|
+
return {
|
|
109
|
+
_text: leaf('bullswarm workflow runs [list] [--all|--historical] [--name <workflow>] [--since <time>] [--until <time>] [--limit <n>] [--json]',
|
|
110
|
+
'Commands: show <id>, delete <id> --yes. Time filters use the workflow initiation timestamp.'),
|
|
111
|
+
list: { _text: leaf('bullswarm workflow runs list [--all|--historical] [--name <workflow>] [--since <time>] [--until <time>] [--limit <n>] [--json]') },
|
|
112
|
+
show: { _text: leaf('bullswarm workflow runs show <shortId|runId> [--json]') },
|
|
113
|
+
delete: { _text: leaf('bullswarm workflow runs delete <shortId|runId> --yes [--force] [--json]') },
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function draftHelp() {
|
|
118
|
+
return {
|
|
119
|
+
_text: leaf('bullswarm workflow draft <command> [options]',
|
|
120
|
+
'Commands: create, show, list, phase, step, set, validate, export, delete, run.'),
|
|
121
|
+
create: { _text: leaf('bullswarm workflow draft create <name> [--description <text>] [--input k=v]... [--required <keys>] [--json]') },
|
|
122
|
+
show: { _text: leaf('bullswarm workflow draft show <name> [--json]') },
|
|
123
|
+
list: { _text: leaf('bullswarm workflow draft list [--json]') },
|
|
124
|
+
phase: {
|
|
125
|
+
_text: leaf('bullswarm workflow draft phase <add|remove> <draft> <phase> [--json]'),
|
|
126
|
+
add: { _text: leaf('bullswarm workflow draft phase add <draft> <phase> [--json]') },
|
|
127
|
+
remove: { _text: leaf('bullswarm workflow draft phase remove <draft> <phase> [--json]') },
|
|
128
|
+
},
|
|
129
|
+
step: {
|
|
130
|
+
_text: leaf('bullswarm workflow draft step <add|remove|set> <draft> <phase> <step-id> [options]'),
|
|
131
|
+
add: { _text: leaf('bullswarm workflow draft step add <draft> <phase> <step-id> [--type <run|fanout|verify|decide>] [--lane <lane>] [--prompt <text>] [step options]') },
|
|
132
|
+
remove: { _text: leaf('bullswarm workflow draft step remove <draft> <phase> <step-id> [--json]') },
|
|
133
|
+
set: { _text: leaf('bullswarm workflow draft step set <draft> <phase> <step-id> <field> --value <text> [--json]') },
|
|
134
|
+
},
|
|
135
|
+
set: { _text: leaf('bullswarm workflow draft set <draft> <field> --value <text> [--json]') },
|
|
136
|
+
validate: { _text: leaf('bullswarm workflow draft validate <name> [--json]') },
|
|
137
|
+
export: { _text: leaf('bullswarm workflow draft export <name> <out-file> [--json]') },
|
|
138
|
+
delete: { _text: leaf('bullswarm workflow draft delete <name> --yes [--json]') },
|
|
139
|
+
run: { _text: leaf('bullswarm workflow draft run <name> [--input k=v]... [--resume <shortId|runId>] [--json] [--quiet]') },
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function collectPaths(node, prefix = []) {
|
|
144
|
+
const paths = prefix.length ? [prefix] : [[]];
|
|
145
|
+
for (const [name, child] of Object.entries(node)) {
|
|
146
|
+
if (name === '_text') continue;
|
|
147
|
+
paths.push(...collectPaths(child, [...prefix, name]));
|
|
148
|
+
}
|
|
149
|
+
return paths;
|
|
150
|
+
}
|
package/src/integrate.js
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
// bullswarm integrate — make the published agent guide discoverable by coding CLIs.
|
|
2
|
+
//
|
|
3
|
+
// Integration is explicit, idempotent, and reversible. The full operating
|
|
4
|
+
// procedure remains in skill/SKILL.md; global instruction files receive only a
|
|
5
|
+
// compact trigger policy so CLI documentation cannot drift in three places.
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
existsSync, lstatSync, mkdirSync, readFileSync, readlinkSync, renameSync,
|
|
9
|
+
symlinkSync, unlinkSync, writeFileSync,
|
|
10
|
+
} from 'node:fs';
|
|
11
|
+
import { dirname, join, resolve } from 'node:path';
|
|
12
|
+
import { fileURLToPath } from 'node:url';
|
|
13
|
+
|
|
14
|
+
const SKILL_SOURCE = fileURLToPath(new URL('../skill', import.meta.url));
|
|
15
|
+
const MARKER_BEGIN = '<!-- bullswarm:begin v2 -->';
|
|
16
|
+
const MARKER_END = '<!-- bullswarm:end -->';
|
|
17
|
+
const MARKER_RE = /<!-- bullswarm:begin v\d+ -->[\s\S]*?<!-- bullswarm:end -->\n?/;
|
|
18
|
+
|
|
19
|
+
const AGENT_LAYOUT = {
|
|
20
|
+
codex: { skill: ['.codex', 'skills', 'bullswarm'], instructions: ['.codex', 'AGENTS.md'] },
|
|
21
|
+
claude: { skill: ['.claude', 'skills', 'bullswarm'], instructions: ['.claude', 'CLAUDE.md'] },
|
|
22
|
+
grok: { skill: ['.grok', 'skills', 'bullswarm'], instructions: ['.grok', 'AGENTS.md'] },
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const INTEGRATION_AGENTS = Object.freeze(Object.keys(AGENT_LAYOUT));
|
|
26
|
+
|
|
27
|
+
export function awarenessBlock() {
|
|
28
|
+
return `${MARKER_BEGIN}
|
|
29
|
+
## Bullswarm delegation
|
|
30
|
+
|
|
31
|
+
Bullswarm is available for bounded external delegation. When delegation,
|
|
32
|
+
offloading, independent verification, or autonomous multi-step execution is
|
|
33
|
+
requested, read the \`bullswarm\` skill before acting. Use \`bullswarm run\` for
|
|
34
|
+
one bounded task, \`bullswarm workflow goal\` for ordinary autonomous multi-step
|
|
35
|
+
work, and workflow drafts only when the graph itself is the contract. Treat
|
|
36
|
+
returned artifacts and verification as evidence, not authority. This policy
|
|
37
|
+
supersedes retired pre-Bullswarm \`offload\` routing instructions. If
|
|
38
|
+
\`BULLSWARM_DEPTH\` is already set, perform the assigned task directly and do
|
|
39
|
+
not recursively invoke Bullswarm unless the task explicitly requires it.
|
|
40
|
+
${MARKER_END}`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function applyAwarenessBlock(filePath, { approved }) {
|
|
44
|
+
if (!approved) return { changed: false, reason: 'not approved' };
|
|
45
|
+
const existing = readOptional(filePath);
|
|
46
|
+
const stripped = existing.replace(MARKER_RE, '').trimEnd();
|
|
47
|
+
const next = stripped ? `${stripped}\n\n${awarenessBlock()}\n` : `${awarenessBlock()}\n`;
|
|
48
|
+
mkdirSync(dirname(filePath), { recursive: true });
|
|
49
|
+
if (next === existing) return { changed: false, reason: 'already current' };
|
|
50
|
+
writeFileSync(filePath, next);
|
|
51
|
+
return { changed: true, reason: existing.match(MARKER_RE) ? 'updated' : 'installed' };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function removeAwarenessBlock(filePath, { approved }) {
|
|
55
|
+
if (!approved) return { changed: false, reason: 'not approved' };
|
|
56
|
+
if (!existsSync(filePath)) return { changed: false, reason: 'not installed' };
|
|
57
|
+
const existing = readFileSync(filePath, 'utf8');
|
|
58
|
+
const next = existing.replace(MARKER_RE, '').trimEnd();
|
|
59
|
+
if (next === existing.trimEnd()) return { changed: false, reason: 'not installed' };
|
|
60
|
+
writeFileSync(filePath, next ? `${next}\n` : '');
|
|
61
|
+
return { changed: true, reason: 'removed' };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function awarenessBlockPresent(filePath) {
|
|
65
|
+
return MARKER_RE.test(readOptional(filePath));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function parseIntegrationAgents(value) {
|
|
69
|
+
const requested = value == null || value === true
|
|
70
|
+
? INTEGRATION_AGENTS
|
|
71
|
+
: String(value).split(',').map((item) => item.trim().toLowerCase()).filter(Boolean);
|
|
72
|
+
const unique = [...new Set(requested)];
|
|
73
|
+
const invalid = unique.filter((agent) => !INTEGRATION_AGENTS.includes(agent));
|
|
74
|
+
if (invalid.length) {
|
|
75
|
+
throw new Error(`unknown integration agent(s): ${invalid.join(', ')}; use ${INTEGRATION_AGENTS.join(', ')}`);
|
|
76
|
+
}
|
|
77
|
+
if (!unique.length) throw new Error('--agents must name at least one agent');
|
|
78
|
+
return unique;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function integrationStatus({
|
|
82
|
+
homeDir = process.env.HOME ?? '', agents = INTEGRATION_AGENTS, skillSource = SKILL_SOURCE,
|
|
83
|
+
} = {}) {
|
|
84
|
+
const selected = parseIntegrationAgents(agents);
|
|
85
|
+
const entries = selected.map((agent) => {
|
|
86
|
+
const paths = pathsFor(homeDir, agent);
|
|
87
|
+
return {
|
|
88
|
+
agent,
|
|
89
|
+
skillPath: paths.skillPath,
|
|
90
|
+
skill: skillLinkStatus(paths.skillPath, skillSource),
|
|
91
|
+
instructionsPath: paths.instructionsPath,
|
|
92
|
+
awareness: awarenessBlockPresent(paths.instructionsPath),
|
|
93
|
+
};
|
|
94
|
+
});
|
|
95
|
+
const legacyPath = join(homeDir, '.claude', 'skills', 'offload');
|
|
96
|
+
return {
|
|
97
|
+
ok: entries.every((entry) => entry.skill.status === 'installed' && entry.awareness),
|
|
98
|
+
skillSource,
|
|
99
|
+
agents: entries,
|
|
100
|
+
legacyOffload: {
|
|
101
|
+
path: legacyPath,
|
|
102
|
+
detected: existsSync(legacyPath),
|
|
103
|
+
action: existsSync(legacyPath)
|
|
104
|
+
? 'bullswarm integrate retire-legacy --yes'
|
|
105
|
+
: null,
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function installIntegration({
|
|
111
|
+
homeDir = process.env.HOME ?? '', agents = INTEGRATION_AGENTS,
|
|
112
|
+
skillSource = SKILL_SOURCE, approved = false,
|
|
113
|
+
} = {}) {
|
|
114
|
+
if (!approved) throw new Error('integration changes global agent configuration; pass --yes to approve');
|
|
115
|
+
const selected = parseIntegrationAgents(agents);
|
|
116
|
+
if (!existsSync(join(skillSource, 'SKILL.md'))) {
|
|
117
|
+
throw new Error(`packaged Bullswarm skill is missing: ${join(skillSource, 'SKILL.md')}`);
|
|
118
|
+
}
|
|
119
|
+
for (const agent of selected) {
|
|
120
|
+
const { skillPath } = pathsFor(homeDir, agent);
|
|
121
|
+
if (skillLinkStatus(skillPath, skillSource).status === 'conflict') {
|
|
122
|
+
throw new Error(`refusing to replace non-Bullswarm skill path: ${skillPath}`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const changes = [];
|
|
126
|
+
for (const agent of selected) {
|
|
127
|
+
const paths = pathsFor(homeDir, agent);
|
|
128
|
+
const skill = installSkillLink(paths.skillPath, skillSource);
|
|
129
|
+
const awareness = applyAwarenessBlock(paths.instructionsPath, { approved: true });
|
|
130
|
+
changes.push({ agent, skill, awareness });
|
|
131
|
+
}
|
|
132
|
+
return { action: 'install', changes, status: integrationStatus({ homeDir, agents: selected, skillSource }) };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function removeIntegration({
|
|
136
|
+
homeDir = process.env.HOME ?? '', agents = INTEGRATION_AGENTS,
|
|
137
|
+
skillSource = SKILL_SOURCE, approved = false,
|
|
138
|
+
} = {}) {
|
|
139
|
+
if (!approved) throw new Error('integration removal changes global agent configuration; pass --yes to approve');
|
|
140
|
+
const selected = parseIntegrationAgents(agents);
|
|
141
|
+
const changes = [];
|
|
142
|
+
for (const agent of selected) {
|
|
143
|
+
const paths = pathsFor(homeDir, agent);
|
|
144
|
+
const skill = removeSkillLink(paths.skillPath, skillSource);
|
|
145
|
+
const awareness = removeAwarenessBlock(paths.instructionsPath, { approved: true });
|
|
146
|
+
changes.push({ agent, skill, awareness });
|
|
147
|
+
}
|
|
148
|
+
return { action: 'remove', changes, status: integrationStatus({ homeDir, agents: selected, skillSource }) };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function retireLegacyOffload({ homeDir = process.env.HOME ?? '', approved = false, now = new Date() } = {}) {
|
|
152
|
+
if (!approved) throw new Error('legacy offload retirement moves a user skill; pass --yes to approve');
|
|
153
|
+
const source = join(homeDir, '.claude', 'skills', 'offload');
|
|
154
|
+
if (!existsSync(source)) return { action: 'retire-legacy', changed: false, reason: 'not installed' };
|
|
155
|
+
const archiveRoot = join(homeDir, '.claude', 'skills-archive');
|
|
156
|
+
mkdirSync(archiveRoot, { recursive: true });
|
|
157
|
+
const stamp = now.toISOString().replace(/[:.]/g, '-');
|
|
158
|
+
const destination = join(archiveRoot, `offload-before-bullswarm-${stamp}`);
|
|
159
|
+
renameSync(source, destination);
|
|
160
|
+
return { action: 'retire-legacy', changed: true, source, destination, recoverable: true };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function integrateUsage() {
|
|
164
|
+
return `usage: bullswarm integrate [status] [--agents codex,claude,grok] [--json]
|
|
165
|
+
bullswarm integrate install --agents codex,claude,grok --yes [--json]
|
|
166
|
+
bullswarm integrate remove --agents codex,claude,grok --yes [--json]
|
|
167
|
+
bullswarm integrate retire-legacy --yes [--json]
|
|
168
|
+
|
|
169
|
+
Install registers the packaged Bullswarm skill and a concise recursion-safe
|
|
170
|
+
awareness rule. Removal only deletes Bullswarm-managed symlinks and marker
|
|
171
|
+
blocks. retire-legacy moves ~/.claude/skills/offload into skills-archive.`;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function cmdIntegrate(opts) {
|
|
175
|
+
const subcommand = opts.rest[0] ?? 'status';
|
|
176
|
+
if (opts.help || subcommand === 'help') {
|
|
177
|
+
console.log(integrateUsage());
|
|
178
|
+
return 0;
|
|
179
|
+
}
|
|
180
|
+
let agents;
|
|
181
|
+
try {
|
|
182
|
+
agents = parseIntegrationAgents(opts.agents);
|
|
183
|
+
let result;
|
|
184
|
+
if (subcommand === 'status') result = { action: 'status', ...integrationStatus({ agents }) };
|
|
185
|
+
else if (subcommand === 'install') result = installIntegration({ agents, approved: opts.yes === true });
|
|
186
|
+
else if (subcommand === 'remove') result = removeIntegration({ agents, approved: opts.yes === true });
|
|
187
|
+
else if (subcommand === 'retire-legacy') result = retireLegacyOffload({ approved: opts.yes === true });
|
|
188
|
+
else {
|
|
189
|
+
console.error(integrateUsage());
|
|
190
|
+
return 2;
|
|
191
|
+
}
|
|
192
|
+
if (opts.json) console.log(JSON.stringify(result, null, 2));
|
|
193
|
+
else printIntegrationResult(result);
|
|
194
|
+
const ok = result.status?.ok ?? result.ok;
|
|
195
|
+
return ok === false && subcommand === 'status' ? 1 : 0;
|
|
196
|
+
} catch (error) {
|
|
197
|
+
console.error(`✗ ${error.message}`);
|
|
198
|
+
return 1;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function pathsFor(homeDir, agent) {
|
|
203
|
+
const layout = AGENT_LAYOUT[agent];
|
|
204
|
+
return {
|
|
205
|
+
skillPath: join(homeDir, ...layout.skill),
|
|
206
|
+
instructionsPath: join(homeDir, ...layout.instructions),
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function installSkillLink(skillPath, skillSource) {
|
|
211
|
+
const current = skillLinkStatus(skillPath, skillSource);
|
|
212
|
+
if (current.status === 'installed') return { changed: false, ...current };
|
|
213
|
+
if (current.status === 'conflict') {
|
|
214
|
+
throw new Error(`refusing to replace non-Bullswarm skill path: ${skillPath}`);
|
|
215
|
+
}
|
|
216
|
+
mkdirSync(dirname(skillPath), { recursive: true });
|
|
217
|
+
symlinkSync(skillSource, skillPath, 'dir');
|
|
218
|
+
return { changed: true, status: 'installed', path: skillPath, target: skillSource };
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function removeSkillLink(skillPath, skillSource) {
|
|
222
|
+
const current = skillLinkStatus(skillPath, skillSource);
|
|
223
|
+
if (current.status === 'missing') return { changed: false, ...current };
|
|
224
|
+
if (current.status === 'conflict') {
|
|
225
|
+
return { changed: false, ...current, reason: 'left conflict untouched' };
|
|
226
|
+
}
|
|
227
|
+
unlinkSync(skillPath);
|
|
228
|
+
return { changed: true, status: 'missing', path: skillPath, target: skillSource };
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function skillLinkStatus(skillPath, skillSource) {
|
|
232
|
+
let stat;
|
|
233
|
+
try { stat = lstatSync(skillPath); } catch { return { status: 'missing', path: skillPath, target: null }; }
|
|
234
|
+
if (!stat.isSymbolicLink()) return { status: 'conflict', path: skillPath, target: null };
|
|
235
|
+
const rawTarget = readlinkSync(skillPath);
|
|
236
|
+
const target = resolve(dirname(skillPath), rawTarget);
|
|
237
|
+
return {
|
|
238
|
+
status: target === resolve(skillSource) ? 'installed' : 'conflict',
|
|
239
|
+
path: skillPath,
|
|
240
|
+
target,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function readOptional(filePath) {
|
|
245
|
+
try { return readFileSync(filePath, 'utf8'); } catch { return ''; }
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function printIntegrationResult(result) {
|
|
249
|
+
if (result.action === 'retire-legacy') {
|
|
250
|
+
console.log(result.changed
|
|
251
|
+
? `✓ archived retired offload skill at ${result.destination}`
|
|
252
|
+
: 'retired offload skill is not installed');
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
const status = result.status ?? result;
|
|
256
|
+
for (const entry of status.agents ?? []) {
|
|
257
|
+
const skill = entry.skill.status === 'installed' ? 'skill ✓' : `skill ${entry.skill.status}`;
|
|
258
|
+
const awareness = entry.awareness ? 'awareness ✓' : 'awareness missing';
|
|
259
|
+
console.log(`${entry.agent.padEnd(8)} ${skill}; ${awareness}`);
|
|
260
|
+
}
|
|
261
|
+
if (status.legacyOffload?.detected) {
|
|
262
|
+
console.log(`⚠ retired Claude offload skill detected: ${status.legacyOffload.path}`);
|
|
263
|
+
console.log(` recoverably archive it with: ${status.legacyOffload.action}`);
|
|
264
|
+
}
|
|
265
|
+
}
|
package/src/setup.js
CHANGED
|
@@ -5,9 +5,8 @@
|
|
|
5
5
|
// entry. Burn rate starts EMPTY and is labeled "learning".
|
|
6
6
|
// U2. The wizard suggests a routing table as an EDITABLE ARTIFACT, never a
|
|
7
7
|
// questionnaire.
|
|
8
|
-
// U3.
|
|
9
|
-
//
|
|
10
|
-
// markers, idempotent on re-run.
|
|
8
|
+
// U3. Cross-agent skill/instruction integration requires explicit approval,
|
|
9
|
+
// uses versioned bullswarm:begin/end markers, and is idempotent.
|
|
11
10
|
// U4. `bullswarm setup` on a configured machine reports state and repairs
|
|
12
11
|
// broken connector files.
|
|
13
12
|
|
|
@@ -15,14 +14,16 @@ import { execFileSync } from 'node:child_process';
|
|
|
15
14
|
import {
|
|
16
15
|
existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, copyFileSync,
|
|
17
16
|
} from 'node:fs';
|
|
18
|
-
import { join
|
|
17
|
+
import { join } from 'node:path';
|
|
19
18
|
import { fileURLToPath } from 'node:url';
|
|
20
19
|
import { stdin as input } from 'node:process';
|
|
21
20
|
import { loadState, saveState } from './lib/state.js';
|
|
21
|
+
import {
|
|
22
|
+
awarenessBlock, applyAwarenessBlock, awarenessBlockPresent,
|
|
23
|
+
installIntegration, retireLegacyOffload,
|
|
24
|
+
} from './integrate.js';
|
|
22
25
|
|
|
23
26
|
const REPO_ROOT = fileURLToPath(new URL('..', import.meta.url));
|
|
24
|
-
const MARKER_BEGIN = '<!-- bullswarm:begin v1 -->';
|
|
25
|
-
const MARKER_END = '<!-- bullswarm:end -->';
|
|
26
27
|
|
|
27
28
|
// --- prompting ------------------------------------------------------------
|
|
28
29
|
// Sequential prompts that work identically on a TTY and with piped answers.
|
|
@@ -121,45 +122,15 @@ export function suggestRoutingTable(enabledPools) {
|
|
|
121
122
|
// --- integration block ------------------------------------------------------------
|
|
122
123
|
|
|
123
124
|
export function integrationBlock() {
|
|
124
|
-
return
|
|
125
|
-
## bullswarm offload policy
|
|
126
|
-
|
|
127
|
-
When a task fits a bounded lane, prefer offloading it:
|
|
128
|
-
|
|
129
|
-
bullswarm run --lane <analyze|build|chore> --add-dir <repo-dir> --task-file <file> --json
|
|
130
|
-
|
|
131
|
-
Read the verdict JSON: ok:true -> use outFile; keepOnClaude:true -> do it in-session;
|
|
132
|
-
ok:false -> the why field names the failed gate. Delegate output is INPUT you verify,
|
|
133
|
-
never the answer. Final synthesis, architecture decisions, and live-context work stay
|
|
134
|
-
with you. Run \`bullswarm health\` after every offload round.
|
|
135
|
-
${MARKER_END}`;
|
|
125
|
+
return awarenessBlock();
|
|
136
126
|
}
|
|
137
127
|
|
|
138
128
|
export function applyIntegrationBlock(filePath, { approved }) {
|
|
139
|
-
|
|
140
|
-
let existing = '';
|
|
141
|
-
try {
|
|
142
|
-
existing = readFileSync(filePath, 'utf8');
|
|
143
|
-
} catch {
|
|
144
|
-
/* new file */
|
|
145
|
-
}
|
|
146
|
-
const stripped = existing
|
|
147
|
-
.replace(new RegExp(`${MARKER_BEGIN}[\\s\\S]*?${MARKER_END}\n?`), '')
|
|
148
|
-
.trimEnd();
|
|
149
|
-
const next = stripped
|
|
150
|
-
? `${stripped}\n\n${integrationBlock()}\n`
|
|
151
|
-
: `${integrationBlock()}\n`;
|
|
152
|
-
mkdirSync(dirname(filePath), { recursive: true });
|
|
153
|
-
writeFileSync(filePath, next);
|
|
154
|
-
return { changed: true };
|
|
129
|
+
return applyAwarenessBlock(filePath, { approved });
|
|
155
130
|
}
|
|
156
131
|
|
|
157
132
|
export function integrationBlockPresent(filePath) {
|
|
158
|
-
|
|
159
|
-
return readFileSync(filePath, 'utf8').includes(MARKER_BEGIN);
|
|
160
|
-
} catch {
|
|
161
|
-
return false;
|
|
162
|
-
}
|
|
133
|
+
return awarenessBlockPresent(filePath);
|
|
163
134
|
}
|
|
164
135
|
|
|
165
136
|
// --- repair ---------------------------------------------------------------
|
|
@@ -398,21 +369,25 @@ export async function runWizard(bullswarmDir, opts = {}) {
|
|
|
398
369
|
console.log(' strategy autopilot: off (enable later with bullswarm strategy apply --yes)');
|
|
399
370
|
}
|
|
400
371
|
|
|
401
|
-
// 6.
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
const
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
372
|
+
// 6. Cross-agent integration — one canonical skill plus concise global
|
|
373
|
+
// awareness rules. Nothing is written without this explicit answer.
|
|
374
|
+
const integrateAnswer = (
|
|
375
|
+
await rl.question('install Bullswarm skill for Codex, Claude, and Grok? [y/N] ')
|
|
376
|
+
).trim().toLowerCase();
|
|
377
|
+
if (integrateAnswer === 'y' || integrateAnswer === 'yes') {
|
|
378
|
+
const integrated = installIntegration({ approved: true });
|
|
379
|
+
console.log(` agent integration: ${integrated.status.ok ? 'ready' : 'incomplete'}`);
|
|
380
|
+
if (integrated.status.legacyOffload.detected) {
|
|
381
|
+
const retireAnswer = (
|
|
382
|
+
await rl.question('archive the retired Claude offload skill? [y/N] ')
|
|
383
|
+
).trim().toLowerCase();
|
|
384
|
+
if (retireAnswer === 'y' || retireAnswer === 'yes') {
|
|
385
|
+
const retired = retireLegacyOffload({ approved: true });
|
|
386
|
+
console.log(` retired offload: ${retired.changed ? `archived at ${retired.destination}` : retired.reason}`);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
} else {
|
|
390
|
+
console.log(' agent integration: skipped (install later with bullswarm integrate install --yes)');
|
|
416
391
|
}
|
|
417
392
|
|
|
418
393
|
rl.close();
|