@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.
- package/.claude/agents/auditor.md +146 -0
- package/.claude/agents/coder.md +164 -0
- package/.claude/agents/grader.md +110 -0
- package/.claude/agents/merger.md +107 -0
- package/.claude/agents/planner.md +92 -0
- package/.claude/agents/researcher.md +105 -0
- package/.claude/agents/reviewer.md +171 -0
- package/.claude/agents/scribe.md +72 -0
- package/.claude/agents/security-reviewer.md +130 -0
- package/.claude/agents/spec-reviewer.md +215 -0
- package/.claude/agents/tester.md +121 -0
- package/.claude/agents/uiux.md +126 -0
- package/.claude/agents/verifier.md +103 -0
- package/.claude/agents/wave-runner.md +74 -0
- package/LICENSE +21 -0
- package/README.md +389 -0
- package/factory/orchestrator/dist/actors.js +46 -0
- package/factory/orchestrator/dist/agents-registry.js +308 -0
- package/factory/orchestrator/dist/args.js +84 -0
- package/factory/orchestrator/dist/artifacts.js +111 -0
- package/factory/orchestrator/dist/attribution.js +160 -0
- package/factory/orchestrator/dist/audit.js +812 -0
- package/factory/orchestrator/dist/autonomy.js +151 -0
- package/factory/orchestrator/dist/budgetAlarm.js +366 -0
- package/factory/orchestrator/dist/budgets.js +196 -0
- package/factory/orchestrator/dist/claims.js +633 -0
- package/factory/orchestrator/dist/cli.js +3185 -0
- package/factory/orchestrator/dist/commit.js +152 -0
- package/factory/orchestrator/dist/coverage.js +188 -0
- package/factory/orchestrator/dist/crossFinding.js +479 -0
- package/factory/orchestrator/dist/crosscheck.js +396 -0
- package/factory/orchestrator/dist/daemon.js +707 -0
- package/factory/orchestrator/dist/db/projector.js +1317 -0
- package/factory/orchestrator/dist/db/queries.js +1714 -0
- package/factory/orchestrator/dist/db/schema.js +305 -0
- package/factory/orchestrator/dist/delegation.js +400 -0
- package/factory/orchestrator/dist/diffstat.js +164 -0
- package/factory/orchestrator/dist/dispatchAudit.js +275 -0
- package/factory/orchestrator/dist/dotenv.js +72 -0
- package/factory/orchestrator/dist/effort.js +202 -0
- package/factory/orchestrator/dist/epic.js +1018 -0
- package/factory/orchestrator/dist/epicWidth.js +238 -0
- package/factory/orchestrator/dist/errorIssues.js +249 -0
- package/factory/orchestrator/dist/errors.js +11 -0
- package/factory/orchestrator/dist/escalation.js +500 -0
- package/factory/orchestrator/dist/events.js +1015 -0
- package/factory/orchestrator/dist/findingAge.js +91 -0
- package/factory/orchestrator/dist/findingContext.js +103 -0
- package/factory/orchestrator/dist/findings.js +1156 -0
- package/factory/orchestrator/dist/gate.js +911 -0
- package/factory/orchestrator/dist/gh.js +146 -0
- package/factory/orchestrator/dist/git.js +87 -0
- package/factory/orchestrator/dist/goalCheck.js +380 -0
- package/factory/orchestrator/dist/graph.js +61 -0
- package/factory/orchestrator/dist/hookDecision.js +106 -0
- package/factory/orchestrator/dist/immutability.js +171 -0
- package/factory/orchestrator/dist/impact.js +280 -0
- package/factory/orchestrator/dist/integration.js +141 -0
- package/factory/orchestrator/dist/judgePreflight.js +155 -0
- package/factory/orchestrator/dist/judges.js +310 -0
- package/factory/orchestrator/dist/lessonAudit.js +399 -0
- package/factory/orchestrator/dist/lessons.js +1329 -0
- package/factory/orchestrator/dist/mcp.js +555 -0
- package/factory/orchestrator/dist/paths.js +82 -0
- package/factory/orchestrator/dist/plan.js +502 -0
- package/factory/orchestrator/dist/planQuorum.js +421 -0
- package/factory/orchestrator/dist/policy.js +1308 -0
- package/factory/orchestrator/dist/policyHook.js +28 -0
- package/factory/orchestrator/dist/preconditions.js +47 -0
- package/factory/orchestrator/dist/projects.js +134 -0
- package/factory/orchestrator/dist/prompts.js +57 -0
- package/factory/orchestrator/dist/provenance.js +201 -0
- package/factory/orchestrator/dist/providers/api-transport.js +117 -0
- package/factory/orchestrator/dist/providers/cli-transport.js +208 -0
- package/factory/orchestrator/dist/providers/index.js +54 -0
- package/factory/orchestrator/dist/providers/schema-validate.js +152 -0
- package/factory/orchestrator/dist/providers/types.js +49 -0
- package/factory/orchestrator/dist/queue.js +263 -0
- package/factory/orchestrator/dist/quorum.js +347 -0
- package/factory/orchestrator/dist/quorumEscalations.js +132 -0
- package/factory/orchestrator/dist/results.js +65 -0
- package/factory/orchestrator/dist/roadmap.js +437 -0
- package/factory/orchestrator/dist/runtime.js +86 -0
- package/factory/orchestrator/dist/sameMistakeKpi.js +243 -0
- package/factory/orchestrator/dist/sandbox.js +165 -0
- package/factory/orchestrator/dist/scaffold.js +373 -0
- package/factory/orchestrator/dist/scheduler.js +408 -0
- package/factory/orchestrator/dist/schemas.js +195 -0
- package/factory/orchestrator/dist/security.js +222 -0
- package/factory/orchestrator/dist/severity.js +293 -0
- package/factory/orchestrator/dist/spec.js +376 -0
- package/factory/orchestrator/dist/specChange.js +497 -0
- package/factory/orchestrator/dist/stack.js +324 -0
- package/factory/orchestrator/dist/symbols.js +966 -0
- package/factory/orchestrator/dist/taskEvents.js +399 -0
- package/factory/orchestrator/dist/taskId.js +126 -0
- package/factory/orchestrator/dist/taxonomy.js +113 -0
- package/factory/orchestrator/dist/testSelect.js +129 -0
- package/factory/orchestrator/dist/testerAudit.js +264 -0
- package/factory/orchestrator/dist/testgate.js +171 -0
- package/factory/orchestrator/dist/usage.js +874 -0
- package/factory/orchestrator/dist/waivers.js +228 -0
- package/factory/orchestrator/dist/waveBudget.js +223 -0
- package/factory/orchestrator/dist/waveConcurrency.js +250 -0
- package/factory/orchestrator/dist/waveNext.js +210 -0
- package/factory/orchestrator/dist/waveSchedule.js +146 -0
- package/factory/orchestrator/dist/worktree.js +207 -0
- package/factory/orchestrator/drizzle/0000_broken_rockslide.sql +183 -0
- package/factory/orchestrator/drizzle/0001_luxuriant_zaladane.sql +1 -0
- package/factory/orchestrator/drizzle/0002_yellow_wild_pack.sql +8 -0
- package/factory/orchestrator/drizzle/0003_damp_riptide.sql +12 -0
- package/factory/orchestrator/drizzle/0004_broad_lady_vermin.sql +1 -0
- package/factory/orchestrator/drizzle/0005_romantic_jocasta.sql +2 -0
- package/factory/orchestrator/drizzle/0006_bent_madelyne_pryor.sql +16 -0
- package/factory/orchestrator/drizzle/0007_overjoyed_goliath.sql +1 -0
- package/factory/orchestrator/drizzle/0008_smart_iron_patriot.sql +2 -0
- package/factory/orchestrator/drizzle/0009_agents_epic_id.sql +2 -0
- package/factory/orchestrator/drizzle/0010_milestone_kind.sql +1 -0
- package/factory/orchestrator/drizzle/meta/_journal.json +83 -0
- package/factory/policies/budgets.yml +262 -0
- package/factory/policies/crosscheck.yml +308 -0
- package/factory/policies/delegation.yml +44 -0
- package/factory/policies/effort.yml +142 -0
- package/factory/policies/guardrails.yml +306 -0
- package/factory/policies/lessons.md +239 -0
- package/factory/policies/scheduler.yml +90 -0
- package/factory/policies/sensitive-paths.yml +148 -0
- package/factory/policies/severity.yml +83 -0
- package/factory/policies/stack.yml +60 -0
- package/factory/policies/taxonomy.yml +340 -0
- package/factory/policies/worktree.yml +95 -0
- package/factory/scaffold/base/.github/workflows/ci.yml +34 -0
- package/factory/scaffold/base/.gitignore.tmpl +11 -0
- package/factory/scaffold/base/AGENTS.md +33 -0
- package/factory/scaffold/base/README.md +25 -0
- package/factory/scaffold/base/biome.json.tmpl +30 -0
- package/factory/scaffold/base/package.json +25 -0
- package/factory/scaffold/base/pnpm-workspace.yaml.tmpl +16 -0
- package/factory/scaffold/base/src/index.ts +4 -0
- package/factory/scaffold/base/test/index.test.ts +8 -0
- package/factory/scaffold/base/tsconfig.json +20 -0
- package/factory/scaffold/base/tsconfig.test.json +9 -0
- package/factory/scaffold/base/vitest.config.ts +29 -0
- package/factory/scaffold/mcp/mcp.manifest.json +22 -0
- package/factory/scaffold/mcp/package.fragment.json +9 -0
- package/factory/scaffold/mcp/src/mcp/README.md +63 -0
- package/factory/scaffold/mcp/src/mcp/guard.ts +110 -0
- package/factory/scaffold/mcp/src/mcp/redact.ts +69 -0
- package/factory/scaffold/mcp/src/mcp/server.ts +55 -0
- package/factory/scaffold/mcp/src/mcp/tools/health.ts +60 -0
- package/factory/scaffold/mcp/test/mcp/guard.test.ts +108 -0
- package/factory/scaffold/mcp/test/mcp/redact.test.ts +61 -0
- package/factory/scaffold/mcp/test/mcp/surface.test.ts +100 -0
- package/factory/scaffold/ui/index.html +12 -0
- package/factory/scaffold/ui/package.fragment.json +13 -0
- package/factory/scaffold/ui/src/App.vue +10 -0
- package/factory/scaffold/ui/src/main.ts +5 -0
- package/factory/scaffold/ui/src/shims.d.ts +11 -0
- package/factory/scaffold/ui/vite.config.ts +20 -0
- package/factory/scaffold/ui-tailwind/package.fragment.json +6 -0
- package/factory/scaffold/ui-tailwind/vite.config.ts +26 -0
- package/factory/specs/schema/event.schema.json +82 -0
- package/factory/specs/schema/finding-evidence.schema.json +45 -0
- package/factory/specs/schema/finding.schema.json +115 -0
- package/factory/specs/schema/grader-verdict.schema.json +50 -0
- package/factory/specs/schema/judge-verdict.schema.json +19 -0
- package/factory/specs/schema/lesson.schema.json +85 -0
- package/factory/specs/schema/mcp-manifest.schema.json +230 -0
- package/factory/specs/schema/result.schema.json +81 -0
- package/factory/specs/schema/spec-change-request.schema.json +46 -0
- package/factory/specs/schema/task-spec.schema.json +134 -0
- 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.
|