@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,142 @@
1
+ # Effort tiers — how much judgment an epic buys.
2
+ #
3
+ # An internal tool and a payments rewrite do not deserve the same number of
4
+ # judge calls, but until this file existed the factory had exactly one flow and
5
+ # the only way to make a small epic cheap was to skip steps by hand and
6
+ # remember which ones. That is the same failure mode sensitive-paths.yml was
7
+ # written for: a decision made fresh every time is a decision made differently
8
+ # every time. So the tiers are declared here and asked for, never recalled.
9
+ #
10
+ # HOST: `smith effort show --plan <plan.json>` reads this file plus the plan's
11
+ # own `effort` field and returns the profile for that epic
12
+ # (factory/orchestrator/src/effort.ts). `.claude/skills/bs/plan.md` and
13
+ # `run.md` run that verb at the top of `/bs plan` and `/bs run` and obey the
14
+ # answer, rather than eyeballing the table below.
15
+ #
16
+ # Scope note: the tier is per EPIC, not per project. It is chosen at `/bs plan`
17
+ # time and stored on the plan file, so one project can run a `small` epic in
18
+ # the morning and a `huge` one in the afternoon. `budgets.yml`'s
19
+ # `effort_scaling:` block is the prose sibling of this file — it says fan-out
20
+ # is a wall-clock optimisation; this says how much judgment each epic buys.
21
+ #
22
+ # THE INVARIANT THAT MAKES THIS SAFE: a tier scales judgment steps only. It
23
+ # never touches a mechanical oracle or a guardrail — see `invariants:` at the
24
+ # bottom, which the host returns alongside every profile so the list cannot be
25
+ # quietly forgotten. A `small` tier that skipped tests would not be cheap, it
26
+ # would be unverified, and the two are not the same thing.
27
+
28
+ version: 1
29
+
30
+ # The tier an epic gets when its plan file names none. `medium` and not `huge`:
31
+ # an unstated tier is an unconsidered one, and the default should be the
32
+ # ordinary case rather than the most expensive one. An epic that wants the full
33
+ # flow says so.
34
+ default_tier: medium
35
+
36
+ # The lowest tier an epic may run at once its live tasks fire crosscheck.yml's
37
+ # `plan_quorum` security triggers (`case: infra`, a security-sensitive
38
+ # `agent_role`, or a `security_keywords` hit in a nonfunctional clause). An
39
+ # internal tool that touches auth is not a small epic — the operator's tier is
40
+ # a request, and this is the floor it is answered against. Raising a tier is
41
+ # always allowed; the floor only ever raises.
42
+ security_floor: medium
43
+
44
+ # ── The tiers ───────────────────────────────────────────────────────
45
+ #
46
+ # Read top to bottom as a subtraction: `huge` is the flow as the `/bs`
47
+ # playbooks (`.claude/skills/bs/`) write it today, unchanged; `medium` drops the
48
+ # judgment steps that pay off least per call; `small` drops the pre-code
49
+ # deliberation as well. Every knob below names a step that is a *judgment* —
50
+ # an agent forming an opinion. Nothing mechanical appears here by construction.
51
+ tiers:
52
+ huge:
53
+ summary: >
54
+ The full flow, exactly as the `/bs` playbooks write it. Large or important
55
+ projects, anything user-facing, anything whose failure is expensive.
56
+ # /bs run step 3 — dispatch `researcher` for a pre-code unknown.
57
+ pre_code_research: when-needed
58
+ # /bs run step 3 — dispatch `uiux` for a UI-affecting acceptance criterion.
59
+ # Already conditional today at every tier; stated at all three so the knob
60
+ # reads as a fact about the flow rather than an omission.
61
+ pre_code_uiux: when-ui-criterion
62
+ # /bs plan steps 3-4 — planner <-> spec-reviewer until nothing S1/S2 is left.
63
+ spec_review_rounds: until-clean
64
+ # /bs plan step 4 — `smith plan quorum` before operator sign-off.
65
+ plan_quorum: always
66
+ # /bs run step 6 — the grader's bounded rubric loop (agent-constraints.md
67
+ # caps it at 2; a tier may lower that number, never raise it).
68
+ grader_rounds: 2
69
+ # /bs run step 7 — which reviewer findings get the adversarial verifier
70
+ # pass before they cost the coder a round trip.
71
+ verifier_severities: [S1-stop-the-line, S2-major]
72
+ # ...plus this share of S3-minor findings, sampled. The 20% spot-check is
73
+ # what today's flow does; the lower tiers drop the sample, not the S1/S2
74
+ # mandate.
75
+ verifier_s3_spot_check_ratio: 0.2
76
+ # /bs run step 13 — the closing spec-reviewer against the assembled branch.
77
+ closing_spec_review: always
78
+
79
+ medium:
80
+ summary: >
81
+ The default. Ordinary product work: the adversarial passes that catch
82
+ real defects stay, the deliberation that mostly confirms is dropped.
83
+ pre_code_research: when-needed
84
+ pre_code_uiux: when-ui-criterion
85
+ spec_review_rounds: until-clean
86
+ # Down from `always`: the quorum computes its own triggers, so running it
87
+ # unconditionally buys an answer of "nothing fired" most of the time. At
88
+ # this tier it runs when crosscheck.yml says something fired.
89
+ plan_quorum: when-triggered
90
+ # Down from 2: round 2 exists for the rubric that moved. Below `huge`,
91
+ # one round scores and a second one is the coder's bounce instead.
92
+ grader_rounds: 1
93
+ verifier_severities: [S1-stop-the-line, S2-major]
94
+ # Down from 0.2: the S3 sample is a calibration measurement on the
95
+ # reviewer, not a defence of this epic's diff.
96
+ verifier_s3_spot_check_ratio: 0
97
+ closing_spec_review: always
98
+
99
+ small:
100
+ summary: >
101
+ Internal tools, scripts, one-file chores — work whose blast radius is
102
+ the operator's own afternoon. Fast, and still gated.
103
+ # Down from when-needed: at this size the coder reading the repo is the
104
+ # research brief. An epic that genuinely needs one is not a small epic.
105
+ pre_code_research: never
106
+ pre_code_uiux: when-ui-criterion
107
+ # Down from until-clean: one spec-review pass, findings surfaced to the
108
+ # operator with the spec at sign-off rather than looped on first.
109
+ spec_review_rounds: single-pass
110
+ plan_quorum: when-triggered
111
+ grader_rounds: 1
112
+ # Down from [S1, S2]: only a stop-the-line finding is worth an
113
+ # adversarial refute pass before it reaches the coder.
114
+ verifier_severities: [S1-stop-the-line]
115
+ verifier_s3_spot_check_ratio: 0
116
+ # Down from always: a closing spec review reads the spec against the code
117
+ # that now exists, which is worth a call when the spec moved. If the epic
118
+ # ran its whole plan without an amendment, it did not move.
119
+ closing_spec_review: when-plan-amended
120
+
121
+ # ── What no tier may touch ──────────────────────────────────────────
122
+ #
123
+ # Returned verbatim by `smith effort show` next to every profile. These are
124
+ # mechanical oracles and guardrails, not judgments: their cost is a command
125
+ # exiting, not an agent thinking, so scaling them saves nothing and buys
126
+ # nothing but a weaker claim about the result.
127
+ invariants:
128
+ - the gate pipeline — schema check, grader verdict, tests, coverage
129
+ evidence, findings intake, severity decision (`smith gate run`)
130
+ - the root-level integration check on the assembled branch
131
+ (`smith integration check`)
132
+ - claim disjointness and worktree isolation (`smith wave check`,
133
+ `smith worktree create/fingerprint/verify`)
134
+ - the security-reviewer's conditional dispatch
135
+ (`smith security triggers`) — sensitive-paths.yml decides it, not a tier
136
+ - the operator's plan sign-off, which no tier may skip or infer
137
+ - the epic-final verdict and its mechanical holds (`smith epic verdict`,
138
+ `smith epic close`)
139
+ - the event log — every dispatch is still a `dispatch_decision` with a
140
+ causal parent, at every tier
141
+ - the escalation ladder after a blocked round (budgets.yml
142
+ `escalation_ladder`) — a cheap epic that fails twice still climbs
@@ -0,0 +1,306 @@
1
+ # Blacksmith — guardrail enforcement policy
2
+ #
3
+ # docs/standards/guardrails.md is the contract; this file is the data half of
4
+ # what enforces it. HOST: `smith policy check`/`smith policy hook`
5
+ # (factory/orchestrator/src/policy.ts), and — via `dist/policyHook.js` —
6
+ # `.claude/hooks/guard.sh`, the PreToolUse gate on every guarded tool call
7
+ # an agent session makes.
8
+ #
9
+ # What this file is NOT: a place for matchers. Deciding whether a command
10
+ # trips a rule is deliberately NOT expressed here as regex-in-YAML — the
11
+ # matchers live in policy.ts, in TypeScript, where they have unit tests
12
+ # (test/policy.test.ts). A rule whose matching logic is data instead of code
13
+ # is a rule nobody can write a test against, and this is a deny gate: the one
14
+ # place in the factory where "we changed it and nothing checked" is not an
15
+ # acceptable failure mode. What belongs here is what an operator would
16
+ # legitimately want to change without touching a matcher: branch names,
17
+ # allowed roots, which commands count as a deploy, and the message an agent
18
+ # sees when a rule fires.
19
+ #
20
+ # Every rule is S1 ("stop the line") per guardrails.md's preamble: these are
21
+ # hard rules the factory enforces mechanically, not policy questions for the
22
+ # gate to weigh. `reason` is the exact string `guard.sh` has printed since
23
+ # Phase 2 — changing it here changes what an agent sees at the moment a
24
+ # command is denied, so treat it as user-facing copy, not an internal note.
25
+ # `reason_on_current_branch` exists only on the two rules (push, merge) that
26
+ # have a second trigger — a *bare* `git push`/`git merge` while already
27
+ # checked out on a protected branch, where the bash's message named the
28
+ # branch the agent was standing on rather than the ref it typed. `{branch}`
29
+ # in either reason is substituted — at every occurrence — with the branch
30
+ # policy.ts resolved, not rendered literally.
31
+
32
+ protected_branches:
33
+ # Literal branch names `push`/`merge` refuse as a destination or as the
34
+ # branch checked out for a bare push/merge (rule 1, rule 3). `patterns` is
35
+ # the shared per-epic integration branch shape, added to `names` for the
36
+ # history-rewrite rule only (rule 5) — worktree.yml's
37
+ # `integration_branch.pattern`: a leaf ref "smith/<epic>" cannot coexist
38
+ # with "smith/<epic>/<task-id>" refs in real git (D/F ref conflict), so the
39
+ # shape is "smith/<epic>/integration", never a bare "smith/<epic>".
40
+ names: [main, master]
41
+ patterns: ["smith/*/integration"]
42
+
43
+ catch_up_remotes:
44
+ # rule 3's one exception: the remotes a protected branch is allowed to
45
+ # fast-forward *itself* to, as `git pull --ff-only <remote> <branch>` or
46
+ # `git merge --ff-only <remote>/<branch>`. Nothing lands that the remote
47
+ # does not already have, so the gate has nothing to protect.
48
+ #
49
+ # This vouches for a name, not a URL — `git remote add` and `git remote
50
+ # set-url` can point any name anywhere, and this gate reads the command
51
+ # line, not .git/config. So keep the list to remotes whose name means the
52
+ # same thing everywhere the factory runs. Omit the key entirely and you get
53
+ # [origin]; an empty list turns the exception off.
54
+ - origin
55
+
56
+ destructive_removal:
57
+ # rule 6: a recursive-force `rm` is allowed only under one of these
58
+ # directories, matched by name at the top of whichever repository the
59
+ # command runs in — its cwd's git toplevel, not this clone. Inside a task
60
+ # worktree that is the worktree, which has neither, so nothing qualifies
61
+ # there (guardrails.md "Filesystem").
62
+ allowed_roots: [workspaces, state]
63
+
64
+ deploy_commands:
65
+ # rule 4: command + subcommand pairs that require per-invocation operator
66
+ # approval (guardrails.md "Deploy + outbound"). Matched as `\b<command>\b
67
+ # .* \b<subcommand>\b` (any one of `subcommands`), the same looseness as
68
+ # every other rule here — see policy.ts's `checkDeployCommand`.
69
+ - { command: wrangler, subcommands: [deploy, publish] }
70
+ - { command: pages, subcommands: [publish] }
71
+
72
+ rules:
73
+ - id: push-to-protected
74
+ severity: S1
75
+ reason: >-
76
+ push to main/master is never allowed from an agent session. Push a
77
+ side branch and open a PR.
78
+ reason_on_current_branch: >-
79
+ push to main/master is never allowed from an agent session (checked
80
+ out on {branch}).
81
+
82
+ - id: force-push
83
+ severity: S1
84
+ reason: >-
85
+ force-push (--force/-f/--force-with-lease) is never allowed from an
86
+ agent session, on any branch. A branch you have already pushed is
87
+ append-only here: add a commit. Rewriting history that is published
88
+ is an operator action.
89
+
90
+ - id: merge-into-protected
91
+ severity: S1
92
+ reason: >-
93
+ a merge lands on the branch you are standing on, never on the branch you
94
+ name, and that is never main/master in an agent session; the operator
95
+ merges integration PRs.
96
+ reason_on_current_branch: >-
97
+ a merge lands on the branch you are standing on and you are on {branch};
98
+ git pull is the same merge with a fetch in front. Check out a side branch
99
+ instead — merging main into a side branch to refresh it is allowed, and
100
+ is how you get a stale PR up to date. To catch {branch} itself up to the
101
+ remote, `git pull --ff-only origin {branch}` is allowed: it moves the
102
+ pointer and lands nothing the remote does not already have.
103
+
104
+ - id: deploy-command
105
+ severity: S1
106
+ reason: >-
107
+ deploy/publish commands require explicit operator approval; never
108
+ autonomous.
109
+
110
+ - id: history-rewrite-on-protected
111
+ severity: S1
112
+ reason: >-
113
+ history rewrite (rebase/amend/filter-branch) on protected branch
114
+ '{branch}' is never allowed.
115
+
116
+ - id: unbounded-rm
117
+ severity: S1
118
+ reason: >-
119
+ rm -rf is only allowed inside workspaces/ or state/.
120
+
121
+ # ---------------------------------------------------------------------------
122
+ # The judge sandbox
123
+ # ---------------------------------------------------------------------------
124
+ #
125
+ # Everything above applies to every agent session. This block applies to one
126
+ # additional condition: a `smith sandbox open` lease is active over the
127
+ # directory the command will run in (factory/orchestrator/src/sandbox.ts).
128
+ # The orchestrator opens a lease before handing a worktree to one of the six
129
+ # read-only judge roles and closes it after.
130
+ #
131
+ # Why it exists: "judges are read-only" was, until now, a sentence in
132
+ # .claude/agents/*.md plus a fingerprint comparison after the fact
133
+ # (immutability.ts). The prose is not enforcement, and the fingerprint is a
134
+ # detector — by the time it fires the write has already landed and the
135
+ # verdict is already untrustworthy. It also cannot see the two things that
136
+ # leave no git trace at all: a judge reaching the network, and a judge
137
+ # writing outside its worktree. Judge roles hold `Read, Grep, Glob, Bash` and
138
+ # no `Edit`/`Write`, so Bash is the only write path a judge has, and the
139
+ # PreToolUse hook already sits on every Bash call — which is what makes
140
+ # refusing up front possible rather than aspirational.
141
+ #
142
+ # What it is not: a container, a seccomp profile, or a read-only mount. A
143
+ # command that writes through an interpreter (`python3 -c "open(...,'w')"`)
144
+ # is invisible to a matcher, and always will be. `smith worktree verify`
145
+ # stays in the pipeline behind this for exactly that reason. Both halves are
146
+ # stated in docs/standards/guardrails.md; neither is sold as the other.
147
+ #
148
+ # The bias here is the opposite of the guard shim's. `guard.sh` answers `ask`
149
+ # rather than `deny` when it cannot decide, because a hook that dead-ends
150
+ # leaves the factory unrepairable from inside. A sandbox rule that
151
+ # over-refuses costs a judge one command and shows the operator exactly which
152
+ # one — nothing deadlocks, because the orchestrator holds the lease and the
153
+ # work continues the moment it closes. So these matchers lean towards
154
+ # refusing, and the lists below are deliberately generous.
155
+ judge_sandbox:
156
+ # The roles this rule set is written for: the six that hold `Bash` and no
157
+ # `Edit`/`Write` (.claude/agents/*.md). `researcher` is deliberately absent
158
+ # — fetching is its job, and it is never leased. A lease is matched to a
159
+ # rule set by role, and a role named in neither list falls back to *this*
160
+ # one, so the list is the vocabulary `smith sandbox open --role` validates
161
+ # against rather than a gate of its own.
162
+ roles: [reviewer, verifier, spec-reviewer, security-reviewer, grader, uiux]
163
+
164
+ # The only paths a judge may write, repo-root-relative. Both are the
165
+ # judge's own output: `state/results/<task-id>.<role>.json` is the verdict
166
+ # artifact every judge template already names, and `state/artifacts/` is
167
+ # where a task's declared evidence lives (P9-22).
168
+ write_roots: [state/results, state/artifacts]
169
+
170
+ # Bare command words that reach the network. Matched in command position
171
+ # (start of a chain segment, or after whitespace) and followed by
172
+ # whitespace or end-of-segment, so `grep -r x src/nc/` does not read as
173
+ # `nc` — see policy.ts's `hasCommandWord`.
174
+ network_commands:
175
+ [curl, wget, nc, ncat, netcat, telnet, ssh, scp, sftp, rsync, ftp, gh, glab, http, httpie,
176
+ aria2c, npx, bunx, pnpx]
177
+
178
+ # Command + subcommand pairs that reach the network. `git fetch`/`remote`
179
+ # are here rather than under writes because their side effect is outbound,
180
+ # and an install subcommand is both a fetch and a mutation of the tree.
181
+ network_subcommands:
182
+ - { command: git, subcommands: [push, pull, fetch, clone, remote, submodule] }
183
+ - { command: npm, subcommands: [install, i, add, ci, update, publish, exec, link] }
184
+ - { command: pnpm, subcommands: [install, i, add, update, publish, dlx, link] }
185
+ - { command: yarn, subcommands: [install, add, upgrade, publish] }
186
+ - { command: bun, subcommands: [install, add, update, publish] }
187
+ - { command: pip, subcommands: [install, download, uninstall] }
188
+ - { command: pip3, subcommands: [install, download, uninstall] }
189
+ - { command: uv, subcommands: [add, sync, pip] }
190
+ - { command: brew, subcommands: [install, upgrade, update, tap] }
191
+ - { command: cargo, subcommands: [install, publish, add] }
192
+ - { command: go, subcommands: [get, install] }
193
+
194
+ # Write verbs whose arguments are all targets. Allowed when every non-flag
195
+ # argument resolves under a `write_roots` entry — `mkdir -p state/results`
196
+ # is a judge preparing to write its own verdict, and refusing it would
197
+ # refuse the one thing a judge is supposed to do.
198
+ target_write_commands: [tee, mkdir, touch]
199
+
200
+ # Write verbs refused outright inside a sandbox, no path check. A judge
201
+ # writes its verdict by redirecting into a write root; it has no business
202
+ # moving, copying, deleting, patching or chmod-ing anything, and the
203
+ # allowlist that would make `cp a b` decidable (which token is the target?)
204
+ # is more machinery than the single legitimate case is worth. `rm` is here
205
+ # even though the `unbounded-rm` rule above would permit `rm state/...`:
206
+ # inside a sandbox, `state/` holds other judges' verdicts and this judge's
207
+ # own lease.
208
+ refused_write_commands:
209
+ [rm, rmdir, mv, cp, ln, dd, truncate, shred, install, patch, chmod, chown, chgrp,
210
+ vi, vim, nano, emacs, ed, pico]
211
+
212
+ # git subcommands that mutate a worktree, the index, refs, or config.
213
+ # `git add`/`commit`/`checkout`/`stash`/`restore` are the five
214
+ # .claude/agents/reviewer.md already names in prose; the rest close the
215
+ # same door from the other side.
216
+ git_write_subcommands:
217
+ [add, commit, checkout, switch, restore, stash, apply, am, reset, clean, rm, mv,
218
+ cherry-pick, revert, rebase, merge, config, tag, worktree, notes, update-ref,
219
+ update-index, gc, prune]
220
+
221
+ rules:
222
+ - id: judge-network
223
+ severity: S1
224
+ reason: >-
225
+ a {role} judge session has no outbound access — no fetch, no remote
226
+ git, no package install. Judge what is in the worktree, and record
227
+ what you could not verify as a finding rather than going to get it.
228
+
229
+ - id: judge-write
230
+ severity: S1
231
+ reason: >-
232
+ a {role} judge session writes nothing but its own verdict. Redirect
233
+ into {write_roots}; every other write is refused while the sandbox is
234
+ open. If the code needs changing, that is a finding, not an edit.
235
+
236
+ - id: judge-sandbox-escape
237
+ severity: S1
238
+ reason: >-
239
+ a {role} judge cannot open, close or inspect its own sandbox lease —
240
+ the orchestrator that opened it closes it when the verdict is in.
241
+
242
+ # ---------------------------------------------------------------------------
243
+ # Roles that write, but not everywhere.
244
+ #
245
+ # The judge sandbox answers one question — "may this role write at all?" — and
246
+ # for a judge the answer is no. A tester writes plenty. What it must not write
247
+ # is the thing it is grading: a tester that can edit `src/` can make a failing
248
+ # test pass by changing the implementation, which is the same self-marking the
249
+ # fresh-context reviewer exists to prevent, arriving one gate earlier.
250
+ #
251
+ # So: the same lease machinery, a different rule set selected by role.
252
+ #
253
+ # Two differences from the judge rules follow from the role actually being
254
+ # allowed to write. `git add`/`commit` stay allowed, because the tester's
255
+ # output contract *requires* a commit on the task branch — only the git
256
+ # subcommands that change tracked content without passing a path check are
257
+ # refused. And `rm` is path-checked rather than refused outright, because
258
+ # deleting a stale test is ordinary tester work.
259
+ #
260
+ # `judge-sandbox-escape` is not listed here because it is not role-dependent:
261
+ # a lease its holder can lift is not a lease, whichever rule set the role
262
+ # draws the rest of its bounds from.
263
+ role_write_scopes:
264
+ scopes:
265
+ - role: tester
266
+ # Repo-root-relative globs (picomatch, posix separators). A directory
267
+ # counts as in scope when something inside it would be, so
268
+ # `mkdir -p factory/orchestrator/test/fixtures` is allowed.
269
+ write_globs:
270
+ - "**/*.test.ts"
271
+ - "**/*.test.tsx"
272
+ - "**/*.spec.ts"
273
+ - "**/*.spec.tsx"
274
+ - "**/test/**"
275
+ - "**/tests/**"
276
+ - "**/__tests__/**"
277
+ - "**/__snapshots__/**"
278
+ - "**/e2e/**"
279
+ - "state/results/**"
280
+ - "state/artifacts/**"
281
+
282
+ # Write verbs refused with no path check, for the reason the judge
283
+ # sandbox gives: deciding which token of `cp a b` is the target is more
284
+ # machinery than the legitimate cases justify — and this role holds
285
+ # `Edit` and `Write`, which *are* path-checked, as the better route to
286
+ # the same edit. `install` is absent on purpose: the tester runs the
287
+ # suite, and `npx playwright install chromium` is not the BSD `install`
288
+ # verb.
289
+ refused_commands:
290
+ [cp, mv, ln, dd, truncate, shred, patch, chmod, chown, chgrp,
291
+ vi, vim, nano, emacs, ed, pico]
292
+
293
+ # git subcommands that put tracked content back under the tester's
294
+ # control without passing a path check. `add`, `commit`, `switch`,
295
+ # `status` and `diff` are deliberately absent — the first two are the
296
+ # tester's output contract.
297
+ refused_git_subcommands: [apply, am, cherry-pick, revert, reset, clean, checkout, restore, stash]
298
+
299
+ rules:
300
+ - id: role-write-scope
301
+ severity: S1
302
+ reason: >-
303
+ a {role} session writes only {write_globs}. The implementation
304
+ belongs to the coder, and a test whose subject the tester may edit
305
+ is a test that grades itself. If the code is wrong, that is a
306
+ finding, not an edit.