@polderlabs/bizar 10.23.5 → 10.23.6
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 +18 -11
- package/cli/commands/claude-cmd.mjs +4 -2
- package/cli/commands/hook.mjs +7 -3
- package/cli/commands/validate.mjs +2 -0
- package/cli/provision.mjs +14 -2
- package/config/claude/CLAUDE.md +18 -11
- package/config/claude/agents/_shared/AGENT_BASELINE.md +6 -1
- package/config/claude/agents/office-manager.md +16 -13
- package/config/claude/commands/quick.md +5 -5
- package/config/claude/hooks/README.md +1 -0
- package/config/claude/hooks/sessionstart-prime.mjs +4 -4
- package/config/claude/hooks/worker-suggest.mjs +27 -27
- package/config/claude/hooks/workflow-route-guard.mjs +92 -0
- package/config/claude/hooks/workflow-route-state.mjs +71 -0
- package/config/claude/settings.json +3 -0
- package/config/workflows/bizar-debug.js +1 -1
- package/config/workflows/bizar-implement.js +25 -76
- package/package.json +1 -1
- 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
|
@@ -59,11 +59,13 @@ actions with `permissionDecision: "ask"`; that escalation list is the
|
|
|
59
59
|
authoritative floor, not a starting point.
|
|
60
60
|
|
|
61
61
|
Native dynamic workflows under `config/workflows/` and `~/.claude/workflows/`
|
|
62
|
-
are the
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
62
|
+
are the required dispatch mechanism for every primary request except an
|
|
63
|
+
unmistakably tiny single-target copy/style/format edit with no behavior or test
|
|
64
|
+
change. Mike may execute that narrow exception directly. All other work enters
|
|
65
|
+
the matching research / implement / debug / review workflow and uses at least
|
|
66
|
+
one explicitly modeled editing subagent with call-level worktree isolation.
|
|
67
|
+
Do not add unnecessary phases or duplicate workers. For work that needs 3+
|
|
68
|
+
long-lived workers with bounded cross-talk,
|
|
67
69
|
Mike invokes a workflow that fans out as a native agent team; the team is
|
|
68
70
|
host-side state under `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1`, and per
|
|
69
71
|
Anthropic's docs `team_name` is deprecated and ignored. When two or more
|
|
@@ -132,10 +134,15 @@ guess-and-try remains prohibited.
|
|
|
132
134
|
The autonomy and approval policy above governs this execution model. The project defaults to `acceptEdits`; eligible operators may opt into Claude Code Auto mode.
|
|
133
135
|
|
|
134
136
|
Every non-empty primary request enters Bizar through `office-manager` (`@mike`).
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
137
|
+
The installer sets Claude Code's global `agent` setting to Mike's frontmatter
|
|
138
|
+
name (`mike`),
|
|
139
|
+
and a session-scoped routing guard requires a successful native Workflow before
|
|
140
|
+
substantive primary-session mutation. Read-only inspection remains available.
|
|
141
|
+
Mike directly executes only the tiny edit exception above. Every other request
|
|
142
|
+
must invoke a native Bizar workflow before mutation; the workflow dispatches
|
|
143
|
+
worktree-isolated subagents while Mike owns integration and final verification.
|
|
144
|
+
A Bizar custom agent already executing its assigned role does not recursively
|
|
145
|
+
dispatch itself.
|
|
139
146
|
|
|
140
147
|
Every shipped agent has `WebSearch` access. Before proposing, explaining,
|
|
141
148
|
troubleshooting, or implementing behavior from an external API, library,
|
|
@@ -145,8 +152,8 @@ relevant page. Guess-and-try integration work is prohibited. When official
|
|
|
145
152
|
documentation is unavailable or ambiguous, inspect authoritative source code
|
|
146
153
|
and report the evidence gap.
|
|
147
154
|
|
|
148
|
-
For
|
|
149
|
-
risk;
|
|
155
|
+
For workflow-routed requests, `office-manager` uses only the phases that reduce
|
|
156
|
+
a known risk; only the tiny edit exception skips this pipeline:
|
|
150
157
|
|
|
151
158
|
1. Research: `greg` (`research-analyst.md`) plus an implementation-context specialist.
|
|
152
159
|
2. Plan: `planner` drafts; `qa-reviewer` challenges assumptions and test shape.
|
|
@@ -56,7 +56,7 @@ export async function runClaudeTeam(args = []) {
|
|
|
56
56
|
console.error(chalk.red(` ✗ Missing ${router}; run bizar install`));
|
|
57
57
|
return 1;
|
|
58
58
|
}
|
|
59
|
-
return spawnClaude(['-p', '--name', name, '--agent', '
|
|
59
|
+
return spawnClaude(['-p', '--name', name, '--agent', 'mike', mission, ...rest]);
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
export async function runClaudeSubagent(args = []) {
|
|
@@ -80,7 +80,9 @@ export async function runClaudeRun(args = []) {
|
|
|
80
80
|
console.log(' Usage: bizar run [--bg] <prompt>');
|
|
81
81
|
return 2;
|
|
82
82
|
}
|
|
83
|
-
return spawnClaude(background
|
|
83
|
+
return spawnClaude(background
|
|
84
|
+
? ['--bg', '-p', '--agent', 'mike', prompt]
|
|
85
|
+
: ['-p', '--agent', 'mike', prompt]);
|
|
84
86
|
}
|
|
85
87
|
|
|
86
88
|
export async function run(name, args, isHelpRequest) {
|
package/cli/commands/hook.mjs
CHANGED
|
@@ -19,6 +19,7 @@ const PRETOOL_SAFETY_LEAVES = new Set([
|
|
|
19
19
|
'pretooluse-editwrite', 'path-ownership-guard',
|
|
20
20
|
'pretooluse-bash', 'git-workflow-guard',
|
|
21
21
|
'agent-model-guard',
|
|
22
|
+
'workflow-route-guard',
|
|
22
23
|
]);
|
|
23
24
|
|
|
24
25
|
export const HOOK_PROGRAMS = Object.freeze({
|
|
@@ -47,6 +48,7 @@ export const HOOK_PROGRAMS = Object.freeze({
|
|
|
47
48
|
'team-lifecycle': 'team-lifecycle.mjs',
|
|
48
49
|
'verify-deliverables': 'verify-deliverables.mjs',
|
|
49
50
|
'worker-suggest': 'worker-suggest.mjs',
|
|
51
|
+
'workflow-route-guard': 'workflow-route-guard.mjs',
|
|
50
52
|
'worktree-archive': 'worktree-archive.mjs',
|
|
51
53
|
'worktree-bootstrap': 'worktree-bootstrap.mjs',
|
|
52
54
|
});
|
|
@@ -55,6 +57,7 @@ export const EVENT_CHAINS = Object.freeze({
|
|
|
55
57
|
'user-prompt-submit': Object.freeze([
|
|
56
58
|
'control-inbox',
|
|
57
59
|
'keyword-router',
|
|
60
|
+
'workflow-route-guard',
|
|
58
61
|
'worker-suggest',
|
|
59
62
|
'thinking-route',
|
|
60
63
|
'telemetry',
|
|
@@ -230,16 +233,17 @@ export function selectEventChain(eventKey, input = '') {
|
|
|
230
233
|
|
|
231
234
|
if (eventKey === 'pre-tool-use') {
|
|
232
235
|
if (/^(Write|Edit|MultiEdit)$/.test(toolName)) {
|
|
233
|
-
return ['pretooluse-editwrite', 'path-ownership-guard'];
|
|
236
|
+
return ['workflow-route-guard', 'pretooluse-editwrite', 'path-ownership-guard'];
|
|
234
237
|
}
|
|
235
238
|
if (toolName === 'Bash') {
|
|
236
|
-
return ['pretooluse-bash', 'git-workflow-guard'];
|
|
239
|
+
return ['workflow-route-guard', 'pretooluse-bash', 'git-workflow-guard'];
|
|
237
240
|
}
|
|
238
|
-
if (toolName === 'Agent') return ['agent-model-guard'];
|
|
241
|
+
if (toolName === 'Agent') return ['workflow-route-guard', 'agent-model-guard'];
|
|
239
242
|
return [];
|
|
240
243
|
}
|
|
241
244
|
|
|
242
245
|
if (eventKey === 'post-tool-use') {
|
|
246
|
+
if (toolName === 'Workflow') return ['workflow-route-guard'];
|
|
243
247
|
if (/^(Write|Edit|MultiEdit)$/.test(toolName)) return ['posttooluse-editwrite'];
|
|
244
248
|
if (toolName === 'Bash') return [];
|
|
245
249
|
return [];
|
package/cli/provision.mjs
CHANGED
|
@@ -608,6 +608,8 @@ const LEGACY_BIZAR_HOOK_FILES = new Set([
|
|
|
608
608
|
'thinking-route.mjs',
|
|
609
609
|
'verify-deliverables.mjs',
|
|
610
610
|
'worker-suggest.mjs',
|
|
611
|
+
'workflow-route-guard.mjs',
|
|
612
|
+
'workflow-route-state.mjs',
|
|
611
613
|
'worktree-bootstrap.mjs',
|
|
612
614
|
]);
|
|
613
615
|
|
|
@@ -835,7 +837,7 @@ export function writeClaudeSettings({ dryRun = false, force = false } = {}) {
|
|
|
835
837
|
const hook = (name, timeout = 15) => resolveHookCommand(name, timeout);
|
|
836
838
|
|
|
837
839
|
// The shipped settings template (`config/claude/settings.json`) is the
|
|
838
|
-
// source of truth for
|
|
840
|
+
// source of truth for the main agent, worktree, workflows, etc. We
|
|
839
841
|
// overlay Bizar-owned keys (mcpServers, hooks, env) on top so the installer
|
|
840
842
|
// honors user preferences without having to fork the template here.
|
|
841
843
|
// 10.22.0 / Phase 4 spirit-of-constraint fix: `model` and
|
|
@@ -848,6 +850,10 @@ export function writeClaudeSettings({ dryRun = false, force = false } = {}) {
|
|
|
848
850
|
const bizarSettings = {
|
|
849
851
|
...shipped,
|
|
850
852
|
$schema: shipped.$schema || 'https://json.schemastore.org/claude-code-settings.json',
|
|
853
|
+
// Bizar must own the primary thread globally. A routing hook can add
|
|
854
|
+
// context, but only the agent setting applies Mike's system prompt and
|
|
855
|
+
// tool surface (including Workflow) to ordinary `claude` launches.
|
|
856
|
+
agent: 'mike',
|
|
851
857
|
mcpServers: {
|
|
852
858
|
bizar: {
|
|
853
859
|
type: 'stdio',
|
|
@@ -892,6 +898,8 @@ export function writeClaudeSettings({ dryRun = false, force = false } = {}) {
|
|
|
892
898
|
cleanupPeriodDays: 7,
|
|
893
899
|
},
|
|
894
900
|
enableWorkflows: shipped.enableWorkflows !== undefined ? shipped.enableWorkflows : true,
|
|
901
|
+
disableWorkflows: false,
|
|
902
|
+
workflowSizeGuideline: shipped.workflowSizeGuideline || 'small',
|
|
895
903
|
alwaysThinkingEnabled: shipped.alwaysThinkingEnabled !== undefined ? shipped.alwaysThinkingEnabled : true,
|
|
896
904
|
autoDreamEnabled: shipped.autoDreamEnabled !== undefined ? shipped.autoDreamEnabled : true,
|
|
897
905
|
showThinkingSummaries: shipped.showThinkingSummaries !== undefined ? shipped.showThinkingSummaries : true,
|
|
@@ -990,11 +998,15 @@ export function writeClaudeSettings({ dryRun = false, force = false } = {}) {
|
|
|
990
998
|
merged.autoMode = existing.autoMode || bizarSettings.autoMode;
|
|
991
999
|
merged.attribution = existing.attribution || bizarSettings.attribution;
|
|
992
1000
|
merged.worktree = { ...(bizarSettings.worktree || {}), ...(existing.worktree || {}) };
|
|
993
|
-
for (const key of ['enableWorkflows', 'alwaysThinkingEnabled', 'autoDreamEnabled', 'showThinkingSummaries']) {
|
|
1001
|
+
for (const key of ['enableWorkflows', 'disableWorkflows', 'workflowSizeGuideline', 'alwaysThinkingEnabled', 'autoDreamEnabled', 'showThinkingSummaries']) {
|
|
994
1002
|
if (merged[key] === undefined) merged[key] = bizarSettings[key];
|
|
995
1003
|
}
|
|
996
1004
|
}
|
|
997
1005
|
|
|
1006
|
+
// The installed harness owns the default main-thread role. Operators can
|
|
1007
|
+
// still override it for one session with `claude --agent <name>`.
|
|
1008
|
+
merged.agent = 'mike';
|
|
1009
|
+
|
|
998
1010
|
// `model` is Bizar-owned routing policy, so refresh it on both ordinary and
|
|
999
1011
|
// forced provision runs. Do not erase an existing operator model if a
|
|
1000
1012
|
// malformed router has no enabled candidate; dispatch will fail closed.
|
package/config/claude/CLAUDE.md
CHANGED
|
@@ -70,11 +70,13 @@ actions with `permissionDecision: "ask"`; that escalation list is the
|
|
|
70
70
|
authoritative floor, not a starting point.
|
|
71
71
|
|
|
72
72
|
Native dynamic workflows under `config/workflows/` and `~/.claude/workflows/`
|
|
73
|
-
are the
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
73
|
+
are the required dispatch mechanism for every primary request except an
|
|
74
|
+
unmistakably tiny single-target copy/style/format edit with no behavior or test
|
|
75
|
+
change. Mike may execute that narrow exception directly. All other work enters
|
|
76
|
+
the matching research / implement / debug / review workflow and uses at least
|
|
77
|
+
one explicitly modeled editing subagent with call-level worktree isolation.
|
|
78
|
+
Do not add unnecessary phases or duplicate workers. For work that needs 3+
|
|
79
|
+
long-lived workers with bounded cross-talk,
|
|
78
80
|
Mike invokes a workflow that fans out as a native agent team; the team is
|
|
79
81
|
host-side state under `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1`, and per
|
|
80
82
|
Anthropic's docs `team_name` is deprecated and ignored. When two or more
|
|
@@ -143,10 +145,15 @@ guess-and-try remains prohibited.
|
|
|
143
145
|
The autonomy and approval policy above governs this execution model. The project defaults to `acceptEdits`; eligible operators may opt into Claude Code Auto mode.
|
|
144
146
|
|
|
145
147
|
Every non-empty primary request enters Bizar through `office-manager` (`@mike`).
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
148
|
+
The installer sets Claude Code's global `agent` setting to Mike's frontmatter
|
|
149
|
+
name (`mike`),
|
|
150
|
+
and a session-scoped routing guard requires a successful native Workflow before
|
|
151
|
+
substantive primary-session mutation. Read-only inspection remains available.
|
|
152
|
+
Mike directly executes only the tiny edit exception above. Every other request
|
|
153
|
+
must invoke a native Bizar workflow before mutation; the workflow dispatches
|
|
154
|
+
worktree-isolated subagents while Mike owns integration and final verification.
|
|
155
|
+
A Bizar custom agent already executing its assigned role does not recursively
|
|
156
|
+
dispatch itself.
|
|
150
157
|
|
|
151
158
|
Every shipped agent has `WebSearch` access. Before proposing, explaining,
|
|
152
159
|
troubleshooting, or implementing behavior from an external API, library,
|
|
@@ -156,8 +163,8 @@ relevant page. Guess-and-try integration work is prohibited. When official
|
|
|
156
163
|
documentation is unavailable or ambiguous, inspect authoritative source code
|
|
157
164
|
and report the evidence gap.
|
|
158
165
|
|
|
159
|
-
For
|
|
160
|
-
risk;
|
|
166
|
+
For workflow-routed requests, `office-manager` uses only the phases that reduce
|
|
167
|
+
a known risk; only the tiny edit exception skips this pipeline:
|
|
161
168
|
|
|
162
169
|
1. Research: `greg` (`research-analyst.md`) plus an implementation-context specialist.
|
|
163
170
|
2. Plan: `planner` drafts; `qa-reviewer` challenges assumptions and test shape.
|
|
@@ -24,7 +24,12 @@ Exact authority is required for pushes, PR mutations, releases, publication, dep
|
|
|
24
24
|
|
|
25
25
|
## 5. Agent coordination
|
|
26
26
|
|
|
27
|
-
Every primary request enters through `@mike`. Mike
|
|
27
|
+
Every primary request enters through `@mike`. Mike directly handles only an
|
|
28
|
+
unmistakably tiny single-target copy/style/format edit with no behavior or test
|
|
29
|
+
change. Every other request enters a matching native Bizar workflow, which
|
|
30
|
+
dispatches at least one explicitly modeled worktree-isolated implementation
|
|
31
|
+
subagent. Use only risk-reducing phases and parallelize only genuinely disjoint
|
|
32
|
+
scopes. A Bizar subagent never recursively dispatches itself.
|
|
28
33
|
|
|
29
34
|
Agent prompts name ownership, deliverable, validation, sibling awareness, and escalation. Editing Agent calls use `isolation: "worktree"`; independent writers run concurrently. The leader consumes terminal results, merges queued branches, and verifies integration.
|
|
30
35
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: mike
|
|
3
|
-
description: Mike —
|
|
4
|
-
tools: Agent, Read, Write, Edit, Bash, Grep, Glob, WebFetch, WebSearch, Skill
|
|
3
|
+
description: Mike — workflow-first orchestrator with a tiny direct-edit exception.
|
|
4
|
+
tools: Workflow, Agent, Read, Write, Edit, Bash, Grep, Glob, WebFetch, WebSearch, Skill
|
|
5
5
|
skills:
|
|
6
6
|
- i-have-adhd
|
|
7
7
|
---
|
|
@@ -9,22 +9,25 @@ skills:
|
|
|
9
9
|
# Mike — adaptive primary orchestrator
|
|
10
10
|
|
|
11
11
|
Follow `_shared/AGENT_BASELINE.md`. You own the user outcome, integration, and
|
|
12
|
-
final verification.
|
|
12
|
+
final verification. Direct execution is a narrow exception; workflows are the
|
|
13
|
+
default for meaningful work.
|
|
13
14
|
|
|
14
|
-
## Route
|
|
15
|
+
## Route, then reassess if scope expands
|
|
15
16
|
|
|
16
17
|
| Shape | Signals | Execution |
|
|
17
18
|
|---|---|---|
|
|
18
|
-
|
|
|
19
|
-
|
|
|
20
|
-
|
|
|
21
|
-
|
|
|
19
|
+
| Tiny direct | one obvious copy, typo, comment, whitespace, or single style-token edit; one target; no behavior or test change | inspect, make the micro-edit, run the smallest proving check yourself |
|
|
20
|
+
| Bounded workflow | known non-trivial implementation, including a logical bug or any behavioral change | invoke `bizar-implement`; it dispatches at least one editing worker with `isolation: "worktree"`; merge and verify |
|
|
21
|
+
| Debug workflow | failing behavior, unclear cause, regression, or interacting state | invoke `bizar-debug`; keep diagnosis and fix evidence separate |
|
|
22
|
+
| Research/shaped | external/version-sensitive behavior, architecture/security, broad review, or interacting components | invoke `bizar-research` or the matching `ultracode*` workflow; parallelize independent lanes and serialize dependencies |
|
|
22
23
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
If a request could reasonably require a regression test, touch multiple files,
|
|
25
|
+
or needs inspection to discover its scope, it is not tiny: invoke a workflow
|
|
26
|
+
before editing. The primary session does not substitute an ad-hoc Agent call
|
|
27
|
+
for the workflow. Research current official docs only for external or
|
|
28
|
+
version-sensitive claims. Inspect installed skills before hard or specialized
|
|
29
|
+
work; if stuck with no match, search skills.sh and review the candidate before
|
|
30
|
+
proposing installation.
|
|
28
31
|
|
|
29
32
|
## Models
|
|
30
33
|
|
|
@@ -11,16 +11,16 @@ Run `$ARGUMENTS` directly in this session. Do NOT delegate to subagents.
|
|
|
11
11
|
|
|
12
12
|
## Mechanism
|
|
13
13
|
|
|
14
|
-
The `worker-suggest` hook recognizes `/quick` directly
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
The `worker-suggest` hook recognizes `/quick` directly, but honors the bypass
|
|
15
|
+
only when the argument still qualifies as an unmistakably tiny edit. Legacy
|
|
16
|
+
`.bizar/.quick-once` sentinels are consumed on their first prompt and cannot
|
|
17
|
+
bypass routing for substantive work.
|
|
17
18
|
|
|
18
19
|
## What this is for
|
|
19
20
|
|
|
20
21
|
- One-line file edits
|
|
21
22
|
- Quick lookups ("find X", "show me Y")
|
|
22
|
-
-
|
|
23
|
-
- Single-tool invocations
|
|
23
|
+
- One-token style adjustments
|
|
24
24
|
|
|
25
25
|
## What this is NOT for
|
|
26
26
|
|
|
@@ -11,6 +11,7 @@ All hooks read Claude Code JSON from stdin and emit either no decision, addition
|
|
|
11
11
|
| `simplify-guard.mjs` | PostToolUse Skill + PreToolUse Bash | require one `/simplify` per commit attempt |
|
|
12
12
|
| `posttooluse-editwrite.mjs` | PostToolUse writes | local telemetry and test reminder |
|
|
13
13
|
| `worker-suggest.mjs` | UserPromptSubmit | ranked skill/agent suggestions |
|
|
14
|
+
| `workflow-route-guard.mjs` | UserPromptSubmit, PreToolUse, PostToolUse | requires a proven successful native workflow before substantive primary mutation; permits narrowly whitelisted, redirect-free Git inspection |
|
|
14
15
|
| `thinking-route.mjs` | UserPromptSubmit | slash and mental-model routing |
|
|
15
16
|
| `telemetry.mjs` | SessionStart/UserPromptSubmit | local correlation and rejection categories |
|
|
16
17
|
| `sessionstart-prime.mjs` | SessionStart | bounded project and handoff context |
|
|
@@ -186,13 +186,13 @@ function startupBriefing(cwd, featureBrief, recentCommits, projectLine, progress
|
|
|
186
186
|
}
|
|
187
187
|
}
|
|
188
188
|
if (progressLast) lines.push(`- Progress: ${progressLast}.`);
|
|
189
|
-
lines.push('- You are @mike.
|
|
189
|
+
lines.push('- You are @mike. Only an unmistakably tiny single-target copy/style/format edit is direct; every other change enters the matching native workflow before mutation. Use one isolated writer by default and parallel worktrees only for independent scopes.');
|
|
190
190
|
lines.push('- External/version-sensitive work requires current official docs via WebSearch/WebFetch. Use relevant installed skills; apply i-have-adhd to user output. WIP=1.');
|
|
191
191
|
lines.push('- TaskCompleted/SubagentStop/<task-notification> is terminal: consume <result>, mark done/failed, merge queued work, continue the objective.');
|
|
192
192
|
// Default-first-stop hint when nothing is active yet.
|
|
193
193
|
if (featureBrief && featureBrief.active.length === 0) {
|
|
194
194
|
lines.push(
|
|
195
|
-
'- First move: read PROGRESS.md and feature_list.json; then choose
|
|
195
|
+
'- First move: read PROGRESS.md and feature_list.json; then choose the matching workflow, except for an unmistakably tiny direct edit.',
|
|
196
196
|
);
|
|
197
197
|
}
|
|
198
198
|
return lines.join('\n');
|
|
@@ -203,14 +203,14 @@ function clearBriefing(cwd, recentCommits, progressLast) {
|
|
|
203
203
|
if (progressLast) lines.push(`- Progress: ${progressLast}.`);
|
|
204
204
|
if (recentCommits.length > 0) lines.push(`- Last commit: ${recentCommits[0]}.`);
|
|
205
205
|
lines.push('- Context preserved in same repo / cwd — only the model turn was reset.');
|
|
206
|
-
lines.push('- You are @mike: continue
|
|
206
|
+
lines.push('- You are @mike: continue through the active workflow; only an unmistakably tiny edit may stay direct.');
|
|
207
207
|
lines.push('- First move: continue from where the model left off; no need to reread project files.');
|
|
208
208
|
return lines.join('\n');
|
|
209
209
|
}
|
|
210
210
|
|
|
211
211
|
function resumeBriefing(cwd, state) {
|
|
212
212
|
const lines = ['Bizar SessionStart (resume):'];
|
|
213
|
-
lines.push('- You are @mike: restore state, then
|
|
213
|
+
lines.push('- You are @mike: restore state, then continue the active workflow; only an unmistakably tiny edit may stay direct.');
|
|
214
214
|
if (state) {
|
|
215
215
|
if (state.activeFeature) lines.push(`- Last active feature: ${state.activeFeature}.`);
|
|
216
216
|
if (state.reason) lines.push(`- Last session ended with: ${state.reason}.`);
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Bizar Background Workers — UserPromptSubmit hook.
|
|
6
6
|
*
|
|
7
|
-
* Runs on every user prompt.
|
|
8
|
-
* fast path
|
|
9
|
-
*
|
|
7
|
+
* Runs on every user prompt. Only unmistakably tiny, single-scope edits take
|
|
8
|
+
* a cheap fast path. Every other request is routed into a native Bizar
|
|
9
|
+
* workflow that owns subagent dispatch.
|
|
10
10
|
*
|
|
11
11
|
* Uses import.meta.url + dynamic import() to resolve the sibling CLI module so
|
|
12
12
|
* the hook works regardless of install path (fixes ERR_MODULE_NOT_FOUND after
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
import { dirname, join } from 'node:path';
|
|
38
38
|
import { existsSync, unlinkSync } from 'node:fs';
|
|
39
39
|
import { fileURLToPath } from 'node:url';
|
|
40
|
+
import { isTinyDirectTask } from './workflow-route-state.mjs';
|
|
40
41
|
|
|
41
42
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
42
43
|
|
|
@@ -46,17 +47,22 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
|
46
47
|
* normal routing path. Keeping the classifier lexical makes it free on the
|
|
47
48
|
* UserPromptSubmit hot path and easy to explain to the model.
|
|
48
49
|
*/
|
|
49
|
-
|
|
50
|
-
if (prompt.length > 500 || prompt.split('\n').length > 3) return false;
|
|
51
|
-
if (!/\b(fix|correct|rename|remove|delete|format|typo|style|padding|margin|color|spacing|align|change|update)\b/i.test(prompt)) return false;
|
|
52
|
-
return !/\b(api|sdk|library|framework|dependency|version|migration|architecture|security|auth|credential|deploy|publish|release|database|workflow|agent team|hook|performance|benchmark|all files|every file|failing|failure|error|crash|root cause|regression)\b/i.test(prompt);
|
|
53
|
-
}
|
|
50
|
+
const isFastLocalTask = isTinyDirectTask;
|
|
54
51
|
|
|
55
52
|
const FAST_ROUTE_POLICY = [
|
|
56
|
-
'Bizar
|
|
57
|
-
'-
|
|
58
|
-
'-
|
|
59
|
-
|
|
53
|
+
'Bizar tiny direct path:',
|
|
54
|
+
'- Direct execution is allowed only because this request is an unmistakably tiny, single-scope copy/style/format edit. Inspect the exact target, make one minimal reversible edit, and run the smallest proving check. Do not plan, research, or dispatch a subagent.',
|
|
55
|
+
'- If inspection reveals behavioral logic, more than one target, ambiguity, a required test change, or any interaction beyond the named micro-edit, stop the direct path and invoke the matching native Bizar workflow before editing further.',
|
|
56
|
+
].join('\n');
|
|
57
|
+
|
|
58
|
+
const ROUTE_POLICY = [
|
|
59
|
+
'Workflow-required Bizar routing policy:',
|
|
60
|
+
'- If this is the primary session, you ARE @mike. Do not implement this request directly in the primary session. Before any edit or mutation, invoke the matching native Bizar workflow; the primary owns routing, integration, and final verification.',
|
|
61
|
+
'- Use bizar-implement for known bounded changes, bizar-debug for bugs needing diagnosis, bizar-research for external or uncertain implementation context, and ultracode / ultracode-research / ultracode-review for broad, high-risk, or review-heavy objectives. Use only phases that reduce a concrete risk.',
|
|
62
|
+
'- The workflow must dispatch at least one implementation subagent with an explicit configured model and call-level `isolation: "worktree"`. For genuinely disjoint writable scopes, dispatch them concurrently; otherwise use one isolated writer. Never create duplicate workers merely to satisfy fan-out.',
|
|
63
|
+
'- Consume terminal agent results, merge queued worktrees with bizar worktree-merge, and run integration checks in the primary session. A subagent may not recursively dispatch itself.',
|
|
64
|
+
'- Do NOT execute any tool you do not have. If a tool you need is missing from your tools list, dispatch to a subagent that has it — do not pretend you have it.',
|
|
65
|
+
'- If you are already running as a Bizar custom agent, follow your assigned role and do not recursively dispatch yourself.',
|
|
60
66
|
].join('\n');
|
|
61
67
|
|
|
62
68
|
let raw = '';
|
|
@@ -72,7 +78,7 @@ process.stdin.on('end', async () => {
|
|
|
72
78
|
|
|
73
79
|
const prompt = String(input.prompt ?? input.user_prompt ?? '').trim();
|
|
74
80
|
|
|
75
|
-
if (input.task_notification ||
|
|
81
|
+
if (input.task_notification || /^<task-notification\b[\s\S]*<result\b[\s\S]*<\/task-notification>\s*$/i.test(prompt)) {
|
|
76
82
|
process.stdout.write(JSON.stringify({
|
|
77
83
|
hookSpecificOutput: {
|
|
78
84
|
hookEventName: 'UserPromptSubmit',
|
|
@@ -83,7 +89,9 @@ process.stdin.on('end', async () => {
|
|
|
83
89
|
}
|
|
84
90
|
|
|
85
91
|
if (/^\/quick(?:\s|$)/i.test(prompt)) {
|
|
86
|
-
|
|
92
|
+
const quickTask = prompt.replace(/^\/quick(?:\s+|$)/i, '').trim();
|
|
93
|
+
const context = !quickTask || isFastLocalTask(quickTask) ? FAST_ROUTE_POLICY : ROUTE_POLICY;
|
|
94
|
+
process.stdout.write(JSON.stringify({ hookSpecificOutput: { hookEventName: 'UserPromptSubmit', additionalContext: context } }) + '\n');
|
|
87
95
|
return;
|
|
88
96
|
}
|
|
89
97
|
|
|
@@ -93,10 +101,11 @@ process.stdin.on('end', async () => {
|
|
|
93
101
|
const quickSentinel = join(input.cwd || process.cwd(), '.bizar', '.quick-once');
|
|
94
102
|
if (existsSync(quickSentinel)) {
|
|
95
103
|
try { unlinkSync(quickSentinel); } catch { /* best-effort one-shot cleanup */ }
|
|
104
|
+
const context = isFastLocalTask(prompt) ? FAST_ROUTE_POLICY : ROUTE_POLICY;
|
|
96
105
|
process.stdout.write(JSON.stringify({
|
|
97
106
|
hookSpecificOutput: {
|
|
98
107
|
hookEventName: 'UserPromptSubmit',
|
|
99
|
-
additionalContext:
|
|
108
|
+
additionalContext: context,
|
|
100
109
|
},
|
|
101
110
|
}) + '\n');
|
|
102
111
|
process.exit(0);
|
|
@@ -130,15 +139,6 @@ process.stdin.on('end', async () => {
|
|
|
130
139
|
return;
|
|
131
140
|
}
|
|
132
141
|
|
|
133
|
-
const routePolicy = [
|
|
134
|
-
'Adaptive Bizar routing policy:',
|
|
135
|
-
'- If this is the primary session, you ARE @mike. Execute small deterministic repository work directly, or use the Agent tool for one @brenda worktree worker when isolation helps. Do not add research, planning, review, or a second worker unless the task needs it.',
|
|
136
|
-
'- For a known, multi-file change, first split only genuinely disjoint edit scopes and dispatch those writers concurrently with call-level `isolation: "worktree"`. A monolithic scope gets one writer, not artificial parallelism.',
|
|
137
|
-
'- Use research and planning only when external/version-sensitive behavior, an unclear root cause, an architectural decision, or interacting scopes make them decision-reducing. When required, run independent research in parallel with repository inspection.',
|
|
138
|
-
'- Do NOT execute any tool you do not have. If a tool you need is missing from your tools list, dispatch to a subagent that has it — do not pretend you have it.',
|
|
139
|
-
'- If you are already running as a Bizar custom agent, follow your assigned role and do not recursively dispatch yourself.',
|
|
140
|
-
].join('\n');
|
|
141
|
-
|
|
142
142
|
let dispatch;
|
|
143
143
|
let recordSuggestion;
|
|
144
144
|
try {
|
|
@@ -150,7 +150,7 @@ process.stdin.on('end', async () => {
|
|
|
150
150
|
process.stdout.write(JSON.stringify({
|
|
151
151
|
hookSpecificOutput: {
|
|
152
152
|
hookEventName: 'UserPromptSubmit',
|
|
153
|
-
additionalContext:
|
|
153
|
+
additionalContext: ROUTE_POLICY,
|
|
154
154
|
},
|
|
155
155
|
}) + '\n');
|
|
156
156
|
process.exit(0);
|
|
@@ -167,7 +167,7 @@ process.stdin.on('end', async () => {
|
|
|
167
167
|
process.stdout.write(JSON.stringify({
|
|
168
168
|
hookSpecificOutput: {
|
|
169
169
|
hookEventName: 'UserPromptSubmit',
|
|
170
|
-
additionalContext:
|
|
170
|
+
additionalContext: ROUTE_POLICY,
|
|
171
171
|
},
|
|
172
172
|
}) + '\n');
|
|
173
173
|
process.exit(0);
|
|
@@ -204,7 +204,7 @@ process.stdin.on('end', async () => {
|
|
|
204
204
|
|
|
205
205
|
// Build additionalContext for the model so Bizar routing is mandatory even
|
|
206
206
|
// when no specialized worker pattern matches.
|
|
207
|
-
let note =
|
|
207
|
+
let note = ROUTE_POLICY;
|
|
208
208
|
if (suggestions.length > 0) {
|
|
209
209
|
const lines = suggestions.map((s) => {
|
|
210
210
|
const skillPart = s.skill ? `, skill=${s.skill}` : '';
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/** Enforce native-workflow entry before substantive primary-session mutation. */
|
|
3
|
+
'use strict';
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
clearWorkflowRequired,
|
|
7
|
+
markWorkflowRequired,
|
|
8
|
+
promptRequiresWorkflow,
|
|
9
|
+
workflowRequired,
|
|
10
|
+
} from './workflow-route-state.mjs';
|
|
11
|
+
import { parseGitCommands } from './git-command-parser.mjs';
|
|
12
|
+
|
|
13
|
+
const READ_ONLY_GIT_SUBCOMMANDS = new Set([
|
|
14
|
+
'diff', 'log', 'ls-files', 'rev-parse', 'show', 'status',
|
|
15
|
+
]);
|
|
16
|
+
const WORKFLOW_SUCCESS = new Set(['completed', 'dry', 'ready-for-integration', 'succeeded', 'success']);
|
|
17
|
+
const WORKFLOW_FAILURE = new Set(['blocked', 'budget-exhausted', 'cancelled', 'canceled', 'error', 'failed', 'failure']);
|
|
18
|
+
|
|
19
|
+
export function isReadOnlyShellInspection(command) {
|
|
20
|
+
const source = String(command || '').trim();
|
|
21
|
+
if (!source || /[\n\r;|<>`]/.test(source) || /\$\(|(?:^|\s)&(?:\s|$)/.test(source)) return false;
|
|
22
|
+
|
|
23
|
+
return source.split(/\s*&&\s*/).every((segment) => {
|
|
24
|
+
const trimmed = segment.trim();
|
|
25
|
+
if (/^echo(?:\s|$)/.test(trimmed)) return true;
|
|
26
|
+
if (!/^git(?:\s|$)/.test(trimmed) || /(?:^|\s)--(?:output|ext-diff)(?:=|\s|$)/.test(trimmed)) return false;
|
|
27
|
+
const parsed = parseGitCommands(trimmed);
|
|
28
|
+
return parsed.length === 1 && READ_ONLY_GIT_SUBCOMMANDS.has(parsed[0].subcommand);
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function workflowCompletedSuccessfully(input) {
|
|
33
|
+
const root = input?.tool_response ?? input?.tool_result ?? input?.toolUseResult;
|
|
34
|
+
if (!root || typeof root !== 'object') return false;
|
|
35
|
+
const statuses = [];
|
|
36
|
+
const visit = (value, depth = 0) => {
|
|
37
|
+
if (!value || typeof value !== 'object' || depth > 3) return;
|
|
38
|
+
if (typeof value.status === 'string') statuses.push(value.status.toLowerCase());
|
|
39
|
+
for (const key of ['result', 'workflow', 'data']) visit(value[key], depth + 1);
|
|
40
|
+
};
|
|
41
|
+
visit(root);
|
|
42
|
+
if (root.is_error === true || root.error || statuses.some((status) => WORKFLOW_FAILURE.has(status))) return false;
|
|
43
|
+
return statuses.some((status) => WORKFLOW_SUCCESS.has(status));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
let raw = '';
|
|
47
|
+
process.stdin.setEncoding('utf8');
|
|
48
|
+
process.stdin.on('data', (chunk) => { raw += chunk; });
|
|
49
|
+
process.stdin.on('end', () => {
|
|
50
|
+
let input = {};
|
|
51
|
+
try { input = JSON.parse(raw || '{}'); } catch { input = {}; }
|
|
52
|
+
|
|
53
|
+
const event = String(input.hook_event_name || '');
|
|
54
|
+
const sessionId = String(input.session_id || '');
|
|
55
|
+
const toolName = String(input.tool_name || '');
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
if (event === 'UserPromptSubmit') {
|
|
59
|
+
if (promptRequiresWorkflow(input)) markWorkflowRequired(input);
|
|
60
|
+
else clearWorkflowRequired(sessionId);
|
|
61
|
+
process.stdout.write('{}\n');
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (event === 'PostToolUse' && toolName === 'Workflow') {
|
|
66
|
+
if (workflowCompletedSuccessfully(input)) clearWorkflowRequired(sessionId);
|
|
67
|
+
process.stdout.write('{}\n');
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (event !== 'PreToolUse' || input.agent_id || !workflowRequired(sessionId)) {
|
|
72
|
+
process.stdout.write('{}\n');
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const readOnlyBash = toolName === 'Bash' && isReadOnlyShellInspection(input.tool_input?.command);
|
|
77
|
+
if (!readOnlyBash && /^(?:Edit|Write|MultiEdit|Bash|Agent)$/.test(toolName)) {
|
|
78
|
+
process.stdout.write(`${JSON.stringify({
|
|
79
|
+
hookSpecificOutput: {
|
|
80
|
+
hookEventName: 'PreToolUse',
|
|
81
|
+
permissionDecision: 'deny',
|
|
82
|
+
permissionDecisionReason: `Bizar workflow routing guard: ${toolName} is unavailable in the primary session until the required native Workflow runs successfully. Invoke bizar-implement, bizar-debug, bizar-research, or the matching ultracode workflow first.`,
|
|
83
|
+
},
|
|
84
|
+
})}\n`);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
} catch (error) {
|
|
88
|
+
process.stderr.write(`[bizar.workflow-route] ${error?.message || String(error)}\n`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
process.stdout.write('{}\n');
|
|
92
|
+
});
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
|
|
5
|
+
const MAX_AGE_MS = 24 * 60 * 60 * 1000;
|
|
6
|
+
|
|
7
|
+
export function isTinyDirectTask(prompt) {
|
|
8
|
+
const text = String(prompt || '').trim();
|
|
9
|
+
if (text.length === 0 || text.length > 240 || text.split('\n').length > 1) return false;
|
|
10
|
+
if (!/^(?:please\s+)?(?:fix|correct|format|adjust|change|update)\b/i.test(text)) return false;
|
|
11
|
+
if (/[?]\s*$/.test(text) || /\b(?:do\s+not|don't|never)\b/i.test(text)) return false;
|
|
12
|
+
if (!/\b(typo|spelling|punctuation|comment|copy|wording|whitespace|formatting|indentation|padding|margin|spacing|color|colour|alignment?)\b/i.test(text)) return false;
|
|
13
|
+
return !/\b(and|also|then|plus|both|across|multiple|several|throughout|entire|everywhere|sitewide|app-wide|components?|files?|api|sdk|library|framework|dependency|version|migration|architecture|security|auth|credential|deploy|publish|release|database|workflow|agent|hook|performance|benchmark|all files|every file|failing|failure|error|crash|root cause|regression|test|tests|logic|handling|parser|parsing|algorithm|calculation|rendering?|generation|tokenizer|lexer)\b/i.test(text);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function safeSessionId(value) {
|
|
17
|
+
return String(value || '').replace(/[^A-Za-z0-9_-]/g, '').slice(0, 80);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function stateDir(env = process.env) {
|
|
21
|
+
const root = env.BIZAR_HOME || join(env.XDG_CONFIG_HOME || join(homedir(), '.config'), 'bizar');
|
|
22
|
+
return join(root, 'workflow-routing');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function routeStatePath(sessionId, env = process.env) {
|
|
26
|
+
const safe = safeSessionId(sessionId);
|
|
27
|
+
return safe ? join(stateDir(env), `${safe}.json`) : '';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function markWorkflowRequired(input, env = process.env) {
|
|
31
|
+
const path = routeStatePath(input?.session_id, env);
|
|
32
|
+
if (!path) return false;
|
|
33
|
+
const dir = stateDir(env);
|
|
34
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
35
|
+
const tmp = `${path}.${process.pid}.tmp`;
|
|
36
|
+
writeFileSync(tmp, `${JSON.stringify({ required: true, createdAt: Date.now(), cwd: String(input?.cwd || '') })}\n`, { mode: 0o600 });
|
|
37
|
+
renameSync(tmp, path);
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function clearWorkflowRequired(sessionId, env = process.env) {
|
|
42
|
+
const path = routeStatePath(sessionId, env);
|
|
43
|
+
if (!path) return false;
|
|
44
|
+
rmSync(path, { force: true });
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function workflowRequired(sessionId, env = process.env, now = Date.now()) {
|
|
49
|
+
const path = routeStatePath(sessionId, env);
|
|
50
|
+
if (!path || !existsSync(path)) return false;
|
|
51
|
+
try {
|
|
52
|
+
const state = JSON.parse(readFileSync(path, 'utf8'));
|
|
53
|
+
if (state?.required !== true || !Number.isFinite(state.createdAt) || now - state.createdAt > MAX_AGE_MS) {
|
|
54
|
+
rmSync(path, { force: true });
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
return true;
|
|
58
|
+
} catch {
|
|
59
|
+
rmSync(path, { force: true });
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function promptRequiresWorkflow(input) {
|
|
65
|
+
const prompt = String(input?.prompt ?? input?.user_prompt ?? '').trim();
|
|
66
|
+
if (!prompt) return false;
|
|
67
|
+
if (input?.task_notification || /^<task-notification\b[\s\S]*<result\b[\s\S]*<\/task-notification>\s*$/i.test(prompt)) return false;
|
|
68
|
+
const quick = prompt.match(/^\/quick(?:\s+([\s\S]*))?$/i);
|
|
69
|
+
if (quick) return Boolean(quick[1]?.trim()) && !isTinyDirectTask(quick[1]);
|
|
70
|
+
return !isTinyDirectTask(prompt);
|
|
71
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/claude-code-settings.json",
|
|
3
|
+
"agent": "mike",
|
|
3
4
|
"mcpServers": {
|
|
4
5
|
"bizar": {
|
|
5
6
|
"command": "npx",
|
|
@@ -65,6 +66,8 @@
|
|
|
65
66
|
"askUserQuestionTimeout": "5m",
|
|
66
67
|
"showThinkingSummaries": true,
|
|
67
68
|
"enableWorkflows": true,
|
|
69
|
+
"disableWorkflows": false,
|
|
70
|
+
"workflowSizeGuideline": "small",
|
|
68
71
|
"disableAutoCompact": false,
|
|
69
72
|
"autoDreamEnabled": true,
|
|
70
73
|
"env": {
|
|
@@ -82,7 +82,7 @@ if (!accepted) {
|
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
phase('Fix')
|
|
85
|
-
const fix = await dispatchAgent(agent, 'fix-author', `
|
|
85
|
+
const fix = await dispatchAgent(agent, 'fix-author', `Implement the smallest fix + regression test for bug ${BUG_ID} based on the accepted hypothesis. Edit and test in your isolated worktree. Do not commit, push, publish, or deploy.\n${barrierRef({ runId: RUN_ID, phase: 'Hypothesis', label: 'hypothesis:initial', summary: accepted.hypothesis?.cause ? accepted.hypothesis.cause.slice(0, 200) : 'accepted hypothesis' }).promptBlock}`, { role: 'implementer', risk: 'medium', capabilities: ['structured-output', 'reasoning'], label: 'fix', phase: 'Fix', isolation: 'worktree' })
|
|
86
86
|
writeArtifact({ runId: RUN_ID, phase: 'Fix', label: 'fix', payload: fix, summary: typeof fix === 'string' ? fix.slice(0, 200) : 'fix proposed', role: 'implementer' })
|
|
87
87
|
|
|
88
88
|
phase('Verify')
|
|
@@ -1,16 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { dispatchAgent, writeArtifact, barrierRef } from './lib/dispatch.js'
|
|
1
|
+
import { dispatchAgent } from './lib/dispatch.js'
|
|
3
2
|
|
|
4
3
|
export const meta = {
|
|
5
4
|
name: 'bizar-implement',
|
|
6
|
-
description: '
|
|
7
|
-
whenToUse: 'Use when
|
|
5
|
+
description: 'Implement one bounded change in one worktree, or run explicitly supplied disjoint lanes concurrently',
|
|
6
|
+
whenToUse: 'Use when implementation scope is understood and external research or root-cause discovery is unnecessary.',
|
|
8
7
|
phases: [
|
|
9
|
-
{ title: '
|
|
10
|
-
{ title: 'Implement', detail: 'Run lanes concurrently in worktrees' },
|
|
11
|
-
{ title: 'Barrier', detail: 'A single agent reconciles all lane outputs' },
|
|
12
|
-
{ title: 'Verify', detail: 'A single agent re-checks the barrier plan against the scope' },
|
|
13
|
-
{ title: 'Synthesis', detail: 'A single integration agent produces the final report' },
|
|
8
|
+
{ title: 'Implement', detail: 'Run one isolated writer, or explicit disjoint writers concurrently' },
|
|
14
9
|
],
|
|
15
10
|
}
|
|
16
11
|
|
|
@@ -20,79 +15,33 @@ const TOPIC = typeof args === 'string'
|
|
|
20
15
|
? args.topic
|
|
21
16
|
: JSON.stringify(args || {})
|
|
22
17
|
const SCOPE = (args && Array.isArray(args.scope)) ? args.scope : []
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const LANES = {
|
|
29
|
-
type: 'object',
|
|
30
|
-
required: ['lanes'],
|
|
31
|
-
properties: {
|
|
32
|
-
lanes: {
|
|
33
|
-
type: 'array',
|
|
34
|
-
items: {
|
|
35
|
-
type: 'object',
|
|
36
|
-
required: ['name', 'scope', 'task'],
|
|
37
|
-
properties: {
|
|
38
|
-
name: { type: 'string' },
|
|
39
|
-
scope: { type: 'array', items: { type: 'string' } },
|
|
40
|
-
task: { type: 'string' },
|
|
41
|
-
},
|
|
42
|
-
},
|
|
43
|
-
},
|
|
44
|
-
},
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
phase('Scope')
|
|
48
|
-
const scoped = await dispatchAgent(agent, 'scope-extractor', `Extract 2-6 disjoint edit lanes for: ${TOPIC}\nProvided scope: ${SCOPE.length ? SCOPE.join(', ') : '(none supplied)'}\nEach lane owns a non-overlapping file scope. Shared root/config/lock files must have one owner. Return lanes with name/scope/task.`, { role: 'implementer', risk: 'medium', capabilities: ['structured-output', 'reasoning'], label: 'scope-extract', phase: 'Scope', schema: LANES })
|
|
49
|
-
if (!scoped || !Array.isArray(scoped.lanes) || scoped.lanes.length === 0) {
|
|
50
|
-
return { status: 'blocked', reason: 'Scope agent produced no lanes.' }
|
|
51
|
-
}
|
|
52
|
-
// Phase B: persist the scope artifact for the next barrier agent.
|
|
53
|
-
const scopeSummary = `scope lanes: ${scoped.lanes.map((l) => l.name).join(', ')}`
|
|
54
|
-
writeArtifact({ runId: RUN_ID, phase: 'Scope', label: 'barrier', payload: scoped, summary: scopeSummary, role: 'implementer' })
|
|
55
|
-
const lanes = scoped.lanes.slice(0, 6)
|
|
56
|
-
if (scoped.lanes.length > lanes.length) {
|
|
57
|
-
log(`Bounded implementation to 6 of ${scoped.lanes.length} lanes.`)
|
|
58
|
-
}
|
|
18
|
+
const suppliedLanes = (args && Array.isArray(args.lanes)) ? args.lanes : []
|
|
19
|
+
const lanes = (suppliedLanes.length > 0
|
|
20
|
+
? suppliedLanes
|
|
21
|
+
: [{ name: 'bounded-change', scope: SCOPE, task: TOPIC }]
|
|
22
|
+
).slice(0, 6)
|
|
59
23
|
|
|
60
24
|
phase('Implement')
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const label = `implement:${i + 1}:${lane.name}`;
|
|
76
|
-
const summary = `lane ${lane.name} files: ${(implementations[i]?.files || []).slice(0, 5).join(', ')}`;
|
|
77
|
-
writeArtifact({ runId: RUN_ID, phase: 'Implement', label, payload: implementations[i], summary, role: 'implementer' });
|
|
25
|
+
const runLane = (lane, index) => dispatchAgent(
|
|
26
|
+
agent,
|
|
27
|
+
`lane-implementer-${index + 1}`,
|
|
28
|
+
`Implement this owned lane for the topic "${TOPIC}".\nLane: ${lane.name || `lane-${index + 1}`}\nTask: ${lane.task || TOPIC}\nWritable scope: ${Array.isArray(lane.scope) && lane.scope.length ? lane.scope.join(', ') : 'discover the smallest necessary scope, then keep it bounded'}\nDo not edit outside the lane's necessary scope. Do not revert sibling work. Add a regression test when behavior changes and run the smallest proving checks. Return changed files, commands, exact results, and blockers. Do not commit, push, publish, or deploy.`,
|
|
29
|
+
{ role: 'implementer', risk: 'medium', capabilities: ['structured-output', 'reasoning'], label: `implement:${index + 1}:${lane.name || 'bounded-change'}`, phase: 'Implement', isolation: 'worktree' },
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
const implementations = lanes.length === 1
|
|
33
|
+
? [await runLane(lanes[0], 0)]
|
|
34
|
+
: await parallel(lanes.map((lane, index) => () => runLane(lane, index)))
|
|
35
|
+
const completed = implementations.filter(Boolean)
|
|
36
|
+
|
|
37
|
+
if (completed.length === 0) {
|
|
38
|
+
return { status: 'blocked', reason: 'No implementation lane completed successfully.', lanes }
|
|
78
39
|
}
|
|
79
40
|
|
|
80
|
-
phase('Barrier')
|
|
81
|
-
const merge = await dispatchAgent(agent, 'barrier-merger', `Reconcile the lane outputs for topic "${TOPIC}" into one MERGE plan. Identify conflicts between worktrees, exact integration order, shared-file ownership, and any human approvals required.\n${barrierRef({ runId: RUN_ID, phase: 'Implement', label: 'implement:summary', summary: `${implementations.length} lanes complete across ${lanes.length} planned` }).promptBlock}`, { role: 'implementer', risk: 'high', capabilities: ['structured-output', 'reasoning', 'architecture'], label: 'barrier-merge', phase: 'Barrier' })
|
|
82
|
-
writeArtifact({ runId: RUN_ID, phase: 'Barrier', label: 'barrier', payload: merge, summary: typeof merge === 'string' ? merge.slice(0, 200) : `barrier merge complete`, role: 'implementer' })
|
|
83
|
-
|
|
84
|
-
phase('Verify')
|
|
85
|
-
const verify = await dispatchAgent(agent, 'barrier-verifier', `Re-check this MERGE plan against the original scope for topic "${TOPIC}". Reject it if any lane output is missing, any conflict is unresolved, or any test gate is unbounded. Return the verified plan plus the exact gating tests.\n${barrierRef({ runId: RUN_ID, phase: 'Barrier', label: 'barrier', summary: `verify against scope: ${SCOPE.length} scope items` }).promptBlock}`, { role: 'adversarial', risk: 'high', capabilities: ['structured-output', 'reasoning'], label: 'barrier-verify', phase: 'Verify' })
|
|
86
|
-
|
|
87
|
-
phase('Synthesis')
|
|
88
|
-
const synthesis = await dispatchAgent(agent, 'integration-reporter', `Produce the final integration report for topic "${TOPIC}". State exact integration order, remaining gates, evidence commands to run, and any required human approvals. Do not claim success without fresh command evidence.\n${barrierRef({ runId: RUN_ID, phase: 'Barrier', label: 'barrier', summary: `merge plan: ${typeof merge === 'string' ? merge.slice(0, 120) : 'complex'}` }).promptBlock}\n${barrierRef({ runId: RUN_ID, phase: 'Verify', label: 'barrier-verify', summary: typeof verify === 'string' ? verify.slice(0, 120) : 'verified' }).promptBlock}`, { role: 'implementer', risk: 'medium', capabilities: ['structured-output', 'reasoning'], label: 'integration-report', phase: 'Synthesis' })
|
|
89
|
-
|
|
90
41
|
return {
|
|
91
42
|
status: 'ready-for-integration',
|
|
92
43
|
topic: TOPIC,
|
|
93
44
|
lanes,
|
|
94
|
-
implementations,
|
|
95
|
-
|
|
96
|
-
verify,
|
|
97
|
-
synthesis,
|
|
45
|
+
implementations: completed,
|
|
46
|
+
next: 'Merge queued worktrees and run integration verification in the primary session.',
|
|
98
47
|
}
|
package/package.json
CHANGED