@juzser/blacksmith 0.4.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.
Files changed (131) hide show
  1. package/.claude/agents/{auditor.md → bs-auditor.md} +5 -5
  2. package/.claude/agents/{coder.md → bs-coder.md} +8 -7
  3. package/.claude/agents/{grader.md → bs-grader.md} +4 -4
  4. package/.claude/agents/{merger.md → bs-merger.md} +4 -4
  5. package/.claude/agents/{planner.md → bs-planner.md} +20 -6
  6. package/.claude/agents/{researcher.md → bs-researcher.md} +6 -5
  7. package/.claude/agents/{reviewer.md → bs-reviewer.md} +13 -5
  8. package/.claude/agents/{scribe.md → bs-scribe.md} +11 -2
  9. package/.claude/agents/{security-reviewer.md → bs-security-reviewer.md} +6 -6
  10. package/.claude/agents/{spec-reviewer.md → bs-spec-reviewer.md} +15 -10
  11. package/.claude/agents/{tester.md → bs-tester.md} +40 -12
  12. package/.claude/agents/bs-uiux.md +257 -0
  13. package/.claude/agents/{verifier.md → bs-verifier.md} +4 -4
  14. package/.claude/agents/{wave-runner.md → bs-wave-runner.md} +14 -5
  15. package/.claude/skills/bs/SKILL.md +99 -21
  16. package/.claude/skills/bs/audit.md +34 -27
  17. package/.claude/skills/bs/dispatch.md +100 -50
  18. package/.claude/skills/bs/lessons.md +28 -14
  19. package/.claude/skills/bs/mcp.md +4 -4
  20. package/.claude/skills/bs/new.md +3 -3
  21. package/.claude/skills/bs/plan.md +27 -18
  22. package/.claude/skills/bs/report.md +11 -11
  23. package/.claude/skills/bs/run.md +181 -49
  24. package/.claude/skills/bs/status.md +14 -7
  25. package/.claude/skills/bs/ui.md +21 -5
  26. package/.claude/skills/bs/waivers.md +78 -2
  27. package/.claude/skills/bs/wave.md +109 -53
  28. package/README.md +53 -32
  29. package/factory/orchestrator/dist/agentNames.js +43 -0
  30. package/factory/orchestrator/dist/agents-registry.js +11 -4
  31. package/factory/orchestrator/dist/agentsSync.js +48 -14
  32. package/factory/orchestrator/dist/artifacts.js +31 -0
  33. package/factory/orchestrator/dist/attribution.js +4 -0
  34. package/factory/orchestrator/dist/audit.js +125 -17
  35. package/factory/orchestrator/dist/budgetAlarm.js +132 -37
  36. package/factory/orchestrator/dist/budgets.js +302 -84
  37. package/factory/orchestrator/dist/claims.js +9 -19
  38. package/factory/orchestrator/dist/cli.js +790 -100
  39. package/factory/orchestrator/dist/cliName.js +27 -0
  40. package/factory/orchestrator/dist/crosscheck.js +136 -8
  41. package/factory/orchestrator/dist/daemon.js +154 -36
  42. package/factory/orchestrator/dist/db/projector.js +278 -29
  43. package/factory/orchestrator/dist/db/queries.js +1831 -94
  44. package/factory/orchestrator/dist/db/schema.js +57 -4
  45. package/factory/orchestrator/dist/declaredArtifactLine.js +18 -0
  46. package/factory/orchestrator/dist/delegation.js +86 -19
  47. package/factory/orchestrator/dist/diffstat.js +15 -8
  48. package/factory/orchestrator/dist/dispatchLint.js +13 -37
  49. package/factory/orchestrator/dist/effort.js +14 -2
  50. package/factory/orchestrator/dist/effortTiers.js +13 -0
  51. package/factory/orchestrator/dist/env.js +26 -0
  52. package/factory/orchestrator/dist/epic.js +323 -38
  53. package/factory/orchestrator/dist/epicBudget.js +93 -0
  54. package/factory/orchestrator/dist/epicWidth.js +22 -5
  55. package/factory/orchestrator/dist/errorIssues.js +10 -4
  56. package/factory/orchestrator/dist/escalation.js +2 -2
  57. package/factory/orchestrator/dist/events.js +105 -3
  58. package/factory/orchestrator/dist/feedback.js +169 -0
  59. package/factory/orchestrator/dist/findingContext.js +26 -0
  60. package/factory/orchestrator/dist/findings.js +83 -12
  61. package/factory/orchestrator/dist/gate.js +120 -10
  62. package/factory/orchestrator/dist/goalCheck.js +2 -2
  63. package/factory/orchestrator/dist/harness.js +25 -17
  64. package/factory/orchestrator/dist/hookDecision.js +92 -3
  65. package/factory/orchestrator/dist/integration.js +9 -7
  66. package/factory/orchestrator/dist/judgePreflight.js +25 -4
  67. package/factory/orchestrator/dist/judgeRoles.js +47 -0
  68. package/factory/orchestrator/dist/judgeStopHook.js +90 -32
  69. package/factory/orchestrator/dist/judges.js +419 -21
  70. package/factory/orchestrator/dist/lessons.js +190 -11
  71. package/factory/orchestrator/dist/mcp.js +6 -6
  72. package/factory/orchestrator/dist/paths.js +10 -7
  73. package/factory/orchestrator/dist/plan.js +269 -33
  74. package/factory/orchestrator/dist/planQuorum.js +5 -2
  75. package/factory/orchestrator/dist/policy.js +1275 -30
  76. package/factory/orchestrator/dist/projectName.js +25 -0
  77. package/factory/orchestrator/dist/promptCapture.js +306 -0
  78. package/factory/orchestrator/dist/promptHook.js +33 -0
  79. package/factory/orchestrator/dist/promptImport.js +281 -0
  80. package/factory/orchestrator/dist/prompts.js +13 -0
  81. package/factory/orchestrator/dist/providers/schema-validate.js +2 -2
  82. package/factory/orchestrator/dist/providers/types.js +1 -1
  83. package/factory/orchestrator/dist/queue.js +4 -4
  84. package/factory/orchestrator/dist/quorum.js +18 -5
  85. package/factory/orchestrator/dist/results.js +25 -7
  86. package/factory/orchestrator/dist/roadmap.js +10 -5
  87. package/factory/orchestrator/dist/run-cli.js +15 -10
  88. package/factory/orchestrator/dist/runner.js +2 -2
  89. package/factory/orchestrator/dist/schemas.js +56 -8
  90. package/factory/orchestrator/dist/severity.js +8 -4
  91. package/factory/orchestrator/dist/spec.js +177 -13
  92. package/factory/orchestrator/dist/specChange.js +13 -9
  93. package/factory/orchestrator/dist/stack.js +6 -6
  94. package/factory/orchestrator/dist/storeRoot.js +90 -0
  95. package/factory/orchestrator/dist/symbols.js +261 -6
  96. package/factory/orchestrator/dist/taskEvents.js +179 -27
  97. package/factory/orchestrator/dist/taskStatus.js +14 -0
  98. package/factory/orchestrator/dist/testerAudit.js +31 -12
  99. package/factory/orchestrator/dist/testgate.js +16 -13
  100. package/factory/orchestrator/dist/uiuxGate.js +83 -0
  101. package/factory/orchestrator/dist/usage.js +97 -40
  102. package/factory/orchestrator/dist/waivers.js +39 -5
  103. package/factory/orchestrator/dist/waveBudget.js +2 -0
  104. package/factory/orchestrator/dist/waveConcurrency.js +143 -7
  105. package/factory/orchestrator/dist/workroot.js +1 -1
  106. package/factory/orchestrator/dist/worktree.js +61 -19
  107. package/factory/orchestrator/drizzle/0013_operator_feedback.sql +19 -0
  108. package/factory/orchestrator/drizzle/0014_misty_madripoor.sql +2 -0
  109. package/factory/orchestrator/drizzle/0015_gifted_omega_flight.sql +1 -0
  110. package/factory/orchestrator/drizzle/0016_milestones_project_default.sql +30 -0
  111. package/factory/orchestrator/drizzle/0017_events_raw_cli_session_id.sql +2 -0
  112. package/factory/orchestrator/drizzle/meta/_journal.json +35 -0
  113. package/factory/policies/budgets.yml +123 -33
  114. package/factory/policies/crosscheck.yml +51 -13
  115. package/factory/policies/delegation.yml +3 -3
  116. package/factory/policies/effort.yml +18 -10
  117. package/factory/policies/guardrails.yml +8 -6
  118. package/factory/policies/harness.yml +5 -5
  119. package/factory/policies/lessons.md +53 -2
  120. package/factory/policies/scheduler.yml +4 -4
  121. package/factory/policies/sensitive-paths.yml +1 -1
  122. package/factory/policies/severity.yml +13 -1
  123. package/factory/policies/stack.yml +6 -6
  124. package/factory/policies/taxonomy.yml +9 -3
  125. package/factory/policies/worktree.yml +15 -9
  126. package/factory/specs/roadmap.md +7 -7
  127. package/factory/specs/schema/event.schema.json +8 -1
  128. package/factory/specs/schema/result.schema.json +21 -8
  129. package/factory/specs/schema/task-spec.schema.json +12 -0
  130. package/package.json +16 -12
  131. package/.claude/agents/uiux.md +0 -133
