@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.
Files changed (141) hide show
  1. package/.claude/agents/{auditor.md → bs-auditor.md} +8 -5
  2. package/.claude/agents/{coder.md → bs-coder.md} +8 -7
  3. package/.claude/agents/{grader.md → bs-grader.md} +16 -9
  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} +16 -5
  8. package/.claude/agents/{scribe.md → bs-scribe.md} +11 -2
  9. package/.claude/agents/{security-reviewer.md → bs-security-reviewer.md} +9 -6
  10. package/.claude/agents/{spec-reviewer.md → bs-spec-reviewer.md} +18 -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} +7 -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 +45 -31
  17. package/.claude/skills/bs/dispatch.md +133 -47
  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 +57 -16
  22. package/.claude/skills/bs/report.md +11 -11
  23. package/.claude/skills/bs/run.md +190 -52
  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 +143 -50
  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 +70 -13
  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 +245 -20
  35. package/factory/orchestrator/dist/autonomy.js +6 -2
  36. package/factory/orchestrator/dist/budgetAlarm.js +132 -37
  37. package/factory/orchestrator/dist/budgets.js +302 -84
  38. package/factory/orchestrator/dist/claims.js +9 -19
  39. package/factory/orchestrator/dist/cli.js +1087 -116
  40. package/factory/orchestrator/dist/cliName.js +27 -0
  41. package/factory/orchestrator/dist/crossFinding.js +20 -0
  42. package/factory/orchestrator/dist/crosscheck.js +136 -8
  43. package/factory/orchestrator/dist/daemon.js +352 -67
  44. package/factory/orchestrator/dist/db/projector.js +327 -54
  45. package/factory/orchestrator/dist/db/queries.js +1831 -94
  46. package/factory/orchestrator/dist/db/schema.js +57 -4
  47. package/factory/orchestrator/dist/declaredArtifactLine.js +18 -0
  48. package/factory/orchestrator/dist/delegation.js +86 -19
  49. package/factory/orchestrator/dist/diffstat.js +15 -8
  50. package/factory/orchestrator/dist/dispatchLint.js +108 -0
  51. package/factory/orchestrator/dist/effort.js +14 -2
  52. package/factory/orchestrator/dist/effortTiers.js +13 -0
  53. package/factory/orchestrator/dist/env.js +26 -0
  54. package/factory/orchestrator/dist/epic.js +494 -42
  55. package/factory/orchestrator/dist/epicBudget.js +93 -0
  56. package/factory/orchestrator/dist/epicWidth.js +22 -5
  57. package/factory/orchestrator/dist/errorIssues.js +182 -11
  58. package/factory/orchestrator/dist/escalation.js +2 -2
  59. package/factory/orchestrator/dist/events.js +164 -14
  60. package/factory/orchestrator/dist/feedback.js +169 -0
  61. package/factory/orchestrator/dist/findingContext.js +26 -0
  62. package/factory/orchestrator/dist/findings.js +108 -14
  63. package/factory/orchestrator/dist/gate.js +120 -10
  64. package/factory/orchestrator/dist/gh.js +19 -2
  65. package/factory/orchestrator/dist/git.js +20 -2
  66. package/factory/orchestrator/dist/goalCheck.js +2 -2
  67. package/factory/orchestrator/dist/harness.js +25 -17
  68. package/factory/orchestrator/dist/hookDecision.js +411 -39
  69. package/factory/orchestrator/dist/integration.js +20 -8
  70. package/factory/orchestrator/dist/issueReporter.js +81 -16
  71. package/factory/orchestrator/dist/judgePreflight.js +25 -4
  72. package/factory/orchestrator/dist/judgeRoles.js +47 -0
  73. package/factory/orchestrator/dist/judgeStopHook.js +239 -0
  74. package/factory/orchestrator/dist/judges.js +419 -21
  75. package/factory/orchestrator/dist/lessons.js +190 -11
  76. package/factory/orchestrator/dist/logCache.js +291 -0
  77. package/factory/orchestrator/dist/mcp.js +6 -6
  78. package/factory/orchestrator/dist/paths.js +20 -7
  79. package/factory/orchestrator/dist/plan.js +399 -33
  80. package/factory/orchestrator/dist/planQuorum.js +5 -2
  81. package/factory/orchestrator/dist/policy.js +1275 -30
  82. package/factory/orchestrator/dist/projectName.js +25 -0
  83. package/factory/orchestrator/dist/projects.js +12 -1
  84. package/factory/orchestrator/dist/promptCapture.js +306 -0
  85. package/factory/orchestrator/dist/promptHook.js +33 -0
  86. package/factory/orchestrator/dist/promptImport.js +281 -0
  87. package/factory/orchestrator/dist/prompts.js +13 -0
  88. package/factory/orchestrator/dist/providers/schema-validate.js +2 -2
  89. package/factory/orchestrator/dist/providers/types.js +1 -1
  90. package/factory/orchestrator/dist/queue.js +471 -30
  91. package/factory/orchestrator/dist/quorum.js +18 -5
  92. package/factory/orchestrator/dist/results.js +25 -7
  93. package/factory/orchestrator/dist/roadmap.js +10 -5
  94. package/factory/orchestrator/dist/run-cli.js +15 -10
  95. package/factory/orchestrator/dist/runner.js +2 -2
  96. package/factory/orchestrator/dist/scheduler.js +3 -3
  97. package/factory/orchestrator/dist/schemas.js +56 -8
  98. package/factory/orchestrator/dist/severity.js +8 -4
  99. package/factory/orchestrator/dist/spec.js +246 -12
  100. package/factory/orchestrator/dist/specChange.js +13 -9
  101. package/factory/orchestrator/dist/stack.js +6 -6
  102. package/factory/orchestrator/dist/storeRoot.js +90 -0
  103. package/factory/orchestrator/dist/symbols.js +261 -6
  104. package/factory/orchestrator/dist/taskEvents.js +192 -31
  105. package/factory/orchestrator/dist/taskStatus.js +14 -0
  106. package/factory/orchestrator/dist/taxonomy.js +11 -0
  107. package/factory/orchestrator/dist/testerAudit.js +31 -12
  108. package/factory/orchestrator/dist/testgate.js +20 -16
  109. package/factory/orchestrator/dist/uiuxGate.js +83 -0
  110. package/factory/orchestrator/dist/usage.js +113 -48
  111. package/factory/orchestrator/dist/waivers.js +99 -12
  112. package/factory/orchestrator/dist/waveBudget.js +2 -0
  113. package/factory/orchestrator/dist/waveConcurrency.js +143 -7
  114. package/factory/orchestrator/dist/waveSchedule.js +84 -0
  115. package/factory/orchestrator/dist/workroot.js +1 -1
  116. package/factory/orchestrator/dist/worktree.js +76 -21
  117. package/factory/orchestrator/drizzle/0013_operator_feedback.sql +19 -0
  118. package/factory/orchestrator/drizzle/0014_misty_madripoor.sql +2 -0
  119. package/factory/orchestrator/drizzle/0015_gifted_omega_flight.sql +1 -0
  120. package/factory/orchestrator/drizzle/0016_milestones_project_default.sql +30 -0
  121. package/factory/orchestrator/drizzle/0017_events_raw_cli_session_id.sql +2 -0
  122. package/factory/orchestrator/drizzle/meta/_journal.json +35 -0
  123. package/factory/policies/budgets.yml +123 -33
  124. package/factory/policies/crosscheck.yml +51 -13
  125. package/factory/policies/delegation.yml +3 -3
  126. package/factory/policies/effort.yml +18 -10
  127. package/factory/policies/guardrails.yml +8 -6
  128. package/factory/policies/harness.yml +5 -5
  129. package/factory/policies/lessons.md +53 -2
  130. package/factory/policies/scheduler.yml +4 -4
  131. package/factory/policies/sensitive-paths.yml +1 -1
  132. package/factory/policies/severity.yml +13 -1
  133. package/factory/policies/stack.yml +6 -6
  134. package/factory/policies/taxonomy.yml +9 -3
  135. package/factory/policies/worktree.yml +15 -9
  136. package/factory/specs/roadmap.md +27 -3
  137. package/factory/specs/schema/event.schema.json +8 -1
  138. package/factory/specs/schema/result.schema.json +21 -8
  139. package/factory/specs/schema/task-spec.schema.json +12 -0
  140. package/package.json +16 -12
  141. package/.claude/agents/uiux.md +0 -133
