@juzser/blacksmith 0.1.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 (172) hide show
  1. package/.claude/agents/auditor.md +146 -0
  2. package/.claude/agents/coder.md +164 -0
  3. package/.claude/agents/grader.md +110 -0
  4. package/.claude/agents/merger.md +107 -0
  5. package/.claude/agents/planner.md +92 -0
  6. package/.claude/agents/researcher.md +105 -0
  7. package/.claude/agents/reviewer.md +171 -0
  8. package/.claude/agents/scribe.md +72 -0
  9. package/.claude/agents/security-reviewer.md +130 -0
  10. package/.claude/agents/spec-reviewer.md +215 -0
  11. package/.claude/agents/tester.md +121 -0
  12. package/.claude/agents/uiux.md +126 -0
  13. package/.claude/agents/verifier.md +103 -0
  14. package/.claude/agents/wave-runner.md +74 -0
  15. package/LICENSE +21 -0
  16. package/README.md +389 -0
  17. package/factory/orchestrator/dist/actors.js +46 -0
  18. package/factory/orchestrator/dist/agents-registry.js +308 -0
  19. package/factory/orchestrator/dist/args.js +84 -0
  20. package/factory/orchestrator/dist/artifacts.js +111 -0
  21. package/factory/orchestrator/dist/attribution.js +160 -0
  22. package/factory/orchestrator/dist/audit.js +812 -0
  23. package/factory/orchestrator/dist/autonomy.js +151 -0
  24. package/factory/orchestrator/dist/budgetAlarm.js +366 -0
  25. package/factory/orchestrator/dist/budgets.js +196 -0
  26. package/factory/orchestrator/dist/claims.js +633 -0
  27. package/factory/orchestrator/dist/cli.js +3185 -0
  28. package/factory/orchestrator/dist/commit.js +152 -0
  29. package/factory/orchestrator/dist/coverage.js +188 -0
  30. package/factory/orchestrator/dist/crossFinding.js +479 -0
  31. package/factory/orchestrator/dist/crosscheck.js +396 -0
  32. package/factory/orchestrator/dist/daemon.js +707 -0
  33. package/factory/orchestrator/dist/db/projector.js +1317 -0
  34. package/factory/orchestrator/dist/db/queries.js +1714 -0
  35. package/factory/orchestrator/dist/db/schema.js +305 -0
  36. package/factory/orchestrator/dist/delegation.js +400 -0
  37. package/factory/orchestrator/dist/diffstat.js +164 -0
  38. package/factory/orchestrator/dist/dispatchAudit.js +275 -0
  39. package/factory/orchestrator/dist/dotenv.js +72 -0
  40. package/factory/orchestrator/dist/effort.js +202 -0
  41. package/factory/orchestrator/dist/epic.js +1018 -0
  42. package/factory/orchestrator/dist/epicWidth.js +238 -0
  43. package/factory/orchestrator/dist/errorIssues.js +249 -0
  44. package/factory/orchestrator/dist/errors.js +11 -0
  45. package/factory/orchestrator/dist/escalation.js +500 -0
  46. package/factory/orchestrator/dist/events.js +1015 -0
  47. package/factory/orchestrator/dist/findingAge.js +91 -0
  48. package/factory/orchestrator/dist/findingContext.js +103 -0
  49. package/factory/orchestrator/dist/findings.js +1156 -0
  50. package/factory/orchestrator/dist/gate.js +911 -0
  51. package/factory/orchestrator/dist/gh.js +146 -0
  52. package/factory/orchestrator/dist/git.js +87 -0
  53. package/factory/orchestrator/dist/goalCheck.js +380 -0
  54. package/factory/orchestrator/dist/graph.js +61 -0
  55. package/factory/orchestrator/dist/hookDecision.js +106 -0
  56. package/factory/orchestrator/dist/immutability.js +171 -0
  57. package/factory/orchestrator/dist/impact.js +280 -0
  58. package/factory/orchestrator/dist/integration.js +141 -0
  59. package/factory/orchestrator/dist/judgePreflight.js +155 -0
  60. package/factory/orchestrator/dist/judges.js +310 -0
  61. package/factory/orchestrator/dist/lessonAudit.js +399 -0
  62. package/factory/orchestrator/dist/lessons.js +1329 -0
  63. package/factory/orchestrator/dist/mcp.js +555 -0
  64. package/factory/orchestrator/dist/paths.js +82 -0
  65. package/factory/orchestrator/dist/plan.js +502 -0
  66. package/factory/orchestrator/dist/planQuorum.js +421 -0
  67. package/factory/orchestrator/dist/policy.js +1308 -0
  68. package/factory/orchestrator/dist/policyHook.js +28 -0
  69. package/factory/orchestrator/dist/preconditions.js +47 -0
  70. package/factory/orchestrator/dist/projects.js +134 -0
  71. package/factory/orchestrator/dist/prompts.js +57 -0
  72. package/factory/orchestrator/dist/provenance.js +201 -0
  73. package/factory/orchestrator/dist/providers/api-transport.js +117 -0
  74. package/factory/orchestrator/dist/providers/cli-transport.js +208 -0
  75. package/factory/orchestrator/dist/providers/index.js +54 -0
  76. package/factory/orchestrator/dist/providers/schema-validate.js +152 -0
  77. package/factory/orchestrator/dist/providers/types.js +49 -0
  78. package/factory/orchestrator/dist/queue.js +263 -0
  79. package/factory/orchestrator/dist/quorum.js +347 -0
  80. package/factory/orchestrator/dist/quorumEscalations.js +132 -0
  81. package/factory/orchestrator/dist/results.js +65 -0
  82. package/factory/orchestrator/dist/roadmap.js +437 -0
  83. package/factory/orchestrator/dist/runtime.js +86 -0
  84. package/factory/orchestrator/dist/sameMistakeKpi.js +243 -0
  85. package/factory/orchestrator/dist/sandbox.js +165 -0
  86. package/factory/orchestrator/dist/scaffold.js +373 -0
  87. package/factory/orchestrator/dist/scheduler.js +408 -0
  88. package/factory/orchestrator/dist/schemas.js +195 -0
  89. package/factory/orchestrator/dist/security.js +222 -0
  90. package/factory/orchestrator/dist/severity.js +293 -0
  91. package/factory/orchestrator/dist/spec.js +376 -0
  92. package/factory/orchestrator/dist/specChange.js +497 -0
  93. package/factory/orchestrator/dist/stack.js +324 -0
  94. package/factory/orchestrator/dist/symbols.js +966 -0
  95. package/factory/orchestrator/dist/taskEvents.js +399 -0
  96. package/factory/orchestrator/dist/taskId.js +126 -0
  97. package/factory/orchestrator/dist/taxonomy.js +113 -0
  98. package/factory/orchestrator/dist/testSelect.js +129 -0
  99. package/factory/orchestrator/dist/testerAudit.js +264 -0
  100. package/factory/orchestrator/dist/testgate.js +171 -0
  101. package/factory/orchestrator/dist/usage.js +874 -0
  102. package/factory/orchestrator/dist/waivers.js +228 -0
  103. package/factory/orchestrator/dist/waveBudget.js +223 -0
  104. package/factory/orchestrator/dist/waveConcurrency.js +250 -0
  105. package/factory/orchestrator/dist/waveNext.js +210 -0
  106. package/factory/orchestrator/dist/waveSchedule.js +146 -0
  107. package/factory/orchestrator/dist/worktree.js +207 -0
  108. package/factory/orchestrator/drizzle/0000_broken_rockslide.sql +183 -0
  109. package/factory/orchestrator/drizzle/0001_luxuriant_zaladane.sql +1 -0
  110. package/factory/orchestrator/drizzle/0002_yellow_wild_pack.sql +8 -0
  111. package/factory/orchestrator/drizzle/0003_damp_riptide.sql +12 -0
  112. package/factory/orchestrator/drizzle/0004_broad_lady_vermin.sql +1 -0
  113. package/factory/orchestrator/drizzle/0005_romantic_jocasta.sql +2 -0
  114. package/factory/orchestrator/drizzle/0006_bent_madelyne_pryor.sql +16 -0
  115. package/factory/orchestrator/drizzle/0007_overjoyed_goliath.sql +1 -0
  116. package/factory/orchestrator/drizzle/0008_smart_iron_patriot.sql +2 -0
  117. package/factory/orchestrator/drizzle/0009_agents_epic_id.sql +2 -0
  118. package/factory/orchestrator/drizzle/0010_milestone_kind.sql +1 -0
  119. package/factory/orchestrator/drizzle/meta/_journal.json +83 -0
  120. package/factory/policies/budgets.yml +262 -0
  121. package/factory/policies/crosscheck.yml +308 -0
  122. package/factory/policies/delegation.yml +44 -0
  123. package/factory/policies/effort.yml +142 -0
  124. package/factory/policies/guardrails.yml +306 -0
  125. package/factory/policies/lessons.md +239 -0
  126. package/factory/policies/scheduler.yml +90 -0
  127. package/factory/policies/sensitive-paths.yml +148 -0
  128. package/factory/policies/severity.yml +83 -0
  129. package/factory/policies/stack.yml +60 -0
  130. package/factory/policies/taxonomy.yml +340 -0
  131. package/factory/policies/worktree.yml +95 -0
  132. package/factory/scaffold/base/.github/workflows/ci.yml +34 -0
  133. package/factory/scaffold/base/.gitignore.tmpl +11 -0
  134. package/factory/scaffold/base/AGENTS.md +33 -0
  135. package/factory/scaffold/base/README.md +25 -0
  136. package/factory/scaffold/base/biome.json.tmpl +30 -0
  137. package/factory/scaffold/base/package.json +25 -0
  138. package/factory/scaffold/base/pnpm-workspace.yaml.tmpl +16 -0
  139. package/factory/scaffold/base/src/index.ts +4 -0
  140. package/factory/scaffold/base/test/index.test.ts +8 -0
  141. package/factory/scaffold/base/tsconfig.json +20 -0
  142. package/factory/scaffold/base/tsconfig.test.json +9 -0
  143. package/factory/scaffold/base/vitest.config.ts +29 -0
  144. package/factory/scaffold/mcp/mcp.manifest.json +22 -0
  145. package/factory/scaffold/mcp/package.fragment.json +9 -0
  146. package/factory/scaffold/mcp/src/mcp/README.md +63 -0
  147. package/factory/scaffold/mcp/src/mcp/guard.ts +110 -0
  148. package/factory/scaffold/mcp/src/mcp/redact.ts +69 -0
  149. package/factory/scaffold/mcp/src/mcp/server.ts +55 -0
  150. package/factory/scaffold/mcp/src/mcp/tools/health.ts +60 -0
  151. package/factory/scaffold/mcp/test/mcp/guard.test.ts +108 -0
  152. package/factory/scaffold/mcp/test/mcp/redact.test.ts +61 -0
  153. package/factory/scaffold/mcp/test/mcp/surface.test.ts +100 -0
  154. package/factory/scaffold/ui/index.html +12 -0
  155. package/factory/scaffold/ui/package.fragment.json +13 -0
  156. package/factory/scaffold/ui/src/App.vue +10 -0
  157. package/factory/scaffold/ui/src/main.ts +5 -0
  158. package/factory/scaffold/ui/src/shims.d.ts +11 -0
  159. package/factory/scaffold/ui/vite.config.ts +20 -0
  160. package/factory/scaffold/ui-tailwind/package.fragment.json +6 -0
  161. package/factory/scaffold/ui-tailwind/vite.config.ts +26 -0
  162. package/factory/specs/schema/event.schema.json +82 -0
  163. package/factory/specs/schema/finding-evidence.schema.json +45 -0
  164. package/factory/specs/schema/finding.schema.json +115 -0
  165. package/factory/specs/schema/grader-verdict.schema.json +50 -0
  166. package/factory/specs/schema/judge-verdict.schema.json +19 -0
  167. package/factory/specs/schema/lesson.schema.json +85 -0
  168. package/factory/specs/schema/mcp-manifest.schema.json +230 -0
  169. package/factory/specs/schema/result.schema.json +81 -0
  170. package/factory/specs/schema/spec-change-request.schema.json +46 -0
  171. package/factory/specs/schema/task-spec.schema.json +134 -0
  172. package/package.json +98 -0
