Configuration
scafld installs a small project-owned control plane:
.scafld/
config.yaml
config.local.yaml
prompts/
core/The current Go runtime treats the spec and session as the hard behavioral contract. Config controls the invariant catalog, harden prompt limits, review provider, model, timeouts, and review focus. Runtime-critical gates are still enforced by the Markdown spec, acceptance criteria, review dossier validation, and lifecycle commands.
Managed vs Project-Owned
Project-owned:
.scafld/config.yaml.scafld/config.local.yaml.scafld/prompts/*.scafld/specs/**.scafld/runs/**
Managed by scafld:
.scafld/core/config.yaml.scafld/core/prompts/*.scafld/core/schemas/*.scafld/core/scripts/*.scafld/core/specs/examples/*
scafld update refreshes .scafld/core/ and safely refreshes project prompt
copies that are still known defaults. Customized project prompts are skipped.
It also refreshes root agent docs and renders generated .scafld/config.yaml
into the current strict runtime shape. Specs, sessions, reviews, and local
config are never overwritten.
.scafld/config.yaml is intentionally sparse. Runtime defaults live in the
binary, and the full example shape lives in .scafld/core/config.yaml. Put only
project-specific invariant IDs, execution environment, provider defaults, or
review-pass overrides in the committed project config.
.scafld/config.local.yaml is narrower again: personal machine settings only.
Use it for local shim paths, provider binaries, and temporary model overrides.
Do not copy the full config shape into local config just to keep an example.
Prompt Overrides
Prompt lookup uses project files first:
.scafld/prompts/harden.md
.scafld/core/prompts/harden.md
built-in fallbackUse project prompts for local voice and policy. Keep core prompts as the reset
copy that package upgrades can refresh. If you have not customized a project
prompt, scafld update keeps it aligned with the bundled prompt contract.
Acceptance Strictness
Every executable criterion must carry an explicit matcher:
Acceptance:
- [ ] `ac1_1` test: Unit tests pass.
- Command: `go test ./...`
- Expected kind: `exit_code_zero`Supported Expected kind values:
exit_code_zeroexit_code_nonzerono_matches
Criteria without a known expected kind are rejected before execution. Free-form prose can explain intent, but the matcher is what scafld executes.
Execution Environment
Acceptance commands run in a deterministic non-login shell. scafld inherits the
process environment, prepends repo-detected version-manager shims, then applies
execution overrides from config. It does not source .zshrc, .bashrc,
rbenv init scripts, asdf init scripts, or other interactive shell startup files.
Toolchain files are detected from checked-in project state. .tool-versions
adds common asdf/mise shim directories, mise.toml adds common mise shim
directories, and language-specific files such as .ruby-version,
.python-version, .node-version, .go-version, and .java-version add the
matching common shim directory. Explicit config paths are placed before
detected shims.
Detected shims are intentionally conservative:
.tool-versions-> asdf and mise shimsmise.toml/.mise.toml-> mise shims.ruby-version-> rbenv shims.python-version-> pyenv shims.node-version/.nvmrc-> nodenv shims.go-version-> goenv shims.java-version-> jenv shims
Shared project command environment belongs in .scafld/config.yaml:
execution:
path_prepend:
- "$HOME/.rbenv/shims"
- "$HOME/.rbenv/bin"
env:
BUNDLE_GEMFILE: "api/Gemfile"path_prepend is added before the inherited PATH, after environment-variable
and ~ expansion. env values are also expanded with the current process
environment. Use .scafld/config.local.yaml for developer-local paths or model
provider binaries that should not be committed.
Task-specific requirements can still live directly in a criterion command, but repo-wide toolchain setup should be declared once in config. That keeps build evidence reproducible without forcing every spec to remember local shell initialization details.
Convention Surface
scafld does not require a CONVENTIONS.md file. Convention adherence is
surfaced through protocol artifacts that the runtime and reviewers already use:
.scafld/config.yamlnames canonical invariant IDs and review passes.- Specs select the invariants that apply to the task and declare scope, out-of-scope work, touchpoints, risks, and acceptance criteria.
AGENTS.mdandCLAUDE.mdgive agents the short operating contract at the root discovery surface..claude/rulesis treated as project rule context when present. scafld can include rule files in review context andscafld configwill cite them asagent_guidance_alignmentevidence.- Optional project docs can explain local style, but they are only binding when a spec, invariant, or review pass explicitly cites them.
This keeps conventions close to enforcement. Prose can help, but config and specs are the contract.
Invariant IDs live in config:
invariants:
canonical:
domain_boundaries: "Respect layer separation and ownership boundaries."
tenant_isolation: "Do not leak data across tenants."Specs select the relevant IDs for a task. Harden prints the configured catalog so the agent can choose the right constraint while tightening the draft. Review prompts include the invariants selected by the spec.
Agent rule files are deliberately advisory until promoted. Put durable policy in
config invariants, spec context, or review passes; use CLAUDE.md and
.claude/rules to help agents find and apply those policies consistently.
Config Proposals
scafld init installs a truthful default config. It does not ask an agent to
guess project policy.
Use scafld config when a repo needs project-specific tightening:
scafld configThe command scans recognizable project surfaces, writes
.scafld/config.proposed.yaml, and prints CONFIG MODE instructions for the
agent. The proposal is evidence-backed: every suggested command or invariant
cites the file that implied it. It also contains agent_instructions, so the
agent knows what to update, what must stay out of runtime config, and which
questions need a human or a deeper repo read. If an existing config contains
old keys the Go runtime does not read, the proposal includes an
ignored_config_keys warning so cleanup is explicit rather than silent.
When recognizable toolchain files exist, the proposal may include
config_patch.execution. For example, .ruby-version can justify rbenv
shims, .python-version can justify pyenv shims, and .tool-versions can
justify asdf/mise shims. Copy only project-specific overrides that need to
outrank the auto-detected defaults.
Config also recognizes common validation surfaces:
Makefile,justfile, andTaskfile.*check/test targetspackage.jsonscripts with npm, pnpm, yarn, or bun based on package manager metadata and lockfiles- Go, Rust, Python, and Ruby manifests for language-specific test commands
- monorepo/workspace files, architecture tests, CI workflows, migrations, and package manifests as invariant evidence
The proposal is not automatically applied because the best project config has to come from an agent or operator that has inspected the repo. The safe flow is:
- Read
.scafld/config.proposed.yaml. - Open the cited sources.
- Copy only verified runtime policy into
.scafld/config.yaml. - Put non-runtime guidance in
AGENTS.md,CLAUDE.md,.claude/rules, or project prompts instead of inventing config fields. - Use suggested commands and review focus while drafting or hardening specs,
unless you translate them into real
review.automated_passesorreview.adversarial_passesentries.
If scafld does not read a field, do not add it to config as if it were enforced.
Review Provider Selection
Review defaults come from .scafld/config.yaml:
review:
external:
provider: "auto" # auto | codex | claude | command | local
command: "./reviewer" # only when provider: command
provider_binary: "/path/bin" # optional selected-provider binary override
idle_timeout_seconds: 180
absolute_max_seconds: 1800
fallback_policy: "warn" # disable makes auto require codex
codex:
model: "gpt-5.5"
binary: "codex"
claude:
model: "claude-opus-4-7"
binary: "claude"
context:
# Aggregate rendered section-body budget for the provider brief.
max_bytes: 16384
files:
- AGENTS.md
- CLAUDE.md
- .claude/rules
- README.md
- docs/review.md
- docs/configuration.md
- docs/execution.md
- .scafld/core/schemas/review_dossier.json
dossier:
max_findings: 12
min_attack_angles: 6
review_depth: "standard"
rerun_policy: "verify_open_blockers"
automated_passes:
spec_compliance:
order: 10
title: "Spec Compliance"
description: "Verify recorded acceptance evidence against the spec."
adversarial_passes:
regression_hunt:
order: 30
title: "Regression Hunt"
description: "Trace callers, importers, and downstream consumers.".scafld/config.local.yaml overlays .scafld/config.yaml, so a developer can
pin a local provider or model without changing the committed project default.
init creates a commented local override file and the repository .gitignore
keeps it uncommitted.
CLI flags override config for a single invocation:
scafld review task --provider auto
scafld review task --provider codex
scafld review task --provider claude
scafld review task --provider command --provider-command "./reviewer"
scafld review task --provider local
scafld review task --provider codex --model gpt-5auto chooses an installed external provider. It prefers Codex, then falls back
to Claude unless fallback_policy: "disable" is set. Provider-specific model
defaults come from review.external.codex.model and
review.external.claude.model; --model overrides either.
local exists for tests and smoke runs; it is not a substitute for adversarial
review and cannot satisfy scafld complete.
Named automated_passes and adversarial_passes are included in the review
prompt in order sequence. They are the configurable review agenda; they do
not create additional local execution steps or mutate the workspace.
review.context.files is the bounded product-contract context included in the
reviewer brief. scafld skips private/local paths such as
.scafld/config.local.yaml, .priv/**, .git/**, and .env* even if listed.
Hardening
Hardening is operator-driven. scafld approve does not force
harden_status: passed, but a complete nontrivial plan spec should usually be
hardened before approval.
The active harden prompt asks the agent to record grounded questions under the
latest ## Harden Rounds entry. harden.max_questions_per_round is read from
config and injected into that prompt as a cap, not a target. --mark-passed
verifies the cited code or archive references and refuses to close the round
when they do not resolve.
