@juzser/blacksmith 0.1.1 → 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.
@@ -58,6 +58,27 @@ types, config, shared fixtures) are never split across concurrent tasks —
58
58
  mark them `serialize-always` and give overlapping tasks a dependency edge
59
59
  instead of concurrent claims.
60
60
 
61
+ Before signing, read the slicing back from the project, not from the plan:
62
+ `smith claims impact --plan <plan.json> <every task id> --repo <project-dir>`
63
+ names each import edge that crosses between tasks the plan gave no
64
+ dependency, and `smith wave schedule <plan.json> --repo <project-dir>` says
65
+ how wide the plan can ever run. Every crossing the first reports is one of
66
+ three things, and the plan says which:
67
+
68
+ - a **dependency edge** — the consumer needs the producer's *new* export, so
69
+ the producer runs first;
70
+ - a **`keeps_exports` promise** on the producer — it only *adds* to that file
71
+ or leaves its exports alone, so both run now and the diff is checked
72
+ against the promise post-run;
73
+ - a **re-slice**.
74
+
75
+ Two shapes cross by construction, so do not draw them: a module's tests split
76
+ from the module (a `src/x.ts` task and a `test/x.test.ts` task always cross),
77
+ and a doc hub (runbook, README) claimed by one task and edited by many —
78
+ each task edits its own section inside its own claims, or one docs task takes
79
+ edges from the others. Target `widest >= 2` when the plan has two or more
80
+ tasks, or say in `planner_notes` why the work is a chain.
81
+
61
82
  ## Living spec doc (architecture §11)
62
83
 
63
84
  You own the epic's living spec markdown: every worker reads it before
@@ -1,33 +1,49 @@
1
1
  ---
2
2
  name: bs
3
- description: Operator console for the Blacksmith factory — invoke as `/bs <subcommand>` (new, plan, run, audit, status, ui, waivers, lessons, report) to scaffold a project, plan or drive an epic through the loop, audit a project on four axes and cut an epic from what the operator accepts, check live status, open the dashboard, answer a waiver batch, triage lesson candidates, or get a progress digest. This file routes; each subcommand's playbook is a sibling file read when that verb runs. Use this whenever the operator wants to interact with Blacksmith itself, from a Claude Code session inside this repo.
3
+ description: Operator console for the Blacksmith factory — invoke as `/bs <subcommand>` (new, plan, run, audit, status, ui, waivers, lessons, report) to scaffold a project, plan or drive an epic through the loop, audit a project on four axes and cut an epic from what the operator accepts, check live status, open the dashboard, answer a waiver batch, triage lesson candidates, or get a progress digest. This file routes; each subcommand's playbook is a sibling file read when that verb runs. Use this whenever the operator wants to interact with Blacksmith itself, from any Claude Code session that has Blacksmith installed — a clone of the repo, or the plugin plus the `smith` CLI from npm.
4
4
  ---
5
5
 
6
6
  # /bs — Blacksmith operator console
7
7
 
8
8
  You (the orchestrator session running this skill) are the human's one interface
9
9
  to the factory. Every subcommand below is a **playbook**, not a script: the
10
- deterministic mechanics run through the real `smith` CLI
11
- (`node factory/orchestrator/dist/cli.js <ns> <action> ...`, or `smith ...` once
12
- linked — `pnpm build` first if `dist/` is stale); the judgment steps —
13
- planning, spec review, coding, testing, reviewing — are separate Claude Code
14
- sessions you dispatch from the matching `.claude/agents/<role>.md`. **This
15
- skill never calls an LLM directly and never embeds a role prompt** — the
16
- templates own that; duplicating them here would let this file drift out of
17
- sync with the real contracts. Cite policy files (`factory/policies/*.yml`)
18
- rather than restating their numbers.
10
+ deterministic mechanics run through the real `smith` CLI, spelled `smith` in
11
+ every command below — on PATH from `npm i -g @juzser/blacksmith`, or inside a
12
+ clone `node factory/orchestrator/dist/cli.js` instead, `pnpm build` first if
13
+ `dist/` is stale. The judgment steps — planning, spec review, coding, testing,
14
+ reviewing — are separate Claude Code sessions you dispatch by role, each under
15
+ the contract in its own agent template beside this skill
16
+ (`.claude/agents/<role>.md`). **This skill never calls an LLM directly and
17
+ never embeds a role prompt** — the templates own that; duplicating them here
18
+ would let this file drift out of sync with the real contracts. Cite policy
19
+ files (`factory/policies/*.yml`) rather than restating their numbers.
19
20
 
