@polderlabs/bizar 10.24.0 → 10.25.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 (43) hide show
  1. package/AGENTS.md +14 -12
  2. package/README.md +3 -3
  3. package/cli/bin.mjs +27 -21
  4. package/cli/commands/claim.mjs +37 -267
  5. package/cli/commands/control.mjs +3 -1
  6. package/cli/commands/goal-bootstrap.mjs +20 -122
  7. package/cli/commands/guard.mjs +57 -202
  8. package/cli/commands/install.mjs +14 -11
  9. package/cli/commands/openkan.mjs +72 -0
  10. package/cli/commands/planning.mjs +26 -0
  11. package/cli/commands/spec-list.mjs +20 -3
  12. package/cli/commands/task.mjs +27 -234
  13. package/cli/commands/workflow-gc.mjs +10 -16
  14. package/cli/control-store.mjs +7 -31
  15. package/cli/openkan-store.mjs +60 -0
  16. package/cli/openkan.mjs +320 -0
  17. package/cli/provision.mjs +34 -1
  18. package/config/claude/CLAUDE.md +14 -12
  19. package/config/claude/agents/_shared/AGENT_BASELINE.md +1 -1
  20. package/config/claude/commands/bizar.md +2 -2
  21. package/config/claude/commands/guard.md +1 -1
  22. package/config/claude/commands/plow-through.md +1 -1
  23. package/config/claude/commands/sprint.md +4 -5
  24. package/config/claude/commands/team.md +1 -1
  25. package/config/claude/hooks/goal-bootstrap.mjs +29 -106
  26. package/config/claude/hooks/path-ownership-guard.mjs +14 -25
  27. package/config/claude/hooks/precompact-priorities.sh +2 -2
  28. package/config/claude/hooks/sessionend-recall.mjs +13 -17
  29. package/config/claude/hooks/sessionstart-prime.mjs +36 -84
  30. package/config/claude/hooks/thinking-route.mjs +1 -1
  31. package/config/claude/hooks/verify-deliverables.mjs +5 -11
  32. package/config/skills/autopilot/SKILL.md +1 -1
  33. package/config/skills/bizar/SKILL.md +4 -4
  34. package/config/skills/goal-bootstrap/SKILL.md +10 -78
  35. package/config/skills/guard/SKILL.md +9 -56
  36. package/config/skills/harness-engineering/SKILL.md +6 -6
  37. package/config/skills/openkan/SKILL.md +34 -0
  38. package/package.json +1 -1
  39. package/packages/sdk/dist/agent/goal-bootstrap.d.ts +59 -9
  40. package/packages/sdk/dist/agent/goal-bootstrap.js +183 -34
  41. package/cli/feature-list-bridge.mjs +0 -400
  42. package/cli/progress-parser.mjs +0 -251
  43. package/cli/task-ledger.mjs +0 -795
package/AGENTS.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # AGENTS.md — Bizar Harness
2
2
 
3
- Bizar Harness is a Claude Code-native, guarded-autonomy harness. It ships project and user-level agents, skills, slash commands, hooks, an MCP server, CLI utilities, and verification scripts. It has no embedded web control plane and no Bizar note-vault subsystem; OpenKan can consume the explicit `bizar control` CLI boundary as an optional external UI.
3
+ Bizar Harness is a Claude Code-native, guarded-autonomy harness. It ships project and user-level agents, skills, slash commands, hooks, an MCP server, CLI utilities, and verification scripts. OpenKan is bundled as Bizar’s default durable planning, progression, task, and PRD-goal system; Bizar integrates with it only through the `.ok/` workspace and its supported CLI boundary.
4
4
 
5
- If you are an agent: read this file, read `PROGRESS.md`, inspect `feature_list.json`, then run `make check` before changing code.
5
+ If you are an agent: read this file, inspect `.ok/` with `bizar task list` and `bizar goals list`, then run `make check` before changing code.
6
6
 
7
7
  ## Commands
8
8
 