@@ -15,19 +15,33 @@ epic. "Hand the wave over" below says how, and says the one thing that makes
15
15
  it safe.
16
16
 
17
17
  Ask the epic's effort tier once, at the top of the run, and keep the answer
18
- for the whole epic — `smith effort show --plan
18
+ for the whole epic — `bs effort show --plan
19
19
  factory/specs/active/<epic>/plan-vN.json`. Step 13 here, and steps 3, 6 and 7
20
20
  of the wave playbook, each name the `profile` field that scales them; nothing
21
21
  else in either file moves. Read `effective`, not `requested`: a plan whose
22
22
  live tasks fire a security trigger is floored, and running it at the tier the
23
23
  file asked for would be running it below the floor.
24
24
 
25
+ Ask the cross-provider gate once too, at the top of the run and before any
26
+ wave is admitted — `bs judge preflight --project <project-dir>`, the same
27
+ call INSTALL.md makes at setup. When it reports the gating pool cannot
28
+ decide (fewer active external providers than `quorum_rule.min_providers`
29
+ after OpenRouter substitution, or a provider the project's own
30
+ `.blacksmith/crosscheck.yml` names but this box cannot reach), STOP and ask
31
+ the operator before admitting a wave — per `.claude/skills/bs/SKILL.md`'s
32
+ "Talking to the operator" rules (options carry a confidence %; not restated
33
+ here): add an OpenRouter key, add a model, pick which providers to run with,
34
+ or continue without cross-check. Skipping this is not a shortcut — steps 15
35
+ and 16 spend a real judge call to find the same gap mid-run, after the epic's
36
+ work is already done. Full substitution and precedence mechanism:
37
+ docs/runbooks/providers.md §9d.
38
+
25
39
  1. Ask the graph how wide this wave can be, then ask the gate whether it may
26
40
  be that wide. Two commands, two different questions — do not skip to the
27
41
  second with a set you picked by eye.
28
42
 
29
43
  ```bash
