@juzser/blacksmith 0.1.0 → 0.2.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 (72) hide show
  1. package/.claude/agents/coder.md +9 -3
  2. package/.claude/agents/grader.md +13 -5
  3. package/.claude/agents/merger.md +2 -0
  4. package/.claude/agents/planner.md +22 -1
  5. package/.claude/agents/researcher.md +3 -1
  6. package/.claude/agents/reviewer.md +9 -2
  7. package/.claude/agents/security-reviewer.md +15 -7
  8. package/.claude/agents/spec-reviewer.md +12 -3
  9. package/.claude/agents/tester.md +5 -2
  10. package/.claude/agents/uiux.md +10 -3
  11. package/.claude/agents/verifier.md +8 -3
  12. package/.claude/skills/bs/SKILL.md +119 -0
  13. package/.claude/skills/bs/audit.md +252 -0
  14. package/.claude/skills/bs/dispatch.md +394 -0
  15. package/.claude/skills/bs/lessons.md +73 -0
  16. package/.claude/skills/bs/mcp.md +29 -0
  17. package/.claude/skills/bs/new.md +35 -0
  18. package/.claude/skills/bs/plan.md +172 -0
  19. package/.claude/skills/bs/report.md +129 -0
  20. package/.claude/skills/bs/run.md +327 -0
  21. package/.claude/skills/bs/status.md +54 -0
  22. package/.claude/skills/bs/ui.md +19 -0
  23. package/.claude/skills/bs/waivers.md +13 -0
  24. package/.claude/skills/bs/wave.md +267 -0
  25. package/README.md +136 -79
  26. package/factory/orchestrator/dist/actors.js +4 -3
  27. package/factory/orchestrator/dist/agents-registry.js +96 -19
  28. package/factory/orchestrator/dist/artifacts.js +36 -4
  29. package/factory/orchestrator/dist/budgets.js +7 -5
  30. package/factory/orchestrator/dist/claims.js +27 -1
  31. package/factory/orchestrator/dist/cli.js +196 -27
  32. package/factory/orchestrator/dist/crosscheck.js +11 -0
  33. package/factory/orchestrator/dist/daemon.js +30 -5
  34. package/factory/orchestrator/dist/db/projector.js +272 -90
  35. package/factory/orchestrator/dist/db/queries.js +83 -14
  36. package/factory/orchestrator/dist/db/schema.js +7 -0
  37. package/factory/orchestrator/dist/events.js +88 -4
  38. package/factory/orchestrator/dist/harness.js +516 -0
  39. package/factory/orchestrator/dist/impact.js +76 -5
  40. package/factory/orchestrator/dist/judges.js +49 -8
  41. package/factory/orchestrator/dist/lessons.js +4 -4
  42. package/factory/orchestrator/dist/mcp.js +4 -3
  43. package/factory/orchestrator/dist/paths.js +169 -15
  44. package/factory/orchestrator/dist/plan.js +61 -1
  45. package/factory/orchestrator/dist/planQuorum.js +36 -8
  46. package/factory/orchestrator/dist/providers/api-transport.js +62 -5
  47. package/factory/orchestrator/dist/providers/cli-transport.js +7 -73
  48. package/factory/orchestrator/dist/providers/index.js +1 -0
  49. package/factory/orchestrator/dist/providers/schema-validate.js +58 -7
  50. package/factory/orchestrator/dist/providers/types.js +5 -0
  51. package/factory/orchestrator/dist/roadmap.js +3 -3
  52. package/factory/orchestrator/dist/run-cli.js +130 -0
  53. package/factory/orchestrator/dist/runner.js +264 -0
  54. package/factory/orchestrator/dist/scaffold.js +3 -2
  55. package/factory/orchestrator/dist/spawn.js +84 -0
  56. package/factory/orchestrator/dist/stack.js +10 -2
  57. package/factory/orchestrator/dist/templateFragments.js +178 -0
  58. package/factory/orchestrator/dist/usage.js +30 -9
  59. package/factory/orchestrator/dist/waveNext.js +42 -14
  60. package/factory/orchestrator/dist/waveSchedule.js +7 -1
  61. package/factory/orchestrator/dist/workroot.js +113 -0
  62. package/factory/orchestrator/drizzle/0011_findings_spec_ref.sql +3 -0
  63. package/factory/orchestrator/drizzle/meta/_journal.json +7 -0
  64. package/factory/policies/budgets.yml +7 -4
  65. package/factory/policies/crosscheck.yml +10 -8
  66. package/factory/policies/harness.yml +82 -0
  67. package/factory/policies/lessons.md +122 -0
  68. package/factory/policies/severity.yml +1 -1
  69. package/factory/policies/taxonomy.yml +6 -2
  70. package/factory/specs/roadmap.md +154 -0
  71. package/factory/specs/schema/task-spec.schema.json +5 -0
  72. package/package.json +10 -7
