@juzser/blacksmith 0.3.0 → 0.5.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/.claude/agents/{auditor.md → bs-auditor.md} +8 -5
- package/.claude/agents/{coder.md → bs-coder.md} +8 -7
- package/.claude/agents/{grader.md → bs-grader.md} +16 -9
- package/.claude/agents/{merger.md → bs-merger.md} +4 -4
- package/.claude/agents/{planner.md → bs-planner.md} +20 -6
- package/.claude/agents/{researcher.md → bs-researcher.md} +6 -5
- package/.claude/agents/{reviewer.md → bs-reviewer.md} +16 -5
- package/.claude/agents/{scribe.md → bs-scribe.md} +11 -2
- package/.claude/agents/{security-reviewer.md → bs-security-reviewer.md} +9 -6
- package/.claude/agents/{spec-reviewer.md → bs-spec-reviewer.md} +18 -10
- package/.claude/agents/{tester.md → bs-tester.md} +40 -12
- package/.claude/agents/bs-uiux.md +257 -0
- package/.claude/agents/{verifier.md → bs-verifier.md} +7 -4
- package/.claude/agents/{wave-runner.md → bs-wave-runner.md} +14 -5
- package/.claude/skills/bs/SKILL.md +99 -21
- package/.claude/skills/bs/audit.md +45 -31
- package/.claude/skills/bs/dispatch.md +133 -47
- package/.claude/skills/bs/lessons.md +28 -14
- package/.claude/skills/bs/mcp.md +4 -4
- package/.claude/skills/bs/new.md +3 -3
- package/.claude/skills/bs/plan.md +57 -16
- package/.claude/skills/bs/report.md +11 -11
- package/.claude/skills/bs/run.md +190 -52
- package/.claude/skills/bs/status.md +14 -7
- package/.claude/skills/bs/ui.md +21 -5
- package/.claude/skills/bs/waivers.md +78 -2
- package/.claude/skills/bs/wave.md +143 -50
- package/README.md +53 -32
- package/factory/orchestrator/dist/agentNames.js +43 -0
- package/factory/orchestrator/dist/agents-registry.js +11 -4
- package/factory/orchestrator/dist/agentsSync.js +70 -13
- package/factory/orchestrator/dist/artifacts.js +31 -0
- package/factory/orchestrator/dist/attribution.js +4 -0
- package/factory/orchestrator/dist/audit.js +245 -20
- package/factory/orchestrator/dist/autonomy.js +6 -2
- package/factory/orchestrator/dist/budgetAlarm.js +132 -37
- package/factory/orchestrator/dist/budgets.js +302 -84
- package/factory/orchestrator/dist/claims.js +9 -19
- package/factory/orchestrator/dist/cli.js +1087 -116
- package/factory/orchestrator/dist/cliName.js +27 -0
- package/factory/orchestrator/dist/crossFinding.js +20 -0
- package/factory/orchestrator/dist/crosscheck.js +136 -8
- package/factory/orchestrator/dist/daemon.js +352 -67
- package/factory/orchestrator/dist/db/projector.js +327 -54
- package/factory/orchestrator/dist/db/queries.js +1831 -94
- package/factory/orchestrator/dist/db/schema.js +57 -4
- package/factory/orchestrator/dist/declaredArtifactLine.js +18 -0
- package/factory/orchestrator/dist/delegation.js +86 -19
- package/factory/orchestrator/dist/diffstat.js +15 -8
- package/factory/orchestrator/dist/dispatchLint.js +108 -0
- package/factory/orchestrator/dist/effort.js +14 -2
- package/factory/orchestrator/dist/effortTiers.js +13 -0
- package/factory/orchestrator/dist/env.js +26 -0
- package/factory/orchestrator/dist/epic.js +494 -42
- package/factory/orchestrator/dist/epicBudget.js +93 -0
- package/factory/orchestrator/dist/epicWidth.js +22 -5
- package/factory/orchestrator/dist/errorIssues.js +182 -11
- package/factory/orchestrator/dist/escalation.js +2 -2
- package/factory/orchestrator/dist/events.js +164 -14
- package/factory/orchestrator/dist/feedback.js +169 -0
- package/factory/orchestrator/dist/findingContext.js +26 -0
- package/factory/orchestrator/dist/findings.js +108 -14
- package/factory/orchestrator/dist/gate.js +120 -10
- package/factory/orchestrator/dist/gh.js +19 -2
- package/factory/orchestrator/dist/git.js +20 -2
- package/factory/orchestrator/dist/goalCheck.js +2 -2
- package/factory/orchestrator/dist/harness.js +25 -17
- package/factory/orchestrator/dist/hookDecision.js +411 -39
- package/factory/orchestrator/dist/integration.js +20 -8
- package/factory/orchestrator/dist/issueReporter.js +81 -16
- package/factory/orchestrator/dist/judgePreflight.js +25 -4
- package/factory/orchestrator/dist/judgeRoles.js +47 -0
- package/factory/orchestrator/dist/judgeStopHook.js +239 -0
- package/factory/orchestrator/dist/judges.js +419 -21
- package/factory/orchestrator/dist/lessons.js +190 -11
- package/factory/orchestrator/dist/logCache.js +291 -0
- package/factory/orchestrator/dist/mcp.js +6 -6
- package/factory/orchestrator/dist/paths.js +20 -7
- package/factory/orchestrator/dist/plan.js +399 -33
- package/factory/orchestrator/dist/planQuorum.js +5 -2
- package/factory/orchestrator/dist/policy.js +1275 -30
- package/factory/orchestrator/dist/projectName.js +25 -0
- package/factory/orchestrator/dist/projects.js +12 -1
- package/factory/orchestrator/dist/promptCapture.js +306 -0
- package/factory/orchestrator/dist/promptHook.js +33 -0
- package/factory/orchestrator/dist/promptImport.js +281 -0
- package/factory/orchestrator/dist/prompts.js +13 -0
- package/factory/orchestrator/dist/providers/schema-validate.js +2 -2
- package/factory/orchestrator/dist/providers/types.js +1 -1
- package/factory/orchestrator/dist/queue.js +471 -30
- package/factory/orchestrator/dist/quorum.js +18 -5
- package/factory/orchestrator/dist/results.js +25 -7
- package/factory/orchestrator/dist/roadmap.js +10 -5
- package/factory/orchestrator/dist/run-cli.js +15 -10
- package/factory/orchestrator/dist/runner.js +2 -2
- package/factory/orchestrator/dist/scheduler.js +3 -3
- package/factory/orchestrator/dist/schemas.js +56 -8
- package/factory/orchestrator/dist/severity.js +8 -4
- package/factory/orchestrator/dist/spec.js +246 -12
- package/factory/orchestrator/dist/specChange.js +13 -9
- package/factory/orchestrator/dist/stack.js +6 -6
- package/factory/orchestrator/dist/storeRoot.js +90 -0
- package/factory/orchestrator/dist/symbols.js +261 -6
- package/factory/orchestrator/dist/taskEvents.js +192 -31
- package/factory/orchestrator/dist/taskStatus.js +14 -0
- package/factory/orchestrator/dist/taxonomy.js +11 -0
- package/factory/orchestrator/dist/testerAudit.js +31 -12
- package/factory/orchestrator/dist/testgate.js +20 -16
- package/factory/orchestrator/dist/uiuxGate.js +83 -0
- package/factory/orchestrator/dist/usage.js +113 -48
- package/factory/orchestrator/dist/waivers.js +99 -12
- package/factory/orchestrator/dist/waveBudget.js +2 -0
- package/factory/orchestrator/dist/waveConcurrency.js +143 -7
- package/factory/orchestrator/dist/waveSchedule.js +84 -0
- package/factory/orchestrator/dist/workroot.js +1 -1
- package/factory/orchestrator/dist/worktree.js +76 -21
- package/factory/orchestrator/drizzle/0013_operator_feedback.sql +19 -0
- package/factory/orchestrator/drizzle/0014_misty_madripoor.sql +2 -0
- package/factory/orchestrator/drizzle/0015_gifted_omega_flight.sql +1 -0
- package/factory/orchestrator/drizzle/0016_milestones_project_default.sql +30 -0
- package/factory/orchestrator/drizzle/0017_events_raw_cli_session_id.sql +2 -0
- package/factory/orchestrator/drizzle/meta/_journal.json +35 -0
- package/factory/policies/budgets.yml +123 -33
- package/factory/policies/crosscheck.yml +51 -13
- package/factory/policies/delegation.yml +3 -3
- package/factory/policies/effort.yml +18 -10
- package/factory/policies/guardrails.yml +8 -6
- package/factory/policies/harness.yml +5 -5
- package/factory/policies/lessons.md +53 -2
- package/factory/policies/scheduler.yml +4 -4
- package/factory/policies/sensitive-paths.yml +1 -1
- package/factory/policies/severity.yml +13 -1
- package/factory/policies/stack.yml +6 -6
- package/factory/policies/taxonomy.yml +9 -3
- package/factory/policies/worktree.yml +15 -9
- package/factory/specs/roadmap.md +27 -3
- package/factory/specs/schema/event.schema.json +8 -1
- package/factory/specs/schema/result.schema.json +21 -8
- package/factory/specs/schema/task-spec.schema.json +12 -0
- package/package.json +16 -12
- package/.claude/agents/uiux.md +0 -133
|
@@ -10,8 +10,9 @@ bind every dispatch below: the project directory is an answer you ask for
|
|
|
10
10
|
rather than a path anything here knows, every write carries
|
|
11
11
|
`--session <id> --plan-version <n> --causal-parent <event-id>`, every
|
|
12
12
|
dispatch carries the compiled lessons block and that task's open-findings
|
|
13
|
-
block,
|
|
14
|
-
|
|
13
|
+
block, every `dispatch_decision` carries a one-line plain-words `reason`
|
|
14
|
+
(dispatch.md's payload contract), and fetched text is wrapped before it is
|
|
15
|
+
quoted. None of it is restated here; a second copy is a copy that drifts.
|
|
15
16
|
|
|
16
17
|
The step numbers are the loop's own. They start at 2 because step 1 — which
|
|
17
18
|
tasks, and how wide — was answered before you were handed anything, and they
|
|
@@ -26,15 +27,17 @@ both:
|
|
|
26
27
|
- **Inline**, in the epic session, when the wave is small enough that the
|
|
27
28
|
epic's window can carry it. Nothing changes: the session id you were
|
|
28
29
|
handed is the one you write to.
|
|
29
|
-
- **In a session of its own** when it is not. Open one with `
|
|
30
|
+
- **In a session of its own** when it is not. Open one with `bs session
|
|
30
31
|
start <wave-id> --continues <event-id>` and write every dispatch below
|
|
31
32
|
there. The epic session then carries the admission and the result rather
|
|
32
33
|
than every turn in between — which is the point, because an epic outlives
|
|
33
34
|
its waves, and a window spent on this wave's dispatches is a window the
|
|
34
|
-
epic does not have for the wave after next (D13).
|
|
35
|
+
epic does not have for the wave after next (D13). Every event you write in
|
|
36
|
+
that session carries `--actor wave-runner` — `.claude/agents/bs-wave-runner.md`'s
|
|
37
|
+
Constraints has the rule and the check it satisfies.
|
|
35
38
|
|
|
36
39
|
After any `error-logged` this wave writes — a worker that died, a judge turn
|
|
37
|
-
that failed, a gate refusal recorded as an error — or a `
|
|
40
|
+
that failed, a gate refusal recorded as an error — or a `bs gate run`
|
|
38
41
|
that came back `blocked` (its `gate-outcome` event with outcome `blocked`),
|
|
39
42
|
or a task the wave marks `failed` (its `task-added` event with payload
|
|
40
43
|
`task_status: failed`), the wave runs the reporting verb once, per
|
|
@@ -46,9 +49,9 @@ not cosmetic — it is the edge an audit walks:
|
|
|
46
49
|
|
|
47
50
|
- **The epic ran you** and you opened a wave session yourself: continue from
|
|
48
51
|
the epic event that admitted this wave.
|
|
49
|
-
- **You were dispatched as a `wave-runner`**: continue from the
|
|
52
|
+
- **You were dispatched as a `bs-wave-runner`**: continue from the
|
|
50
53
|
`dispatch_decision` that dispatched you, the event id handed to you with
|
|
51
|
-
the wave. `
|
|
54
|
+
the wave. `bs delegation check` resolves your session by matching a
|
|
52
55
|
`session-start`'s `causal_parent` against that dispatch, and it is the
|
|
53
56
|
only thing that proves the dispatches you are about to write are your
|
|
54
57
|
own log's rather than an agent talking about itself. Continue from
|
|
@@ -57,12 +60,12 @@ not cosmetic — it is the edge an audit walks:
|
|
|
57
60
|
for the same reason: a grant is earned by owning the log first.
|
|
58
61
|
- **You are taking over a wave whose runner died or capped**: continue from
|
|
59
62
|
that runner's last event, in a session of your own. Do not write into its
|
|
60
|
-
log. One session has one author, and `
|
|
63
|
+
log. One session has one author, and `bs delegation check` reads exactly
|
|
61
64
|
that: dispatches appended to a dead delegate's log by whoever picked the
|
|
62
65
|
wave up are reported as violations, correctly, because from the log's side
|
|
63
66
|
they cannot be told apart from an agent narrating its own work. Open your
|
|
64
67
|
own instead:
|
|
65
|
-
`
|
|
68
|
+
`bs session start <wave-id>-takeover --continues <dead-session>#<n>`.
|
|
66
69
|
That keeps the chain whole — the epic's folded reads still see every
|
|
67
70
|
dispatch on both sides of the handover — and it makes the takeover a fact
|
|
68
71
|
in the log rather than something a reader has to infer from two agents and
|
|
@@ -114,18 +117,22 @@ the difference between a factory and a queue with extra steps:
|
|
|
114
117
|
keep the wave moving.
|
|
115
118
|
|
|
116
119
|
The single place the wave rejoins is step 10's merge queue, which is
|
|
117
|
-
serial on purpose — `
|
|
118
|
-
`
|
|
120
|
+
serial on purpose — `bs queue run` rebases one task at a time onto
|
|
121
|
+
`bs/<epic>/integration` and runs the cumulative tests between. Merging
|
|
119
122
|
is the one thing that cannot be done in parallel, and it is already the
|
|
120
123
|
one thing this playbook never asks you to.
|
|
121
124
|
|
|
122
125
|
## The steps
|
|
123
126
|
|
|
124
|
-
2. Per admitted task, and for all of them together: `
|
|
127
|
+
2. Per admitted task, and for all of them together: `bs worktree create
|
|
125
128
|
<project-dir> <epic> <task-id>`.
|
|
126
|
-
3. Pre-code, if the task needs it: dispatch `researcher` for an unknown, or
|
|
127
|
-
`uiux` (`.claude/agents/uiux.md`) for any UI-affecting acceptance
|
|
128
|
-
criterion — before the coder starts, not after.
|
|
129
|
+
3. Pre-code, if the task needs it: dispatch `bs-researcher` for an unknown, or
|
|
130
|
+
`bs-uiux` (`.claude/agents/bs-uiux.md`) for any UI-affecting acceptance
|
|
131
|
+
criterion — before the coder starts, not after. Bracket the uiux spec
|
|
132
|
+
turn with `bs judge dispatch --role uiux --kind spec` / `bs judge report
|
|
133
|
+
--role uiux --kind spec`, the same way every judge turn in steps 5–7 is
|
|
134
|
+
bracketed — the gate's uiux stage (step 7) reads this turn back and
|
|
135
|
+
blocks a flagged task that never closed it.
|
|
129
136
|
- `profile.preCodeResearch` gates the researcher: `when-needed` is the
|
|
130
137
|
line above, `never` (`small`) means the coder reading the repo *is* the
|
|
131
138
|
brief. An epic that genuinely cannot start without a research brief is
|
|
@@ -134,44 +141,108 @@ one thing this playbook never asks you to.
|
|
|
134
141
|
every tier: no tier ships a UI change without a spec to review it
|
|
135
142
|
against. It is listed as a knob so the answer is asked for rather than
|
|
136
143
|
assumed, not because a tier can turn it off.
|
|
137
|
-
- A returned brief goes through `
|
|
144
|
+
- A returned brief goes through `bs research check --brief <path>`
|
|
138
145
|
before it is attached to a task spec ("Fetched and quoted text goes into
|
|
139
146
|
a prompt fenced" above). Exit 1 sends it back; on exit 0, carry
|
|
140
147
|
`recommendation.provenance` into the coder's prompt so the task knows
|
|
141
148
|
whether its research rests on this repo or on a fetched page. Any raw
|
|
142
|
-
fetched text you quote alongside it is wrapped with `
|
|
143
|
-
4. Dispatch **`coder`** (`.claude/agents/coder.md`) in that worktree.
|
|
144
|
-
Token/diff caps (`budgets.yml
|
|
149
|
+
fetched text you quote alongside it is wrapped with `bs prompt wrap`.
|
|
150
|
+
4. Dispatch **`bs-coder`** (`.claude/agents/bs-coder.md`) in that worktree.
|
|
151
|
+
Token/diff caps (`budgets.yml`, sized per effort tier) and YAGNI
|
|
145
152
|
are the coder's own constraints — don't restate them here, the template
|
|
146
153
|
does. What the template does not read is the schema, so when the spec
|
|
147
154
|
carries `keeps_exports`, restate the promise in the dispatch in one line:
|
|
148
155
|
the file list, and "keep every existing export's name and declaration;
|
|
149
156
|
adding is fine".
|
|
150
|
-
|
|
157
|
+
|
|
158
|
+
Once every coder of the wave has been dispatched — this fan-out has gone
|
|
159
|
+
out for every admitted task — audit the wave's own parallelism while it
|
|
160
|
+
can still be corrected: `bs wave audit --session <id> --epic <epic>`.
|
|
161
|
+
`wave check` is the step-1 gate that certified, before any dispatch, that
|
|
162
|
+
these tasks could run together; `wave audit` reads the log back after
|
|
163
|
+
dispatch and says whether they did.
|
|
164
|
+
It is a read, so it costs nothing against "No lockstep barrier" above and
|
|
165
|
+
waits for no task. Read the `verdict` field (`WAVE_VERDICTS`,
|
|
166
|
+
`waveConcurrency.ts`), not only the exit code — `partial` also exits 0:
|
|
167
|
+
- `serialized` or `partial` — the wave is not running the width it was
|
|
168
|
+
admitted at, and steps 5–7 have not gone out yet, so it can still be
|
|
169
|
+
fixed: dispatch every remaining phase of this wave as one message of
|
|
170
|
+
parallel calls, per "One message, many dispatches" above, and record
|
|
171
|
+
the verdict as an operator-visible event — `error-logged` with error
|
|
172
|
+
`contract.constraint-decay` (`taxonomy.yml`): the wave-runner stopped
|
|
173
|
+
honoring that section's standing constraint.
|
|
174
|
+
- `unobserved` — the log holds no dispatch for any task this wave
|
|
175
|
+
admitted. That is a defect in the log, not in how the wave ran; record
|
|
176
|
+
it the same way, as `error-logged` with error `execution.env-failure`.
|
|
177
|
+
- `unlinked` — the wave's work merged, or was dispatched, off the epic's
|
|
178
|
+
own lineage (a wave-runner session started without `--continues`).
|
|
179
|
+
Real work, not a defect in dispatch, but its width is unmeasured —
|
|
180
|
+
record it as `error-logged` with error `contract.constraint-decay` so
|
|
181
|
+
the session gets linked under `--continues` next time.
|
|
182
|
+
- `parallel` or `single` — nothing further; the wave ran as wide as it
|
|
183
|
+
was admitted.
|
|
184
|
+
5. Dispatch **`bs-tester`** (`.claude/agents/bs-tester.md`) for missing unit
|
|
151
185
|
coverage and epic-level e2e/screenshots.
|
|
186
|
+
- The moment it returns, project its result: `bs results record --task
|
|
187
|
+
<task-id> --result <tester-result.json> --agent tester --provider <name>
|
|
188
|
+
--model-tier <tier> --worktree <dir> --session ... --plan-version N
|
|
189
|
+
--causal-parent ...`. `--agent` stamps the envelope, token_usage
|
|
190
|
+
included, as `gate run` does; `--worktree` is the coder's worktree the
|
|
191
|
+
tester shares, and records the HEAD that proves the screenshots fresh
|
|
192
|
+
(D2) — step 7's uiux stage blocks `screenshots-stale` when this HEAD
|
|
193
|
+
doesn't match the gate's. The tester has no tests of its own to run, so
|
|
194
|
+
it never reaches `gate run` itself — without this, its screenshots sit
|
|
195
|
+
in the result file and never reach the task page.
|
|
152
196
|
- Then the **uiux visual pass**, but only when all three hold: the task
|
|
153
197
|
is UI-affecting, the tester actually returned screenshot artifacts, and
|
|
154
|
-
step 3 wrote a uiux spec for this task (agent-interviews.md N-6).
|
|
155
|
-
any one and
|
|
156
|
-
|
|
198
|
+
step 3 wrote a uiux spec for this task (agent-interviews.md N-6). For a
|
|
199
|
+
UI-affecting task, miss any one and stop, do not skip — step 7's gate
|
|
200
|
+
blocks a flagged task with no fresh, reviewed screenshot set, so a
|
|
201
|
+
missing condition here is not a quiet pass-through to the gate, it is a
|
|
202
|
+
reason to fix the condition first. Dispatch it with the spec path and the
|
|
157
203
|
image paths and **nothing else**: no diff, no components, no test code.
|
|
158
204
|
The pass is a judgment about the rendered screen, and a uiux session
|
|
159
205
|
that reads the diff ends up reviewing intent, which the reviewer
|
|
160
206
|
already covers.
|
|
161
|
-
|
|
207
|
+
Bracket it with `bs judge dispatch --role uiux --kind visual` / `bs
|
|
208
|
+
judge report --role uiux --kind visual`: a dispatch with no `--kind` is
|
|
209
|
+
refused (`judges.kind-required`), because a kindless turn is one the gate's
|
|
210
|
+
uiux stage never reads.
|
|
211
|
+
- **Blast radius.** When the task's claims touch a shared primitive (a
|
|
212
|
+
design-system/kit component or a global stylesheet), a screenshot of
|
|
213
|
+
the task's own screens is not the full surface: the tester re-shoots
|
|
214
|
+
and the uiux visual pass reviews every screen that renders that
|
|
215
|
+
primitive, not only the task's screens. The tester determines that
|
|
216
|
+
list — from the screenshot suite already on disk or from the
|
|
217
|
+
primitive's importers — and records it alongside the screenshots it
|
|
218
|
+
captures. The "max 4 per feature" screenshot cap (bs-tester.md) still applies per
|
|
219
|
+
screen; it is the screen count that grows, not the shots on each one.
|
|
220
|
+
6. Dispatch **`bs-grader`** (`.claude/agents/bs-grader.md`) — bounded rubric
|
|
162
221
|
loop, `profile.graderRounds` rounds (2 at `huge`, 1 below; never more
|
|
163
222
|
than 2, agent-constraints.md "grader (v3)"), before any gate
|
|
164
223
|
runs; it never decides pass/fail itself. At one round the grader scores
|
|
165
224
|
once and a failed criterion is the coder's bounce, not a re-grade. Its result file is an **input to
|
|
166
225
|
step 7**, not a note to the operator: pass it as `--grader` or the rubric
|
|
167
|
-
gates nothing (D-34).
|
|
226
|
+
gates nothing (D-34). A round-2 `fail` re-scopes via dispatch.md's "Round
|
|
227
|
+
counting and escalation", not a round 3 on this id.
|
|
168
228
|
7. Run the gate pipeline:
|
|
169
|
-
`
|
|
229
|
+
`bs gate run <task-id> --worktree <dir> --checks checks.json --result
|
|
170
230
|
result.json --grader state/results/<task-id>.grader-r<round>.json
|
|
171
|
-
--findings findings.json --
|
|
231
|
+
--findings findings.json --plan <plan.json> --uiux-visual
|
|
232
|
+
state/results/<task-id>.uiux-visual.json --session ... --plan-version N
|
|
172
233
|
--causal-parent ...` (schema check → grader verdict → tests → coverage
|
|
173
234
|
evidence → findings intake → severity decision,
|
|
174
235
|
`docs/guide/operator-guide/queue-and-gate.md` §5).
|
|
236
|
+
- `--plan` is what lets the gate's uiux stage read the task's
|
|
237
|
+
`ui_affecting` flag; without it the stage records "unverifiable" and
|
|
238
|
+
does not block. `--uiux-visual` hands over the visual pass's result
|
|
239
|
+
file when step 5 ran one — omit it for a task that is not UI-affecting.
|
|
240
|
+
- `checks.json`'s unit check must be the project's full test command,
|
|
241
|
+
never a hand-picked list of files — `gate run` runs each check command
|
|
242
|
+
literally, with no narrowing of its own. Narrowing by changed files is
|
|
243
|
+
`bs queue run --select-test-cmd` (`factory/orchestrator/src/testSelect.ts`),
|
|
244
|
+
a queue-tier flag `--batch` refuses; it does not reach `checks.json`. A
|
|
245
|
+
scoped-down unit check is how red tests have merged before.
|
|
175
246
|
- `--grader` takes step 6's file, latest round. A criterion that came back
|
|
176
247
|
`fail` or `partial` blocks before any check command runs — the outcome is
|
|
177
248
|
`blocked` with `reason: "grader-fail"`, and the payload's
|
|
@@ -182,11 +253,11 @@ one thing this playbook never asks you to.
|
|
|
182
253
|
add (D-40). `blocked` with `reason: "coverage-evidence"` means the run
|
|
183
254
|
produced no per-file number for a file this task's claims name; fix the
|
|
184
255
|
reporter or the include glob, not the code. Ask the same question
|
|
185
|
-
outside a gate run with `
|
|
256
|
+
outside a gate run with `bs coverage check <worktree-dir> --plan
|
|
186
257
|
<plan.json> --task <task-id>` (§5c).
|
|
187
|
-
- Findings come from dispatching **`reviewer`**
|
|
188
|
-
(`.claude/agents/reviewer.md`, fresh context, read-only) then
|
|
189
|
-
**`verifier`** (`.claude/agents/verifier.md`, adversarial refute
|
|
258
|
+
- Findings come from dispatching **`bs-reviewer`**
|
|
259
|
+
(`.claude/agents/bs-reviewer.md`, fresh context, read-only) then
|
|
260
|
+
**`bs-verifier`** (`.claude/agents/bs-verifier.md`, adversarial refute
|
|
190
261
|
mandate) — never skip the adversarial stage on an S1/S2 finding
|
|
191
262
|
(`crosscheck.yml` `asymmetric_roles`). Which findings that covers is
|
|
192
263
|
`profile.verifierSeverities`, plus a
|
|
@@ -196,25 +267,26 @@ one thing this playbook never asks you to.
|
|
|
196
267
|
thing a cheaper tier drops; `S1-stop-the-line` is in every tier's list
|
|
197
268
|
and no tier may remove it. A finding outside the tier's list still goes
|
|
198
269
|
to the coder — it is verified by the coder's fix, not waived.
|
|
199
|
-
- Dispatch **`security-reviewer`** only when its conditional triggers
|
|
270
|
+
- Dispatch **`bs-security-reviewer`** only when its conditional triggers
|
|
200
271
|
fire — never per-task by default. Ask, do not recall:
|
|
201
|
-
`
|
|
272
|
+
`bs security triggers --task <spec.json>` and dispatch iff
|
|
202
273
|
`dispatchSecurityReviewer` is true ("Dispatching the security-reviewer"
|
|
203
274
|
above).
|
|
204
275
|
- Every judge dispatched in steps 5–7 — uiux visual pass, grader,
|
|
205
276
|
reviewer, verifier, security-reviewer — is bracketed by
|
|
206
|
-
`
|
|
277
|
+
`bs worktree fingerprint` / `bs worktree verify`
|
|
207
278
|
("Fingerprint the worktree around every judge" above). Exit 1 discards
|
|
208
279
|
that judge's result; it does not become a finding against the coder.
|
|
209
|
-
- Each of those is also bracketed by `
|
|
210
|
-
`
|
|
211
|
-
it" above)
|
|
280
|
+
- Each of those is also bracketed by `bs judge dispatch` /
|
|
281
|
+
`bs judge report` ("Declare each judge's artifact before you dispatch
|
|
282
|
+
it" above); paste `judge dispatch`'s `expected_line` inline in the
|
|
283
|
+
judge's prompt, or the `Agent` call is refused. `bs judge outstanding --task <task-id>` exits 1 while any
|
|
212
284
|
judge still owes its file — re-poke it and report before running the
|
|
213
285
|
gate, because the gate now refuses to score with a non-empty outstanding
|
|
214
286
|
set (`reason: judges-outstanding`) rather than reading a silent judge as
|
|
215
287
|
zero findings.
|
|
216
288
|
- The gate scores tests it did not write, so once it has run, make the log
|
|
217
|
-
say who did: `
|
|
289
|
+
say who did: `bs tester check <session-id> --task <task-id>`
|
|
218
290
|
(`crosscheck.yml` `role_isolation`, operator-guide/dispatch-audits.md §2d). Exit 1
|
|
219
291
|
means no `tester` dispatch precedes this task's `testgate-result`, the
|
|
220
292
|
coder and tester dispatches share one `agent_id`, or the answer is
|
|
@@ -223,9 +295,9 @@ one thing this playbook never asks you to.
|
|
|
223
295
|
downstream still goes green — step 5 is what prevents that, and this is
|
|
224
296
|
the log checking that step 5 happened.
|
|
225
297
|
8. Every dispatch in steps 3–7 carries the compiled lessons block for that
|
|
226
|
-
role (`
|
|
298
|
+
role (`bs lessons for-dispatch <role> --plan … --task …`), every
|
|
227
299
|
worktree dispatch also carries the open-findings block for that task
|
|
228
|
-
(`
|
|
300
|
+
(`bs findings for-dispatch --plan … --task …`) — both under "Dispatch
|
|
229
301
|
contract" above — and each is a `dispatch_decision` event with a `causal_parent`
|
|
230
302
|
chaining back to the prompt/decision that caused it (architecture §7) —
|
|
231
303
|
this is the timeline, not optional bookkeeping.
|
|
@@ -236,12 +308,25 @@ one thing this playbook never asks you to.
|
|
|
236
308
|
rung, never loop past one. Count the rounds from this task's
|
|
237
309
|
`dispatch_decision` events, not from the agent's own account of itself
|
|
238
310
|
— see "Round counting and escalation" above. Then have the log check
|
|
239
|
-
you: `
|
|
311
|
+
you: `bs escalation check <session-id> --task <task-id>`, which
|
|
240
312
|
exits 1 if the rung you just climbed is not evidenced.
|
|
241
|
-
10. Gate outcome `pass`/`pass-with-waivers-pending` → before admitting,
|
|
242
|
-
|
|
313
|
+
10. Gate outcome `pass`/`pass-with-waivers-pending` → before admitting, audit
|
|
314
|
+
the wave's own parallelism: `bs wave audit --session <id> --epic
|
|
315
|
+
<epic>`. It reads the log back and says whether the tasks admitted
|
|
316
|
+
together actually ran together — `parallel` when every admitted task was
|
|
317
|
+
in flight at once, `partial` when two or more overlapped but never all,
|
|
318
|
+
`serialized` when work is recorded but no two tasks ever overlapped,
|
|
319
|
+
`single` when the wave admitted one task (nothing to be parallel about),
|
|
320
|
+
`unlinked` when the work merged or dispatched but off the epic's own
|
|
321
|
+
lineage (width unmeasured, not zero — usually a wave-runner session
|
|
322
|
+
started without `--continues`), and `unobserved` when the wave was
|
|
323
|
+
admitted and the log shows no work for any of its tasks. Exit 1 on
|
|
324
|
+
`serialized`, exit 2 on `unobserved`, exit 0 on `unlinked` — each
|
|
325
|
+
is a fact for the wave-loop's hand-back, not a reason to hold the merge:
|
|
326
|
+
report the verdict and keep going to admission. Then ask what the diff
|
|
327
|
+
did to everyone outside the claims, and whether it
|
|
243
328
|
kept what the spec promised:
|
|
244
|
-
`
|
|
329
|
+
`bs claims impact <worktree-dir> <spec.json>`. Exit 1 means a
|
|
245
330
|
`proven` break — this task removed an export a file outside its claims
|
|
246
331
|
still imports — **or a broken promise**: a file the spec's
|
|
247
332
|
`keeps_exports` names lost an export or changed one's declaration.
|
|
@@ -249,10 +334,14 @@ one thing this playbook never asks you to.
|
|
|
249
334
|
entries in the bounce. A `possible` / `signature-changed` entry in
|
|
250
335
|
`breaks` exits 0 and is a note: the scanner reads text, not types
|
|
251
336
|
(operator-guide/wave.md §2). Then admit into the
|
|
252
|
-
merge queue: `
|
|
253
|
-
--test-cmd "<cumulative test command>" --tasks tasks.json
|
|
254
|
-
|
|
255
|
-
|
|
337
|
+
merge queue: `bs queue run <epic> --project <project-dir>
|
|
338
|
+
--test-cmd "<cumulative test command>" --tasks tasks.json --plan
|
|
339
|
+
<plan.json> --session <id> --plan-version <n> --causal-parent
|
|
340
|
+
<event-id>` — the same write triple as every other dispatch above; a
|
|
341
|
+
merge that lands with no session to log it into is a fact the
|
|
342
|
+
timeline never gets. On a
|
|
343
|
+
`rebase-conflict` outcome, dispatch **`bs-merger`**
|
|
344
|
+
(`.claude/agents/bs-merger.md`) with both diffs + specs, and note three
|
|
256
345
|
things the queue's own behaviour forces (agent-interviews.md N-12):
|
|
257
346
|
- **Into the failing task's existing worktree** — a fresh one would not
|
|
258
347
|
reproduce the conflict. The result gives you only `taskId` and
|
|
@@ -263,7 +352,7 @@ one thing this playbook never asks you to.
|
|
|
263
352
|
- The queue already ran `git rebase --abort` (`queue.ts`), so the
|
|
264
353
|
merger arrives at a clean tree and **replays** the rebase rather than
|
|
265
354
|
resuming it — and it **never lands the merge**. When it returns
|
|
266
|
-
`resolution: "mechanical"`, re-run `
|
|
355
|
+
`resolution: "mechanical"`, re-run `bs queue run`; the queue stays
|
|
267
356
|
the single place a merge can happen.
|
|
268
357
|
|
|
269
358
|
`resolution: "escalated"` — low confidence, or both sides changed the
|
|
@@ -271,7 +360,11 @@ one thing this playbook never asks you to.
|
|
|
271
360
|
auto-resolve a semantic conflict silently (`worktree.yml`
|
|
272
361
|
`conflict_resolution_ladder`).
|
|
273
362
|
|
|
274
|
-
|
|
363
|
+
An `integration-dirty` outcome names a `worktree` holding
|
|
364
|
+
`bs/<epic>/integration` with uncommitted tracked changes (`dirty`):
|
|
365
|
+
commit or stash those there, then re-run `bs queue run`.
|
|
366
|
+
|
|
367
|
+
Once the wave is merged, check the epic's spend — `bs budget alarm
|
|
275
368
|
<session-id> [--epic <epic>]`. Exit 1 means either the epic crossed
|
|
276
369
|
`alarm_ratio` (re-plan the remaining work to fit, or ask the operator —
|
|
277
370
|
extension is always an operator question) **or** the log is too holey to
|
package/README.md
CHANGED
|
@@ -125,7 +125,7 @@ provider.
|
|
|
125
125
|
**The log is the source of truth**
|
|
126
126
|
|
|
127
127
|
Every prompt, dispatch, gate result and error is an append-only event on disk.
|
|
128
|
-
The dashboard is a projection of that log, and `
|
|
128
|
+
The dashboard is a projection of that log, and `bs db rebuild` reconstructs
|
|
129
129
|
the entire database from the log alone. Nothing the factory did exists only in
|
|
130
130
|
a chat transcript.
|
|
131
131
|
|
|
@@ -168,18 +168,27 @@ and two inside Claude Code:
|
|
|
168
168
|
/plugin install blacksmith@blacksmith
|
|
169
169
|
```
|
|
170
170
|
|
|
171
|
-
|
|
171
|
+
Two optional plugin modules come from the same marketplace, bs-mod (a live
|
|
172
|
+
HUD of your epic above the prompt) and pr-mod (the repo's open pull requests),
|
|
173
|
+
and [`docs/guide/mods.md`](docs/guide/mods.md) has what each needs:
|
|
174
|
+
|
|
175
|
+
```
|
|
176
|
+
/plugin install bs-mod@blacksmith
|
|
177
|
+
/plugin install pr-mod@blacksmith
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Then `bs init` in the project you want it to work on. That creates
|
|
172
181
|
`.blacksmith/` beside your code — the event log, your epic plans, a roadmap and
|
|
173
|
-
a `stack.yml` to answer — and writes nothing anywhere else; `
|
|
182
|
+
a `stack.yml` to answer — and writes nothing anywhere else; `BS_HOME` moves
|
|
174
183
|
that root if you want one home for several projects. You now have `/bs`.
|
|
175
184
|
|
|
176
185
|
**Both halves are required.** The package
|
|
177
186
|
([`@juzser/blacksmith`](https://www.npmjs.com/package/@juzser/blacksmith)) is the
|
|
178
|
-
deterministic `
|
|
179
|
-
dispatches. `
|
|
187
|
+
deterministic `bs` CLI; the plugin is `/bs` and the fourteen agent roles it
|
|
188
|
+
dispatches. `bs` alone never gives you `/bs`, because Claude Code loads
|
|
180
189
|
skills from a project's `.claude/`, your `~/.claude/`, or a plugin — never from
|
|
181
|
-
`node_modules` — and a `/bs` with no `
|
|
182
|
-
`latest`: `0.1.0` predates `
|
|
190
|
+
`node_modules` — and a `/bs` with no `bs` on PATH can run nothing. Take
|
|
191
|
+
`latest`: `0.1.0` predates `bs init` and keeps state inside its own install
|
|
183
192
|
directory, which the next `npm i` replaces.
|
|
184
193
|
|
|
185
194
|
<details>
|
|
@@ -197,17 +206,21 @@ bash scripts/check.sh # the gate CI runs; ends in `== PASS ==`
|
|
|
197
206
|
|
|
198
207
|
Open a Claude Code session in the clone and you have `/bs` already, from the
|
|
199
208
|
checkout's own `.claude/` — **do not also install the plugin there.** You would
|
|
200
|
-
get two of everything (`bs` and `blacksmith:bs`, `auditor` and
|
|
201
|
-
`blacksmith:auditor`, once per role), pay the always-on cost twice, and the two
|
|
209
|
+
get two of everything (`bs` and `blacksmith:bs`, `bs-auditor` and
|
|
210
|
+
`blacksmith:bs-auditor`, once per role), pay the always-on cost twice, and the two
|
|
202
211
|
copies are free to disagree: the plugin's is a pinned checkout of `main`, the
|
|
203
212
|
project's is whatever branch you have out. `claude plugin disable blacksmith`
|
|
204
|
-
settles it.
|
|
213
|
+
settles it. It also turns off the prompt recorder in that clone, which only
|
|
214
|
+
the plugin registers; a clone that wants its prompts recorded keeps the plugin
|
|
215
|
+
enabled and accepts the double listing
|
|
216
|
+
([INSTALL.md](INSTALL.md#the-install--bs-and-bs)).
|
|
205
217
|
|
|
206
218
|
The dashboard is clone-only because `ui/` is in neither the tarball nor the
|
|
207
|
-
plugin, so `
|
|
219
|
+
plugin, so `bs ui serve` answers `ui.not-built` in an install and means it.
|
|
208
220
|
Enforcement is clone-only because this repo's `.claude/settings.json` deny
|
|
209
|
-
rules and its policy hook resolve paths against a checkout; the plugin ships
|
|
210
|
-
`hooks/hooks.json
|
|
221
|
+
rules and its policy hook resolve paths against a checkout; the plugin ships one
|
|
222
|
+
`hooks/hooks.json`, holding only the non-blocking prompt-capture hook, and
|
|
223
|
+
loads neither the deny rules nor the policy guard, since a `/bs` that asked
|
|
211
224
|
you about every Bash command would be worse than one that asks about none. The
|
|
212
225
|
rest of that port is scoped in
|
|
213
226
|
[`docs/specs/plugin-port-scope.md`](docs/specs/plugin-port-scope.md).
|
|
@@ -233,9 +246,9 @@ would rather drive it yourself, it is three commands:
|
|
|
233
246
|
```bash
|
|
234
247
|
$EDITOR factory/policies/stack.yml # your stack answers: language, frontend,
|
|
235
248
|
# database, deploy target. `none` is fine.
|
|
236
|
-
|
|
249
|
+
bs stack check # which answers the templates honour,
|
|
237
250
|
# which they only record, which they refuse
|
|
238
|
-
|
|
251
|
+
bs new my-app --target-dir ~/code/my-app # add --ui for a frontend
|
|
239
252
|
```
|
|
240
253
|
|
|
241
254
|
The last call scaffolds (TS strict, Biome, Vitest, CI), installs, runs the
|
|
@@ -278,7 +291,7 @@ it is the ordinary loop: `/bs plan` against that epic, then `/bs run`, and
|
|
|
278
291
|
closing the epic marks its findings fixed. There is no audit-specific run
|
|
279
292
|
path; the command's value is the insight and the ranking.
|
|
280
293
|
|
|
281
|
-
Underneath it is the `
|
|
294
|
+
Underneath it is the `bs audit` family — `open`, `record`, `consolidate`,
|
|
282
295
|
`decide`, `cut`, `resolve`, `close` — and
|
|
283
296
|
[`docs/specs/audit-command-scope.md`](docs/specs/audit-command-scope.md) is the
|
|
284
297
|
contract each of them keeps.
|
|
@@ -300,10 +313,10 @@ Day to day, from a Claude Code session opened in this repo:
|
|
|
300
313
|
| `/bs lessons` | Review pending lesson candidates |
|
|
301
314
|
| `/bs report` | Render the scribe's progress digest |
|
|
302
315
|
|
|
303
|
-
Each of those is a playbook, not a script: the deterministic half is a `
|
|
304
|
-
command you can run yourself — `
|
|
316
|
+
Each of those is a playbook, not a script: the deterministic half is a `bs`
|
|
317
|
+
command you can run yourself — `bs --help` lists all of them — and the
|
|
305
318
|
judgment half is a Claude Code session the playbook dispatches. That is also
|
|
306
|
-
the line between the two installs: the `
|
|
319
|
+
the line between the two installs: the `bs` half travels in the package, the
|
|
307
320
|
playbooks are read from a clone.
|
|
308
321
|
|
|
309
322
|
→ **[The operator loop](docs/guide/operator-loop.md)** — the six steps, in the
|
|
@@ -319,7 +332,7 @@ v1. From there the loop admits a wave whose path claims do not overlap, sends
|
|
|
319
332
|
researcher and UI/UX work ahead of code where the epic needs it, runs a coder
|
|
320
333
|
and a tester in a worktree, grades the result against its own acceptance
|
|
321
334
|
criteria, then puts it through the gates and a serial merge queue into
|
|
322
|
-
`
|
|
335
|
+
`bs/<epic>/integration`. One epic, one integration PR, merged by you.
|
|
323
336
|
|
|
324
337
|
→ The pipeline diagram and the reasoning behind each stage:
|
|
325
338
|
**[architecture §3 — The loop](docs/specs/black-smith-architecture.md#3-the-loop)**.
|
|
@@ -339,8 +352,10 @@ runs, plus branch protection — not by trust. Full rules:
|
|
|
339
352
|
branch solely through the serial merge queue.
|
|
340
353
|
- **No autonomous deploy or outbound sends.** Deploys, publishes and message
|
|
341
354
|
sends each need per-invocation approval.
|
|
342
|
-
- **Budgets are declared per role
|
|
343
|
-
|
|
355
|
+
- **Budgets are declared per role and sized per effort tier.** A medium epic
|
|
356
|
+
gets 16M tokens with an alarm at 70%, and a coder task 220K tokens and 700
|
|
357
|
+
diff lines; a small epic gets half the task numbers and 4M, a huge one
|
|
358
|
+
double and 32M. Fan-out is bounded by the
|
|
344
359
|
claim graph, and `max_in_flight_tasks` is available on top of it, off by
|
|
345
360
|
default. Which of these *block* versus *report* is spelled out in
|
|
346
361
|
[`factory/policies/budgets.yml`](factory/policies/budgets.yml) — the task cap
|
|
@@ -351,11 +366,14 @@ a public issue.
|
|
|
351
366
|
|
|
352
367
|
## The dashboard
|
|
353
368
|
|
|
354
|
-
Optional, and deliberately small: `/bs ui` (or `
|
|
355
|
-
read-only pages to `127.0.0.1`. They are a projection of the event log
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
369
|
+
Optional, and deliberately small: `/bs ui` (or `bs ui serve`) binds eleven
|
|
370
|
+
read-only pages to `127.0.0.1`. They are a projection of the event log —
|
|
371
|
+
`bs db rebuild` reconstructs them from it, `bs stats` prints the same facts in
|
|
372
|
+
a terminal, and nothing you click there dispatches an agent. It also reads
|
|
373
|
+
the local Claude Code session registry, transcripts and prompt history (read-only;
|
|
374
|
+
`--claude-config-dir`, then `$CLAUDE_CONFIG_DIR`, then Claude Code's default),
|
|
375
|
+
served as `GET /api/cli-sessions` to loopback requests only. The factory runs
|
|
376
|
+
without ever opening the dashboard.
|
|
359
377
|
|
|
360
378
|
<table>
|
|
361
379
|
<tr valign="top">
|
|
@@ -386,14 +404,16 @@ shows you. It is part of the clone, not of the package.
|
|
|
386
404
|
|
|
387
405
|
Phases 1–9 are built and merged: loop runner, worktree engine, gates, state and
|
|
388
406
|
analytics, dashboard, self-extension, cross-provider judges, hardening. Phase 10
|
|
389
|
-
is half in: `
|
|
407
|
+
is half in: `bs daemon` watches the factory in the background and its ops
|
|
390
408
|
runbook is written; the hosted UI stays deferred. Beside the phases, `/bs audit`
|
|
391
409
|
is built: an existing project can be read on four axes and one epic cut from
|
|
392
410
|
what you accept.
|
|
393
411
|
|
|
394
|
-
The CLI is on npm as `@juzser/blacksmith`, at `0.
|
|
395
|
-
`
|
|
396
|
-
|
|
412
|
+
The CLI is on npm as `@juzser/blacksmith`, at `0.5.0`. Two binaries are
|
|
413
|
+
the ones you run: `bs`, and `bs-run` (since `0.2.0`), which runs a rendered
|
|
414
|
+
turn on a `cli` harness such as Codex. `bs-prompt-hook` is the lean hook
|
|
415
|
+
entry that records operator prompts. `smith` and `smith-run` remain as
|
|
416
|
+
deprecated aliases. `0.1.1` was the first version that ran beside
|
|
397
417
|
you and knew it was a package, rather than out of the clone-shaped install
|
|
398
418
|
`0.1.0` was. The package is the binaries and what they
|
|
399
419
|
read. The dashboard, the docs, the test suite and `/bs` itself come from a
|
|
@@ -423,8 +443,9 @@ version in
|
|
|
423
443
|
| [`docs/specs/audit-command-scope.md`](docs/specs/audit-command-scope.md) | What `/bs audit` promises an audited project, and why |
|
|
424
444
|
| [`docs/specs/plugin-port-scope.md`](docs/specs/plugin-port-scope.md) | How `/bs` runs without a clone, and what the plugin leaves behind |
|
|
425
445
|
| [`docs/guide/dashboard.md`](docs/guide/dashboard.md) | The dashboard tour |
|
|
446
|
+
| [`docs/guide/mods.md`](docs/guide/mods.md) | The optional bs-mod HUD and pr-mod |
|
|
426
447
|
| [`docs/runbooks/providers.md`](docs/runbooks/providers.md) | Setting up the cross-provider judges |
|
|
427
|
-
| [`docs/runbooks/ops.md`](docs/runbooks/ops.md) | Running `
|
|
448
|
+
| [`docs/runbooks/ops.md`](docs/runbooks/ops.md) | Running `bs daemon` unattended |
|
|
428
449
|
| [`docs/README.md`](docs/README.md) | Everything else, one line each |
|
|
429
450
|
|
|
430
451
|
Agents read [`AGENTS.md`](AGENTS.md) and [`CLAUDE.md`](CLAUDE.md) instead — this
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one place a role becomes a Claude Code subagent name, a template file
|
|
3
|
+
* name, and back. The role (`coder`) is data everywhere else: events,
|
|
4
|
+
* policies, ledgers, env names. Only the subagent carries the `bs-` prefix,
|
|
5
|
+
* so that it cannot collide with another plugin's or a user's `coder`.
|
|
6
|
+
*/
|
|
7
|
+
export const AGENT_PREFIX = 'bs-';
|
|
8
|
+
/** `coder` -> `bs-coder`: what `subagent_type` and a template's `name:` carry. */
|
|
9
|
+
export function agentNameFor(role) {
|
|
10
|
+
return `${AGENT_PREFIX}${role}`;
|
|
11
|
+
}
|
|
12
|
+
/** `coder` -> `bs-coder.md`: the template file under `.claude/agents/`. */
|
|
13
|
+
export function templateFileFor(role) {
|
|
14
|
+
return `${agentNameFor(role)}.md`;
|
|
15
|
+
}
|
|
16
|
+
/** `bs-coder.md` -> `coder`; null for a file that is not a `bs-<role>.md` template. */
|
|
17
|
+
export function roleOfTemplateFile(file) {
|
|
18
|
+
if (!file.endsWith('.md'))
|
|
19
|
+
return null;
|
|
20
|
+
return roleOfAgentType(file.slice(0, -'.md'.length), { bareAccepted: false });
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* What Claude Code puts in a hook's `agent_type` or a dispatch's
|
|
24
|
+
* `subagent_type` -> the role. Strips an optional `<namespace>:` and then the
|
|
25
|
+
* `bs-` prefix. For one transition the bare pre-prefix name (`reviewer`,
|
|
26
|
+
* `blacksmith:reviewer`) is accepted too, because a newer CLI can run with an
|
|
27
|
+
* older plugin still installed. Returns a *candidate* role, not a verified one:
|
|
28
|
+
* any other non-empty name (`general-purpose`) comes back as itself, so the
|
|
29
|
+
* caller must check the result against its own role set. Null only for an
|
|
30
|
+
* empty name, a bare `bs-` or `<ns>:`, or (with `bareAccepted: false`) a name
|
|
31
|
+
* without the prefix.
|
|
32
|
+
*/
|
|
33
|
+
export function roleOfAgentType(agentType, options = {}) {
|
|
34
|
+
const colon = agentType.lastIndexOf(':');
|
|
35
|
+
const name = colon === -1 ? agentType : agentType.slice(colon + 1);
|
|
36
|
+
if (name.startsWith(AGENT_PREFIX)) {
|
|
37
|
+
const role = name.slice(AGENT_PREFIX.length);
|
|
38
|
+
return role.length > 0 ? role : null;
|
|
39
|
+
}
|
|
40
|
+
if (options.bareAccepted === false)
|
|
41
|
+
return null;
|
|
42
|
+
return name.length > 0 ? name : null;
|
|
43
|
+
}
|
|
@@ -289,15 +289,22 @@ export function foldAgents(events) {
|
|
|
289
289
|
if (record.event_type === ERROR_EVENT_TYPE) {
|
|
290
290
|
const payload = record.payload;
|
|
291
291
|
// `task_ref` is this event's own spelling; eventTaskId covers the two
|
|
292
|
-
// the rest of the log uses (D-245).
|
|
292
|
+
// the rest of the log uses (D-245). `agent_role` is the dispatch's own
|
|
293
|
+
// key and this event's usual spelling, but a hand-written one may only
|
|
294
|
+
// carry `agent` (the Result file's spelling), so either key alone still
|
|
295
|
+
// finds the role -- `agent_role` tried first because it was already
|
|
296
|
+
// this fold's only key before `agent` was added as a fallback, same
|
|
297
|
+
// reason task-result-recorded above tries `agent` first for its own
|
|
298
|
+
// history; not the same order, because each fold keeps its own.
|
|
293
299
|
const taskId = eventTaskId(record) ?? payload.task_ref;
|
|
300
|
+
const role = payload.agent_role ?? payload.agent ?? null;
|
|
294
301
|
if (taskId) {
|
|
295
|
-
closeOpen(open, taskId,
|
|
302
|
+
closeOpen(open, taskId, role, event_id, record.ts, 'error');
|
|
296
303
|
}
|
|
297
|
-
else if (
|
|
304
|
+
else if (role) {
|
|
298
305
|
// Only with a role: a session-level error names nobody, and every
|
|
299
306
|
// epic-level agent stays where it was.
|
|
300
|
-
closeOpenEpicLevel(openEpicLevel, record.session_id,
|
|
307
|
+
closeOpenEpicLevel(openEpicLevel, record.session_id, role, payload.epic_id, event_id, record.ts, 'error');
|
|
301
308
|
}
|
|
302
309
|
continue;
|
|
303
310
|
}
|