30
- smith wave next factory/specs/active/<epic>/plan-vN.json \
44
+ bs wave next factory/specs/active/<epic>/plan-vN.json \
31
45
  --session <session-id> --repo <project-dir>
32
46
  ```
33
47
 
@@ -46,7 +60,7 @@ file asked for would be running it below the floor.
46
60
 
47
61
  Then put that proposal through the gate that actually admits it:
48
62
 
49
- `smith wave check factory/specs/active/<epic>/plan-vN.json <task-id>...`
63
+ `bs wave check factory/specs/active/<epic>/plan-vN.json <task-id>...`
50
64
  — claims must be pairwise disjoint and share no `worktree.yml`
51
65
  `serialize_always_globs` hotspot. A violation means cut a dependency
52
66
  edge and run the pair serially instead of forcing the wave.
@@ -66,6 +80,16 @@ file asked for would be running it below the floor.
66
80
  operator's call. So a proposed wave can still be refused here. That is
67
81
  the gate working, not the proposal being wrong — drop tasks from the
68
82
  tail of `wave` and re-check.
83
+ - With `--session`, `wave check` admits only live tasks of an ingested
84
+ plan. A plan version whose tasks the log never saw is refused
85
+ `cli.plan-not-ingested`: run `bs plan ingest` on it, then re-check.
86
+ An id with no task row is refused `plan.unknown-task`, and a task
87
+ that is `completed`, `superseded` or `waived` is refused
88
+ `cli.task-not-live`, naming each task and its status. Drop those ids.
89
+ A `failed` or `escalated` task stays admissible: an operator still
90
+ holds it, and the escalation ladder re-admits it. A follow-up
91
+ `findings raise` minted stays admissible while it is live, and a wave
92
+ of follow-ups alone still needs the plan it names ingested first.
69
93
  - Never narrow a wave because a narrow one feels safer. A wave of one
70
94
  passes every check in this file — one task is disjoint with nothing,
71
95
  shares a hotspot with nothing, crosses no import edge — so `valid: true`
@@ -84,9 +108,9 @@ would cost:
84
108
  - **Inline**, in this session, when the wave is small enough for this
85
109
  window. Read `wave.md` and work it here.
86
110
  - **In a session of its own**, when it is not, but you still want to drive
87
- it yourself: `smith session start <wave-id> --continues <session-id>#<n>`,
111
+ it yourself: `bs session start <wave-id> --continues <session-id>#<n>`,
88
112
  where `<n>` is the index of the event that admitted this wave.
89
- - **Dispatched**, as the **`wave-runner`** agent, when the wave would eat
113
+ - **Dispatched**, as the **`bs-wave-runner`** agent, when the wave would eat
90
114
  the window this epic needs to reach its own end. It is the only role
91
115
  `factory/policies/delegation.yml` grants `Agent`, and it earns that by
92
116
  opening its own log first — so hand it the event id of *your dispatch*,