@@ -7,13 +7,13 @@ on demand knows only what is in the log, never whether anyone was reading it.
7
7
 
8
8
  ## 1. Ask the watcher
9
9
 
10
- `smith daemon status [--dir <dir>]`. It prints JSON and **exits 1 whenever a
10
+ `bs daemon status [--dir <dir>]`. It prints JSON and **exits 1 whenever a
11
11
  current daemon is not watching** — that is the health check doing its job,
12
12
  not a command that failed, so never report the non-zero exit as an error.
13
- Three readings:
13
+ Four readings:
14
14
 
15
15
  - `running: false` — nothing holds the lock. A fine steady state if the
16
- operator never started one (`smith daemon start` if they want one): say so
16
+ operator never started one (`bs daemon start` if they want one): say so
17
17
  in a clause and go to step 2, which folds the log live anyway. What you
18
18
  must not do is render `lastTick` as though it were current.
19
19
  - `stale: true` — a daemon holds the lock and has published nothing for
@@ -24,6 +24,10 @@ Three readings:
24
24
  `<dir>/daemon.log`.
25
25
  - `running: true, stale: false` — the numbers are fresh. Say how fresh
26
26
  (`reportAgeSeconds`) rather than implying "now".
27
+ - an `error` object with `code: "daemon.unreadable-state"` — the daemon's
28
+ own state is unreadable, so nobody can say whether one is running. Lead
29
+ with it, name the file from `error.details.path`, and pass on the
30
+ recovery in `error.message`. Never render it as `running: false`.
27
31
 