20
21
  **The project directory is an answer you ask for, not a path this file
21
22
  knows.** A project this factory builds is not part of it: `smith new` puts one
22
- *beside* this clone when no `--target-dir` says otherwise, and nothing
23
- downstream reads that location — `workspaces/` inside the repo is still a legal
24
- answer, just no longer the assumed one. Every verb that touches the project's
25
- git takes the directory itself: `<project-dir>` as a positional on the
26
- `worktree` family, `--project <dir>` on everything else. A task's worktrees
27
- are placed beside whatever directory it was handed, so a project outside this
28
- repo keeps its worktrees outside it too (`AGENTS.md` "Worktrees"). Ask for it
29
- once, at the top of a run, and carry that one answer through every command
30
- below — `<project-dir>` here means that answer, never a fixed path.
23
+ *outside* the factory when no `--target-dir` says otherwise (beside the clone
24
+ if you run one, in the directory you ran an installed `smith` from), and
25
+ nothing downstream reads that location — `workspaces/` inside the repo is
26
+ still a legal answer, just no longer the assumed one. Every verb that touches
27
+ the project's git takes the directory itself: `<project-dir>` as a positional
28
+ on the `worktree` family, `--project <dir>` on everything else. A task's
29
+ worktrees are placed beside whatever directory it was handed, so a project
30
+ outside this repo keeps its worktrees outside it too (`AGENTS.md` "Worktrees",
31
+ a clone-only file — see "Where the files below live"). Ask for it once, at the
32
+ top of a run, and carry that one answer through every command below —
33
+ `<project-dir>` here means that answer, never a fixed path.
34
+
35
+ **Where the files below live.** This skill cites three kinds of path and they
36
+ do not all resolve the same way. Playbooks and agent templates are beside this
37
+ file and always resolve. `factory/policies/*.yml`, the JSON Schemas and the
38
+ scaffold are **read-only assets the CLI ships**: in a clone they are in the
39
+ checkout, and in an install they are inside the package — ask `smith` rather
40
+ than guessing, since `smith init` prints `repoRoot` (where they are) and
41
+ `workRoot` (where everything written goes) and is safe to re-run, keeping any
42
+ file it already seeded. `docs/` and `AGENTS.md` are **clone-only**: they are in
43
+ the repository and in no install, so a citation to one is a pointer for an
44
+ operator who has the repo, never a file to assume you can open. Read, do not
45
+ assume — and when a cited file is not there, say so instead of substituting a
46
+ remembered number.
31
47
 
32
48
  Every write command needs an event-log envelope: `--session <id>
33
49
  --plan-version <n> --causal-parent <event-id> [--actor operator]`. Open a
@@ -19,8 +19,10 @@ the `audit_id` the way you keep a session id.
19
19
  nothing reads a cwd and nothing keeps a "current audit" on the side, so two
20
20
  audits on two projects cannot be confused. The project must be a git
21
21
  checkout; a directory this factory did not build is as auditable as one it
22
- did. Then `mkdir -p state/audit` in this clone: the judges' artifacts live
23
- there, under runtime state, never under the project.
22
+ did. Then `mkdir -p state/audit` in the factory's **work root** — the clone
23
+ itself, or the `.blacksmith/` that `smith init` made beside your code; `smith
24
+ init` prints it as `workRoot`. The judges' artifacts live there, under runtime
25
+ state, never under the project.
24
26
 
