@polderlabs/bizar 10.30.0 → 10.32.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.
- package/AGENTS.md +36 -52
- package/README.md +21 -15
- package/cli/banner.mjs +1 -1
- package/cli/commands/ao.mjs +35 -6
- package/cli/commands/improve-proposal.mjs +12 -4
- package/cli/commands/improve.mjs +44 -13
- package/cli/commands/install.mjs +10 -0
- package/cli/commands/openkan.mjs +1 -1
- package/cli/commands/release-provenance.mjs +35 -20
- package/cli/commands/worker.mjs +3 -0
- package/cli/core/backup-store.mjs +51 -19
- package/cli/core/runtime-manifest.mjs +31 -0
- package/cli/doctor.mjs +20 -0
- package/cli/install/banner.mjs +1 -1
- package/cli/install/paths.mjs +1 -1
- package/cli/openkan.mjs +8 -11
- package/cli/provision.mjs +39 -22
- package/cli/update.mjs +44 -0
- package/config/ao/worker-rules.md +15 -0
- package/config/claude/CLAUDE.md +32 -52
- package/config/claude/commands/bizar.md +3 -3
- package/config/claude/compatibility.mjs +22 -0
- package/config/claude/hooks/permission-request.mjs +14 -182
- package/config/claude/hooks/persistent-mode.mjs +1 -1
- package/config/claude/settings.json +3 -5
- package/config/compatibility-manifest.json +13 -0
- package/config/policy/AGENT_BEHAVIOR.md +18 -0
- package/config/skills/bizar/SKILL.md +9 -3
- package/package.json +5 -3
- package/packages/sdk/dist/federation/index.d.ts +5 -0
- package/packages/sdk/dist/federation/index.js +31 -4
- package/packages/sdk/dist/federation/pii.js +14 -9
- package/packages/sdk/dist/federation/policy.d.ts +1 -1
- package/packages/sdk/dist/federation/policy.js +3 -0
- package/packages/sdk/dist/index.d.ts +2 -0
- package/packages/sdk/dist/index.js +4 -0
- package/packages/sdk/dist/mcp/bin.js +2 -25
- package/packages/sdk/dist/mcp/server.d.ts +4 -0
- package/packages/sdk/dist/mcp/server.js +53 -10
- package/packages/sdk/dist/mcp/stdio.d.ts +6 -0
- package/packages/sdk/dist/mcp/stdio.js +66 -0
- package/packages/sdk/dist/policy/action-policy.d.ts +13 -0
- package/packages/sdk/dist/policy/action-policy.js +8 -0
- package/packages/sdk/dist/policy/authority.d.ts +2 -0
- package/packages/sdk/dist/policy/authority.js +2 -0
- package/packages/sdk/dist/policy/capability-registry.d.ts +46 -0
- package/packages/sdk/dist/policy/capability-registry.js +24 -0
- package/packages/sdk/dist/policy/completion-policy.d.ts +25 -0
- package/packages/sdk/dist/policy/completion-policy.js +10 -0
- package/packages/sdk/dist/policy/evidence-policy.d.ts +2 -0
- package/packages/sdk/dist/policy/evidence-policy.js +2 -0
- package/packages/sdk/dist/policy/execution-context.d.ts +28 -0
- package/packages/sdk/dist/policy/execution-context.js +43 -0
- package/packages/sdk/dist/policy/index.d.ts +9 -0
- package/packages/sdk/dist/policy/index.js +9 -0
- package/packages/sdk/dist/policy/retry-policy.d.ts +10 -0
- package/packages/sdk/dist/policy/retry-policy.js +12 -0
- package/packages/sdk/dist/policy/tool-behavior.d.ts +37 -0
- package/packages/sdk/dist/policy/tool-behavior.js +19 -0
- package/packages/sdk/dist/release/known-good-releases.d.ts +2 -1
- package/packages/sdk/dist/release/known-good-releases.js +4 -16
- package/packages/sdk/dist/release/signature.js +13 -2
- package/packages/sdk/dist/version.d.ts +1 -1
- package/packages/sdk/dist/version.js +1 -1
- package/packages/sdk/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -32,7 +32,9 @@ make session-end # lifecycle compatibility target
|
|
|
32
32
|
- **MUST** verify evidence before claiming completion.
|
|
33
33
|
- **MUST NOT** commit `console.log`, `debugger`, `.only()`, credentials, generated secrets, or runtime logs.
|
|
34
34
|
- **MUST NOT** use a persistent Claude daemon. Claude Code and the Agent SDK run in-process; background work uses Claude Code's Agent tool.
|
|
35
|
-
- **MUST NOT** rebase or force-push
|
|
35
|
+
- **MUST NOT** rebase or force-push unless an explicit operator restriction or
|
|
36
|
+
host/runtime contract forbids it; Bizar itself does not gate high-impact
|
|
37
|
+
actions by impact.
|
|
36
38
|
|
|
37
39
|
### Worktree discipline
|
|
38
40
|
|
|
@@ -58,16 +60,16 @@ edit, test, and iterate without pausing for routine decisions. Routine
|
|
|
58
60
|
decisions (file layout, naming, scope of a single commit, choosing between
|
|
59
61
|
two equivalent stdlib calls, picking a verification command from the Makefile,
|
|
60
62
|
or completing an OpenKan task after `make check` is green) do NOT require
|
|
61
|
-
human approval and MUST NOT trigger a permission handoff. PreToolUse hooks
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
63
|
+
human approval and MUST NOT trigger a permission handoff. PreToolUse hooks may
|
|
64
|
+
classify high-impact actions and add evidence guidance, but they must not deny
|
|
65
|
+
or ask solely because an action is externally visible, irreversible, or
|
|
66
|
+
powerful. Invalid target/schema/state is a typed contract error, not a
|
|
67
|
+
permission handoff.
|
|
68
|
+
|
|
69
|
+
Mike performs bounded read-only orientation, then chooses the smallest capable
|
|
70
|
+
execution surface for the requested outcome. Direct work is the default when
|
|
71
|
+
one agent can safely complete and verify the task; native Agent teams are used
|
|
72
|
+
only when independent lanes materially improve throughput or correctness. Ask one
|
|
71
73
|
concise clarification question only when a material choice, acceptance
|
|
72
74
|
criterion, safety boundary, or unresolved constraint would change the work;
|
|
73
75
|
otherwise continue autonomously. The team is host-side state under
|
|
@@ -86,46 +88,25 @@ default. If no enabled configured candidate exists, dispatch fails with an
|
|
|
86
88
|
actionable configuration error. Bizar never retries a failed dispatch by cycling
|
|
87
89
|
aliases, providers, or tiers.
|
|
88
90
|
|
|
89
|
-
The authoritative
|
|
91
|
+
The authoritative default is maximum autonomy; pushes, pull-request
|
|
90
92
|
mutations, releases, package publication, deployments, production/shared-
|
|
91
93
|
infrastructure writes, credential changes, public exposure, irreversible
|
|
92
|
-
destruction
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
> `permissions.allow` so subagents do not prompt for commits, pushes, PRs,
|
|
103
|
-
> or deploys. Operators who want HITL back can move the following exact
|
|
104
|
-
> patterns from `permissions.allow` into `permissions.ask` in their local
|
|
105
|
-
> `~/.claude/settings.json` override:
|
|
106
|
-
>
|
|
107
|
-
> - `Bash(git push *)`
|
|
108
|
-
> - `Bash(git -C * push *)`
|
|
109
|
-
> - `Bash(git --git-dir=* push *)`
|
|
110
|
-
> - `Bash(git push --force *)`
|
|
111
|
-
> - `Bash(git push -f *)`
|
|
112
|
-
> - `Bash(git rebase *)`
|
|
113
|
-
> - `Bash(gh pr create *)`
|
|
114
|
-
> - `Bash(gh pr merge *)`
|
|
115
|
-
> - `Bash(gh release create *)`
|
|
116
|
-
> - `Bash(npm publish *)`
|
|
117
|
-
> - `Bash(bun publish *)`
|
|
118
|
-
> - `Bash(pnpm publish *)`
|
|
119
|
-
> - `Bash(vercel deploy *)`
|
|
120
|
-
> - `Bash(wrangler deploy *)`
|
|
121
|
-
> - `Bash(flyctl deploy *)`
|
|
94
|
+
destruction proceed when explicitly requested and contract-valid. Stronger
|
|
95
|
+
evidence and rollback handling are required; a permission prompt is not.
|
|
96
|
+
|
|
97
|
+
The default maximum-autonomy profile is implemented by the host runtime;
|
|
98
|
+
`permission-request.mjs` is a fail-open compatibility hook. The workflow and
|
|
99
|
+
PreToolUse hooks may emit advisory evidence guidance but never create a Bizar
|
|
100
|
+
approval gate.
|
|
101
|
+
Historical F-176 approval-floor wording is retained only in superseded audit
|
|
102
|
+
records. Operators may configure their host independently, but Bizar does not
|
|
103
|
+
reintroduce an impact-based or role-based gate.
|
|
122
104
|
|
|
123
105
|
> Note: `disableAutoCompact: false` is shipped by default so Claude Code can
|
|
124
106
|
> compact automatically before the context limit. Hook `precompact-priorities.sh`
|
|
125
107
|
> snapshots bounded state and preserves evidence and decisions on compaction.
|
|
126
|
-
Local
|
|
127
|
-
|
|
128
|
-
hook chain.
|
|
108
|
+
Local and external mutations follow the same action-contract path; hooks add
|
|
109
|
+
evidence guidance only.
|
|
129
110
|
|
|
130
111
|
Agents fetch current official documentation via WebSearch + WebFetch before
|
|
131
112
|
acting on an external API, library, framework, CLI, configuration format, or
|
|
@@ -140,14 +121,11 @@ The autonomy and approval policy above governs this execution model. The project
|
|
|
140
121
|
Every non-empty primary request enters Bizar through `office-manager` (`@mike`).
|
|
141
122
|
The installer sets Claude Code's global `agent` setting to Mike's frontmatter
|
|
142
123
|
name (`mike`),
|
|
143
|
-
and the routing hook supplies the
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
default Agent team and continues autonomously. It asks one concise
|
|
124
|
+
and the routing hook supplies the coordination policy. Mike gathers bounded
|
|
125
|
+
read-only context, selects direct execution or a narrowly scoped Agent team
|
|
126
|
+
based on coordination value, and continues autonomously. It asks one concise
|
|
147
127
|
clarification only when a material choice or unresolved constraint would
|
|
148
|
-
change the work.
|
|
149
|
-
single Agents and native workflows are explicit or resumed modes. Mike owns
|
|
150
|
-
integration and final verification.
|
|
128
|
+
change the work. Mike owns integration and final verification.
|
|
151
129
|
A Bizar custom agent already executing its assigned role does not recursively
|
|
152
130
|
dispatch itself.
|
|
153
131
|
|
|
@@ -202,3 +180,9 @@ not a general note vault, semantic search service, or knowledge-base API.
|
|
|
202
180
|
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.
|
|
203
181
|
4. The OpenKan task records fresh verification evidence and no required work remains.
|
|
204
182
|
5. `/simplify` reviews the staged diff before the approval-gated commit.
|
|
183
|
+
|
|
184
|
+
<!-- openwolf:begin -->
|
|
185
|
+
# OpenWolf
|
|
186
|
+
|
|
187
|
+
This project uses OpenWolf for context management. Read and follow .wolf/OPENWOLF.md at session start. Check .wolf/cerebrum.md before generating code. Grep .wolf/anatomy.md for a file's path before reading it (never read the whole index).
|
|
188
|
+
<!-- openwolf:end -->
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<div align="center">
|
|
2
2
|
|
|
3
|
-
<img src="docs/assets/bizar-banner.svg" alt="Bizar:
|
|
3
|
+
<img src="docs/assets/bizar-banner.svg" alt="Bizar: maximum autonomy for Agent Orchestrator" width="100%" />
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@polderlabs/bizar)
|
|
6
6
|
[](LICENSE)
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|