28
32
  When there is a `lastTick`, render its triage split before the digest below,
29
33
  because it is the only record of what happened while nobody was watching:
@@ -34,20 +38,23 @@ its own and needs no decision.
34
38
 
35
39
  ## 2. Fold the log
36
40
 
37
- `smith stats overview [--session <id>]`. Render the JSON as a short digest,
41
+ `bs stats overview [--session <id>]`. Render the JSON as a short digest,
38
42
  field for field — never invent numbers the query didn't return:
39
43
 
40
44
  - **Live agents**: `liveAgents` grouped by role/model/provider,
41
45
  `liveAgentCount` as the headline (with `liveAgentCountDelta5m` as a
42
46
  ▲/▼ note).
43
- - **Budget**: `tokensByEpic`, called out as "X% of the 4,000,000-token epic
44
- cap" (`budgets.yml`) — flag any epic ≥70% (`budgets.yml` `alarm_ratio`).
47
+ - **Budget**: `tokensByEpic`, called out as "X% of the epic cap" — the cap
48
+ for the epic's effort tier in `budgets.yml` (4M small / 16M medium / 32M
49
+ huge; `bs budget alarm` prints the cap it judged each epic against) — flag any epic ≥70% (`budgets.yml` `alarm_ratio`).
45
50
  `tokensByEpic` counts recorded spend only, so it is a floor: say "at least
46
- X%", and run `smith budget alarm <session>` for the projection that prices
51
+ X%", and run `bs budget alarm <session>` for the projection that prices
47
52
  the dispatches nothing recorded a Result for.
48
53
  - **In flight**: `epicsInFlight`, `recentDispatches` (last 10).
49
54
  - **Alerts**: `alerts.escalations` and `alerts.pendingWaivers` — call these