@@ -15,15 +15,17 @@ make check-arch # architectural and removed-surface checks
15
15
  make verify-removed-surfaces # prove deleted subsystems are absent
16
16
  make verify-repo-structure # prove tracked/package paths are clean
17
17
  make clean-check # debug-artifact/static hygiene gate
18
- make vcr # feature-ledger reality ratio
18
+ bizar task list # OpenKan task progression
19
+ bizar plan list # OpenKan plans
20
+ bizar goals list # OpenKan PRD goals
19
21
  make session-start # lifecycle compatibility target
20
22
  make session-end # lifecycle compatibility target
21
23
  ```
22
24
 
23
25
  ## Hard constraints
24
26
 
25
- - **MUST** update `PROGRESS.md` before and after each logical code change.
26
- - **MUST** keep WIP=1 in `feature_list.json`.
27
+ - **MUST** keep the scoped OpenKan task current in `.ok/`: claim before implementation, update status/evidence at each durable handoff, and complete only with verification evidence.
28
+ - **MUST** use OpenKan PRDs and plans for durable goals and progression; `PROGRESS.md` and `feature_list.json` are legacy historical records, not live control state.
27
29
  - **MUST** keep one logical operation per commit and keep its docs in the same commit.
28
30
  - **MUST** run targeted tests, then `make check`; run `make e2e` for cross-component changes.
29
31
  - **MUST** verify evidence before claiming completion.
@@ -52,7 +54,7 @@ Agents execute clear, local, reversible work autonomously — they inspect,
52
54
  edit, test, and iterate without pausing for routine decisions. Routine
53
55
  decisions (file layout, naming, scope of a single commit, choosing between
54
56
  two equivalent stdlib calls, picking a verification command from the Makefile,
55
- or marking a task `passing` after `make check` is green) do NOT require
57
+ or completing an OpenKan task after `make check` is green) do NOT require
56
58
  human approval and MUST NOT trigger a permission handoff. PreToolUse hooks
57
59
  still deny prohibited actions and escalate externally visible or irreversible
58
60
  actions with `permissionDecision: "ask"`; that escalation list is the
@@ -176,16 +178,16 @@ isolation.
176
178
  - `scripts/` + `.harness/` + `templates/` — verification, feature/eval state, audit output, and reusable contracts.
177
179
 
178
180
  The harness has no embedded browser/server UI layer or local web editor.
179
- `bizar control` is a machine-readable subprocess boundary for optional OpenKan
180
- integration; OpenKan owns HTTP, WebSocket, and presentation concerns. Session
181
+ `bizar control` is a machine-readable subprocess boundary over the default OpenKan
182
+ workspace; OpenKan owns durable task/plan/PRD state, HTTP, WebSocket, and presentation concerns. Session
181
183
  handoff, control inbox, and learning logs are bounded operational records for
182
184
  autonomy; they are not a general note vault, semantic search service, or
183
185
  knowledge-base API.
184
186
 
185
187
  ## State and evidence
186
188
 
187
- - `PROGRESS.md`current objective, evidence, next actions, blockers.
188
- - `feature_list.json` — WIP=1 feature state.
189
+ - `.ok/`authoritative OpenKan tasks, plans, PRDs, progression, evidence, and scoped ownership.
190
+ - `PROGRESS.md` and `feature_list.json` — legacy historical records; do not use them for new work.
189
191
  - `DECISIONS.md` and `docs/decisions/` — current architecture decisions.
190
192
  - `.harness/evals/` — feature evaluation records.
191
193
  - `~/.config/bizar/telemetry/` — local correlation and rejected-action feedback.
@@ -194,7 +196,7 @@ knowledge-base API.
194
196
  ## Definition of done
195
197
 
196
198
  1. Behavior is implemented with a regression test.
197
- 2. Documentation and feature state describe the actual code.
199
+ 2. Documentation and OpenKan task/plan/PRD state describe the actual code.
198
200
  3. `make verify-removed-surfaces`, `make verify-repo-structure`, `make check-arch`, `make test`, `make e2e`, `make clean-check`, and `make check` pass as applicable.
199
- 4. `PROGRESS.md` records fresh evidence and no required work remains.
201
+ 4. The OpenKan task records fresh verification evidence and no required work remains.
200
202
  5. `/simplify` reviews the staged diff before the approval-gated commit.
package/README.md CHANGED
@@ -52,8 +52,8 @@ bizar install
52
52
  bizar models
53
53
  ```