|
|
10
10
|
[](https://www.npmjs.com/package/@polderlabs/openkan)
|
|
11
11
|
|
|
12
|
-
###
|
|
12
|
+
### Maximum autonomy for Agent Orchestrator workers
|
|
13
13
|
|
|
14
14
|
Run focused Codex workers under Agent Orchestrator. Bizar supplies their
|
|
15
15
|
repository policy, guardrails, skills, and verification evidence.
|
|
@@ -27,9 +27,10 @@ each worker's implementation and verification discipline explicit. AO owns
|
|
|
27
27
|
multi-agent coordination, worktrees, branches, PR/review/CI feedback, previews,
|
|
28
28
|
and browser state; Bizar does not duplicate those surfaces.
|
|
29
29
|
|
|
30
|
-
It
|
|
31
|
-
AO model choices live in the registered
|
|
32
|
-
Claude aliases live in
|
|
30
|
+
It records evidence and rollback context for high-impact actions while keeping
|
|
31
|
+
execution autonomous by default. AO model choices live in the registered
|
|
32
|
+
project's AO configuration. Standalone Claude aliases live in global Claude
|
|
33
|
+
configuration.
|
|
33
34
|
|
|
34
35
|
| You want | Bizar provides |
|
|
35
36
|
| --- | --- |
|
|
@@ -67,11 +68,16 @@ Install Bizar, then configure the repository with a running AO daemon.
|
|
|
67
68
|
|
|
68
69
|
```sh
|
|
69
70
|
npm install -g @polderlabs/bizar
|
|
70
|
-
bizar ao
|
|
71
|
+
bizar ao check
|
|
72
|
+
bizar ao install
|
|
73
|
+
bizar ao update
|
|
71
74
|
bizar ao setup
|
|
72
75
|
```
|
|
73
76
|
|
|
74
|
-
`bizar ao
|
|
77
|
+
`bizar ao check` runs AO health checks. `bizar ao install` and `bizar ao update`
|
|
78
|
+
open AO through its supported lifecycle command; the AO desktop app owns
|
|
79
|
+
installation and updates. `bizar ao setup` remains the separate, explicit
|
|
80
|
+
repository configuration step: it preserves AO's existing project configuration while selecting
|
|
75
81
|
Codex for both AO roles and materializing Bizar's managed repository-local
|
|
76
82
|
worker rules at `.ao/bizar-worker-rules.md`.
|
|
77
83
|
AO remains responsible for spawning workers, messaging, PR claims, review/CI
|
|
@@ -176,10 +182,10 @@ risk. It does not create parallel workers merely to look busy.
|
|
|
176
182
|
| --- | --- |
|
|
177
183
|
| Research, planning, implementation, review, verification | Architecture, accessibility, security, tests, documentation, performance, build repair, operations, and domain analysis |
|
|
178
184
|
|
|
179
|
-
##
|
|
185
|
+
## Evidence that stays in the loop
|
|
180
186
|
|
|
181
|
-
Bizar
|
|
182
|
-
|
|
187
|
+
Bizar executes valid operations autonomously and records stronger evidence and
|
|
188
|
+
rollback context when the effect is consequential.
|
|
183
189
|
|
|
184
190
|
| Category | Default behavior |
|
|
185
191
|
| --- | --- |
|
|
@@ -187,12 +193,12 @@ for consequential actions.
|
|
|
187
193
|
| Parallel code changes | Uses isolated worktrees and scoped task ownership |
|
|
188
194
|
| Ambiguous material design choice | Asks one concise clarification before execution |
|
|
189
195
|
| Commit | Locally allowed, with a fresh simplify review reminder |
|
|
190
|
-
| Push, PR mutation, release, publish, deploy |
|
|
191
|
-
| Rebase, force-push, broad destructive commands |
|
|
196
|
+
| Push, PR mutation, release, publish, deploy | Executes when requested and contract-valid; records evidence |
|
|
197
|
+
| Rebase, force-push, broad destructive commands | Executes when requested and contract-valid; advisory context only |
|
|
192
198
|
|
|
193
|
-
The goal is
|
|
194
|
-
|
|
195
|
-
|
|
199
|
+
The goal is clear request fidelity: invalid targets, schemas, signatures, and
|
|
200
|
+
lifecycle states fail deterministically, while explicit operator restrictions
|
|
201
|
+
remain binding.
|
|
196
202
|
|
|
197
203
|
## What gets installed
|
|
198
204
|
|
package/cli/banner.mjs
CHANGED
|
@@ -14,7 +14,7 @@ export function showBanner() {
|
|
|
14
14
|
console.log(chalk.hex('#6366f1').bold(RUNE_HELM));
|
|
15
15
|
console.log(chalk.hex('#a855f7')(' Guarded autonomous agent system for Claude Code'));
|
|
16
16
|
console.log();
|
|
17
|
-
console.log(chalk.dim(' 84 agents ·
|
|
17
|
+
console.log(chalk.dim(' 84 agents · maximum autonomy · evidence verification · MCP · Skills CLI'));
|
|
18
18
|
console.log();
|
|
19
19
|
}
|
|
20
20
|
|
package/cli/commands/ao.mjs
CHANGED
|
@@ -10,7 +10,7 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
|
10
10
|
import { fileURLToPath } from 'node:url';
|
|
11
11
|
import { basename, dirname, resolve } from 'node:path';
|
|
12
12
|
|
|
13
|
-
export const AO_RULES_FILE = '.ao/bizar-
|
|
13
|
+
export const AO_RULES_FILE = '.ao/bizar-managed-rules.md';
|
|
14
14
|
export const AO_ORCHESTRATOR_RULES = 'Use AO for coordination and Bizar as the Codex worker harness. Spawn focused AO workers for implementation; workers own changes, verification, commits, and PR follow-up.';
|
|
15
15
|
const WORKER_RULES_TEMPLATE = resolve(dirname(fileURLToPath(import.meta.url)), '../../config/ao/worker-rules.md');
|
|
16
16
|
|
|
@@ -21,13 +21,15 @@ function optionValue(args, flag) {
|
|
|
21
21
|
|
|
22
22
|
export function parseAoArgs(args = []) {
|
|
23
23
|
const [first, ...rest] = args;
|
|
24
|
-
const subcommand = ['setup', 'doctor', 'status', 'sessions', 'help'].includes(first) ? first : 'forward';
|
|
24
|
+
const subcommand = ['check', 'install', 'update', 'setup', 'doctor', 'status', 'sessions', 'help'].includes(first) ? first : 'forward';
|
|
25
25
|
return {
|
|
26
26
|
subcommand,
|
|
27
27
|
forward: subcommand === 'forward' ? args : rest,
|
|
28
28
|
project: optionValue(args, '--project'),
|
|
29
29
|
model: optionValue(args, '--model'),
|
|
30
30
|
permissions: optionValue(args, '--permissions'),
|
|
31
|
+
workerAgent: optionValue(args, '--worker-agent'),
|
|
32
|
+
orchestratorAgent: optionValue(args, '--orchestrator-agent'),
|
|
31
33
|
help: args.includes('--help') || args.includes('-h') || first === 'help',
|
|
32
34
|
};
|
|
33
35
|
}
|
|
@@ -42,8 +44,12 @@ export function defaultProjectId(cwd) {
|
|
|
42
44
|
}
|
|
43
45
|
|
|
44
46
|
export function configuredProjectConfig(current = {}, options = {}) {
|
|
45
|
-
const worker = { ...(current.worker || {})
|
|
46
|
-
const orchestrator = { ...(current.orchestrator || {})
|
|
47
|
+
const worker = { ...(current.worker || {}) };
|
|
48
|
+
const orchestrator = { ...(current.orchestrator || {}) };
|
|
49
|
+
if (!worker.agent) worker.agent = 'codex';
|
|
50
|
+
if (!orchestrator.agent) orchestrator.agent = 'codex';
|
|
51
|
+
if (options.workerAgent) worker.agent = options.workerAgent;
|
|
52
|
+
if (options.orchestratorAgent) orchestrator.agent = options.orchestratorAgent;
|
|
47
53
|
const agentConfig = { ...(current.agentConfig || {}) };
|
|
48
54
|
if (options.model) agentConfig.model = options.model;
|
|
49
55
|
if (options.permissions) agentConfig.permissions = options.permissions;
|
|
@@ -116,6 +122,9 @@ function help(output = process.stdout) {
|
|
|
116
122
|
bizar ao — Agent Orchestrator bridge (AO-primary)
|
|
117
123
|
|
|
118
124
|
Usage:
|
|
125
|
+
bizar ao check
|
|
126
|
+
bizar ao install
|
|
127
|
+
bizar ao update
|
|
119
128
|
bizar ao doctor
|
|
120
129
|
bizar ao setup [--project <id>] [--model <id>] [--permissions <mode>]
|
|
121
130
|
bizar ao status
|
|
@@ -123,7 +132,9 @@ Usage:
|
|
|
123
132
|
bizar ao <any supported ao command> [args...]
|
|
124
133
|
|
|
125
134
|
AO remains the sole owner of sessions, worktrees, PRs, review feedback,
|
|
126
|
-
previews, and browser state.
|
|
135
|
+
previews, and browser state. Check runs AO's health checks. Install and update
|
|
136
|
+
open AO through its supported lifecycle command; the AO desktop app owns updates.
|
|
137
|
+
The setup command registers this repository with AO,
|
|
127
138
|
selects Codex for both AO roles, and preserves existing AO project settings.
|
|
128
139
|
It creates and configures the repository-local ${AO_RULES_FILE} as AO worker
|
|
129
140
|
rules, preserving a file that is already present.
|
|
@@ -160,7 +171,9 @@ export function setupAo(options = {}) {
|
|
|
160
171
|
projectId ||= defaultProjectId(cwd);
|
|
161
172
|
|
|
162
173
|
if (!projectDetails) {
|
|
163
|
-
const
|
|
174
|
+
const addArgs = ['project', 'add', '--path', cwd, '--id', projectId];
|
|
175
|
+
addArgs.push('--worker-agent', options.workerAgent || 'codex', '--orchestrator-agent', options.orchestratorAgent || 'codex');
|
|
176
|
+
const added = runAo(addArgs, options);
|
|
164
177
|
if (!added.ok) return added;
|
|
165
178
|
const details = runAo(['project', 'get', projectId, '--json'], options);
|
|
166
179
|
if (!details.ok) return details;
|
|
@@ -188,6 +201,16 @@ export function doctorAo(options = {}) {
|
|
|
188
201
|
};
|
|
189
202
|
}
|
|
190
203
|
|
|
204
|
+
export function checkAo(options = {}) {
|
|
205
|
+
return runAo(['doctor', '--json'], options);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export function installAo(options = {}) {
|
|
209
|
+
return runAo(['start', '--json'], options);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export const updateAo = installAo;
|
|
213
|
+
|
|
191
214
|
export function run(args = [], options = {}) {
|
|
192
215
|
const parsed = parseAoArgs(args);
|
|
193
216
|
const output = options.output || process;
|
|
@@ -196,9 +219,15 @@ export function run(args = [], options = {}) {
|
|
|
196
219
|
return true;
|
|
197
220
|
}
|
|
198
221
|
try {
|
|
222
|
+
if (parsed.subcommand === 'setup' && isAoSession(options.env || process.env) && !options.allowNested) {
|
|
223
|
+
throw new Error('AO_OWNED_OPERATION: AO setup cannot create competing lifecycle state from an AO worker');
|
|
224
|
+
}
|
|
199
225
|
let result;
|
|
200
226
|
if (parsed.subcommand === 'setup') result = setupAo({ ...options, ...parsed });
|
|
227
|
+
else if (parsed.subcommand === 'check') result = checkAo(options);
|
|
201
228
|
else if (parsed.subcommand === 'doctor') result = doctorAo(options);
|
|
229
|
+
else if (parsed.subcommand === 'install') result = installAo(options);
|
|
230
|
+
else if (parsed.subcommand === 'update') result = updateAo(options);
|
|
202
231
|
else if (parsed.subcommand === 'status') result = runAo(['status', '--json'], options);
|
|
203
232
|
else if (parsed.subcommand === 'sessions') result = runAo(['session', 'ls', ...parsed.forward], options);
|
|
204
233
|
else result = runAo(parsed.forward, options);
|
|
@@ -37,7 +37,7 @@ import { createHash } from 'node:crypto';
|
|
|
37
37
|
* originalSha256: string,
|
|
38
38
|
* find: string,
|
|
39
39
|
* newText: string,
|
|
40
|
-
* verification: { command
|
|
40
|
+
* verification: { command?: string, argv?: string[], cwd?: string, timeoutMs?: number, expectedExitCode?: number },
|
|
41
41
|
* rollbackPlan: { kind: 'replace-back', note: string } | { kind: 'manual', note: string, manualCommand?: string },
|
|
42
42
|
* reason: string,
|
|
43
43
|
* createdAt: string,
|
|
@@ -98,8 +98,16 @@ export function validateProposal(raw) {
|
|
|
98
98
|
if (raw.find === raw.newText) {
|
|
99
99
|
throw new TypeError('Proposal.newText must differ from Proposal.find');
|
|
100
100
|
}
|
|
101
|
-
if (typeof raw.verification !== 'object'
|
|
102
|
-
|
|
101
|
+
if (typeof raw.verification !== 'object'
|
|
102
|
+
|| (!Array.isArray(raw.verification.argv) && (typeof raw.verification.command !== 'string' || raw.verification.command.length === 0))) {
|
|
103
|
+
throw new TypeError('Proposal.verification requires a non-empty argv array or command string');
|
|
104
|
+
}
|
|
105
|
+
if (Array.isArray(raw.verification.argv)
|
|
106
|
+
&& (raw.verification.argv.length === 0 || raw.verification.argv.some((arg) => typeof arg !== 'string' || arg.length === 0))) {
|
|
107
|
+
throw new TypeError('Proposal.verification.argv must contain non-empty strings');
|
|
108
|
+
}
|
|
109
|
+
if (typeof raw.verification.command === 'string' && /(?:^|[^\\])(?:;|&&|\|\||`|\$\()/.test(raw.verification.command) && !Array.isArray(raw.verification.argv)) {
|
|
110
|
+
throw new TypeError('Proposal.verification.command contains shell syntax; use verification.argv');
|
|
103
111
|
}
|
|
104
112
|
if (typeof raw.rollbackPlan !== 'object' || (raw.rollbackPlan.kind !== 'replace-back' && raw.rollbackPlan.kind !== 'manual')) {
|
|
105
113
|
throw new TypeError('Proposal.rollbackPlan.kind must be "replace-back" or "manual"');
|
|
@@ -152,4 +160,4 @@ export function planRollback(proposal, currentBytes) {
|
|
|
152
160
|
return { kind: 'replace-back', newBytes: currentBytes.replace(proposal.newText, proposal.find) };
|
|
153
161
|
}
|
|
154
162
|
return { kind: 'manual', manualCommand: proposal.rollbackPlan.manualCommand };
|
|
155
|
-
}
|
|
163
|
+
}
|
package/cli/commands/improve.mjs
CHANGED
|
@@ -56,7 +56,7 @@ import {
|
|
|
56
56
|
statSync,
|
|
57
57
|
writeFileSync,
|
|
58
58
|
} from 'node:fs';
|
|
59
|
-
import { isAbsolute, join, resolve } from 'node:path';
|
|
59
|
+
import { dirname, isAbsolute, join, resolve } from 'node:path';
|
|
60
60
|
import { spawnSync } from 'node:child_process';
|
|
61
61
|
import { ensureSecureDir } from './secure-dir.mjs';
|
|
62
62
|
import {
|
|
@@ -145,19 +145,57 @@ function readCurrentFile({ file, cwd }) {
|
|
|
145
145
|
return { abs, bytes: readFileSync(abs, 'utf8') };
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
+
/** Parse a deliberately small argv form without invoking a shell. */
|
|
149
|
+
export function parseSimpleArgv(command) {
|
|
150
|
+
if (typeof command !== 'string' || !command.trim()) throw new TypeError('verification command must be non-empty');
|
|
151
|
+
if (/(?:;|&&|\|\||`|\$\(|>|<|\n|\r)/.test(command)) {
|
|
152
|
+
throw new TypeError('verification command contains shell syntax; use argv');
|
|
153
|
+
}
|
|
154
|
+
const argv = [];
|
|
155
|
+
let token = '';
|
|
156
|
+
let quote = null;
|
|
157
|
+
let escaped = false;
|
|
158
|
+
for (const char of command.trim()) {
|
|
159
|
+
if (escaped) { token += char; escaped = false; continue; }
|
|
160
|
+
if (char === '\\' && quote !== "'") { escaped = true; continue; }
|
|
161
|
+
if (quote) {
|
|
162
|
+
if (char === quote) quote = null;
|
|
163
|
+
else token += char;
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
if (char === '"' || char === "'") { quote = char; continue; }
|
|
167
|
+
if (/\s/.test(char)) {
|
|
168
|
+
if (token) { argv.push(token); token = ''; }
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
token += char;
|
|
172
|
+
}
|
|
173
|
+
if (escaped || quote || !token && argv.length === 0) throw new TypeError('verification command has unterminated quoting');
|
|
174
|
+
if (token) argv.push(token);
|
|
175
|
+
return argv;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function runVerification(verification, cwd) {
|
|
179
|
+
const verifyCwd = verification.cwd || cwd;
|
|
180
|
+
const timeout = Number(verification.timeoutMs) || 60_000;
|
|
181
|
+
const argv = Array.isArray(verification.argv) ? verification.argv : parseSimpleArgv(verification.command);
|
|
182
|
+
return { result: spawnSync(argv[0], argv.slice(1), { cwd: verifyCwd, encoding: 'utf8', timeout, shell: false }), cwd: verifyCwd };
|
|
183
|
+
}
|
|
184
|
+
|
|
148
185
|
function doPropose({ flags, cwd }) {
|
|
149
186
|
if (!flags.file || !flags.find || !flags.new || !flags.verify) {
|
|
150
187
|
process.stderr.write('usage: bizar improve propose --file <path> --find <substr> --new <text> --verify <cmd> [--reason <why>]\n');
|
|
151
188
|
process.exit(2);
|
|
152
189
|
}
|
|
153
190
|
const { abs, bytes } = readCurrentFile({ file: flags.file, cwd });
|
|
191
|
+
const argv = parseSimpleArgv(flags.verify);
|
|
154
192
|
const proposal = {
|
|
155
193
|
id: newProposalId({ targetFile: abs, cwd }),
|
|
156
194
|
targetFile: abs,
|
|
157
195
|
originalSha256: sha256Text(bytes),
|
|
158
196
|
find: flags.find,
|
|
159
197
|
newText: flags.new,
|
|
160
|
-
verification: { command: flags.verify, cwd, expectedExitCode: 0 },
|
|
198
|
+
verification: { command: flags.verify, argv, cwd, expectedExitCode: 0 },
|
|
161
199
|
rollbackPlan: { kind: 'replace-back', note: `Reverse the find/newText swap on ${abs}` },
|
|
162
200
|
reason: String(flags.reason ?? '(no reason supplied)'),
|
|
163
201
|
createdAt: new Date().toISOString(),
|
|
@@ -218,12 +256,7 @@ function doRun({ flags, cwd }) {
|
|
|
218
256
|
writeFileSync(abs, plan.newBytes, { mode: statSync(abs).mode & 0o777 });
|
|
219
257
|
|
|
220
258
|
// Verify.
|
|
221
|
-
const verifyCwd = proposal.verification
|
|
222
|
-
const verify = spawnSync('sh', ['-c', proposal.verification.command], {
|
|
223
|
-
cwd: verifyCwd,
|
|
224
|
-
encoding: 'utf8',
|
|
225
|
-
timeout: Number(proposal.verification.timeoutMs) || 60_000,
|
|
226
|
-
});
|
|
259
|
+
const { result: verify, cwd: verifyCwd } = runVerification(proposal.verification, cwd);
|
|
227
260
|
const verifyExit = verify.status ?? -1;
|
|
228
261
|
const verifyStdoutSha = sha256Text(verify.stdout ?? '');
|
|
229
262
|
const verifyStderrSha = sha256Text(verify.stderr ?? '');
|
|
@@ -245,6 +278,7 @@ function doRun({ flags, cwd }) {
|
|
|
245
278
|
rolledBackSha256: rollback.kind === 'replace-back' ? sha256Text(rollback.newBytes) : null,
|
|
246
279
|
verification: {
|
|
247
280
|
command: proposal.verification.command,
|
|
281
|
+
argv: proposal.verification.argv,
|
|
248
282
|
cwd: verifyCwd,
|
|
249
283
|
exitCode: verifyExit,
|
|
250
284
|
stdoutSha256: verifyStdoutSha,
|
|
@@ -268,6 +302,7 @@ function doRun({ flags, cwd }) {
|
|
|
268
302
|
afterSha256: summary.afterSha256,
|
|
269
303
|
verification: {
|
|
270
304
|
command: proposal.verification.command,
|
|
305
|
+
argv: proposal.verification.argv,
|
|
271
306
|
cwd: verifyCwd,
|
|
272
307
|
exitCode: verifyExit,
|
|
273
308
|
stdoutSha256: verifyStdoutSha,
|
|
@@ -286,11 +321,7 @@ function doVerify({ flags, cwd }) {
|
|
|
286
321
|
}
|
|
287
322
|
const proposalPath = isAbsolute(flags.proposal) ? flags.proposal : resolve(cwd, flags.proposal);
|
|
288
323
|
const proposal = validateProposal(JSON.parse(readFileSync(proposalPath, 'utf8')));
|
|
289
|
-
const verify =
|
|
290
|
-
cwd: proposal.verification.cwd || cwd,
|
|
291
|
-
encoding: 'utf8',
|
|
292
|
-
timeout: Number(proposal.verification.timeoutMs) || 60_000,
|
|
293
|
-
});
|
|
324
|
+
const { result: verify } = runVerification(proposal.verification, cwd);
|
|
294
325
|
process.stdout.write(JSON.stringify({
|
|
295
326
|
ok: (verify.status ?? -1) === (proposal.verification.expectedExitCode ?? 0),
|
|
296
327
|
exitCode: verify.status ?? -1,
|
package/cli/commands/install.mjs
CHANGED
|
@@ -197,6 +197,16 @@ export async function update(args, isHelpRequest) {
|
|
|
197
197
|
showUpdateHelp();
|
|
198
198
|
return;
|
|
199
199
|
}
|
|
200
|
+
const versionIndex = args.indexOf('--version');
|
|
201
|
+
if (versionIndex >= 0) {
|
|
202
|
+
const version = args[versionIndex + 1];
|
|
203
|
+
const artifactIndex = args.indexOf('--artifact-dir');
|
|
204
|
+
const artifactDir = artifactIndex >= 0 ? args[artifactIndex + 1] : undefined;
|
|
205
|
+
const { runVerifiedUpdate } = await import('../update.mjs');
|
|
206
|
+
const result = await runVerifiedUpdate(args, { artifactDir });
|
|
207
|
+
if (!result?.ok) process.exit(1);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
200
210
|
// v10.19.6 — route update through the same flag-parsing + installer
|
|
201
211
|
// pipeline as install so `--dry-run`, `--force`, `--yes`, etc. do
|
|
202
212
|
// what they claim. See `runUpdateWithFlags` for the testable
|
package/cli/commands/openkan.mjs
CHANGED
|
@@ -18,7 +18,7 @@ function help() {
|
|
|
18
18
|
bizar openkan — default durable planning and goals surface
|
|
19
19
|
|
|
20
20
|
Usage:
|
|
21
|
-
bizar openkan install Install @polderlabs/openkan@
|
|
21
|
+
bizar openkan install Install @polderlabs/openkan@0.5.0 from npm
|
|
22
22
|
ok init Initialise .ok/ in this project
|
|
23
23
|
ok task <add|list|show|update|...> Manage durable tasks
|
|
24
24
|
ok plan <add|list|show|update> Manage plans and phases
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
* The private key is operator-held. The CLI never persists it.
|
|
26
26
|
*/
|
|
27
27
|
|
|
28
|
-
import { createHash,
|
|
28
|
+
import { createHash, generateKeyPairSync } from 'node:crypto';
|
|
29
29
|
import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
|
|
30
30
|
import { execSync } from 'node:child_process';
|
|
31
31
|
import { join } from 'node:path';
|
|
@@ -49,8 +49,9 @@ export const USAGE = `
|
|
|
49
49
|
--package-json <path> Root package.json (default: ./package.json).
|
|
50
50
|
--sdk-package-json <path> SDK package.json (default: ./packages/sdk/package.json).
|
|
51
51
|
--private-key <path> PEM-encoded ed25519 private key. If omitted, the
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
an ephemeral fixture key is generated and returned
|
|
53
|
+
for tests; production releases must provide one.
|
|
54
|
+
--tarball <path> Exact artifact bytes to bind to provenance/signature.
|
|
54
55
|
--git-sha <sha> Override the git sha (default: read from git rev-parse HEAD).
|
|
55
56
|
--quiet Suppress progress output.
|
|
56
57
|
|
|
@@ -87,16 +88,17 @@ function minisignEncode(sigB64, trustedComment = `signed by bizar ${CURRENT_RELE
|
|
|
87
88
|
`untrusted comment: ${untrustedComment}`,
|
|
88
89
|
sigB64,
|
|
89
90
|
`trusted comment: ${trustedComment}`,
|
|
90
|
-
Buffer.from(trustedComment
|
|
91
|
+
Buffer.from(`trusted comment: ${trustedComment}\n`, 'utf8').toString('base64'),
|
|
91
92
|
'',
|
|
92
93
|
].join('\n');
|
|
93
94
|
}
|
|
94
95
|
|
|
95
|
-
function
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
96
|
+
function ephemeralSigningKey() {
|
|
97
|
+
const pair = generateKeyPairSync('ed25519');
|
|
98
|
+
return {
|
|
99
|
+
privateKeyPem: pair.privateKey.export({ type: 'pkcs8', format: 'pem' }).toString(),
|
|
100
|
+
publicKeyPem: pair.publicKey.export({ type: 'spki', format: 'pem' }).toString(),
|
|
101
|
+
};
|
|
100
102
|
}
|
|
101
103
|
|
|
102
104
|
/**
|
|
@@ -115,6 +117,8 @@ export function buildReleaseArtifacts({
|
|
|
115
117
|
privateKeyPem,
|
|
116
118
|
outDir,
|
|
117
119
|
timestamp,
|
|
120
|
+
artifactBytes,
|
|
121
|
+
artifactName,
|
|
118
122
|
}) {
|
|
119
123
|
const sdkName = sdkPackageJson.name;
|
|
120
124
|
const sdkVersion = sdkPackageJson.version;
|
|
@@ -142,9 +146,11 @@ export function buildReleaseArtifacts({
|
|
|
142
146
|
// Use the SBOM as the artifact for provenance + signing so the
|
|
143
147
|
// signed payload IS the SBOM. Operators can also sign the
|
|
144
148
|
// tarball — adjust the `subject.name` accordingly.
|
|
149
|
+
const signedArtifact = artifactBytes ?? sbomBytes;
|
|
150
|
+
const signedArtifactSha256 = createHash('sha256').update(signedArtifact).digest('hex');
|
|
145
151
|
const statement = buildProvenanceAttestation({
|
|
146
|
-
artifactName: `${rootPackageJson.name}-${version}.
|
|
147
|
-
artifactSha256:
|
|
152
|
+
artifactName: artifactName ?? `${rootPackageJson.name}-${version}.tgz`,
|
|
153
|
+
artifactSha256: signedArtifactSha256,
|
|
148
154
|
version,
|
|
149
155
|
gitSha,
|
|
150
156
|
materialUri: `git+https://github.com/DrB0rk/BizarHarness@${gitSha}`,
|
|
@@ -154,20 +160,24 @@ export function buildReleaseArtifacts({
|
|
|
154
160
|
const provenanceJsonl = JSON.stringify(statement) + '\n';
|
|
155
161
|
const provenanceSha256 = createHash('sha256').update(provenanceJsonl, 'utf8').digest('hex');
|
|
156
162
|
|
|
157
|
-
// Signature: ed25519 over the
|
|
158
|
-
//
|
|
159
|
-
//
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
+
// Signature: ed25519 over the exact artifact bytes. Production callers
|
|
164
|
+
// provide a private key; the generated key fallback is explicit and
|
|
165
|
+
// returns its public key so fixtures remain interoperable and auditable.
|
|
166
|
+
const trustedComment = `signed by bizar ${CURRENT_RELEASE_KEY_ID} sha256=${signedArtifactSha256}`;
|
|
167
|
+
// Minisign authenticates the trusted-comment envelope. Keep the exact
|
|
168
|
+
// bytes identical to the verifier so generated fixtures are interoperable.
|
|
169
|
+
const message = Buffer.from(`trusted comment: ${trustedComment}\n`, 'utf8');
|
|
170
|
+
const keyId = Buffer.from(CURRENT_RELEASE_KEY_ID, 'hex').subarray(0, 8);
|
|
163
171
|
let sigB64;
|
|
172
|
+
let signingPublicKeyPem = null;
|
|
164
173
|
if (privateKeyPem) {
|
|
165
174
|
sigB64 = signWithEd25519(message, privateKeyPem, keyId);
|
|
166
175
|
} else {
|
|
167
|
-
|
|
168
|
-
sigB64 =
|
|
176
|
+
const ephemeral = ephemeralSigningKey();
|
|
177
|
+
sigB64 = signWithEd25519(message, ephemeral.privateKeyPem, keyId);
|
|
178
|
+
signingPublicKeyPem = ephemeral.publicKeyPem;
|
|
169
179
|
}
|
|
170
|
-
const minisigText = minisignEncode(sigB64);
|
|
180
|
+
const minisigText = minisignEncode(sigB64, trustedComment);
|
|
171
181
|
|
|
172
182
|
mkdirSync(outDir, { recursive: true, mode: 0o700 });
|
|
173
183
|
const sbomPath = join(outDir, `${version}.sbom.cdx.json`);
|
|
@@ -189,6 +199,8 @@ export function buildReleaseArtifacts({
|
|
|
189
199
|
outDir,
|
|
190
200
|
sdkName,
|
|
191
201
|
sdkVersion,
|
|
202
|
+
signedArtifactSha256,
|
|
203
|
+
signingPublicKeyPem,
|
|
192
204
|
};
|
|
193
205
|
}
|
|
194
206
|
|
|
@@ -231,6 +243,7 @@ export async function run(subargs) {
|
|
|
231
243
|
const privateKeyPem = flags['private-key']
|
|
232
244
|
? readFileSync(String(flags['private-key']), 'utf8')
|
|
233
245
|
: null;
|
|
246
|
+
const artifactBytes = flags.tarball ? readFileSync(String(flags.tarball)) : undefined;
|
|
234
247
|
|
|
235
248
|
const artifacts = buildReleaseArtifacts({
|
|
236
249
|
version,
|
|
@@ -238,6 +251,8 @@ export async function run(subargs) {
|
|
|
238
251
|
sdkPackageJson: sdkPkg,
|
|
239
252
|
gitSha,
|
|
240
253
|
privateKeyPem,
|
|
254
|
+
artifactBytes,
|
|
255
|
+
artifactName: flags.tarball ? `${rootPkg.name}-${version}.tgz` : undefined,
|
|
241
256
|
outDir,
|
|
242
257
|
});
|
|
243
258
|
|
package/cli/commands/worker.mjs
CHANGED
|
@@ -104,6 +104,9 @@ export async function run(name, args, isHelpRequest) {
|
|
|
104
104
|
return true;
|
|
105
105
|
}
|
|
106
106
|
if (subcommand !== 'start') { usage(); return false; }
|
|
107
|
+
if (process.env.AO_SESSION_ID || process.env.AO_PROJECT_ID) {
|
|
108
|
+
throw new Error('AO_OWNED_OPERATION: bizar worker start is disabled inside an Agent Orchestrator worker; request AO coordination instead');
|
|
109
|
+
}
|
|
107
110
|
const parsed = parseStart(args.slice(1));
|
|
108
111
|
const plan = buildWorkerPlan({ repoRoot: process.cwd(), ...parsed });
|
|
109
112
|
mkdirSync(workerRoot(), { recursive: true, mode: 0o700 });
|