25
27
  ## 2. Open the audit
26
28
 
@@ -140,11 +140,11 @@ memory of its previous attempt and will happily report round 1 forever.
140
140
  evidence that the model was too small; it is usually evidence the spec was
141
141
  unclear, and a bigger model will implement the same misreading more
142
142
  convincingly.
143
- - **Escalate to opus only after two failed rounds**, and log the escalation in
144
- the `dispatch_decision` (`model_tier`, `model`, plus the reason). An unlogged
145
- escalation is a cost you cannot attribute later — and `model_tier` alone
146
- cannot even tell you *which* frontier model you escalated to, since opus and
147
- fable share the tier.
143
+ - **Escalate to the frontier tier only after two failed rounds**, and log the
144
+ escalation in the `dispatch_decision` (`model_tier`, `model`, plus the
145
+ reason). An unlogged escalation is a cost you cannot attribute later — and
146
+ `model_tier` alone cannot even tell you *which* frontier model you escalated
147
+ to, since opus and fable share the tier.
148
148
  - **The grader's 2-round cap is a hard stop, not a ladder rung.** At a round-2
149
149
  `fail` the task goes back to the planner for re-scoping. Do not re-dispatch
150
150
  the grader, and do not escalate the *grader* — the gates decide pass/fail,
@@ -1,8 +1,10 @@
1
1
  # `/bs new <project> [--ui]` — scaffold a target project
2
2
 
3
3
  1. Ask where the project should live, then run `smith new <project> [--ui]
4
- [--target-dir <dir>]`. Without `--target-dir` it lands beside this repo, in
5
- `<repo-parent>/<project>`, which is a default rather than a requirement —
4
+ [--target-dir <dir>]`. Without `--target-dir` it lands beside the clone, in
5
+ `<repo-parent>/<project>` — or, when `smith` is an installed package rather
6
+ than a clone, in the directory you run it from, never inside the package.
7
+ Either way that is a default rather than a requirement —
6
8
  the answer becomes the `<project-dir>` every later command takes, so take
7
9
  it here rather than assuming it six steps in. The call
8
10
  copies `factory/scaffold/` (TS strict, Biome, Vitest, CI) and layers
@@ -37,8 +37,8 @@ there means "not looked at", not "looked at and clean".
37
37
  guessed (architecture §5) — dispatch `researcher`
38
38
  (`.claude/agents/researcher.md`) for an unfamiliar repo, or do it
39
39
  directly with Grep/Read for a small one.
40
- 2. Dispatch a **`planner`** session (`.claude/agents/planner.md`, opus/
41
- fable) with the goal + the claims analysis. It drafts the epic spec and
40
+ 2. Dispatch a **`planner`** session (`.claude/agents/planner.md`, frontier
41
+ tier) with the goal + the claims analysis. It drafts the epic spec and
42
42
  task specs (`factory/specs/schema/task-spec.schema.json` shape).
43
43
  3. Dispatch a **`spec-reviewer`** session (`.claude/agents/spec-reviewer.md`)
44
44
  — a *different model* than the planner's, per its own frontmatter — to
@@ -117,9 +117,26 @@ there means "not looked at", not "looked at and clean".
117
117
  can pay it cheaply; a `widest: 1` plan serializes the whole epic while
118
118
  every gate downstream reports a healthy wave of one. Deferrals for
119
119
  `dependency-pending` never appear there, because a chain of real
120
- dependencies is the shape of the work and not a defect. Exit 2 is
121
- information, not a stop — decide whether to re-slice, and say which you
122
- chose when you present the plan.
120
+ dependencies is the shape of the work and not a defect. Exit 2 whose
121
+ `constraints` name `symbol-coupled` is a **re-plan round**, not
122
+ information: hand the planner the constraint list — the file pairs from
123
+ the deferral detail — and ask, per pair, for a dependency edge, a
124
+ `keeps_exports` promise on the producer, or a re-slice.
125
+ `claim-overlap` / `serialize-hotspot` constraints are the re-slice case
126
+ above. Exit 2 with no such constraint remains information, not a stop —
127
+ decide whether to re-slice, and say which you chose when you present the
128
+ plan.
129
+
130
+ Beside the schedule, the read that names what it only counts:
131
+
132
+ ```bash
133
+ smith claims impact --plan factory/specs/active/<epic>/plan-v1.json \
134
+ <task-id>... --repo <project-dir>
135
+ ```
136
+
137
+ Its `crossings` are the file pairs a `symbol-coupled` constraint points
138
+ at; `promised` holds the ones a producer's `keeps_exports` already
139
+ answers, which the schedule admits and the diff is later checked against.
123
140
  7. Log the sign-off itself as a decision checkpoint (this is exactly what
124
141
  `smith dream`'s "plan sign-off" extraction looks for, `lessons.ts`):
125
142
  `smith event append '{"session_id":"...","actor":"operator",