54
54
 
55
- Restart Claude Code after installation. The installer adds Bizar's agents,
56
- skills, commands, hooks, and settings to your user-level Claude configuration.
55
+ Restart Claude Code after installation. The installer adds Bizar's agents, skills, commands, hooks, settings, and the
56
+ default OpenKan planning runtime to your user-level Claude configuration.
57
57
  It preserves your configured gateway endpoint and credential values during a
58
58
  clean reinstall.
59
59
 
@@ -242,7 +242,7 @@ make check
242
242
  - [Documentation index](docs/INDEX.md)
243
243
  - [Architecture](docs/architecture.md)
244
244
  - [Model routing decisions](docs/decisions/)
245
- - [Current progress and evidence](PROGRESS.md)
245
+ - [Durable project progress, plans, and goals](.ok/)
246
246
  - [MIT license](LICENSE)
247
247
 
248
248
  ## License
package/cli/bin.mjs CHANGED
@@ -126,9 +126,12 @@ function showHelp() {
126
126
  run Run Claude Code once (optionally --bg)
127
127
  rca Analyze a GitHub issue (Claude Code CLI sample)
128
128
  cost <subcommand> Atomic cost gate (SQLite-backed room budget tracker)
129
- claim <subcommand> GitHub-style claim protocol over feature_list.json
130
- task <subcommand> Durable dependency/worktree/path task coordination
131
- control <subcommand> Machine-readable agents/tasks/sessions/messages API
129
+ openkan <subcommand> Install, initialise, and operate the default OpenKan workspace
130
+ task <subcommand> OpenKan-backed durable task lifecycle
131
+ plan <subcommand> OpenKan plans and phases
132
+ goals <subcommand> OpenKan PRDs, goals, and milestones
133
+ claim <subcommand> OpenKan task lease shortcut
134
+ control <subcommand> Machine-readable agents/tasks/plans/goals/sessions/messages API
132
135
  workflow <subcommand> Session-bound autopilot workflow state
133
136
  hook <name> Run a portable Claude Code hook
134
137
  worktree-merge <branch> Merge a feature branch with archive tag (no work lost)
@@ -387,8 +390,6 @@ async function main() {
387
390
  }
388
391
 
389
392
  case 'claim': {
390
- // F-035 MetaHarness — GitHub-style claim protocol over feature_list.json.
391
- // Subcommands: <featureId> | release | handoff | steal | status | list | transition
392
393
  const mod = await importCommand('claim');
393
394
  if (!mod) {
394
395
  console.error(chalk.red(` ✗ Could not load claim command module`));
@@ -420,6 +421,21 @@ async function main() {
420
421
  break;
421
422
  }
422
423
 
424
+ case 'openkan': {
425
+ const mod = await importCommand('openkan');
426
+ if (!mod) { process.exit(EXIT_ERROR); return; }
427
+ await mod.run(cmd, cmdArgs, isHelpRequest);
428
+ break;
429
+ }
430
+
431
+ case 'plan':
432
+ case 'goals': {
433
+ const mod = await importCommand('planning');
434
+ if (!mod) { process.exit(EXIT_ERROR); return; }
435
+ await mod.run(cmd, cmdArgs, isHelpRequest);
436
+ break;
437
+ }
438
+
423
439
  case 'control': {
424
440
  const mod = await importCommand('control');
425
441
  if (!mod) {
@@ -532,8 +548,8 @@ async function main() {
532
548
 
533
549
  case 'guard': {
534
550
  // F-206 — `/guard` progress-guarding loop. The CLI is read-only
535
- // with respect to the repo (plan + PROGRESS.md + feature_list +
536
- // checks.jsonl). See cli/commands/guard.mjs for the verdict
551
+ // with respect to the repo (plan + OpenKan .ok state + checks.jsonl).
552
+ // See cli/commands/guard.mjs for the verdict
537
553
  // semantics and side-effect rules.
538
554
  const mod = await importCommand('guard');
539
555
  if (!mod) {
@@ -548,20 +564,10 @@ async function main() {
548
564
  }
549
565
 
550
566
  case 'goal-bootstrap': {
551
- // F-207 Mike autonomous goal / ultragoal bootstrap. Single-action
552
- // CLI: read feature_list.json + docs/specs/ and emit a
553
- // discriminated-union verdict (resume | bootstrap | idle). The
554
- // bootstrap action writes exactly one durable artifact (the
555
- // charter) under docs/specs/. See cli/commands/goal-bootstrap.mjs.
556
- const mod = await importCommand('goal-bootstrap');
557
- if (!mod) {
558
- console.error(chalk.red(` ✗ Could not load goal-bootstrap command module`));
559
- process.exit(EXIT_ERROR);
560
- return;
561
- }
562
- dbg('loaded command module:', 'goal-bootstrap');
563
- const code = await mod.run(cmdArgs);
564
- if (typeof code === 'number') process.exit(code);
567
+ console.error(chalk.yellow(' ! goal-bootstrap is retired; OpenKan PRDs are the default goal system.'));
568
+ const mod = await importCommand('planning');
569
+ if (!mod) { process.exit(EXIT_ERROR); return; }
570
+ await mod.run('goals', cmdArgs, isHelpRequest);
565
571
  break;
566
572
  }
567
573
 
@@ -1,282 +1,52 @@
1
- /**
2
- * cli/commands/claim.mjs
3
- *
4
- * F-035 (MetaHarness) — `bizar claim` subcommand surface.
5
- *
6
- * Wraps cli/feature-list-bridge.mjs (over feature_list.json).
7
- *
8
- * Subcommands:
9
- * bizar claim <featureId> # claim with --who (or $USER)
10
- * --who <id> (default: $USER)
11
- * --type <human|agent> (default: human)
12
- * --reason <text> Free-form claim reason
13
- * bizar claim release <featureId> # release
14
- * --who <id> Optional claimant check
15
- * --reason <text>
16
- * bizar claim handoff <featureId> --to <id> # handoff
17
- * --from <id>
18
- * --reason <text>
19
- * bizar claim steal <featureId> --by <id> # steal
20
- * --type <human|agent>
21
- * --reason <steal-reason> Must be one of STEAL_REASONS
22
- * bizar claim status <featureId> # Show current claim + history
23
- * bizar claim list # list all features with status
24
- * --status <s> One of CLAIM_STATUSES (repeatable)
25
- * --by-claimant <id> Filter by claimant id
26
- * --human-only / --agent-only
27
- * bizar claim transition <featureId> <newStatus> # active|paused|blocked|completed
28
- * --who <id>
29
- * --reason <text>
30
- *
31
- * --json flag for machine output.
32
- */
1
+ /** OpenKan task-claim shortcut replacing the feature_list.json claim bridge. */
2
+ import { OpenKanError, runOpenKanOk } from '../openkan.mjs';
33
3
 
34
- import chalk from 'chalk';
35
-
36
- import { FeatureListBridge, ClaimError, CLAIM_STATUSES, STEAL_REASONS, defaultFeatureListPath } from '../feature-list-bridge.mjs';
37
-
38
- function parseFlags(args) {
39
- const flags = { _: [], statuses: [] };
40
- for (let i = 0; i < args.length; i++) {
41
- const a = args[i];
42
- if (a === '--json') flags.json = true;
43
- else if (a === '--who' && i + 1 < args.length) { flags.who = args[++i]; }
44
- else if (a.startsWith('--who=')) flags.who = a.slice('--who='.length);
45
- else if (a === '--type' && i + 1 < args.length) { flags.type = args[++i]; }
46
- else if (a.startsWith('--type=')) flags.type = a.slice('--type='.length);
47
- else if (a === '--reason' && i + 1 < args.length) { flags.reason = args[++i]; }
48
- else if (a.startsWith('--reason=')) flags.reason = a.slice('--reason='.length);
49
- else if (a === '--to' && i + 1 < args.length) { flags.to = args[++i]; }
50
- else if (a.startsWith('--to=')) flags.to = a.slice('--to='.length);
51
- else if (a === '--from' && i + 1 < args.length) { flags.from = args[++i]; }
52
- else if (a.startsWith('--from=')) flags.from = a.slice('--from='.length);
53
- else if (a === '--by' && i + 1 < args.length) { flags.by = args[++i]; }
54
- else if (a.startsWith('--by=')) flags.by = a.slice('--by='.length);
55
- else if (a === '--status' && i + 1 < args.length) { flags.statuses.push(args[++i]); }
56
- else if (a.startsWith('--status=')) flags.statuses.push(a.slice('--status='.length));
57
- else if (a === '--by-claimant' && i + 1 < args.length) { flags.byClaimant = args[++i]; }
58
- else if (a.startsWith('--by-claimant=')) flags.byClaimant = a.slice('--by-claimant='.length);
59
- else if (a === '--human-only') flags.humanOnly = true;
60
- else if (a === '--agent-only') flags.agentOnly = true;
61
- else if (a === '--file' && i + 1 < args.length) { flags.file = args[++i]; }
62
- else if (a.startsWith('--file=')) flags.file = a.slice('--file='.length);
63
- else if (a === '--help' || a === '-h') flags.help = true;
64
- else flags._.push(a);
65
- }
66
- return flags;
67
- }
68
-
69
- function makeClaimant(flags, fallbackId) {
70
- const id = flags.who || flags.by || fallbackId;
71
- const type = flags.type || 'human';
72
- if (!id) return null;
73
- return { type, id, name: id };
74
- }
75
-
76
- function bridge(flags) {
77
- return new FeatureListBridge({
78
- filePath: flags.file || defaultFeatureListPath(),
79
- });
80
- }
81
-
82
- function showHelp() {
83
- console.log(`
84
- bizar claim — GitHub-ish claim protocol over feature_list.json
4
+ function help() {
5
+ process.stdout.write(`
6
+ bizar claim claim an OpenKan task lease
85
7
 
86
8
  Usage:
87
- bizar claim <featureId> [--who <id>] [--type human|agent] [--reason <text>]
88
- bizar claim release <featureId> [--who <id>] [--reason <text>]
89
- bizar claim handoff <featureId> --to <id> [--from <id>] [--reason <text>]
90
- bizar claim steal <featureId> --by <id> [--type human|agent] --reason <reason>
91
- bizar claim status <featureId>
92
- bizar claim list [--status <s>] [--by-claimant <id>] [--human-only|--agent-only]
93
- bizar claim transition <featureId> <active|paused|blocked|completed> [--who <id>]
9
+ bizar claim <task-id> [--who <agent>] [--lease-ms <ms>]
10
+ bizar claim release <task-id> [--who <agent>]
11
+ bizar claim heartbeat <task-id> [--who <agent>] [--lease-ms <ms>]
12
+ bizar claim list [--status <status>] [--json]
94
13
 
95
- Steal reasons (must match exactly):
96
- ${STEAL_REASONS.join(' | ')}
97
-
98
- Status states (must match exactly):
99
- ${CLAIM_STATUSES.join(' | ')}
100
-
101
- Flags:
102
- --json Machine-readable JSON output
103
- --file <path> Override feature_list.json path
104
- --who <id> / --by <id> Claimant identifier (default: $USER)
105
- --type human|agent Claimant type (default: human)
106
- --reason <text> Free-form reason (mandatory for steal)
107
- --to / --from <id> Recipient / sender for handoff
108
- --human-only / --agent-only Restrict list to human or agent claims
109
- --status <s> Filter list (repeat for multiple)
110
- --by-claimant <id> Filter list by claimant id
111
- --help, -h Show this help
112
-
113
- Examples:
114
- bizar claim F-035 --who mike --reason "porting MetaHarness from ruflo"
115
- bizar claim steal F-014 --by steve --reason stale
116
- bizar claim handoff F-035 --to karen --from mike
117
- bizar claim list --status claimed --agent-only
14
+ Claims are OpenKan .ok locks, not feature_list.json annotations. Use
15
+ \`bizar task update\` for status transitions and \`bizar goals\` for PRDs.
118
16
  `);
119
17
  }
120
18
 
121
- function out(result, flags, formatter) {
122
- if (flags.json) {
123
- process.stdout.write(JSON.stringify(result, null, 2) + '\n');
124
- } else if (formatter) {
125
- formatter(result);
126
- } else {
127
- console.log(JSON.stringify(result, null, 2));
128
- }
19
+ function owner(args) {
20
+ const i = args.findIndex((arg) => arg === '--who' || arg === '--owner');
21
+ const equals = args.find((arg) => arg.startsWith('--who=') || arg.startsWith('--owner='));
22
+ return equals ? equals.slice(equals.indexOf('=') + 1) : i >= 0 ? args[i + 1] : process.env.BIZAR_AGENT_ID || process.env.USER || 'agent';
23
+ }
24
+ function withoutWho(args) {
25
+ return args.filter((arg, index) => !['--who', '--owner'].includes(arg) && args[index - 1] !== '--who' && args[index - 1] !== '--owner' && !arg.startsWith('--who=') && !arg.startsWith('--owner='));
129
26
  }
130
27
 
131
28
  export async function run(name, args, isHelpRequest) {
132
29
  if (name !== 'claim') return false;
133
- const flags = parseFlags(args);
134
- if (flags.help || isHelpRequest) {
135
- showHelp();
136
- return true;
137
- }
138
-
139
- const [sub, ...rest] = flags._;
140
- const fallback = process.env.USER || 'unknown';
141
- const who = flags.who || fallback;
142
-
30
+ if (isHelpRequest || args.length === 0 || ['--help', '-h', 'help'].includes(args[0])) { help(); return true; }
31
+ const [subcommand, taskId] = args;
32
+ const agent = owner(args);
143
33
  try {
144
- const br = bridge(flags);
145
-
146
- if (!sub || sub === 'help') {
147
- showHelp();
148
- return true;
149
- }
150
-
151
- if (sub === 'list' || sub === 'ls') {
152
- const filters = {};
153
- if (flags.statuses.length) filters.status = flags.statuses;
154
- if (flags.byClaimant) filters.claimantId = flags.byClaimant;
155
- if (flags.humanOnly) filters.claimedByHuman = true;
156
- if (flags.agentOnly) filters.claimedByAgent = true;
157
- const rows = br.list(filters);
158
- out({ count: rows.length, features: rows }, flags, (r) => {
159
- console.log(chalk.bold(` Claims (${r.count}):`));
160
- for (const f of r.features) {
161
- const who = f.claimant ? `${f.claimant.name || f.claimant.id} (${f.claimant.type})` : '—';
162
- console.log(` ${chalk.cyan(f.id)} [${f.claimStatus || 'unclaimed'}] by ${who}`);
163
- }
164
- });
165
- return true;
166
- }
167
-
168
- if (sub === 'status' || sub === 'show' || sub === 'get') {
169
- const featureId = rest[0];
170
- if (!featureId) {
171
- console.error(chalk.red(' ✗ Usage: bizar claim status <featureId>'));
172
- return false;
173
- }
174
- const feature = br.get(featureId);
175
- out(feature, flags, (f) => {
176
- console.log(chalk.bold(` ${chalk.cyan(f.id)} — ${f.behavior || ''}`));
177
- const who = f.claimant ? `${f.claimant.name || f.claimant.id} (${f.claimant.type})` : 'unclaimed';
178
- console.log(` state = ${f.state}`);
179
- console.log(` claim = ${f.claimStatus || 'unclaimed'} (by ${who})`);
180
- console.log(` claimedAt = ${f.claimedAt || '—'}`);
181
- console.log(` claimReason = ${f.claimReason || '—'}`);
182
- const hist = Array.isArray(f.claimHistory) ? f.claimHistory : [];
183
- console.log(` history = ${hist.length} event(s)`);
184
- for (const e of hist.slice(-8)) {
185
- console.log(` ${e.ts} ${chalk.dim(e.event)}${e.reason ? ' — ' + e.reason : ''}`);
186
- }
187
- });
188
- return true;
189
- }
190
-
191
- if (sub === 'release') {
192
- const featureId = rest[0];
193
- if (!featureId) {
194
- console.error(chalk.red(' ✗ Usage: bizar claim release <featureId> [--who <id>] [--reason <text>]'));
195
- return false;
196
- }
197
- const claimant = makeClaimant({ who, type: flags.type }, fallback);
198
- const result = br.release(featureId, claimant, flags.reason || '');
199
- out(result, flags, (r) =>
200
- console.log(chalk.green(` ✓ Released ${featureId}`) + (r.previousClaimant ? chalk.dim(` (was held by ${r.previousClaimant.name || r.previousClaimant.id})`) : '')),
201
- );
202
- return true;
203
- }
204
-
205
- if (sub === 'handoff') {
206
- const featureId = rest[0];
207
- if (!featureId || !flags.to) {
208
- console.error(chalk.red(' ✗ Usage: bizar claim handoff <featureId> --to <id> [--from <id>] [--reason <text>]'));
209
- return false;
210
- }
211
- const fromClaimant = flags.from ? { type: flags.type || 'human', id: flags.from, name: flags.from } : null;
212
- const toClaimant = { type: flags.type || 'human', id: flags.to, name: flags.to };
213
- const result = br.handoff(featureId, fromClaimant, toClaimant, flags.reason || '');
214
- out(result, flags, (r) =>
215
- console.log(chalk.green(` ✓ Handed off ${featureId} → ${flags.to}`)),
216
- );
217
- return true;
218
- }
219
-
220
- if (sub === 'steal') {
221
- const featureId = rest[0];
222
- if (!featureId || !flags.by) {
223
- console.error(chalk.red(' ✗ Usage: bizar claim steal <featureId> --by <id> --reason <reason>'));
224
- return false;
225
- }
226
- if (!flags.reason) {
227
- console.error(chalk.red(' ✗ steal requires --reason (one of: ' + STEAL_REASONS.join(', ') + ')'));
228
- return false;
229
- }
230
- const newClaimant = { type: flags.type || 'human', id: flags.by, name: flags.by };
231
- const result = br.steal(featureId, newClaimant, flags.reason);
232
- out(result, flags, (r) =>
233
- console.log(chalk.green(` ✓ Stole ${featureId} (reason: ${r.stealReason}); previous owner: ${r.previousClaimant?.name || r.previousClaimant?.id || '—'}`)),
234
- );
235
- return true;
236
- }
237
-
238
- if (sub === 'transition') {
239
- const [featureId, newStatus] = rest;
240
- if (!featureId || !newStatus) {
241
- console.error(chalk.red(' ✗ Usage: bizar claim transition <featureId> <active|paused|blocked|completed> [--who <id>]'));
242
- return false;
243
- }
244
- if (!CLAIM_STATUSES.includes(newStatus)) {
245
- console.error(chalk.red(` ✗ Unknown status '${newStatus}'. Must be one of: ${CLAIM_STATUSES.join(', ')}`));
246
- return false;
247
- }
248
- const claimant = makeClaimant({ who: flags.who, type: flags.type }, fallback);
249
- const result = br.transition(featureId, newStatus, claimant, flags.reason || '');
250
- out(result, flags, (r) =>
251
- console.log(chalk.green(` ✓ ${featureId} → ${newStatus}` + (r.unchanged ? ' (unchanged)' : ''))),
252
- );
253
- return true;
254
- }
255
-
256
- // Default: `bizar claim <featureId>` — claim it.
257
- if (!sub.startsWith('-') && sub) {
258
- const featureId = sub;
259
- const claimant = makeClaimant({ who: flags.who, type: flags.type }, fallback);
260
- if (!claimant) {
261
- console.error(chalk.red(' ✗ No claimant id (set $USER or pass --who <id>)'));
262
- return false;
263
- }
264
- const result = br.claim(featureId, claimant, flags.reason || '');
265
- out(result, flags, (r) =>
266
- console.log(chalk.green(` ✓ Claimed ${featureId} by ${r.feature.claimant.name || r.feature.claimant.id} (${r.feature.claimStatus})`)),
267
- );
268
- return true;
269
- }
270
-
271
- console.error(chalk.red(` ✗ Unknown subcommand: ${sub}`));
272
- showHelp();
273
- return false;
274
- } catch (err) {
275
- if (err instanceof ClaimError) {
276
- console.error(chalk.red(` ✗ ${err.code}: ${err.message}`));
34
+ let command;
35
+ if (subcommand === 'list' || subcommand === 'ls') command = ['task', 'list', ...args.slice(1)];
36
+ else if (subcommand === 'release' || subcommand === 'heartbeat') {
37
+ if (!taskId) { help(); process.exitCode = 2; return true; }
38
+ command = ['task', subcommand, taskId, '--owner', agent, ...withoutWho(args.slice(2))];
277
39
  } else {
278
- console.error(chalk.red(` ✗ ${err && err.message ? err.message : String(err)}`));
279
- }
280
- return false;
40
+ command = ['task', 'claim', subcommand, '--owner', agent, ...withoutWho(args.slice(1))];
41
+ }
42
+ const result = runOpenKanOk(command);
43
+ if (result.stdout) process.stdout.write(result.stdout);
44
+ if (result.stderr) process.stderr.write(result.stderr);
45
+ if (!result.ok) process.exitCode = result.status || 1;
46
+ } catch (error) {
47
+ const prefix = error instanceof OpenKanError ? error.code : 'OPENKAN_ERROR';
48
+ process.stderr.write(` ✗ ${prefix}: ${error.message || String(error)}\n`);
49
+ process.exitCode = 1;
281
50
  }
51
+ return true;
282
52
  }
@@ -43,7 +43,7 @@ function help() {
43
43
 
44
44
  Usage:
45
45
  bizar control snapshot --json
46
- bizar control agents|tasks|sessions|messages --json
46
+ bizar control agents|tasks|plans|goals|sessions|messages --json
47
47
  bizar control message --agent <agent>|--session <id> --text <text> [--from <sender>]
48
48
  bizar control session start --agent <agent> --prompt <text> [--name <name>]
49
49
  bizar control session send <id> --text <text> [--from <sender>]
@@ -64,6 +64,8 @@ export async function run(name, args, isHelpRequest) {
64
64
  if (resource === 'snapshot') print(getControlSnapshot(root), flags);
65
65
  else if (resource === 'agents') print({ agents: listControlAgents(root) }, flags);
66
66
  else if (resource === 'tasks') print(listControlTasks(root), flags);
67
+ else if (resource === 'plans') print({ plans: listControlTasks(root).plans }, flags);
68
+ else if (resource === 'goals') print({ goals: listControlTasks(root).goals }, flags);
67
69
  else if (resource === 'sessions') print({ sessions: listControlSessions(root) }, flags);
68
70
  else if (resource === 'messages') print(listControlMessages(root, { status: flags.status }), flags);
69
71
  else if (resource === 'message') {