@@ -0,0 +1,327 @@
1
+ # `/bs run <epic>` — the epic tier
2
+
3
+ [`dispatch.md`](dispatch.md) binds every agent this playbook dispatches;
4
+ none of it is restated here.
5
+
6
+ The loop playbook (architecture §3, §5, §11): one wave at a time, and every
7
+ task in the wave at once.
8
+
9
+ **Two tiers, two logs.** This file is the epic tier. It admits a wave (step
10
+ 1), hands it to [`wave.md`](wave.md) — the wave playbook, steps 2-10 — and
11
+ picks the loop back up at step 11 once the wave has landed. That is D13's
12
+ fix: each tier owns the log for what it dispatches, so an epic session stops
13
+ carrying every wave's turns in a window it needs to reach the end of the
14
+ epic. "Hand the wave over" below says how, and says the one thing that makes
15
+ it safe.
16
+
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
19
+ factory/specs/active/<epic>/plan-vN.json`. Step 13 here, and steps 3, 6 and 7
20
+ of the wave playbook, each name the `profile` field that scales them; nothing
21
+ else in either file moves. Read `effective`, not `requested`: a plan whose
22
+ live tasks fire a security trigger is floored, and running it at the tier the
23
+ file asked for would be running it below the floor.
24
+
25
+ 1. Ask the graph how wide this wave can be, then ask the gate whether it may
26
+ be that wide. Two commands, two different questions — do not skip to the
27
+ second with a set you picked by eye.
28
+
29
+ ```bash
30
+ smith wave next factory/specs/active/<epic>/plan-vN.json \
31
+ --session <session-id> --repo <project-dir>
32
+ ```
33
+
34
+ `wave` is the widest set the plan admits right now: dependencies landed,
35
+ claims pairwise disjoint, no shared hotspot, producers ordered ahead of
36
+ the consumers that import them. Every task it left out is in `deferred`
37
+ with a reason (`dependency-pending`, `claim-overlap`, `serialize-hotspot`,
38
+ `symbol-coupled`) and the ids that held it back, so a short wave is always
39
+ a wave with an explanation. It reads and writes nothing — ask it again
40
+ whenever you want a fresh answer. `--session` is what lets it see live
41
+ task status and the follow-ups `findings raise` minted into the log but
42
+ into no plan file; without it you are scheduling from the plan as written
43
+ hours ago. Exit 1 means work remains and none of it can start — a stall
44
+ to report, not an empty answer. An empty `wave` with `remaining: 0` is the
45
+ epic finished.
46
+
47
+ Then put that proposal through the gate that actually admits it:
48
+
49
+ `smith wave check factory/specs/active/<epic>/plan-vN.json <task-id>...`
50
+ — claims must be pairwise disjoint and share no `worktree.yml`
51
+ `serialize_always_globs` hotspot. A violation means cut a dependency
52
+ edge and run the pair serially instead of forcing the wave.
53
+ - The same verdict carries `symbolImpact`. `valid: true` with
54
+ `symbolImpact.status: "coupled"` means the claims really are disjoint
55
+ and that is not enough: the two tasks sit on either end of an import
56
+ edge the plan declared no dependency for. A crossing is not
57
+ overridable. The pair runs in the same wave only when the producer's
58
+ spec lists the exporting file in `keeps_exports` — then the crossing
59
+ sits in `symbolImpact.promised`, not `crossings`, and the promise is
60
+ verified against the diff at wave.md step 10. Otherwise split the wave
61
+ and run the producer first (operator-guide/wave.md §2).
62
+ - `wave check` is also where the wave is priced and where
63
+ `max_in_flight_tasks` is enforced (`budgets.yml`, `over-fan-out`).
64
+ `wave next` deliberately asks neither question: a proposer that also
65
+ priced the wave would have to choose which task to drop, and that is an
66
+ operator's call. So a proposed wave can still be refused here. That is
67
+ the gate working, not the proposal being wrong — drop tasks from the
68
+ tail of `wave` and re-check.
69
+ - Never narrow a wave because a narrow one feels safer. A wave of one
70
+ passes every check in this file — one task is disjoint with nothing,
71
+ shares a hotspot with nothing, crosses no import edge — so `valid: true`
72
+ on a singleton is not evidence the wave was right, only that nothing
73
+ refused it. That is the failure this step exists to prevent: a factory
74
+ whose whole claim is parallel execution, running its plan one task at a
75
+ time and passing every gate while it does.
76
+
77
+ ## Steps 2-10 — hand the wave over
78
+
79
+ The admitted wave now goes to [`wave.md`](wave.md), the wave playbook:
80
+ worktrees, pre-code, coder, tester, grader, the gate pipeline, bounces, and
81
+ the merge queue. Three shapes, in order of how much of this window the wave
82
+ would cost:
83
+
84
+ - **Inline**, in this session, when the wave is small enough for this
85
+ window. Read `wave.md` and work it here.
86
+ - **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>`,
88
+ where `<n>` is the index of the event that admitted this wave.
89
+ - **Dispatched**, as the **`wave-runner`** agent, when the wave would eat
90
+ the window this epic needs to reach its own end. It is the only role
91
+ `factory/policies/delegation.yml` grants `Agent`, and it earns that by
92
+ opening its own log first — so hand it the event id of *your dispatch*,
93
+ not the admission, because that is the edge the audit walks.
94
+
95
+ Whichever shape, hand over the project directory, the epic id, the live plan
96
+ path, the admitted task ids, the effort `profile` resolved above, and the
97
+ session id and event id the wave writes from. Take back each task's terminal
98
+ state, the merge-queue outcome, the findings raised, any escalation, and the
99
+ wave session's last event id if it opened one — chain the next command off
100
+ that.
101
+
102
+ Splitting the tier does not split the log. Every read in this playbook folds
103
+ the lineage, so a dispatch a wave session recorded is one this session can
104
+ still audit; that is what `--continues` buys, and it is why the split is safe
105
+ to make (D13, D-266). What it does not buy is a shortcut. A wave session
106
+ opened without `--continues` writes into a log nothing here reads, and the
107
+ verbs below then report a wave that appears never to have run rather than
108
+ 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
110
+ turns that silence into a red: a `wave-runner` that dispatched before opening
111
+ its log, or against the wrong event, is `unverifiable` and exits 1.
112
+
113
+ **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
115
+ reports the width the wave actually ran at, not the width it was admitted
116
+ at. Ask it with *this* session: it folds the lineage, so it sees the
117
+ dispatches a wave session made under the admission this one wrote. Exit `1`
118
+ is a wave whose tasks never once overlapped — the wave playbook's three
119
+ rules were not followed, and the log will say so long after this session has
120
+ forgotten. Exit `2` means the wave was admitted and no dispatch was recorded
121
+ under it, which is the wrong `--state-dir`, or a wave session opened without
122
+ `--continues`, rather than a stalled run. Ask before reporting the wave
123
+ done; a green gate on serialized work is exactly the outcome the two
124
+ playbooks are written to prevent.
125
+
126
+ 11. Repeat until every task in the live plan version is
127
+ `completed`/`superseded`/`waived`. Dispatch the **`planner`** again for
128
+ the epic verdict against acceptance criteria — gaps found → a NEW
129
+ `plan-v(n+1)` with inferred tasks (never a live-graph mutation,
130
+ architecture §12), auto-scheduled at confidence ≥0.8 else parked for an
131
+ operator tick. **Every new version is ingested before the loop resumes
132
+ on it**, whichever step cut it:
133
+
134
+ ```bash
135
+ smith plan ingest factory/specs/active/<epic>/plan-v<n+1>.json \
136
+ --session <session-id> --plan-version <n+1> --causal-parent <event-id>
137
+ ```
138
+
139
+ Cutting a version writes `plan-version-created` and moves the findings
140
+ it cites; it does not write the tasks it added. Until they are ingested
141
+ (`/bs plan` step 7) the new task is on no board and carries no claims,
142
+ budget or edges in the DB, and the first gate event to name its id
143
+ creates it as a bare row (D-46, D-254). It is idempotent: the tasks the
144
+ version carried forward are skipped, and the output's `added` should
145
+ equal what the amendment's `diff` added.
146
+ 12. Run the full check suite **at the project root, on the assembled
147
+ branch**. Every gate up to here ran inside a task worktree, so every
148
+ green you have so far is a green about a worktree — the envkit epic
149
+ 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
151
+ command refuses to move your working tree, and refuses a dirty one):
152
+
153
+ ```bash
154
+ smith integration check --epic <epic> --project <project-dir> \
155
+ --checks <checks.json> \
156
+ --session <session-id> --plan-version <n> --causal-parent <event-id>
157
+ ```
158
+
159
+ Exit 1 means the assembled branch is broken. Raise a finding, fix it as
160
+ a task, and run this again — it pins its result to the head sha, so any
161
+ merge after it lands makes the record stale and the verdict below will
162
+ say so.
163
+
164
+ `--plan-version <n>` is the live version, here and on every record steps
165
+ 13-16 write. The envelope leaves the flag optional, and an omitted one
166
+ stamps the record `plan_version: 1` — so on an epic that amended, the
167
+ check, the review and the close all claim to have verified a plan that
168
+ no longer exists. csb-signing-policy-1 closed at v3 with its integration
169
+ check and its closing spec review both stamped v1
170
+ (`csb-signing-policy-1-2026-09-11#197`, `#202`); the goal check, passed
171
+ the flag, says v3. Read the version off `smith plan ingest`'s receipt and
172
+ pass it every time.
173
+ 13. Dispatch the **`spec-reviewer`** again — this time against the code.
174
+ The pre-code review at `/bs plan` step 3 read the spec against nothing;
175
+ this one reads it against the assembled branch, which is the only reading
176
+ that can see a criterion the finished code proves wrong. The envkit epic
177
+ deadlocked on exactly that: a correct S2 whose fix criterion 3 mandated
178
+ and criterion 1 forbade (D-33). It writes to a close-specific path so it
179
+ cannot clobber the pre-code artifact; hand that file — each item naming the
180
+ `criterion_ref` it is against — to:
181
+
182
+ ```bash
183
+ smith epic spec-review --epic <epic> --project <project-dir> \
184
+ --plan factory/specs/active/<epic>/plan-vN.json \
185
+ --reviewed-by spec-reviewer \
186
+ [--evidence state/results/<epic>.spec-review-close-vN.json] \
187
+ --session <session-id> --plan-version <n> --causal-parent <event-id>
188
+ ```
189
+
190
+ Its `dispatch_decision` — and step 14's, and the planner's when it
191
+ renders a verdict — goes against the reserved `<epic>/integration` ref,
192
+ never a `<epic>/spec-review-rN` you coin for the occasion
193
+ ([`dispatch.md`](dispatch.md), "Dispatch a role with no claims against
194
+ the reserved ref").
195
+
196
+ `profile.closingSpecReview` decides whether this step runs at all:
197
+ `always` (`huge`, `medium`), or `when-plan-amended` (`small`) — a closing
198
+ review reads the spec against the code that now exists, which earns its
199
+ call when the spec moved. An epic that ran its whole plan without a
200
+ `plan amend` did not move. When the tier skips it, say so to the operator;
201
+ a skipped review and a clean one are not the same fact either.
202
+
203
+ When it does run, run it even when the review is clean — "ran and found
204
+ nothing" and "never ran" are different facts, and the verdict below
205
+ distinguishes them. It
206
+ exits 0 even when it raises findings: what it found blocks the plan, not
207
+ this command. A spec finding is unwaivable at S1/S2 and no task's diff can
208
+ contain the fix, so the answer is `smith plan amend --plan … --findings …
209
+ --rationale … --sites …` (§6a of the operator guide). `--sites` is every
210
+ place that shape occurs, not only the file the finding was reported
211
+ against — answer it before writing the changes, because it is the question
212
+ that decides how much the amendment fixes (D-123). It cuts plan v(n+1):
213
+ ingest it (step 11's `smith plan ingest`), go back to step 11 with the new
214
+ version, and re-run this review against the branch that results. Never
215
+ record a spec defect as a coder failure; that is
216
+ the deadlock this step exists to end.
217
+ 14. Check the plan against the **goal it was cut from**. Every gate up to
218
+ here reads text the planner produced, so all of them go green on a plan
219
+ that decomposes the wrong problem. This one reads the `- goal:` line of
220
+ the roadmap milestone that owns the epic — the one reference the planner
221
+ did not write. Get the clause list first (read-only, no event):
222
+
223
+ ```bash
224
+ smith epic goal --epic <epic>
225
+ ```
226
+
227
+ Hand those clauses and the live plan to a **`spec-reviewer`** session —
228
+ a fresh one, never the planner's, and never the same dispatch as step 13:
229
+ `smith dispatch check` refuses to let one dispatch answer for both.
230
+ Dispatch it *after* step 13's record is written, not alongside it — a
231
+ dispatch that predates the previous record has already answered for that
232
+ one, so two sessions fired up front leave this record unaccounted for and
233
+ `smith dispatch check` reports that record `unverifiable`. Take back one
234
+ verdict per clause, in the goal's order, and record it:
235
+
236
+ ```bash
237
+ smith epic goal-check --epic <epic> \
238
+ --plan factory/specs/active/<epic>/plan-vN.json \
239
+ --coverage state/results/<epic>.goal-coverage-vN.json \
240
+ --checked-by spec-reviewer \
241
+ --session <session-id> --plan-version <n> --causal-parent <event-id>
242
+ ```
243
+
244
+ `covered` must name live plan task ids; `out-of-scope` must give a reason,
245
+ and that reason is quoted back to the epic judge. `uncovered` mints an
246
+ S2-major spec finding against the plan file — no task diff can contain
247
+ that fix, so the answer is `smith plan amend`, which cuts v(n+1) and sends
248
+ you back to step 11 — through `smith plan ingest`, as in step 13. The
249
+ command exits 0 even when it raises findings.
250
+
251
+ **It refuses (`cli.no-epic-goal`) when the owning milestone states no
252
+ goal, and the epic then cannot close.** That is deliberate — there is no
253
+ `not-required` escape hatch, because "no goal is stated" is the absence of
254
+ the only text this gate can grade against. Fix the roadmap, do not work
255
+ around it: give the milestone a `- goal:` line, or add the epic to the
256
+ `- epics:` list of one that has it. Tell the operator you edited the
257
+ roadmap; it is a scope surface (`/bs plan` step 8).
258
+ 15. Before opening the PR, run the epic-final verdict — the last
259
+ `quorum_triggers` gate, and the one that decides whether this epic is
260
+ integrable at all:
261
+
262
+ ```bash
263
+ smith epic verdict --epic <epic> --project <project-dir> \
264
+ --session <session-id> --plan-version <n> --causal-parent <event-id>
265
+ ```
266
+
267
+ Mechanical oracles first: non-terminal tasks, open blocking findings, a
268
+ missing/failed/stale integration-root check, a missing/stale closing spec
269
+ review, or a missing/stale spec-vs-goal check return `hold` (exit 1)
270
+ without spending a judge call. `--project` is required — the verdict
271
+ reads the integration branch head to decide whether the records from
272
+ steps 12, 13 and 14 still cover it. On `hold`, do not open the PR — report
273
+ the `blockers` to the operator and go back to step 11.
274
+
275
+ Once the oracles pass, the judge call is real money when an external
276
+ provider is `active` in `crosscheck.yml`: codex spends the operator's
277
+ ChatGPT quota, DeepSeek spends prepaid credit, and step 16 runs the
278
+ verdict again before it writes, so a clean close is two spends, not one
279
+ (csb-signing-policy-1 wrote two `quorum-decision`s against its
280
+ integration ref, `#210` from the verdict and `#215` from the close).
281
+ Nothing in the `quorum-decision` payload names the purse, so say which
282
+ one it is to the operator before the first call, not after the second.
283
+ 16. Record the close. The verdict above is a read-only probe and writes
284
+ nothing; `epic close` is what makes it a fact in the log (D-43):
285
+
286
+ ```bash
287
+ smith epic close --epic <epic> --project <project-dir> \
288
+ --session <session-id> --plan-version <n> --causal-parent <event-id>
289
+ ```
290
+
291
+ It re-runs the verdict, then emits `epic-closed` on `go` and refuses
292
+ (exit 1, no event) on `hold`. **Never pass `--override-rationale`
293
+ yourself** — closing over a hold is the operator's call; ask for it and
294
+ quote the blockers. If the epic was cut by `/bs audit`
295
+ ([`audit.md`](audit.md) step 10), follow the close with
296
+ `smith audit resolve <project-dir> --epic <epic> --session <session-id> --causal-parent <event-id>`
297
+ so every finding the epic carried is marked `fixed` in the project's
298
+ audit store — the store, not the epic, is where a finding's life ends.
299
+ 17. Open **one integration PR per epic** with the scribe-written body
300
+ (`/bs report`'s playbook, [`report.md`](report.md)) — screenshots, test
301
+ results, reviewer verdict, waivers granted, timeline link. The head is
302
+ `smith/<epic>/integration`. The base is the target repo's `main` unless
303
+ the epic was cut from another epic's integration branch, in which case
304
+ the PR stacks on that branch and the operator merges the parent's PR
305
+ first — csb-signing-policy-1 was cut from `smith/csb-audit-1/integration`
306
+ and opened PR #54 against it, behind PR #53; once the parent has merged
307
+ and its branch is deleted, GitHub retargets the child to `main`. The
308
+ operator reviews on GitHub; this session never merges to `main`
309
+ (`docs/standards/guardrails.md`).
310
+
311
+ Before the first push, read the authorship the branch is about to make
312
+ public — `git log --format=%ae <base>..HEAD | sort -u` — and stop on any
313
+ address that is not a noreply one; a coder's worktree commit carries
314
+ whatever `user.email` its session inherited.
315
+
316
+ Then write the PR down. No verb records it for you: nothing in `src/`
317
+ opens a PR — `gh` runs in your session, never in the factory — so a
318
+ timeline that ends at `epic-closed` never shows what the close was for.
319
+ Append it by hand, under the reserved ref and the live plan version:
320
+
321
+ ```bash
322
+ 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>}}'
323
+ ```
324
+
325
+ The receipt says whether the timeline reads the type; it does, and it
326
+ titles the row `<owner>/<repo>#<n> (<head> → <base>)` — the one line
327
+ that shows a stacked PR's base before anyone merges in the wrong order.
@@ -0,0 +1,54 @@
1
+ # `/bs status` — live agent count, budget burn, epic phase
2
+
3
+ Two questions, in this order: **was anything watching while no session was
4
+ open**, and **what does the log say now**. Step 2 alone has always answered
5
+ the second; it cannot answer the first, because a session that folds the log
6
+ on demand knows only what is in the log, never whether anyone was reading it.
7
+
8
+ ## 1. Ask the watcher
9
+
10
+ `smith daemon status [--dir <dir>]`. It prints JSON and **exits 1 whenever a
11
+ current daemon is not watching** — that is the health check doing its job,
12
+ not a command that failed, so never report the non-zero exit as an error.
13
+ Three readings:
14
+
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
17
+ in a clause and go to step 2, which folds the log live anyway. What you
18
+ must not do is render `lastTick` as though it were current.
19
+ - `stale: true` — a daemon holds the lock and has published nothing for
20
+ three of its own intervals. This is worse than stopped, because the pid
21
+ file makes the box look tended while something is wedged mid-tick. Lead
22
+ with it, say how long the silence has run (`reportAgeSeconds`, or "never
23
+ published" when it is `null`), and point the operator at
24
+ `<dir>/daemon.log`.
25
+ - `running: true, stale: false` — the numbers are fresh. Say how fresh
26
+ (`reportAgeSeconds`) rather than implying "now".
27
+
28
+ When there is a `lastTick`, render its triage split before the digest below,
29
+ because it is the only record of what happened while nobody was watching:
30
+ `attention` open findings, of which `newAttention` are ones no earlier tick
31
+ had seen; then `autoAdmitted` against `operatorHeld` — the second is the
32
+ operator's actual queue, the first is what the scheduler already admitted on
33
+ its own and needs no decision.
34
+
35
+ ## 2. Fold the log
36
+
37
+ `smith stats overview [--session <id>]`. Render the JSON as a short digest,
38
+ field for field — never invent numbers the query didn't return:
39
+
40
+ - **Live agents**: `liveAgents` grouped by role/model/provider,
41
+ `liveAgentCount` as the headline (with `liveAgentCountDelta5m` as a
42
+ ▲/▼ 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`).
45
+ `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
47
+ the dispatches nothing recorded a Result for.
48
+ - **In flight**: `epicsInFlight`, `recentDispatches` (last 10).
49
+ - **Alerts**: `alerts.escalations` and `alerts.pendingWaivers` — call these
50
+ out first if non-zero, they're what the operator came here to see.
51
+ - **Milestones**: `milestoneProgress` — one line each, `%` complete.
52
+
53
+ If the UI is already running, just point the operator at it instead of
54
+ re-rendering the same data as text.
@@ -0,0 +1,19 @@
1
+ # `/bs ui` — serve the local dashboard
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>`).
7
+
8
+ **That build needs a clone.** `ui/` is not in the package's `files`, so an
9
+ install — npm or plugin — ships no dashboard at all and `ui.not-built` is the
10
+ 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
12
+ clone-only and stop, rather than sending the operator to a `pnpm` script they
13
+ do not have.
14
+
15
+ `--roadmap-path` travels with `--db`/`--state-dir`: the read path
16
+ re-projects each session on the first request, and that rebuilds the
17
+ milestones table from the roadmap file it was given — unset, it falls
18
+ back to Blacksmith's own `factory/specs/roadmap.md`, so a dashboard
19
+ pointed at another project's db would show the factory's roadmap.
@@ -0,0 +1,13 @@
1
+ # `/bs waivers` — answer the pending waiver batch
2
+
3
+ 1. `smith waivers pending <epic> --session <session-id>` — every S3/S4
4
+ finding for the epic with no waiver decision yet (a decided fingerprint
5
+ never resurfaces).
6
+ 2. Present the whole batch to the operator as **one** question ("ignore
7
+ these?") — never ask per-finding (architecture §11, `severity.yml`).
8
+ 3. Write the answers to `decisions.json`
9
+ (`Array<{fingerprint, decision: "granted"|"denied", operatorNote}>`) and
10
+ run `smith waivers apply decisions.json --session ... --plan-version ...
11
+ --causal-parent ... --actor operator`. S1/S2 findings are rejected by
12
+ the command itself if attempted — they go through the escalation ladder
13
+ instead, never a waiver.