@@ -9,13 +9,13 @@ pending proposals lets them sit invisible instead of surfacing them.
9
9
 
10
10
  1. **Ask what the factory is answerable for, then run the scheduler.**
11
11
  `smith projects list` reads the roadmap's `- project:` bullets — the
12
- register `smith new` writes to — and prints this clone plus every
12
+ register `smith new` writes to — and prints the factory itself plus every
13
13
  project it built that has a checkout, ending in the `--project` line to
14
14
  pass on. Do not assemble that list from memory: the maintenance pass
15
15
  reads one lockfile per flag and reports one proposal per repo, so a repo
16
16
  left off the line is not reported as missing, it is simply never
17
17
  mentioned. (The daemon raises `unwatched-project` for a child project with
18
- a checkout that is left off the line — not for this clone, which is in
18
+ a checkout that is left off the line — not for the factory itself, which is in
19
19
  the pass by default, and not for a declared project with no checkout at
20
20
  all, which `smith projects list` marks with `?` instead of leaving it
21
21
  off the printed list. The two surfaces no longer answer the same
@@ -97,7 +97,7 @@ pending proposals lets them sit invisible instead of surfacing them.
97
97
  check before reporting it as a stall. A `partial` verdict is **not** a
98
98
  finding; put `widest` in the digest as the one number that says how wide
99
99
  this factory has ever actually run.
100
- 6. Dispatch **`scribe`** (`.claude/agents/scribe.md`, haiku) with that
100
+ 6. Dispatch **`scribe`** (`.claude/agents/scribe.md`, small tier) with that
101
101
  JSON plus step 2's admissions as input: shipped / in-flight / blocked /
102
102
  budget burn / next milestone / **N rechecks pending, M maintenance
103
103
  bumps auto-schedulable** (never silently dropped), ≤300 words, linking
@@ -53,10 +53,12 @@ file asked for would be running it below the floor.
53
53
  - The same verdict carries `symbolImpact`. `valid: true` with
54
54
  `symbolImpact.status: "coupled"` means the claims really are disjoint
55
55
  and that is not enough: the two tasks sit on either end of an import
56
- edge the plan declared no dependency for. Split the wave and run the
57
- producer first — there is no override for a crossing, because the
58
- declared-edge case is already refused above it
59
- (operator-guide/wave.md §2).
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).
60
62
  - `wave check` is also where the wave is priced and where
61
63
  `max_in_flight_tasks` is enforced (`budgets.yml`, `over-fan-out`).
62
64
  `wave next` deliberately asks neither question: a proposer that also
@@ -2,11 +2,18 @@
2
2
 
