@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.
- package/.claude/agents/coder.md +9 -3
- package/.claude/agents/grader.md +13 -5
- package/.claude/agents/merger.md +2 -0
- package/.claude/agents/planner.md +22 -1
- package/.claude/agents/researcher.md +3 -1
- package/.claude/agents/reviewer.md +9 -2
- package/.claude/agents/security-reviewer.md +15 -7
- package/.claude/agents/spec-reviewer.md +12 -3
- package/.claude/agents/tester.md +5 -2
- package/.claude/agents/uiux.md +10 -3
- package/.claude/agents/verifier.md +8 -3
- package/.claude/skills/bs/SKILL.md +119 -0
- package/.claude/skills/bs/audit.md +252 -0
- package/.claude/skills/bs/dispatch.md +394 -0
- package/.claude/skills/bs/lessons.md +73 -0
- package/.claude/skills/bs/mcp.md +29 -0
- package/.claude/skills/bs/new.md +35 -0
- package/.claude/skills/bs/plan.md +172 -0
- package/.claude/skills/bs/report.md +129 -0
- package/.claude/skills/bs/run.md +327 -0
- package/.claude/skills/bs/status.md +54 -0
- package/.claude/skills/bs/ui.md +19 -0
- package/.claude/skills/bs/waivers.md +13 -0
- package/.claude/skills/bs/wave.md +267 -0
- package/README.md +136 -79
- package/factory/orchestrator/dist/actors.js +4 -3
- package/factory/orchestrator/dist/agents-registry.js +96 -19
- package/factory/orchestrator/dist/artifacts.js +36 -4
- package/factory/orchestrator/dist/budgets.js +7 -5
- package/factory/orchestrator/dist/claims.js +27 -1
- package/factory/orchestrator/dist/cli.js +196 -27
- package/factory/orchestrator/dist/crosscheck.js +11 -0
- package/factory/orchestrator/dist/daemon.js +30 -5
- package/factory/orchestrator/dist/db/projector.js +272 -90
- package/factory/orchestrator/dist/db/queries.js +83 -14
- package/factory/orchestrator/dist/db/schema.js +7 -0
- package/factory/orchestrator/dist/events.js +88 -4
- package/factory/orchestrator/dist/harness.js +516 -0
- package/factory/orchestrator/dist/impact.js +76 -5
- package/factory/orchestrator/dist/judges.js +49 -8
- package/factory/orchestrator/dist/lessons.js +4 -4
- package/factory/orchestrator/dist/mcp.js +4 -3
- package/factory/orchestrator/dist/paths.js +169 -15
- package/factory/orchestrator/dist/plan.js +61 -1
- package/factory/orchestrator/dist/planQuorum.js +36 -8
- package/factory/orchestrator/dist/providers/api-transport.js +62 -5
- package/factory/orchestrator/dist/providers/cli-transport.js +7 -73
- package/factory/orchestrator/dist/providers/index.js +1 -0
- package/factory/orchestrator/dist/providers/schema-validate.js +58 -7
- package/factory/orchestrator/dist/providers/types.js +5 -0
- package/factory/orchestrator/dist/roadmap.js +3 -3
- package/factory/orchestrator/dist/run-cli.js +130 -0
- package/factory/orchestrator/dist/runner.js +264 -0
- package/factory/orchestrator/dist/scaffold.js +3 -2
- package/factory/orchestrator/dist/spawn.js +84 -0
- package/factory/orchestrator/dist/stack.js +10 -2
- package/factory/orchestrator/dist/templateFragments.js +178 -0
- package/factory/orchestrator/dist/usage.js +30 -9
- package/factory/orchestrator/dist/waveNext.js +42 -14
- package/factory/orchestrator/dist/waveSchedule.js +7 -1
- package/factory/orchestrator/dist/workroot.js +113 -0
- package/factory/orchestrator/drizzle/0011_findings_spec_ref.sql +3 -0
- package/factory/orchestrator/drizzle/meta/_journal.json +7 -0
- package/factory/policies/budgets.yml +7 -4
- package/factory/policies/crosscheck.yml +10 -8
- package/factory/policies/harness.yml +82 -0
- package/factory/policies/lessons.md +122 -0
- package/factory/policies/severity.yml +1 -1
- package/factory/policies/taxonomy.yml +6 -2
- package/factory/specs/roadmap.md +154 -0
- package/factory/specs/schema/task-spec.schema.json +5 -0
- 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.
|