@@ -106,12 +130,12 @@ to make (D13, D-266). What it does not buy is a shortcut. A wave session
106
130
  opened without `--continues` writes into a log nothing here reads, and the
107
131
  verbs below then report a wave that appears never to have run rather than
108
132
  failing — which is the same silence, arriving as a green. When you dispatched
109
- the wave rather than ran it, `smith delegation check <session-id>` is what
133
+ the wave rather than ran it, `bs delegation check <session-id>` is what
110
134
  turns that silence into a red: a `wave-runner` that dispatched before opening
111
135
  its log, or against the wrong event, is `unverifiable` and exits 1.
112
136
 
113
137
  **Then check that it did.** Once the wave's tasks reach a terminal state,
114
- `smith wave audit --session <id> --epic <epic>` reads the log back and
138
+ `bs wave audit --session <id> --epic <epic>` reads the log back and
115
139
  reports the width the wave actually ran at, not the width it was admitted
116
140
  at. Ask it with *this* session: it folds the lineage, so it sees the
117
141
  dispatches a wave session made under the admission this one wrote. Exit `1`
@@ -123,8 +147,9 @@ under it, which is the wrong `--state-dir`, or a wave session opened without
123
147
  done; a green gate on serialized work is exactly the outcome the two
124
148
  playbooks are written to prevent.
125
149
 
126
- 11. Repeat until every task in the live plan version is
127
- `completed`/`superseded`/`waived`. Dispatch the **`planner`** again for
150
+ 11. Repeat until every task in the live plan version is `completed`/`waived`,
151
+ or `superseded` by a successor that is itself terminal-OK (recursively —
152
+ a successor chain counts too). Dispatch the **`bs-planner`** again for
128
153
  the epic verdict against acceptance criteria — gaps found → a NEW
129
154
  `plan-v(n+1)` with inferred tasks (never a live-graph mutation,
130
155
  architecture §12), auto-scheduled at confidence ≥0.8 else parked for an
@@ -132,7 +157,7 @@ playbooks are written to prevent.
132
157
  on it**, whichever step cut it:
133
158
 
134
159
  ```bash
135
- smith plan ingest factory/specs/active/<epic>/plan-v<n+1>.json \
160
+ bs plan ingest factory/specs/active/<epic>/plan-v<n+1>.json --project <project-dir> \
136
161
  --session <session-id> --plan-version <n+1> --causal-parent <event-id>
137
162
  ```
138
163
 
@@ -147,11 +172,11 @@ playbooks are written to prevent.
147
172
  branch**. Every gate up to here ran inside a task worktree, so every
148
173
  green you have so far is a green about a worktree — the envkit epic
149
174
  shipped six green lint gates on a branch whose `pnpm lint` exited 1