3
3
  `smith ui serve [--port 4680] [--db state/smith.db] [--state-dir …]
4
4
  [--roadmap-path …]`. If it errors `ui.not-built`, run `pnpm build:ui`
5
- first (builds `ui/server/dist` + `ui/dist`), then retry. Print the local
5
+ first (builds `ui/server/dist` + `ui/dist`), then retry, and print the local
6
6
  URL (`http://127.0.0.1:<port>`).
7
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
+
8
15
  `--roadmap-path` travels with `--db`/`--state-dir`: the read path
9
16
  re-projects each session on the first request, and that rebuilds the
10
17
  milestones table from the roadmap file it was given — unset, it falls
11
- back to black-smith's own `factory/specs/roadmap.md`, so a dashboard
12
- pointed at another project's db would show this repo's roadmap.
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.
@@ -122,7 +122,10 @@ one thing this playbook never asks you to.
122
122
  4. Dispatch **`coder`** (`.claude/agents/coder.md`) in that worktree.
123
123
  Token/diff caps (`budgets.yml`: 150k tokens, 400 diff lines) and YAGNI
124
124
  are the coder's own constraints — don't restate them here, the template
125
- does.
125
+ does. What the template does not read is the schema, so when the spec
126
+ carries `keeps_exports`, restate the promise in the dispatch in one line:
127
+ the file list, and "keep every existing export's name and declaration;
128
+ adding is fine".
126
129
  5. Dispatch **`tester`** (`.claude/agents/tester.md`) for missing unit
127
130
  coverage and epic-level e2e/screenshots.
128
131
  - Then the **uiux visual pass**, but only when all three hold: the task
@@ -207,7 +210,7 @@ one thing this playbook never asks you to.
207
210
  this is the timeline, not optional bookkeeping.
208
211
  9. Gate outcome `blocked` → bounce to the coder on the **same branch**.
209
212
  After 2 failed rounds on the same task, escalate model tier