50
55
  out first if non-zero, they're what the operator came here to see.
56
+ `pendingWaivers` is narrower than `bs waivers pending`: only waivers on
57
+ finished work (completed tasks, closed epics).
51
58
  - **Milestones**: `milestoneProgress` — one line each, `%` complete.
52
59
 
53
60
  If the UI is already running, just point the operator at it instead of
@@ -1,14 +1,30 @@
1
1
  # `/bs ui` — serve the local dashboard
2
2
 
3
- `smith ui serve [--port 4680] [--db state/smith.db] [--state-dir …]
4
- [--roadmap-path …]`. If it errors `ui.not-built`, run `pnpm build:ui`
5
- first (builds `ui/server/dist` + `ui/dist`), then retry, and print the local
6
- URL (`http://127.0.0.1:<port>`).
3
+ `bs ui serve [--port 4680] [--db state/smith.db] [--state-dir …]
4
+ [--roadmap-path …] [--claude-config-dir …] [--store <dir>…]`. If it errors `ui.not-built`,
5
+ run `pnpm build:ui` first (builds `ui/server/dist` + `ui/dist`), then retry,
6
+ and print the local URL (`http://127.0.0.1:<port>`).
7
+
8
+ Besides the event log, the server reads — read-only — the local Claude Code
9
+ session registry, transcripts and prompt history, from `--claude-config-dir`, else
10
+ `$CLAUDE_CONFIG_DIR`, else Claude Code's default, and serves them as
11
+ `GET /api/cli-sessions`, loopback-only.
12
+
13
+ Home, Kanban and the project list also read every project with a live CLI
14
+ session: its git toplevel is a store when `<top>/.blacksmith/state/events` or
15
+ `<top>/state/events` exists (rechecked at most every 5 s), and `--store <dir>`
16
+ adds one by hand. Foreign stores are strictly read; their projection lives in
17
+ `state/ui-stores/<storeId>.db` of the served clone, kept for a grace period after a
18
+ store drops and pruned at startup when stale. In a foreign store an untagged row reads
19
+ as the store's label, any other explicit project as itself, so `?project=X` returns each
20
+ matching row once. These routes and `/api/tasks/*` are local-only, like
21
+ `/api/cli-sessions`. A foreign Kanban card opens through `?store=<storeId>`; its history,
22
+ artifacts and waivers stay empty. Details: `docs/guide/dashboard.md`.
7
23
 
8
24
  **That build needs a clone.** `ui/` is not in the package's `files`, so an
9
25
  install — npm or plugin — ships no dashboard at all and `ui.not-built` is the
10
26
  permanent answer there, not a stale build. Check before you advise: if there
11
- is no `ui/` beside the `smith` you are running, say the dashboard is
27
+ is no `ui/` beside the `bs` you are running, say the dashboard is
12
28
  clone-only and stop, rather than sending the operator to a `pnpm` script they
13
29
  do not have.
14
30
 
@@ -1,13 +1,89 @@
1
1
  # `/bs waivers` — answer the pending waiver batch
2
2
 
3
- 1. `smith waivers pending <epic> --session <session-id>` — every S3/S4
3
+ 1. `bs waivers pending <epic> --session <session-id>` — every S3/S4
4
4
  finding for the epic with no waiver decision yet (a decided fingerprint
5
5
  never resurfaces).
