ADR 0065: discern setup keeps its promises
Amendments.
- Vocabulary: current spellings are
standards(formerlyratchets),done(formerlyfinish),accept(formerlygraduate),update(formerlyintegrate),mapwhere it names the command, config, or tree (formerlydocs), and known/customjob(formerly gatecapability/ customcheck); the decisions below are unchanged.- ADR 0128 — tracked agent files: §7's uniformly-gitignored posture is reversed — the canonical agent file and its pointer mirrors are tracked by default; the promise-keeping headline of this record stands.
- ADR 0313 — final-tree transaction: the order in §1 is replaced. Ordinary completion commits the marker before its final checks, runs the Gate last, and returns canonical Proof for that clean commit. The explicit
--unprovenpath persists that state and carries no Proof.- ADR 0351 — replay and owned rollback: a clean completed commit replays or validates through
setup donewithout another marker. A failed new transaction returns to the predecessor only while discern still owns the exact tip; otherwise the changed state is retained with recovery.- ADR 0322, current journey:
SETUP_PAGE_REGISTRYowns the live sequential page order and continuations. Numbered step references below record the brief at the time of this decision; they are not current routing instructions.
Status: accepted
Hardens the setup flow established by ADR 0036 (unify setup into discern setup) and the incompleteness signaling of ADR 0037; revises the pre-setup verb redirect 0036 introduced. Builds on ADR 0034 (the generated agent files are gitignored build artifacts) and the involve-don't-gate brief of ADR 0044.
Context
§Running discern setup end-to-end with an unfamiliar agent (OpenAI Codex, in a real Deno project) surfaced a cluster of defects that share one root: the scaffold prints promises its own structure does not keep at the moment it prints them. The brief is prose; the files and state behind it didn't match.
- The brief prescribes an impossible order. Step 8 tells the agent to run
discern done(the "prove the gate is green" proof ADR 0036 calls for) beforediscern setup done. Butdoneis one of theSETUP_GATED_VERBS— it hard-redirects with "this project isn't set up yet" untilsetup donerecords[meta].bootstrapped. The proof step cannot execute in the order the brief gives. Worse, no test caught it: the engine test harness scaffolds withbootstrapped = trueby default, so every gate test runs in the one state where the contradiction is invisible.
- An existing agent file wasn't carried into the tracked source.
compileGuidelinesregenerates each agent file from the compiled guidance, so a project that already had a hand-authoredCLAUDE.md/AGENTS.mdsaw it replaced without its content first being folded into the trackedguidance.mdthe pipeline reads from.
- The brief says "flesh out the stub" of a
guidance.mdthat was never laid.laySkeletonsseedsmap/andTODO.mdonly. The agent had to createguidance.mdfrom nothing, andsetup donecould not catch its absence —findSkeletonMarkersonly flags aguidance.mdthat exists and still carries a marker.
- The scaffold links to files it doesn't create. The seed
map/README.mdlinks_adr/README.mdand_internal/; neither is laid by setup. Dead links on day one.
- The project name's casing is lost. Skeleton fills reconstruct the display name from the lowercase slug (
displayNameFromSlug(slug)), soListOfListsOfListsbecomesListoflistsoflistsinTODO.md— even though the correctly-casedprojectNameis in hand during a fresh scaffold.
- One provider's write failure aborts all of refresh. In
compileGuidelinesthe skills job runs first and unguarded; a sandbox denial writing.agents/skillsthrows and takes down the later MCP-wiring and agent-file compile, even though.claude/skillsalready succeeded.
- A documentation contradiction. The brief states the agent files are "all gitignored build artifacts except
AGENTS.md", while the shipped.gitignore.fragment(and ADR 0034) ignoreAGENTS.md.
Each is a separate bug, but the unifying lesson is the one discern already applies elsewhere: ADR 0044 makes "you can revert this" true by backing it with an atomic commit; ADR 0037 makes "setup is done" true by backing it with the setup done gate. Setup's brief and skeletons were not held to that bar — they asserted without backing.
Decision
§Every promise discern setup prints is backed by structure that exists when it prints it; existing guidance is adopted rather than replaced; and a fresh setup runs isolated on its own branch. Concretely:
discern setup doneproves completion structurally. It records[meta].bootstrapped = truewith[meta].setup_completion = "proven"only after the marker-bearing commit passes refresh, doctor, the worktree probe, and the final Gate.discern setup done --unprovenis the named escape path: it persistssetup_completion = "unproven", returns no Proof, cannot be accepted, and can later converge through ordinary completion. The agent no longer runsdoneas a separate pre-completion step —setup doneowns the green-gate proof.
- The gate's proof verbs are usable during setup.
done,prepare,test, andstandardsare removed fromSETUP_GATED_VERBSso the agent can iterate while wiring jobs — and test a standard it wires — during Step 7. They are not silent: while!bootstrappedeach carries ahints[]entry stating setup is unfinished and this output is indicative untildiscern setup donepasses.map,accept, andupdatestay gated — pre-setup they browse an empty tree or act on branch work that doesn't exist yet. This revises ADR 0036's uniform redirect: the "empty gate reads as a false all-green" risk it guarded against is now covered by ADR 0037's incompleteness signaling (thestatusbanner, the session reminder, and thesetup donegate), so gating the proof verbs only blocked their legitimate use.
- A fresh setup adopts any existing agent file into
guidance.md. Before the first compile, on a fresh install (nodiscern.tomlyet — so any agent file on disk is the user's, never one discern generated), each pre-existingCLAUDE.md/AGENTS.md/GEMINI.mdhas its content folded into the trackedguidance.mdsource under a labelled heading, deduped by content. The compile then re-emits it as part of the generated body, so existing instructions flow into the author-once → compile-everywhere pipeline rather than being replaced by it. Duplicate content is harmless — the agent reconciles it in Step 4.
- Setup ships what it references.
laySkeletonslays the marked instruction source and the Map's_adr/skeleton (README.md+0000-template.md, the canonical ADR format the design-principles template depends on). A fresh-scaffold architectural test forbids the class: every relative link in a scaffolded Map page resolves, and every file the brief says to fill is laid carrying a marker.
- Skeleton fills use the correctly-cased project name. The display name comes from
SetupConfig.projectName(casing preserved from the directory), falling back to slug title-casing only when resumed setup no longer has the fresh input.
refreshisolates per-artifact failures. The skills and agent-file jobs incompileGuidelinesare wrapped like the MCP job already is; a failure in one is recorded and reported, and the others still run. The result reports partial success rather than throwing.
- The agent files are uniformly gitignored build artifacts. The brief's "except
AGENTS.md" carve-out is removed, settling the contradiction in favour of the shipped.gitignore.fragmentand ADR 0034.AGENTS.md,CLAUDE.md, andGEMINI.mdare all generated, all gitignored; the tracked, reviewable source isguidance.md.
- A fresh setup runs on its own branch. In a git repo, a fresh
discern setuprequires a Git repository and normally a clean working tree (it refuses uncommitted tracked changes, pointing at--allow-dirty) and creates + checks out a dedicateddiscern-setupbranch before writing anything — so the several commits setup makes never land on the user's current branch, and the whole effort is trivial to roll back or land when ready.--allow-dirtykeeps the explicitly chosen current-branch path;--dry-runhas no effects, and--reseedrefreshes an existing installation without re-detecting its provider selection. A non-Git directory refuses withgit initas its one next action.
The explicit nos:
- Incompleteness signaling is untouched (ADR 0037). The loud "SETUP STARTED — NOT FINISHED" frame, the tail-survivable footer, and the
setup donegate all stand. Makingsetup donerun the gate strengthens it: completion is now proven, not asserted. setup donedoes not weaken to a warning. A red doctor or Gate run blocks proven completion. The only bypass is the explicit, persisted, non-acceptable--unprovenstate.- No new schema or CLI interactivity. The completion marker stays
[meta].bootstrapped;discern setupthe command stays non-interactive.
Consequences
§- The brief's prescribed flow is executable end-to-end, and a regression test drives it from the un-bootstrapped state — the state the prior suite never exercised — so the contradiction cannot silently return.
- Setup can run over a project that already has its own agent instructions: their content is folded into
guidance.mdand re-emitted, with any duplicates the agent tidies in Step 4. A guard test asserts a pre-existingCLAUDE.md's content reaches bothguidance.mdand the recompiled file. - A fresh setup is isolated on its own branch, so experimenting is low-stakes and rollback is one
git branch -D. The cost is a little friction: a dirty repo must commit/stash (or pass--allow-dirty) before setup will run. setup doneis slower — it runs the full gate, including tests — but that cost buys a real definition-of-done, and it is a one-time event that already asked the agent to rundoneby hand.- Un-gating the proof verbs (
done/prepare/test/standards) leans on ADR 0037's signaling to carry the "not done yet" message; the per-command hint makes that explicit at each call site rather than relying only onstatus. - The single-source discipline holds: the
_adr/skeleton is shared with thewrite-adrskill via a byte-identity guard, so the canonical format lives in one place even though it is laid from two. doctorblocking ordinarysetup donemeans an environment problem can hold proven completion. This is intended:--unprovenrecords the exception truthfully without manufacturing acceptance evidence.
Alternatives considered
§- Reorder the brief:
setup donefirst, thendone. Rejected — it marks setup complete before proving the gate, the opposite of what the proof step is for, andsetup donerefuses while markers remain anyway. - Keep
donegated; let onlysetup donerun it internally. Rejected as insufficient: the agent still needsdone/prepare/testto iterate while wiring jobs in Step 7, and blocking them there is the exact footgun that surfaced. Un-gating with a hint serves both. - Detect a pre-existing agent file by a generated-file sentinel rather than the fresh-install gate. Rejected: the canonical agent file is full-bodied, but its mirrors are bare
@AGENTS.mdpointers carrying no sentinel, so a generated pointer would be misread as the user's content. "Nodiscern.tomlyet ⇒ nothing discern generated" is unambiguous and needs no content sniffing. - Isolate setup in a worktree instead of a branch. Rejected: setup runs at the bootstrap moment, before discern's worktree workflow is configured (there is no
discern.tomlyet), and a plain branch needs no per-worktree resources or config — it is the lighter, always-available isolation here. - Soften the
_adr/reference too, like_internal/. Rejected: the design principles name "write an ADR" as their override mechanism, so the ADR format and template should exist from the first commit, not only after thewrite-adrskill is first run. - Flip
AGENTS.mdto tracked. Rejected here: it would reverse ADR 0034 and change the shipped gitignore for every project. The contradiction is settled in favour of the existing decision; revisiting 0034 is a separate question.