210
- automatically (sonnet → opus, logged — `budgets.yml`
213
+ automatically (mid → frontier, logged — `budgets.yml`
211
214
  `escalation_ladder`); after 3, escalate to the operator. Never skip a
212
215
  rung, never loop past one. Count the rounds from this task's
213
216
  `dispatch_decision` events, not from the agent's own account of itself
@@ -215,13 +218,16 @@ one thing this playbook never asks you to.
215
218
  you: `smith escalation check <session-id> --task <task-id>`, which
216
219
  exits 1 if the rung you just climbed is not evidenced.
217
220
  10. Gate outcome `pass`/`pass-with-waivers-pending` → before admitting,
218
- ask what the diff did to everyone outside the claims:
221
+ ask what the diff did to everyone outside the claims, and whether it
222
+ kept what the spec promised:
219
223
  `smith claims impact <worktree-dir> <spec.json>`. Exit 1 means a
220
224
  `proven` break — this task removed an export a file outside its claims
221
- still imports — and that is a bounce to the coder, not a merge. A
222
- `possible` / `signature-changed` entry exits 0 and is a note: the
223
- scanner reads text, not types (operator-guide/wave.md §2). Then admit
224
- into the
225
+ still imports — **or a broken promise**: a file the spec's
226
+ `keeps_exports` names lost an export or changed one's declaration.
227
+ Either is a bounce to the coder, not a merge; quote the `promises`
228
+ entries in the bounce. A `possible` / `signature-changed` entry in
229
+ `breaks` exits 0 and is a note: the scanner reads text, not types
230
+ (operator-guide/wave.md §2). Then admit into the
225
231
  merge queue: `smith queue run <epic> --project <project-dir>
226
232
  --test-cmd "<cumulative test command>" --tasks tasks.json`. On a
227
233
  `rebase-conflict` outcome, dispatch **`merger`**
package/README.md CHANGED
@@ -151,71 +151,72 @@ a chat transcript.
151
151
 
152
152
  ## Install
153
153
 
154
- Two ways in, and they are **not** equivalent. A clone is what drives an epic.
155
- The package is the `smith` CLI on a machine that has no clone.
154
+ Say **"install Blacksmith"** to a Claude Code session and it does the whole
155
+ thing: [`INSTALL.md`](INSTALL.md) is an executable runbook, and it stops to ask
156
+ before anything that touches your machine.
156
157
 
157
- ### A clone — the whole factory
158
+ By hand it is one command in a shell:
158
159
 
159
160
  ```bash
160
- git clone https://github.com/juzser/blacksmith.git && cd blacksmith
161
- pnpm install --frozen-lockfile
162
- pnpm run build # tsc -> factory/orchestrator/dist/
163
- node factory/orchestrator/dist/cli.js --help
161
+ npm i -g @juzser/blacksmith
164
162
  ```
165
163
 
166
- Verify it with the same gate CI runs (this one also needs `python3` + PyYAML):
164
+ and two inside Claude Code:
167
165
 
168
- ```bash
169
- bash scripts/check.sh # ends in `== PASS ==` on a good install
166
+ ```
167
+ /plugin marketplace add juzser/blacksmith
168
+ /plugin install blacksmith@blacksmith
170
169
  ```
171
170
 
172
- Driving a real epic additionally needs the **Claude Code CLI** — the planner and
173
- every worker run as Claude Code sessions. Open one **in the clone** and you have
174
- `/bs`; that is the operator surface everything under [Using it](#using-it)
175
- assumes.
171
+ Then `smith init` in the project you want it to work on. That creates
172
+ `.blacksmith/` beside your code — the event log, your epic plans, a roadmap and
173
+ a `stack.yml` to answer — and writes nothing anywhere else; `SMITH_HOME` moves
174
+ that root if you want one home for several projects. You now have `/bs`.
176
175
 
177
- **Rather not do this by hand?** Open a Claude Code session in the clone and say
178
- *"install Blacksmith"*. [`INSTALL.md`](INSTALL.md) is an executable runbook: it
179
- asks before touching anything outside the clone, and it doubles as the human
180
- version — per-platform setup (macOS, Debian/Ubuntu, Fedora, Alpine, WSL2),
181
- troubleshooting, and the known platform gaps stated rather than papered over.
176
+ **Both halves are required.** The package
177
+ ([`@juzser/blacksmith`](https://www.npmjs.com/package/@juzser/blacksmith)) is the
178
+ deterministic `smith` CLI; the plugin is `/bs` and the fourteen agent roles it
179
+ dispatches. `smith` alone never gives you `/bs`, because Claude Code loads
180
+ skills from a project's `.claude/`, your `~/.claude/`, or a plugin — never from
181
+ `node_modules` — and a `/bs` with no `smith` on PATH can run nothing. Take
182
+ `latest`: `0.1.0` predates `smith init` and keeps state inside its own install
183
+ directory, which the next `npm i` replaces.
182
184
 
183
- ### The package — `smith` inside a project you already have
185
+ <details>
186
+ <summary><b>A clone instead — the whole factory</b></summary>
184
187
 
185
- The CLI is published as
186
- [`@juzser/blacksmith`](https://www.npmjs.com/package/@juzser/blacksmith):
188
+ A clone is for hacking on Blacksmith itself, and for the two things an install
189
+ does not carry: the dashboard and this repo's own enforcement.
187
190
 
188
191
  ```bash
189
- cd /path/to/your-project
190
- npx @juzser/blacksmith init # creates .blacksmith/ beside your code
191
- npx @juzser/blacksmith --help # or: npm i -g @juzser/blacksmith && smith --help
192
+ git clone https://github.com/juzser/blacksmith.git && cd blacksmith
193
+ pnpm install --frozen-lockfile
194
+ pnpm run build # tsc -> factory/orchestrator/dist/
195
+ bash scripts/check.sh # the gate CI runs; ends in `== PASS ==`
192
196
  ```
193
197
 
194
- The tarball is the `smith` binary plus everything it reads at runtime — the
195
- policies, the JSON Schemas, the scaffold templates, the roadmap default, the
196
- agent role files, the database migrations. Installed, `smith` writes beside
197
- **you**, not beside itself: state, epic plans and your `.env` go under
198
- `.blacksmith/` in the directory you run it from, and `smith new` scaffolds
199
- there too. `SMITH_HOME` overrides that root if you want one home for several
200
- projects. (In a clone it still writes into the clone, as it always has — the
201
- layout under the root is identical either way, which is what makes the two
202
- installs one codebase.)
203
-
204
- **What a package install does not give you is `/bs`.** `/bs` is not a CLI
205
- command; it is a Claude Code **skill** — `.claude/skills/bs/`, a router and ten
206
- playbooks that a session reads and follows. The playbooks ship in the tarball,
207
- but they land under `node_modules/@juzser/blacksmith/`, which is not a place
208
- Claude Code looks for skills or agent definitions. So a package gives you the
209
- `smith` verbs and not the loop that drives them: validate plans, run the gates,
210
- append events, read stats — yes; `/bs plan` and `/bs run` — from a clone.
211
- Closing that gap is its own milestone, scoped and not yet cut:
198
+ Open a Claude Code session in the clone and you have `/bs` already, from the
199
+ checkout's own `.claude/` — **do not also install the plugin there.** You would
200
+ get two of everything (`bs` and `blacksmith:bs`, `auditor` and
201
+ `blacksmith:auditor`, once per role), pay the always-on cost twice, and the two
202
+ copies are free to disagree: the plugin's is a pinned checkout of `main`, the
203
+ project's is whatever branch you have out. `claude plugin disable blacksmith`
204
+ settles it.
205
+
206
+ The dashboard is clone-only because `ui/` is in neither the tarball nor the
207
+ plugin, so `smith ui serve` answers `ui.not-built` in an install and means it.
208
+ Enforcement is clone-only because this repo's `.claude/settings.json` deny
209
+ rules and its policy hook resolve paths against a checkout; the plugin ships no
210
+ `hooks/hooks.json` and loads neither (`Hooks (0)`), since a `/bs` that asked
211
+ you about every Bash command would be worse than one that asks about none. The
212
+ rest of that port is scoped in
212
213
  [`docs/specs/plugin-port-scope.md`](docs/specs/plugin-port-scope.md).
213
214
 
214
- > **Take `latest`.** `0.1.1` is the first release that knows it is a package:
215
- > `smith init`, a work root beside your project, a shipped roadmap for
216
- > `smith new` to read. `0.1.0`, the release before it, has none of that — it
217
- > derives every path from a repo root and keeps state inside its own install
218
- > directory, which the next `npm i` replaces. Nothing above describes `0.1.0`.
215
+ `INSTALL.md` Part 2 is the long form of the clone above: per-platform
216
+ setup (macOS, Debian/Ubuntu, Fedora, Alpine, WSL2), the stack interview,
217
+ troubleshooting, and the known platform gaps stated rather than papered over.
218
+
219
+ </details>
219
220
 
220
221
  ## Using it
221
222
 
@@ -390,10 +391,12 @@ runbook is written; the hosted UI stays deferred. Beside the phases, `/bs audit`
390
391
  is built: an existing project can be read on four axes and one epic cut from
391
392
  what you accept.
392
393
 
393
- The CLI is on npm as `@juzser/blacksmith`, at `0.1.1` — the first version
394
- that runs beside you and knows it is a package, rather than out of the
395
- clone-shaped install `0.1.0` was. The package is the binary and what it
396
- reads. The dashboard, the docs, the test suite and `/bs` itself come from a
394
+ The CLI is on npm as `@juzser/blacksmith`, at `0.2.0` — two binaries now,
395
+ `smith` and `smith-run`, the second being what runs a rendered turn on a
396
+ `cli` harness such as Codex. `0.1.1` was the first version that ran beside
397
+ you and knew it was a package, rather than out of the clone-shaped install
398
+ `0.1.0` was. The package is the binaries and what they
399
+ read. The dashboard, the docs, the test suite and `/bs` itself come from a
397
400
  clone; a plugin is what would move `/bs`, and it is
398
401
  [scoped, not cut](docs/specs/plugin-port-scope.md).
399
402
 
@@ -418,7 +421,7 @@ version in
418
421
  | [`docs/guide/extending.md`](docs/guide/extending.md) | Adding agents, policies, taxonomy values |
419
422
  | [`docs/specs/black-smith-architecture.md`](docs/specs/black-smith-architecture.md) | Why it is shaped this way |
420
423
  | [`docs/specs/audit-command-scope.md`](docs/specs/audit-command-scope.md) | What `/bs audit` promises an audited project, and why |
421
- | [`docs/specs/plugin-port-scope.md`](docs/specs/plugin-port-scope.md) | What it would take to run `/bs` without a clone |
424
+ | [`docs/specs/plugin-port-scope.md`](docs/specs/plugin-port-scope.md) | How `/bs` runs without a clone, and what the plugin leaves behind |
422
425
  | [`docs/guide/dashboard.md`](docs/guide/dashboard.md) | The dashboard tour |
423
426
  | [`docs/runbooks/providers.md`](docs/runbooks/providers.md) | Setting up the cross-provider judges |
424
427
  | [`docs/runbooks/ops.md`](docs/runbooks/ops.md) | Running `smith daemon` unattended |
@@ -226,7 +226,33 @@ export function readClaimList(task) {
226
226
  'wave for the reason that it may not edit anything. Admitting it defers the mistake to ' +
227
227
  'the first out-of-claim edit; task-spec.schema.json requires at least one claim.', { task_id: task.task_id });
228
228
  }
229
- return { task_id: task.task_id, claims: claims };
229
+ const promises = readPromiseList(task);
230
+ return promises === undefined
231
+ ? { task_id: task.task_id, claims: claims }
232
+ : { task_id: task.task_id, claims: claims, keeps_exports: promises };
233
+ }
234
+ /**
235
+ * The `keeps_exports` half of the same door. A promise the wave gate cannot
236
+ * read must not read as "no promise": that direction is safe for the gate
237
+ * (the crossing serializes), but it also hides the plan error until a
238
+ * `plan validate` nobody re-ran. The field is optional; absent stays absent.
239
+ */
240
+ function readPromiseList(task) {
241
+ const promises = task.keeps_exports;
242
+ if (promises === undefined)
243
+ return undefined;
244
+ if (!Array.isArray(promises)) {
245
+ throw new ClaimsError('claims.unreadable-promises', `Task "${task.task_id}" declares keeps_exports as ${describeType(promises)}, not a list ` +
246
+ 'of file paths, so the wave gate cannot tell which crossings it promised to keep. ' +
247
+ 'keeps_exports is a list of repo-relative file paths (task-spec.schema.json).', { task_id: task.task_id, received: describeType(promises) });
248
+ }
249
+ const badIndex = promises.findIndex((file) => typeof file !== 'string' || file === '');
250
+ if (badIndex !== -1) {
251
+ const received = promises[badIndex] === '' ? 'an empty string' : describeType(promises[badIndex]);
252
+ throw new ClaimsError('claims.unreadable-promises', `Task "${task.task_id}" declares keeps_exports entry ${badIndex} as ${received}, not a ` +
253
+ 'file path, so the wave gate cannot tell which crossings it promised to keep.', { task_id: task.task_id, index: badIndex, received });
254
+ }
255
+ return promises;
230
256
  }
231
257
  /**
232
258
  * Refuse an edge list no traversal can read, before it is traversed.