6
6
  2. Present the whole batch to the operator as **one** question ("ignore
7
7
  these?") — never ask per-finding (architecture §11, `severity.yml`).
8
8
  3. Write the answers to `decisions.json`
9
9
  (`Array<{fingerprint, decision: "granted"|"denied", operatorNote}>`) and
10
- run `smith waivers apply decisions.json --session ... --plan-version ...
10
+ run `bs waivers apply decisions.json --session ... --plan-version ...
11
11
  --causal-parent ... --actor operator`. S1/S2 findings are rejected by
12
12
  the command itself if attempted — they go through the escalation ladder
13
13
  instead, never a waiver.
14
+ Task waivers are a separate step. A task whose plan row says `waived`
15
+ carries no gate run, and the epic close holds it as a blocker ("waived
16
+ without operator approval") until the operator's approval is recorded.
17
+ Ask the operator, then run `bs waivers approve-task <task-id> --note
18
+ "<why>" --session ... --causal-parent ... --actor operator`. Only the
19
+ operator actor is accepted; an approval written under any other actor is
20
+ refused, and one appended by hand under another actor is not honoured. An
21
+ approval is tied to the plan version it is given under (`--plan-version`):
22
+ a task waived again by a newer plan needs a fresh approval.
23
+ 4. A denial discharges nothing — the finding stays open with no further
24
+ move of its own, and an open finding blocks the epic verdict. The
25
+ command's output carries `findingIdsToCarry`: every finding id this batch
26
+ denied. Carry the whole list forward until each id is closed — by the
27
+ route its **scope** allows (`bs findings list --session <id>` shows
28
+ the scope), by a later grant, or by `refuted`. The two scopes do not share
29
+ a route:
30
+
31
+ - **`spec` finding** (owned by `<epic>/integration`) — cut a
32
+ `bs plan amend`, and its `--findings` must name every spec id from
33
+ the list, or the finding stays open forever with no path back (issue
34
+ #221). The amendment moves it to `amend-pending`; epic close moves it to
35
+ `amended`. See `docs/guide/operator-guide/findings.md` §6a.
36
+ - **`diff` finding** — `plan amend` refuses it
37
+ (`plan.amendment-not-spec-scoped`): the code is wrong, not the plan. If
38
+ the owning task is still open, bounce the finding to it like any gate
39
+ finding. If the owner has already merged, fix it in a **follow-up
40
+ task**:
41
+ 1. Write a draft, `Array<{filePath, finding}>` (the `gate run`
42
+ findings shape). `filePath` is the denied record's `file_path`;
43
+ `finding` is the **whole** record `bs findings list` prints,
44
+ minus the three fields raise derives and overwrites —
45
+ `fingerprint`, `file_path` and `finding_status`. Keep the rest:
46
+ the schema requires `failure_scenario` and `found_by` too, and a
47
+ draft without them fails with `findings.invalid-record`. Leave
48
+ `task_id`, `finding_id` and `epic_id` as they are; routing rewrites
49
+ them. Do not touch `finding_category` or `summary`: the follow-up
50
+ id is derived from the fingerprint they make.
51
+ 2. Raise it against the plan with no `--task`:
52
+ `bs findings raise --plan <plan.json> --findings <draft.json>
53
+ --session <id> --plan-version <n> --causal-parent <event-id>
54
+ --actor operator`. Routing sees the owner is closed and mints
55
+ `<epic>/followup-<fp8>` — read `taskId` from the printed row
56
+ (`attribution: follow-up`). A denied fingerprint is not suppressed
57
+ on a re-raise; a granted one is.
58
+ 3. Check its claims before dispatching. A follow-up **inherits the
59
+ owner's whole claim set** — and when ownership of the file was
60
+ ambiguous, the **union** of every candidate's claims — so it can
61
+ overlap tasks still in flight. `bs wave next <plan.json>
62
+ --session <id> --causal-parent <event-id>` shows it: it folds in
63
+ live status and logged follow-ups, and defers an overlapping task
64
+ with `claim-overlap`. `wave check` validates only the ids it is
65
+ given and cannot see the conflict.
66
+ 4. Dispatch it like any task, with these gaps filled by hand. It
67
+ carries no acceptance criteria and no budget: **write the ACs into
68
+ the dispatch brief** (the finding's fix, plus the test that proves
69
+ it). `wave check` prices it at the coder cap, and `gate run` reports
70
+ its budget as not-declared. It is not in the plan, so
71
+ `lessons for-dispatch`, `findings for-dispatch` and `coverage check`
72
+ refuse it with `cli.task-not-in-plan`: splice
73
+ `bs lessons for-dispatch <role> --case-type bugfix` without
74
+ `--plan`/`--task` — at the cost of every claim-path lesson, since
75
+ claims are then `[]` and those lessons drop silently — and replace the findings block with
76
+ `bs findings list --session <id> --task <epic>/followup-<fp8>`.
77
+ At its gate, run `gate run` **without** `--plan`. With it, a judge
78
+ finding on the merged owner's file routes to yet another follow-up
79
+ (listed under `reattributedFindings`) instead of blocking this one,
80
+ and that stray follow-up then blocks the epic until it completes or
81
+ its findings are waived. Without it the finding blocks here, at the
82
+ cost of coverage checking the total rather than the follow-up's
83
+ claims.
84
+ 5. Close **both** findings by hand once the follow-up merges — nothing
85
+ links the re-raised finding to the denied one, and neither moves on
86
+ its own. Walk each with `bs findings transition <finding-id>
87
+ <status> --session <id> --plan-version <n> --causal-parent
88
+ <event-id> --actor operator` through `confirmed`, `fix-pending`, `fix-landed`,
89
+ `fix-verified` (start at the first edge its status allows).
@@ -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, and fetched text is wrapped before it is quoted. None of it is
14
- restated here; a second copy is a copy that drifts.
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 `smith session
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 `smith gate run`
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. `smith delegation check` resolves your session by matching a
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 `smith delegation check` reads exactly
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
- `smith session start <wave-id>-takeover --continues <dead-session>#<n>`.
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 — `smith queue run` rebases one task at a time onto
118
- `smith/<epic>/integration` and runs the cumulative tests between. Merging
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: `smith worktree create
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,14 +141,14 @@ 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 `smith research check --brief <path>`
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 `smith prompt wrap`.
143
- 4. Dispatch **`coder`** (`.claude/agents/coder.md`) in that worktree.
144
- Token/diff caps (`budgets.yml`: 150k tokens, 400 diff lines) and YAGNI
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:
@@ -150,7 +157,7 @@ one thing this playbook never asks you to.
150
157
 
151
158
  Once every coder of the wave has been dispatched — this fan-out has gone
152
159
  out for every admitted task — audit the wave's own parallelism while it
153
- can still be corrected: `smith wave audit --session <id> --epic <epic>`.
160
+ can still be corrected: `bs wave audit --session <id> --epic <epic>`.
154
161
  `wave check` is the step-1 gate that certified, before any dispatch, that
155
162
  these tasks could run together; `wave audit` reads the log back after
156
163
  dispatch and says whether they did.
@@ -167,20 +174,50 @@ one thing this playbook never asks you to.
167
174
  - `unobserved` — the log holds no dispatch for any task this wave
168
175
  admitted. That is a defect in the log, not in how the wave ran; record
169
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.
170
182
  - `parallel` or `single` — nothing further; the wave ran as wide as it
171
183
  was admitted.
172
- 5. Dispatch **`tester`** (`.claude/agents/tester.md`) for missing unit
184
+ 5. Dispatch **`bs-tester`** (`.claude/agents/bs-tester.md`) for missing unit
173
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.
174
196
  - Then the **uiux visual pass**, but only when all three hold: the task
175
197
  is UI-affecting, the tester actually returned screenshot artifacts, and
176
- step 3 wrote a uiux spec for this task (agent-interviews.md N-6). Miss
177
- any one and skip it — say so; a "visual pass" over screenshots that do
178
- not exist is worse than no pass. Dispatch it with the spec path and the
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
179
203
  image paths and **nothing else**: no diff, no components, no test code.
180
204
  The pass is a judgment about the rendered screen, and a uiux session
181
205
  that reads the diff ends up reviewing intent, which the reviewer
182
206
  already covers.
183
- 6. Dispatch **`grader`** (`.claude/agents/grader.md`) — bounded rubric
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
184
221
  loop, `profile.graderRounds` rounds (2 at `huge`, 1 below; never more
185
222
  than 2, agent-constraints.md "grader (v3)"), before any gate
186
223
  runs; it never decides pass/fail itself. At one round the grader scores
@@ -189,12 +226,23 @@ one thing this playbook never asks you to.
189
226
  gates nothing (D-34). A round-2 `fail` re-scopes via dispatch.md's "Round
190
227
  counting and escalation", not a round 3 on this id.
191
228
  7. Run the gate pipeline:
192
- `smith gate run <task-id> --worktree <dir> --checks checks.json --result
229
+ `bs gate run <task-id> --worktree <dir> --checks checks.json --result
193
230
  result.json --grader state/results/<task-id>.grader-r<round>.json
194
- --findings findings.json --session ... --plan-version N
231
+ --findings findings.json --plan <plan.json> --uiux-visual
232
+ state/results/<task-id>.uiux-visual.json --session ... --plan-version N
195
233
  --causal-parent ...` (schema check → grader verdict → tests → coverage
196
234
  evidence → findings intake → severity decision,
197
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.
198
246
  - `--grader` takes step 6's file, latest round. A criterion that came back
199
247
  `fail` or `partial` blocks before any check command runs — the outcome is
200
248
  `blocked` with `reason: "grader-fail"`, and the payload's
@@ -205,11 +253,11 @@ one thing this playbook never asks you to.
205
253
  add (D-40). `blocked` with `reason: "coverage-evidence"` means the run
206
254
  produced no per-file number for a file this task's claims name; fix the
207
255
  reporter or the include glob, not the code. Ask the same question
208
- outside a gate run with `smith coverage check <worktree-dir> --plan
256
+ outside a gate run with `bs coverage check <worktree-dir> --plan
209
257
  <plan.json> --task <task-id>` (§5c).
210
- - Findings come from dispatching **`reviewer`**
211
- (`.claude/agents/reviewer.md`, fresh context, read-only) then
212
- **`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
213
261
  mandate) — never skip the adversarial stage on an S1/S2 finding
214
262
  (`crosscheck.yml` `asymmetric_roles`). Which findings that covers is
215
263
  `profile.verifierSeverities`, plus a
@@ -219,25 +267,26 @@ one thing this playbook never asks you to.
219
267
  thing a cheaper tier drops; `S1-stop-the-line` is in every tier's list
220
268
  and no tier may remove it. A finding outside the tier's list still goes
221
269
  to the coder — it is verified by the coder's fix, not waived.
222
- - Dispatch **`security-reviewer`** only when its conditional triggers
270
+ - Dispatch **`bs-security-reviewer`** only when its conditional triggers
223
271
  fire — never per-task by default. Ask, do not recall:
224
- `smith security triggers --task <spec.json>` and dispatch iff
272
+ `bs security triggers --task <spec.json>` and dispatch iff
225
273
  `dispatchSecurityReviewer` is true ("Dispatching the security-reviewer"
226
274
  above).
227
275
  - Every judge dispatched in steps 5–7 — uiux visual pass, grader,
228
276
  reviewer, verifier, security-reviewer — is bracketed by
229
- `smith worktree fingerprint` / `smith worktree verify`
277
+ `bs worktree fingerprint` / `bs worktree verify`
230
278
  ("Fingerprint the worktree around every judge" above). Exit 1 discards
231
279
  that judge's result; it does not become a finding against the coder.
232
- - Each of those is also bracketed by `smith judge dispatch` /
233
- `smith judge report` ("Declare each judge's artifact before you dispatch
234
- it" above). `smith judge outstanding --task <task-id>` exits 1 while any
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
235
284
  judge still owes its file — re-poke it and report before running the
236
285
  gate, because the gate now refuses to score with a non-empty outstanding
237
286
  set (`reason: judges-outstanding`) rather than reading a silent judge as
238
287
  zero findings.
239
288
  - The gate scores tests it did not write, so once it has run, make the log
240
- say who did: `smith tester check <session-id> --task <task-id>`
289
+ say who did: `bs tester check <session-id> --task <task-id>`
241
290
  (`crosscheck.yml` `role_isolation`, operator-guide/dispatch-audits.md §2d). Exit 1
242
291
  means no `tester` dispatch precedes this task's `testgate-result`, the
243
292
  coder and tester dispatches share one `agent_id`, or the answer is
@@ -246,9 +295,9 @@ one thing this playbook never asks you to.
246
295
  downstream still goes green — step 5 is what prevents that, and this is
247
296
  the log checking that step 5 happened.
248
297
  8. Every dispatch in steps 3–7 carries the compiled lessons block for that
249
- role (`smith lessons for-dispatch <role> --plan … --task …`), every
298
+ role (`bs lessons for-dispatch <role> --plan … --task …`), every
250
299
  worktree dispatch also carries the open-findings block for that task
251
- (`smith findings for-dispatch --plan … --task …`) — both under "Dispatch
300
+ (`bs findings for-dispatch --plan … --task …`) — both under "Dispatch
252
301
  contract" above — and each is a `dispatch_decision` event with a `causal_parent`
253
302
  chaining back to the prompt/decision that caused it (architecture §7) —
254
303
  this is the timeline, not optional bookkeeping.
@@ -259,22 +308,25 @@ one thing this playbook never asks you to.
259
308
  rung, never loop past one. Count the rounds from this task's
260
309
  `dispatch_decision` events, not from the agent's own account of itself
261
310
  — see "Round counting and escalation" above. Then have the log check
262
- you: `smith escalation check <session-id> --task <task-id>`, which
311
+ you: `bs escalation check <session-id> --task <task-id>`, which
263
312
  exits 1 if the rung you just climbed is not evidenced.
264
313
  10. Gate outcome `pass`/`pass-with-waivers-pending` → before admitting, audit
265
- the wave's own parallelism: `smith wave audit --session <id> --epic
314
+ the wave's own parallelism: `bs wave audit --session <id> --epic
266
315
  <epic>`. It reads the log back and says whether the tasks admitted
267
316
  together actually ran together — `parallel` when every admitted task was
268
317
  in flight at once, `partial` when two or more overlapped but never all,
269
318
  `serialized` when work is recorded but no two tasks ever overlapped,
270
319
  `single` when the wave admitted one task (nothing to be parallel about),
271
- and `unobserved` when the wave was admitted and the log shows no work for
272
- any of its tasks. Exit 1 on `serialized`, exit 2 on `unobserved` — either
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
273
325
  is a fact for the wave-loop's hand-back, not a reason to hold the merge:
274
326
  report the verdict and keep going to admission. Then ask what the diff
275
327
  did to everyone outside the claims, and whether it
276
328
  kept what the spec promised:
277
- `smith claims impact <worktree-dir> <spec.json>`. Exit 1 means a
329
+ `bs claims impact <worktree-dir> <spec.json>`. Exit 1 means a
278
330
  `proven` break — this task removed an export a file outside its claims
279
331
  still imports — **or a broken promise**: a file the spec's
280
332
  `keeps_exports` names lost an export or changed one's declaration.
@@ -282,10 +334,14 @@ one thing this playbook never asks you to.
282
334
  entries in the bounce. A `possible` / `signature-changed` entry in
283
335
  `breaks` exits 0 and is a note: the scanner reads text, not types
284
336
  (operator-guide/wave.md §2). Then admit into the
285
- merge queue: `smith queue run <epic> --project <project-dir>
286
- --test-cmd "<cumulative test command>" --tasks tasks.json`. On a
287
- `rebase-conflict` outcome, dispatch **`merger`**
288
- (`.claude/agents/merger.md`) with both diffs + specs, and note three
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
289
345
  things the queue's own behaviour forces (agent-interviews.md N-12):
290
346
  - **Into the failing task's existing worktree** — a fresh one would not
291
347
  reproduce the conflict. The result gives you only `taskId` and
@@ -296,7 +352,7 @@ one thing this playbook never asks you to.
296
352
  - The queue already ran `git rebase --abort` (`queue.ts`), so the
297
353
  merger arrives at a clean tree and **replays** the rebase rather than
298
354
  resuming it — and it **never lands the merge**. When it returns
299
- `resolution: "mechanical"`, re-run `smith queue run`; the queue stays
355
+ `resolution: "mechanical"`, re-run `bs queue run`; the queue stays
300
356
  the single place a merge can happen.
301
357
 
302
358
  `resolution: "escalated"` — low confidence, or both sides changed the
@@ -305,10 +361,10 @@ one thing this playbook never asks you to.
305
361
  `conflict_resolution_ladder`).
306
362
 
307
363
  An `integration-dirty` outcome names a `worktree` holding
308
- `smith/<epic>/integration` with uncommitted tracked changes (`dirty`):
309
- commit or stash those there, then re-run `smith queue run`.
364
+ `bs/<epic>/integration` with uncommitted tracked changes (`dirty`):
365
+ commit or stash those there, then re-run `bs queue run`.
310
366
 
311
- Once the wave is merged, check the epic's spend — `smith budget alarm
367
+ Once the wave is merged, check the epic's spend — `bs budget alarm
312
368
  <session-id> [--epic <epic>]`. Exit 1 means either the epic crossed
313
369
  `alarm_ratio` (re-plan the remaining work to fit, or ask the operator —
314
370
  extension is always an operator question) **or** the log is too holey to