150
- (D-42). Check out `smith/<epic>/integration` in the project first (this
175
+ (D-42). Check out `bs/<epic>/integration` in the project first (this
151
176
  command refuses to move your working tree, and refuses a dirty one):
152
177
 
153
178
  ```bash
154
- smith integration check --epic <epic> --project <project-dir> \
179
+ bs integration check --epic <epic> --project <project-dir> \
155
180
  --checks <checks.json> \
156
181
  --session <session-id> --plan-version <n> --causal-parent <event-id>
157
182
  ```
@@ -167,25 +192,35 @@ playbooks are written to prevent.
167
192
  check, the review and the close all claim to have verified a plan that
168
193
  no longer exists. One dogfooded product epic closed at v3 with its
169
194
  integration check and its closing spec review both stamped v1; the goal
170
- check, passed the flag, said v3. Read the version off `smith plan ingest`'s receipt and
195
+ check, passed the flag, said v3. Read the version off `bs plan ingest`'s receipt and
171
196
  pass it every time.
172
- 13. Dispatch the **`spec-reviewer`** again — this time against the code.
197
+ 13. Dispatch the **`bs-spec-reviewer`** again — this time against the code.
173
198
  The pre-code review at `/bs plan` step 3 read the spec against nothing;
174
199
  this one reads it against the assembled branch, which is the only reading
175
200
  that can see a criterion the finished code proves wrong. The envkit epic
176
201
  deadlocked on exactly that: a correct S2 whose fix criterion 3 mandated
177
202
  and criterion 1 forbade (D-33). It writes to a close-specific path so it
178
- cannot clobber the pre-code artifact; hand that file — each item naming the
203
+ cannot clobber the pre-code artifact. Put
204
+ `Declared artifact: /abs/path/to/state/results/<epic>.spec-review-close-vN.json`
205
+ inline on its own line in the dispatch prompt (the PreToolUse guard refuses
206
+ a judge dispatch without it); hand that file — each item naming the
179
207
  `criterion_ref` it is against — to:
180
208
 
181
209
  ```bash
182
- smith epic spec-review --epic <epic> --project <project-dir> \
210
+ bs epic spec-review --epic <epic> --project <project-dir> \
183
211
  --plan factory/specs/active/<epic>/plan-vN.json \
184
212
  --reviewed-by spec-reviewer \
185
- [--evidence state/results/<epic>.spec-review-close-vN.json] \
213
+ --evidence state/results/<epic>.spec-review-close-vN.json \
186
214
  --session <session-id> --plan-version <n> --causal-parent <event-id>
187
215
  ```
188
216
 
217
+ `--evidence` is required unless you pass `--no-findings` instead, and
218
+ the two are exclusive: a reviewer that found nothing writes `[]` to that
219
+ path, and `--no-findings` is the operator attesting a review that ran
220
+ outside the factory — recorded as `attested_by: operator`, never read as
221
+ a file. A missing flag is refused (`cli.missing-flag`), because an empty
222
+ review read from an absent file is how a capped reviewer closed clean.
223
+
189
224
  Its `dispatch_decision` — and step 14's, and the planner's when it
190
225
  renders a verdict — goes against the reserved `<epic>/integration` ref,
191
226
  never a `<epic>/spec-review-rN` you coin for the occasion
@@ -204,12 +239,12 @@ playbooks are written to prevent.
204
239
  distinguishes them. It
205
240
  exits 0 even when it raises findings: what it found blocks the plan, not
206
241
  this command. A spec finding is unwaivable at S1/S2 and no task's diff can
207
- contain the fix, so the answer is `smith plan amend --plan … --findings …
242
+ contain the fix, so the answer is `bs plan amend --plan … --findings …
208
243
  --rationale … --sites …` (§6a of the operator guide). `--sites` is every
209
244
  place that shape occurs, not only the file the finding was reported
210
245
  against — answer it before writing the changes, because it is the question
211
246
  that decides how much the amendment fixes (D-123). It cuts plan v(n+1):
212
- ingest it (step 11's `smith plan ingest`), go back to step 11 with the new
247
+ ingest it (step 11's `bs plan ingest`), go back to step 11 with the new
213
248
  version, and re-run this review against the branch that results. Never
214
249
  record a spec defect as a coder failure; that is
215
250
  the deadlock this step exists to end.
@@ -220,20 +255,22 @@ playbooks are written to prevent.
220
255
  did not write. Get the clause list first (read-only, no event):
221
256
 
222
257
  ```bash
223
- smith epic goal --epic <epic>
258
+ bs epic goal --epic <epic>
224
259
  ```
225
260
 
226
- Hand those clauses and the live plan to a **`spec-reviewer`** session —
261
+ Hand those clauses and the live plan to a **`bs-spec-reviewer`** session —
227
262
  a fresh one, never the planner's, and never the same dispatch as step 13:
228
- `smith dispatch check` refuses to let one dispatch answer for both.
263
+ `bs dispatch check` refuses to let one dispatch answer for both.
229
264
  Dispatch it *after* step 13's record is written, not alongside it — a
230
265
  dispatch that predates the previous record has already answered for that
231
266
  one, so two sessions fired up front leave this record unaccounted for and
232
- `smith dispatch check` reports that record `unverifiable`. Take back one
233
- verdict per clause, in the goal's order, and record it:
267
+ `bs dispatch check` reports that record `unverifiable`. Take back one
268
+ verdict per clause, in the goal's order, and record it. Its prompt carries
269
+ `Declared artifact: /abs/path/to/state/results/<epic>.goal-coverage-vN.json`
270
+ inline on its own line, for the same reason:
234
271
 
235
272
  ```bash
236
- smith epic goal-check --epic <epic> \
273
+ bs epic goal-check --epic <epic> \
237
274
  --plan factory/specs/active/<epic>/plan-vN.json \
238
275
  --coverage state/results/<epic>.goal-coverage-vN.json \
239
276
  --checked-by spec-reviewer \
@@ -243,8 +280,8 @@ playbooks are written to prevent.
243
280
  `covered` must name live plan task ids; `out-of-scope` must give a reason,
244
281
  and that reason is quoted back to the epic judge. `uncovered` mints an
245
282
  S2-major spec finding against the plan file — no task diff can contain
246
- that fix, so the answer is `smith plan amend`, which cuts v(n+1) and sends
247
- you back to step 11 — through `smith plan ingest`, as in step 13. The
283
+ that fix, so the answer is `bs plan amend`, which cuts v(n+1) and sends
284
+ you back to step 11 — through `bs plan ingest`, as in step 13. The
248
285
  command exits 0 even when it raises findings.
249
286
 
250
287
  **It refuses (`cli.no-epic-goal`) when the owning milestone states no
@@ -259,54 +296,155 @@ playbooks are written to prevent.
259
296
  integrable at all:
260
297
 
261
298
  ```bash
262
- smith epic verdict --epic <epic> --project <project-dir> \
299
+ bs epic verdict --epic <epic> --project <project-dir> \
263
300
  --session <session-id> --plan-version <n> --causal-parent <event-id>
264
301
  ```
265
302
 
266
303
  Mechanical oracles first: non-terminal tasks, open blocking findings, a
267
- missing/failed/stale integration-root check, a missing/stale closing spec
268
- review, or a missing/stale spec-vs-goal check return `hold` (exit 1)
269
- without spending a judge call. `--project` is required — the verdict
270
- reads the integration branch head to decide whether the records from
271
- steps 12, 13 and 14 still cover it. On `hold`, do not open the PR — report
272
- the `blockers` to the operator and go back to step 11.
304
+ missing/failed/stale integration-root check, a closing spec review that's
305
+ stale or missing when the tier required one, or a missing/stale
306
+ spec-vs-goal check return `hold` (exit 1) without spending a judge call.
307
+ A tier that waived the review (step 13: `when-plan-amended` on a plan
308
+ still at v1) is not a hold on that ground — the verdict records the skip
309
+ as a fact instead of staying silent about it, same as step 13 asks. A
310
+ review that IS on record is still walked through every staleness check
311
+ regardless of tier. `--project` is required — the verdict reads the
312
+ integration branch head to decide whether the records from steps 12, 13
313
+ and 14 still cover it. On `hold`, do not open the PR — report the
314
+ `blockers` to the operator and go back to step 11.
273
315
 
274
316
  Once the oracles pass, the judge call is real money when an external
275
317
  provider is `active` in `crosscheck.yml`: codex spends the operator's
276
- ChatGPT quota, DeepSeek spends prepaid credit, and step 16 runs the
277
- verdict again before it writes, so a clean close is two spends, not one
278
- (one dogfooded epic wrote two `quorum-decision`s against its
279
- integration ref, one from the verdict and one from the close).
280
- Nothing in the `quorum-decision` payload names the purse, so say which
281
- one it is to the operator before the first call, not after the second.
318
+ ChatGPT quota, DeepSeek spends prepaid credit. Say which purse it is to
319
+ the operator before this call, not after.
320
+
321
+ Step 16 no longer pays twice for the same integration head: the
322
+ `quorum-decision` this call writes now carries `integration_head`, and a
323
+ clean `go` against an unchanged head is reused rather than re-asked
324
+ (issue #270). Anything that invalidates it — a new commit, a `hold`, or a
325
+ decision from before `integration_head` existed — still re-runs.
282
326
  16. Record the close. The verdict above is a read-only probe and writes
283
327
  nothing; `epic close` is what makes it a fact in the log (D-43):
284
328
 
285
329
  ```bash
286
- smith epic close --epic <epic> --project <project-dir> \
330
+ bs epic close --epic <epic> --project <project-dir> \
287
331
  --session <session-id> --plan-version <n> --causal-parent <event-id>
288
332
  ```
289
333
 
290
- It re-runs the verdict, then emits `epic-closed` on `go` and refuses
291
- (exit 1, no event) on `hold`. **Never pass `--override-rationale`
334
+ It reuses step 15's `go` decision when the integration head hasn't moved
335
+ (see step 15), otherwise re-runs the verdict, then emits `epic-closed` on
336
+ `go` and refuses (exit 1, no event) on `hold`. **Never pass `--override-rationale`
292
337
  yourself** — closing over a hold is the operator's call; ask for it and
293
338
  quote the blockers. If the epic was cut by `/bs audit`
294
339
  ([`audit.md`](audit.md) step 10), follow the close with
295
- `smith audit resolve <project-dir> --epic <epic> --session <session-id> --causal-parent <event-id>`
296
- so every finding the epic carried is marked `fixed` in the project's
297
- audit store — the store, not the epic, is where a finding's life ends.
298
- 17. Open **one integration PR per epic** with the scribe-written body
299
- (`/bs report`'s playbook, [`report.md`](report.md)) — screenshots, test
300
- results, reviewer verdict, waivers granted, timeline link. The head is
301
- `smith/<epic>/integration`. The base is the target repo's `main` unless
340
+ `bs audit resolve <project-dir> --epic <epic> --session <session-id> --causal-parent <event-id> [--specs-dir <dir>]`
341
+ so every finding this plan still claims is marked `fixed` in the
342
+ project's audit store — the rest come back `deferred`, left exactly as
343
+ they were, because the plan scoped them out on purpose. The store, not
344
+ the epic, is where a finding's life ends. `resolve` reads the epic's
345
+ newest plan from the work root's specs dir by default; when this
346
+ project's plans live elsewhere, pass `--specs-dir` naming the same specs
347
+ dir the epic was run from, or it refuses rather than guess.
348
+ 17. Open **one integration PR per epic** with a scribe-written body. The head is
349
+ `bs/<epic>/integration`. The base is the target repo's `main` unless
302
350
  the epic was cut from another epic's integration branch, in which case
303
351
  the PR stacks on that branch and the operator merges the parent's PR
304
- first — a child epic cut from `smith/<parent>/integration` opens its PR
352
+ first — a child epic cut from `bs/<parent>/integration` opens its PR
305
353
  against that branch, behind the parent's PR; once the parent has merged
306
354
  and its branch is deleted, GitHub retargets the child to `main`. The
307
355
  operator reviews on GitHub; this session never merges to `main`
308
356
  (`docs/standards/guardrails.md`).
309
357
 
358
+ **Build the fact pack first.** The scribe has no `Bash` and a small turn
359
+ cap, so it cannot run `git` or `bs`, and a scribe left to grep raw logs
360
+ caps and fills the gaps by invention — a wrong event id, a merged task
361
+ called dead, "no waivers" over two grants. Compute every fact in your own
362
+ session, from the epic session with `--lineage` so the waves' records
363
+ count. Every fact comes from the log or from git, never from the SQLite
364
+ projection: `stats kanban` and `stats timeline` read a projection that
365
+ can lag the log, and `db rebuild` clears it before it re-reads, so do
366
+ not refresh it for this.
367
+
368
+ ```bash
369
+ # one pass over the log (pass an --n that covers the whole lineage)
370
+ bs event tail <session-id> --lineage --n 100000
371
+ # the fingerprint map: finding_id, task_id and fingerprint of the epic's findings
372
+ bs findings list --session <session-id> --epic <epic>
373
+ # the goal the summary states
374
+ bs epic goal --epic <epic> [--roadmap-path <file>]
375
+ # the branch itself
376
+ git log --oneline <base>..bs/<epic>/integration
377
+ git diff --shortstat <base>...bs/<epic>/integration
378
+ ```
379
+
380
+ From the `event tail` pass keep, each with its `event_id`:
381
+ - `waiver-granted` and `waiver-denied` records whose
382
+ `payload.fingerprint` matches one of the epic's findings, mapped to
383
+ that finding's id and task. Match on the stored `fingerprint` that
384
+ `findings list` prints, and drop a decision that matches none: an
385
+ ancestor session can carry an earlier epic's decisions. Grants come
386
+ from here, not from
387
+ `findings list --status`, because a grant only moves a raised or
388
+ confirmed finding to `waived`; an amended or fix-verified one keeps
389
+ its status.
390
+ - `wave-merged` records: `payload.task_ids` and the event id.
391
+ - `epic-closed` from step 16: its event id, `machine_verdict`, and
392
+ `payload.summary.tasks` — each task's `task_status`, read from the log
393
+ at close. A follow-up cleared by closing all its findings (fixed, refuted
394
+ or waived) still reads `todo` there; cross-check it against the findings and never publish it
395
+ as an open task.
396
+ - `audit-resolved`, for an epic cut by `/bs audit`: every record whose
397
+ `payload.epic` is this epic — one epic can resolve more than once, and
398
+ the lineage carries a parent epic's resolve too. List each event id.
399
+ `fixed` and `deferred` are fingerprint arrays: fixed is the union of
400
+ every kept record's `fixed`; deferred is the last record's `deferred`
401
+ minus that union. Write the two counts into the pack yourself.
402
+ - `integration-check` records whose `payload.epic_id` is this epic and
403
+ whose `head_sha` equals the `epic-closed` record's
404
+ `summary.integration.check.head_sha`: each check's `name` and `pass`.
405
+
406
+ `epic goal` reads this repo's roadmap by default; when the epic's
407
+ roadmap lives elsewhere, pass `--roadmap-path` naming it, or the goal
408
+ you hand over is the wrong one. For a UI epic, take the screenshots
409
+ from each task's tester result, `state/results/<task-id>.tester*.json`,
410
+ field `artifacts` (the files sit under `state/artifacts/<task-id>/`);
411
+ [`docs/standards/stack.md`](../../../docs/standards/stack.md) wants
412
+ them on this PR. The pack carries a screenshot only as an image you
413
+ have uploaded or attached — never a local path, which no reviewer can
414
+ open; one you cannot attach stays out.
415
+
416
+ Write only these filtered facts into one pack file — never raw command
417
+ output, which puts the scribe back to grepping logs. Dispatch the scribe with that file as its **only source**, and the
418
+ PR-body scratch file as its write root, under one rule: **omit, never
419
+ infer**. A fact the pack does not hold stays out of the body; a count,
420
+ a status, or an event id is copied from the pack, never worked out. If
421
+ the pack is wrong, rebuild it and dispatch again; do not let the scribe
422
+ reach past it into `state/events/`.
423
+
424
+ The PR body opens with a short plain-language summary, written under
425
+ `.claude/skills/bs/SKILL.md`'s "Talking to the operator" section — cite
426
+ that section for how to write it, do not restate its rule here. In the
427
+ project's own terms, not Blacksmith's: what this epic changed, what is
428
+ still open, and what decision the operator must make, if any. The
429
+ existing fact sections below follow it as the technical part the rule
430
+ asks for.
431
+
432
+ The PR body has this shape and nothing else: the plain-language opening
433
+ above; **Summary** (the epic id
434
+ and its goal, one paragraph); **Tasks** (one line per task: id,
435
+ `task_status`, and its `wave-merged` event id when it has one);
436
+ **Waivers** (granted and denied, each with its finding id and event id,
437
+ or the word "none" only when both lists in the pack are empty);
438
+ **Verification** (the verdict outcome, the `epic-closed` event id, the
439
+ `integration-check` results, the `audit-resolved` event ids and the
440
+ fixed and deferred counts when there are any, the shortstat, and the
441
+ commit count from the log);
442
+ **Screenshots** (only when the pack carries them); **Timeline** (the
443
+ dashboard link, when you put one in the pack). The scribe's 300-word
444
+ cap binds the prose — Summary and the sentences around the lists; the
445
+ Tasks, Waivers and check lists are artifacts and are never cut to fit
446
+ it, since a dropped line reads as a task or waiver that was not there.
447
+
310
448
  Before the first push, read the authorship the branch is about to make
311
449
  public — `git log --format=%ae <base>..HEAD | sort -u` — and stop on any
312
450
  address that is not a noreply one; a coder's worktree commit carries
@@ -318,7 +456,7 @@ playbooks are written to prevent.
318
456
  Append it by hand, under the reserved ref and the live plan version:
319
457
 
320
458
  ```bash
321
- smith event append '{"session_id":"<session-id>","actor":"orchestrator","event_type":"integration-pr-opened","task_id":"<epic>/integration","plan_version":<n>,"causal_parent":"<event-id>","payload":{"step":17,"pr_url":"<url>","pr_number":<n>,"repo":"<owner>/<repo>","base_ref":"<base>","head_ref":"smith/<epic>/integration","head_sha":"<sha>","base_sha":"<sha>","commits":<n>,"changed_files":<n>,"additions":<n>,"deletions":<n>}}'
459
+ bs event append '{"session_id":"<session-id>","actor":"orchestrator","event_type":"integration-pr-opened","task_id":"<epic>/integration","plan_version":<n>,"causal_parent":"<event-id>","payload":{"step":17,"pr_url":"<url>","pr_number":<n>,"repo":"<owner>/<repo>","base_ref":"<base>","head_ref":"bs/<epic>/integration","head_sha":"<sha>","base_sha":"<sha>","commits":<n>,"changed_files":<n>,"additions":<n>,"deletions":<n>}}'
322
460
  ```
323
461
 
324
462
  The receipt says whether the timeline reads the type; it does, and it
@@ -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).