@@ -0,0 +1,262 @@
1
+ # Blacksmith — budget policy
2
+ #
3
+ # Token/turn caps per tier (architecture §2, §4, agent-constraints.md
4
+ # "budgets (factory-wide)").
5
+ #
6
+ # What is mechanical, and what is a request (P9-18, 2026-08-08; `failed_rounds`
7
+ # added P9-32 and `alarm_ratio` P9-33, both 2026-08-10; `max_in_flight_tasks`
8
+ # added 2026-08-26, the same day `epic.cap_tokens` grew a reader that actually
9
+ # blocks). Exactly six numbers here reach a runtime host:
10
+ #
11
+ # * `epic.cap_tokens` — three readers now, and only the newest of them
12
+ # blocks. planQuorum's budget trigger sums *declared* task budgets against
13
+ # it before any dispatch and only ever surfaces the finding for a human to
14
+ # act on; `smith budget alarm` compares it against spend counted from the
15
+ # event log, after wave merge, and also stops nothing itself. `smith wave
16
+ # check` (src/waveBudget.ts) is the reader that blocks: at wave admission —
17
+ # before any task in the wave has been dispatched — it sums the wave's
18
+ # declared task budgets against headroom under the cap (measured spend plus
19
+ # a declared price for every dispatch nothing has measured yet, the same
20
+ # projection `smith budget alarm` computes) and refuses to admit a wave
21
+ # that would cross it. It is not a stricter version of the other two
22
+ # readers' advice; it is the only one of the three standing upstream of the
23
+ # spend it is judging, so it is the only one that can refuse the spend
24
+ # instead of merely describing it afterward.
25
+ # * `epic.alarm_ratio` — `smith budget alarm` (src/budgetAlarm.ts), which
26
+ # reports each epic's measured and projected spend against
27
+ # `alarm_ratio * cap_tokens` and exits 1 on a crossing. Measured spend is a
28
+ # floor, not a total: a judge returns findings rather than a Result, so its
29
+ # tokens are in no `task-result-recorded` event and are priced from the
30
+ # caps below instead. A role with no cap here cannot be priced at all, and
31
+ # the epic is reported unverifiable rather than under — which is why the
32
+ # `task:` block's coverage of the roles the factory actually dispatches is
33
+ # load-bearing and not merely documentary.
34
+ # * a task's `tokens` — compared at the gate against the result's
35
+ # `token_usage.total_tokens`.
36
+ # * a task's `diff_lines` — compared at the gate against a real
37
+ # `git diff --numstat` (src/diffstat.ts), lockfiles and generated output
38
+ # excluded.
39
+ # * `escalation_ladder.rungs[].failed_rounds` — counted from `gate-outcome`
40
+ # events by `smith escalation check` (src/escalation.ts), which asserts the
41
+ # rung's `enforce` obligation against what the log shows happened next.
42
+ # * `epic.max_in_flight_tasks` — `smith wave check`, which refuses to admit a
43
+ # wave that would put more of the epic's tasks in flight at once (admitted,
44
+ # not yet merged) than this. Null by default; see the field's own comment
45
+ # below for why it is not the mechanism that bounds cost.
46
+ #
47
+ # The task gate still only records an overrun and reports it; it still does
48
+ # not block on one. An overrun there is as often evidence the plan
49
+ # under-estimated the task as evidence the task overran, and a cap the worker
50
+ # polices itself becomes a pressure on the very work it is supposed to be
51
+ # measuring (D-29: a coder spent its tail shaving lines to land exactly on
52
+ # `diff_lines`, stopping mid-procedure to do it).
53
+ #
54
+ # The epic cap no longer works that way: `smith wave check` blocks on one.
55
+ # The difference is not that an epic overrun matters more than a task overrun
56
+ # — it is *when* each is checked. The task gate polices work already
57
+ # underway, so refusing there distorts the work it is judging. Wave admission
58
+ # asks its question before any task in the wave has started, when there is
59
+ # nothing in flight yet for a refusal to put pressure on. A decision made
60
+ # before the work exists cannot become pressure on the work.
61
+ #
62
+ # Every other number below is prompt-level: read by agents from their own
63
+ # template and by the operator from the /bs playbook. They are requests, not
64
+ # limits, and the sections that follow say which is which. `max_turns` is no
65
+ # longer among them at all — no dispatch harness can enforce a turn limit
66
+ # (agent-interviews.md M-4), so plan validation now rejects it as a task budget
67
+ # field and the number belongs in the dispatch prompt.
68
+ #
69
+ # No concurrency cap on cost grounds, by design (removed 2026-08-05). Workers
70
+ # run in isolated worktrees on disjoint path claims, so parallelism is bounded
71
+ # by the claim graph, not by a worker count — and fan-out buys wall-clock
72
+ # without buying extra tokens (architecture §15.3). Hundreds of concurrent
73
+ # subagents is a supported shape. That reasoning holds only as long as the
74
+ # token cap it leans on actually binds, which — before `smith wave check` —
75
+ # `epic.cap_tokens` did not. `epic.max_in_flight_tasks` below is not that
76
+ # removed cap come back: it defaults to null, and cost stays bounded by
77
+ # `cap_tokens`, not by a worker count. It exists for an operator who wants a
78
+ # wall-clock or rate-limit bound for a reason of their own — a provider's
79
+ # concurrent-request limit, a laptop's CPU count — and setting it changes
80
+ # nothing about what bounds spend.
81
+
82
+ epic:
83
+ cap_tokens: 4000000 # planner + all workers + judges, one epic.
84
+ # Raised from 2,000,000 by operator decision on
85
+ # 2026-08-11 (dogfood-mcp-1), which is the
86
+ # operator answer the `alarm_ratio` note below
87
+ # requires — an extension, not a silent widening.
88
+ # The number the envkit-mcp-surface dogfood
89
+ # measured: 1,529,963 tokens for two of four
90
+ # tasks, both of them the two smallest in the
91
+ # plan. The old cap could not have carried that
92
+ # epic to close, and the alarm said so at 109%
93
+ # before the third task was ever dispatched.
94
+ # Two things this does NOT fix, both worth a
95
+ # follow-up: (1) there is no per-epic override,
96
+ # so a number raised for one epic is raised for
97
+ # every epic black-smith will ever run; (2) a
98
+ # large share of that 1.5M was rework — stopped
99
+ # dispatches and re-dispatches that `smith
100
+ # escalation check` recorded as ladder
101
+ # violations — so this cap now accommodates
102
+ # waste as well as work. A cap sized from a
103
+ # badly-run epic is a ceiling measured at the
104
+ # wrong place.
105
+ alarm_ratio: 0.7 # at 2.8M tokens: planner must re-plan remaining
106
+ # work to fit, or ask the operator. Epics that
107
+ # cannot fit are split at spec time, never
108
+ # silently extended (extension is always an
109
+ # operator question). Checked by `smith budget
110
+ # alarm <session>`, which the /bs run loop runs at
111
+ # each wave merge — before P9-33 this number's
112
+ # only reader was a human remembering to look.
113
+ max_in_flight_tasks: null # Off by default. "Fan-out buys wall-clock
114
+ # without buying extra tokens" above is the
115
+ # standing decision, and it only holds while the
116
+ # token cap actually binds — which `smith wave
117
+ # check` now makes true. So this is NOT the
118
+ # mechanism that bounds cost; `cap_tokens` is,
119
+ # and it is checked regardless of how this field
120
+ # is set. This knob exists for an operator who
121
+ # wants a wall-clock or rate-limit bound for a
122
+ # reason of their own — a provider's
123
+ # concurrent-request limit, a laptop's CPU count
124
+ # — and setting it makes `smith wave check`
125
+ # refuse to admit a wave that would put more of
126
+ # the epic's tasks in flight (admitted, not yet
127
+ # merged) than this number at once.
128
+
129
+ task:
130
+ # Defaults a planner sizes a task's own `budget` block against. The task's
131
+ # declared numbers are what the gate checks; these are the shape of a
132
+ # reasonable declaration, not a second cap applied on top.
133
+ coder:
134
+ cap_tokens: 150000 # Second reader, added 2026-08-26: `smith wave
135
+ # check` prices a task that exists only in the
136
+ # event log at this number. A follow-up minted by
137
+ # `findings raise` has claims but no declared
138
+ # budget (D-48/P9-31) — no plan ever had a field
139
+ # for one — so the wave's sum would otherwise be
140
+ # unverifiable and the follow-up inadmissible,
141
+ # which would leave the factory's own repair path
142
+ # reachable only by hand-editing the plan the
143
+ # follow-up exists to avoid. This is a charge,
144
+ # not a waiver: it is the most such a task may
145
+ # spend, and a wave of follow-ups is refused when
146
+ # that many coder caps will not fit under the
147
+ # epic headroom. A task the PLAN declares with no
148
+ # `budget.tokens` is a different silence and is
149
+ # still refused — the schema requires that field,
150
+ # so its absence is a plan that never passed
151
+ # `smith plan validate`.
152
+ cap_diff_lines: 400 # excludes lockfiles/generated files
153
+ researcher:
154
+ cap_tokens: 60000
155
+ judges: # spec-reviewer, reviewer, verifier, grader
156
+ cap_tokens: 40000
157
+ # Not priced here, and dispatched anyway: security-reviewer, merger, tester,
158
+ # uiux, planner, scribe. `smith budget alarm` cannot put a number on those
159
+ # dispatches, so an epic that used one is reported unverifiable rather than
160
+ # under — the omission is visible instead of free. Left unpriced on purpose
161
+ # until there is real spend data to size them from; inventing a cap would
162
+ # make the projection look complete without making it true.
163
+
164
+ pre_code_budget:
165
+ # researcher + uiux together, before any code is written for the epic
166
+ share_of_epic_budget_max: 0.15
167
+
168
+ context_window:
169
+ # Working-memory discipline (agent-constraints.md "context window
170
+ # (factory-wide)"). Distinct from the caps above: those are about cost, this
171
+ # is about the quality of what a session still holds. Prompt-level today
172
+ # (baked into .claude/agents/*); mechanical with the phase-9 loop daemon.
173
+ auto_compact_ratio: 0.6 # compact at 60% of the model's context window.
174
+ # The other 40% is the room needed to ACT on the
175
+ # summary: a session that compacts at 90% knows
176
+ # what to do and has no budget left to do it,
177
+ # which is what produces re-read loops, dropped
178
+ # constraints and half-applied edits.
179
+ max_compactions_per_task: 1 # a second one means the task is over-scoped:
180
+ # stop and return economy.budget-exceeded to the
181
+ # planner for splitting, exactly as a token-cap
182
+ # hit does. Chained compactions are how a session
183
+ # quietly loses the contract it was given.
184
+ counts_against_budget: true # tokens spent before a compaction still count
185
+ # against the caps above — compacting is not a
186
+ # way around the 150k coder cap.
187
+ compacting_roles: [planner, coder, tester, researcher, uiux, scribe]
188
+ narrowing_roles: [reviewer, verifier, grader, spec-reviewer, security-reviewer, merger, auditor]
189
+ # narrowing_roles hold evidence rather than build on it: a judge that compacts
190
+ # summarizes away the diff it is judging, and a merger that compacts loses one
191
+ # side of the conflict. At 60% they narrow scope and return a verdict on what
192
+ # they hold (judges) or escalate to the operator (merger) — they never compact.
193
+ keep:
194
+ - epic/task ids and the live plan version
195
+ - claimed globs
196
+ - decisions taken and why
197
+ - what is done vs. what remains
198
+ - open questions and blocking unknowns
199
+ drop:
200
+ - raw file contents
201
+ - raw tool/command output
202
+ - superseded reasoning
203
+ note: >
204
+ Durable state belongs in the event log, not the context window
205
+ (architecture §7). Anything that must survive a compaction is an event or a
206
+ result artifact; the window is working memory, the log is memory.
207
+
208
+ escalation_ladder:
209
+ # architecture §12, agent-constraints.md "budgets (factory-wide)"
210
+ #
211
+ # `failed_rounds` and `enforce` are the machine-readable half, added P9-32.
212
+ # src/escalation.ts reads them and `smith escalation check` asserts them
213
+ # against the log; `trigger`/`action` stay as the human contract and must say
214
+ # the same thing. A failed round is one `gate-outcome` with outcome
215
+ # `blocked` for the task.
216
+ #
217
+ # enforce values (closed set — an unrecognised one is reported unverifiable,
218
+ # never skipped):
219
+ # model-tier — the next builder dispatch for the task must be a strictly
220
+ # higher model_tier than the one that failed.
221
+ # operator — the ladder stops here: no further builder dispatch until the
222
+ # operator has answered (a `user_prompt`, or a waiver granted
223
+ # or denied for the task).
224
+ # A rung with no `action` declares no obligation and asserts nothing; a rung
225
+ # with an `action` and no `enforce` is a rule nothing can check, and the
226
+ # audit says so rather than passing it.
227
+ rungs:
228
+ - rung: 1
229
+ trigger: bounded retry on the same contract
230
+ failed_rounds: 1
231
+ - rung: 2
232
+ trigger: 2 failed rounds on the same task
233
+ failed_rounds: 2
234
+ enforce: model-tier
235
+ action: escalate model tier automatically (sonnet -> opus), logged
236
+ - rung: 3
237
+ trigger: 3 failed rounds on the same task
238
+ failed_rounds: 3
239
+ enforce: operator
240
+ action: escalate to operator
241
+ note: >
242
+ Rungs are never skipped and never looped past their bound; doing so is a
243
+ coordination error (deadlock/livelock/starvation, taxonomy.yml).
244
+
245
+ effort_scaling:
246
+ # architecture §15.3, §17 (equal-budget study: extra agents buy wall-clock
247
+ # + isolation, not reasoning)
248
+ #
249
+ # How many agents a step gets. *Which steps run at all* is the other half,
250
+ # and it lives in `factory/policies/effort.yml`: a per-epic tier
251
+ # (small/medium/huge) that scales the deliberation steps — pre-code briefs,
252
+ # spec-review rounds, the plan quorum, grader rounds, verifier severities,
253
+ # the closing spec review — and nothing else. Ask `smith effort show`; the
254
+ # two files do not overlap, and neither may touch a mechanical gate.
255
+ - rule: small fixes get a single coder, not a fan-out.
256
+ - rule: >
257
+ wave fan-out (concurrent dispatch of multiple coder/tester pairs) only
258
+ when claims are disjoint per static analysis (architecture §5); claims
259
+ that overlap get a dependency edge and run serially instead.
260
+ - rule: >
261
+ the scheduler prefers the cheapest shape that meets the acceptance
262
+ criteria — fan-out is a wall-clock optimization, not a default.
@@ -0,0 +1,308 @@
1
+ # Blacksmith — multi-provider cross-check policy
2
+ #
3
+ # architecture §6 "Multi-provider cross-check (anti tunnel vision)". The
4
+ # judge tier is provider-agnostic by contract; the config seam has existed
5
+ # since Phase 1, and Phase 8 (`factory/orchestrator/src/providers/`,
6
+ # `src/quorum.ts`) wires it up for real: `transport` selects which
7
+ # transport module runs a provider (cli-transport.ts / api-transport.ts),
8
+ # `mode` gates how much power its verdict has (see below). `enabled: false`
9
+ # means never invoked at all, regardless of `mode`, and both external
10
+ # providers ship that way. Not a judgement about them: `enabled` is the one
11
+ # field in this file that describes a MACHINE rather than the contract — it
12
+ # answers "does this box have that judge", and for a box the repo has never
13
+ # seen the honest answer is no. Switched on where the precondition is unmet a
14
+ # provider does not fail loudly; it spends a doomed call on every quorum
15
+ # trigger, has it caught, and leaves a failure row that reads like an outage.
16
+ # So turn on the one you actually have — a one-line edit, runbook §2, and
17
+ # `smith judge preflight` says beforehand whether it will work.
18
+ # Full setup/calibration/promotion procedure: docs/runbooks/providers.md.
19
+ #
20
+ # Shadow mode (Phase 8 design decision): `mode: shadow` means a provider's
21
+ # verdicts are RECORDED (judge-verdict events, `smith stats providers`) but
22
+ # have ZERO gating power — quorum.ts falls back to the native verdict alone
23
+ # whenever no provider in a case is `mode: active`. Flipping a provider to
24
+ # `mode: active` in this file (an operator edit, never a runtime write) is
25
+ # the entire promotion step; `mode: shadow` or `enabled: false` is the
26
+ # entire rollback step.
27
+
28
+ providers:
29
+ claude:
30
+ kind: native
31
+ enabled: true
32
+ codex:
33
+ kind: api
34
+ transport: cli # `codex exec` headless — ChatGPT-subscription auth (`codex login` once), no API key spend. See docs/runbooks/providers.md.
35
+ command: codex
36
+ # NOT `--json` (D-118): that wraps the verdict as an escaped string inside
37
+ # an `item.completed` event, where no JSON scanner can reach it, and puts a
38
+ # `thread.started` banner ahead of it. Plain `exec` writes the agent's final
39
+ # message — and only that — to stdout.
40
+ #
41
+ # Re-derived against the live binary on 2026-08-17, codex-cli 0.147.0, on a
42
+ # subscribed account (D-118 was originally derived from two calls made while
43
+ # the account was near its quota limit). Both layers still hold, unchanged:
44
+ # `--json` still nests the verdict in `item.completed.item.text`, and plain
45
+ # `exec` still echoes the whole prompt to stderr — which the transport merges
46
+ # into its extraction buffer by design — so a prompt containing `[]` or `{}`
47
+ # still plants decoy JSON ahead of the answer. Over that real buffer the two
48
+ # extractor generations diverge exactly as D-118 claimed: first-parseable
49
+ # returns `[]`, first-validating returns the verdict. If you change these
50
+ # args, re-run that comparison rather than reasoning about it.
51
+ args: ["exec", "--color", "never"]
52
+ model_tier: mid # taxonomy.yml model_tier — judge tier, architecture §4
53
+ # PRECONDITION: `codex` on PATH, and `codex login` run on every machine
54
+ # that dispatches a quorum trigger. Without either the CLI transport still
55
+ # spawns, fails, and quorum.ts records a provider error per trigger —
56
+ # caught, so nothing gates on it, but it is latency and noise.
57
+ # `smith judge preflight` checks the first half locally; only a real call
58
+ # can check the second.
59
+ # `auto`, not `true`. This is the one field in the file that describes a
60
+ # MACHINE, and the file is checked in, so `true` here would be a claim
61
+ # about every clone: the day this was written `true`, CI — which has no
62
+ # `codex` binary — started failing `judge preflight`'s soundness check,
63
+ # correctly, for a line that was true where it was typed.
64
+ #
65
+ # `auto` resolves against the precondition above, on the box reading the
66
+ # file: `codex` on PATH means enabled, no `codex` means silently off, and
67
+ # neither box needs to edit this line. What it cannot know is the second
68
+ # half — whether `codex login` was run — so a box with the binary and no
69
+ # session still spends a call per trigger and records the failure.
70
+ #
71
+ # Turned on for THIS box, 2026-09-02, by the operator who owns it: `codex`
72
+ # resolves on PATH and `codex login` is done, both checked by `smith judge
73
+ # preflight` and by a real `codex exec` call. Rollback for one box is
74
+ # `enabled: false` here, which `auto` never overrides.
75
+ enabled: auto
76
+ # `active`, not `shadow`: the operator's call (docs/runbooks/providers.md
77
+ # §3 offers shadow-then-calibrate and this box declined it).
78
+ #
79
+ # Be exact about what that buys today, because it is less than it looks:
80
+ # a shadow provider is still CALLED and still recorded as a participant
81
+ # (quorum.ts header note) — shadow costs the same call and forfeits only
82
+ # gating power. With one external judge promoted and `min_providers: 2`,
83
+ # a finding claude raised escalates either way. The difference is where
84
+ # codex's rationale lands: under `gating.rationales` when active, only
85
+ # under `participants` when shadow.
86
+ #
87
+ # So the promotion is not what makes the cross-check real — a second
88
+ # provider is (see quorum_rule.accept_non_gating_actives). What it does
89
+ # is make this box gate the moment one is enabled, with no second edit
90
+ # and no second decision to remember. Rollback is `mode: shadow` here.
91
+ mode: active
92
+ deepseek:
93
+ kind: api
94
+ transport: api
95
+ base_url: https://api.deepseek.com
96
+ model: deepseek-reasoner
97
+ response_format_json_object: true
98
+ api_key_env: DEEPSEEK_API_KEY
99
+ model_tier: mid
100
+ # PRECONDITION: DEEPSEEK_API_KEY must be readable by the dispatching
101
+ # process -- exported, or in the clone's gitignored `.env`, which the CLI
102
+ # loads at start without overriding anything already exported (D-270).
103
+ # Without it api-transport.ts throws `Environment variable "DEEPSEEK_API_KEY"
104
+ # is not set` on every quorum trigger; quorum.ts catches it, so the gate
105
+ # holds on the native verdict alone, but each trigger records a provider
106
+ # error. `smith judge preflight` reports the name it looked for and never
107
+ # the value it found, so it is safe to run anywhere.
108
+ #
109
+ # `auto`, not `true`, for the same reason codex is: this file is checked in
110
+ # and the key is a fact about one box. A box holding the key gets a second
111
+ # vendor; a box without one -- CI included -- silently gets none, and
112
+ # neither has to edit this line.
113
+ #
114
+ # Funded for THIS box, 2026-09-04, by the operator who owns the key.
115
+ enabled: auto
116
+ # `shadow`, deliberately, and the difference from codex above is the point:
117
+ # this box declined shadow for codex and chose it here. A shadow provider
118
+ # is still called and still recorded as a participant -- it forfeits only
119
+ # gating power -- so what the mode buys is a run of recorded disagreement
120
+ # to read before a second vendor gets a vote (docs/runbooks/providers.md
121
+ # §3). Promote by editing this to `active`; that is the calibration pass,
122
+ # and it is an operator decision, not a default.
123
+ mode: shadow
124
+
125
+ quorum_triggers:
126
+ # architecture §6, agent-constraints.md "cross-check (phase 8)" — all four,
127
+ # nothing more.
128
+ - any S1/S2 finding, before it blocks a task
129
+ - epic-level final verdict, before the integration PR opens
130
+ - same-mistake findings (judgment.same-mistake)
131
+ - planner verdicts below the confidence threshold
132
+
133
+ plan_quorum:
134
+ # architecture §6 "Plan quorum" — critique-only, never committee writes.
135
+ triggers:
136
+ - epic budget ≥ 50% of budgets.yml per-epic cap
137
+ - epic case is infra, or carries a security-sensitive tag
138
+ - planner verdict confidence below threshold
139
+ judges: 2 providers distinct from the planner's
140
+ on_disagreement: surface both rationales to the operator with the plan
141
+ # Machine-readable form of the three triggers above, read by
142
+ # src/crosscheck.ts's parsePlanQuorum() -> src/planQuorum.ts's
143
+ # evaluatePlanQuorumTriggers(). The prose above is the human contract;
144
+ # these fields are what the code actually gates on.
145
+ budget_ratio: 0.5 # trigger 1: sum(plan.tasks[].budget.tokens) >= this * budgets.yml epic.cap_tokens
146
+ # Same line task-spec.schema.json's `confidence` field and scheduler.yml's
147
+ # maintenance.auto_schedule_confidence both draw (>=0.8 auto-schedules).
148
+ # Repeated here (not imported) because this is a crosscheck trigger, not a
149
+ # scheduling decision — the two policies are allowed to diverge later.
150
+ confidence_threshold: 0.8 # trigger 3
151
+ security_cases: [infra] # trigger 2, taxonomy.yml `case` vocabulary
152
+ security_roles: [security-reviewer] # trigger 2, taxonomy.yml `agent` vocabulary
153
+ # trigger 2's third arm: contract.nonfunctional_clauses text matched
154
+ # case-insensitively as a substring (task-spec.schema.json has no `tags`
155
+ # field to check instead).
156
+ security_keywords:
157
+ - auth
158
+ - authz
159
+ - authentication
160
+ - authorization
161
+ - credential
162
+ - secret
163
+ - token
164
+ - password
165
+ - crypto
166
+ - encryption
167
+ - tls
168
+ - injection
169
+ - xss
170
+ - csrf
171
+ - sandbox
172
+ - permission
173
+ - privilege
174
+ - sanitize
175
+ - escalation
176
+
177
+ quorum_rule:
178
+ agreement: "2-of-3"
179
+ min_providers: 2
180
+ tie_break: a third provider breaks the tie, or the case escalates to the
181
+ operator with both rationales side by side.
182
+ # This box promotes exactly one external judge (see providers.codex), and one
183
+ # cannot meet min_providers 2. On a finding claude raised — which is nearly
184
+ # all of them, since gate.ts defaults a finding's finder to the native
185
+ # provider — claude is excluded as the finder, the gating pool is [codex]
186
+ # alone, and the case escalates for insufficient-providers just as it did
187
+ # before the promotion, now having paid for the call.
188
+ #
189
+ # Set true because that is still the better trade here, not because the
190
+ # arithmetic is in doubt: the escalation now carries a second model's
191
+ # rationale instead of none, and the day a second provider is enabled the
192
+ # pool meets the rule with no further edit. What it buys today is a human
193
+ # deciding with two opinions in front of them rather than one — which is the
194
+ # whole point of the file — not an automatic override.
195
+ #
196
+ # It silences one advisory in `smith judge preflight` and nothing else. A
197
+ # provider enabled where it cannot be called is still a problem, and
198
+ # `gating.canDecide` still reports false, because it is false.
199
+ accept_non_gating_actives: true
200
+
201
+ asymmetric_roles:
202
+ finder_ne_critic: true
203
+ critic_mandate: refute, not confirm
204
+ # The machine-readable form of finder_ne_critic (P9-23). Each pair says
205
+ # "this critic must not run on that finder's own model"; `smith dispatch
206
+ # check <session>` asserts it against the event log after the fact, using
207
+ # the `model` field every dispatch_decision now carries. Without this list
208
+ # the rule was unenforceable prose: it named no roles.
209
+ pairs:
210
+ - finder: planner
211
+ critic: spec-reviewer
212
+ - finder: reviewer
213
+ critic: verifier
214
+ note: >
215
+ adversarial stage-gating is expected to kill most candidate findings
216
+ before they cost a round-trip (Refute-or-Promote, §17); reviewer quality
217
+ is tracked as kill-rate (findings that survive verification and land as
218
+ verified fixes), never findings volume.
219
+
220
+ role_isolation:
221
+ # The other half of "a worker never grades its own work". `finder_ne_critic`
222
+ # above compares MODELS, which is the wrong question for a tester: a tester
223
+ # may legitimately run on the coder's model, and forcing a second vendor onto
224
+ # it would buy nothing. What it may not do is run inside the coder's own
225
+ # turn — a coder that writes its own tests grades itself, and every gate
226
+ # downstream still goes green.
227
+ #
228
+ # The only evidence of a separate turn the event log can hold is a separate
229
+ # `dispatch_decision`, written by the node that dispatched, never by an agent
230
+ # about itself. `smith tester check <session>` asserts that against the log
231
+ # after the fact, per `testgate-result`.
232
+ #
233
+ # That reading survives delegation.yml granting `Agent` because of what the
234
+ # grant costs: a grantee must open its own session first, so its dispatches
235
+ # are its own log's, and no grant may hand a role its own auditor from the
236
+ # `pairs` below. `smith delegation check` enforces both, which is what keeps
237
+ # a widened grant from quietly disarming this file.
238
+ #
239
+ # One entry, and it is meant to stay one until a second role starts grading
240
+ # work it could have written: every other grader in the pipeline is a judge,
241
+ # and a judge cannot write at all (`role_write_scopes` in guardrails.yml).
242
+ pairs:
243
+ - worker: coder
244
+ auditor: tester
245
+ note: >
246
+ the audit answers "was a tester dispatched separately, and did it report,
247
+ before this task's tests were graded?" — not "did that tester write the
248
+ tests". Nothing in the log distinguishes a test file the tester authored
249
+ from one it merely ran; `role-write-scope` fences where a leased tester may
250
+ write, and the two checks are complementary, not substitutes.
251
+
252
+ independent_finder:
253
+ # Everything else on this page is SUBTRACTIVE. quorum.ts's vocabulary is
254
+ # `confirm | refute`, a critic is handed one claim and told to kill it, and
255
+ # the strongest thing a quorum can do is drop a finding the native reviewer
256
+ # already raised. That makes the external tier a brake and never an eye: a
257
+ # bug the native reviewer's context did not surface is a bug no amount of
258
+ # cross-checking will ever reach, because nothing outside that context is
259
+ # ever asked to look.
260
+ #
261
+ # This block is the other direction. A finder from a different vendor reads
262
+ # the same diff in a fresh context, returns its own evidence, and
263
+ # crossFinding.ts reconciles the two lists. A quorum can now RAISE.
264
+ #
265
+ # Three things it deliberately does not do:
266
+ #
267
+ # 1. Silence is not a refutation. A native finding the independent finder did
268
+ # not raise stays exactly as it was. The finder was never asked about that
269
+ # claim — it was asked to read a diff — so its not mentioning something is
270
+ # absence of evidence. Subtracting on it would let a lazy or truncated
271
+ # second opinion delete real findings.
272
+ # 2. Same file and category with different wording is not the same finding.
273
+ # That is `co-located`: recorded for the operator, never merged and never
274
+ # auto-raised, because nothing here can tell one bug described twice from
275
+ # two bugs in one function.
276
+ # 3. An independent-only finding is minted, not privileged. It enters the
277
+ # gate as an ordinary finding and, at S1/S2, meets the same
278
+ # `quorum_triggers` critic as any other — so a third-party vendor's
279
+ # unshared opinion still has to survive a refute pass before it blocks.
280
+ enabled: false # ships off: this is the one judge call that costs a diff
281
+ mode: shadow # reconciliations are recorded and gate nothing; flip to `active` only after a calibration pass (docs/runbooks/providers.md)
282
+ # No code default: crosscheck.ts falls back to an EMPTY list, so this line is
283
+ # the only place a vendor is ever chosen. Delete it and the finder refuses by
284
+ # name rather than picking one for you.
285
+ providers: [codex] # must be external and must not be the native provider — a "second opinion" from the same model is one opinion billed twice
286
+ # THE OPERATOR MANDATE. A critic judges a CLAIM, so quorum.ts can send the
287
+ # summary and the failure scenario and never the source. A finder cannot: it
288
+ # has nothing to read but the diff. Shipping worktree source to a third-party
289
+ # API is a decision no code in this repo has standing to make quietly, so it
290
+ # is a switch, it ships false, and `smith crossfind run` REFUSES rather than
291
+ # falling back to a diffless "find bugs" prompt that would invent findings.
292
+ send_diff: false
293
+ max_diff_bytes: 120000 # refuse rather than truncate: half a diff produces confident findings about code that is not there
294
+ # How a fingerprint both sides raised resolves when they disagree on how bad
295
+ # it is. `highest-wins` takes the worse of the two; `native-wins` keeps the
296
+ # native severity and records the disagreement without acting on it.
297
+ severity_resolution: highest-wins
298
+
299
+ mechanical_oracles_first: >
300
+ wherever a deterministic check exists (schema validation, mutation
301
+ testing, coverage, type checks), it runs first and its verdict is final —
302
+ LLM judgment, cross-provider or not, is reserved for what machines can't
303
+ decide.
304
+
305
+ trust_boundary: >
306
+ external providers judge; they never gain write access to worktrees or the
307
+ factory. Their findings are data, not commands. All external calls go
308
+ through the same event log and budget accounting as native dispatches.
@@ -0,0 +1,44 @@
1
+ # Blacksmith — who may hold `Agent` (dispatch topology, architecture §4)
2
+ #
3
+ # Every other policy here tunes how work is judged. This one decides who may
4
+ # create work at all, so it lives in its own file: an operator widening a
5
+ # provider list in crosscheck.yml should not be one keystroke away from
6
+ # widening a dispatch grant.
7
+ #
8
+ # The rule the factory has always run on is "the dispatching node owns the
9
+ # event log for what it dispatches" (docs/standards/agent-constraints.md).
10
+ # Until D13 step 3 that was enforced by granting `Agent` to nobody, which also
11
+ # capped an epic at one operator session. A grantee that opens its own session
12
+ # with `smith session start <id> --continues <dispatch-event-id>` is a real
13
+ # dispatching node with a real log, so the cap can lift without the rule
14
+ # bending.
15
+ #
16
+ # What a grant may not do — asserted by `smith delegation check` and by
17
+ # delegation.test.ts against the shipped files, so a widened grant fails CI:
18
+ #
19
+ # - dispatch itself (a role that dispatches itself writes dispatches about
20
+ # itself, and `smith tester check` reads a second dispatch as proof of a
21
+ # second turn),
22
+ # - dispatch its own auditor from crosscheck.yml `role_isolation.pairs`
23
+ # (a coder that picks and prompts its own tester grades itself), or
24
+ # - dispatch its own critic from crosscheck.yml `asymmetric_roles.pairs`
25
+ # (a finder that picks its critic's model closes the tunnel-vision hole
26
+ # crosscheck.yml exists to open).
27
+ #
28
+ # `wave-runner` satisfies all three by construction: it is nobody's worker and
29
+ # nobody's finder. It is the only grant, and it is meant to stay the only one
30
+ # until a second role has an epic-shaped reason to outlive a window.
31
+
32
+ version: 1
33
+
34
+ grants:
35
+ wave-runner:
36
+ # One wave of `.claude/skills/bs/wave.md`, steps 2-10. Not planner and not
37
+ # spec-reviewer: planning is the epic tier's, and a wave that could re-plan
38
+ # itself could plan its way out of the plan it was admitted under.
39
+ may_dispatch: [coder, tester, grader, reviewer, verifier,
40
+ security-reviewer, uiux, researcher, merger, scribe]
41
+ # The whole point. A wave-runner that dispatches without opening a log
42
+ # writes its wave's dispatches into the epic's log under its own name, and
43
+ # every check that reads a dispatch as a separate turn starts lying.
44
+ must_open_session: true