docs: close configuration.md drift against the code it documents - #63
Conversation
dichovsky
left a comment
There was a problem hiding this comment.
Routine review of a95c9f2. Reviewer roster per .github/ISSUE_TRIAGE.md: general code reviewer (always) + docs-only prose-vs-code verification in place of the TypeScript reviewer. No security reviewer — the diff touches no src/store/, src/process.ts, src/which.ts, src/fs-safe.ts, src/setup/, or the Launcher.
Scope discipline is clean. One file, one commit, +56/−4, three hunks, all tracing to #30. No unrelated edits, no reflowed paragraphs, no drive-by improvements. Line widths (max 92) match the pre-change file. All three of the issue's Acceptance criteria are met.
A claim-by-claim audit of 35 assertions against source found 33 verified. The defects below are concentrated in the precedence rewrite.
HIGH
1. configuration.md:362-364 — the CREW_LAUNCH_TOKEN bullet states the reap condition backwards.
The new text says the token is stamped on the Agent "so a failed teardown can scope its reap". The reap runs only when teardown is confirmed, and is deliberately skipped when it is not. src/launcher/session.ts:604 returns before the reap (if (!tornDown) return false;), guarding store.reapByLaunchToken(launchToken) at :607; the contract comment at :580-584 states the reason — "if teardown failed the panes' participant processes may still be alive, and removing their (untouched) Agent rows would break them with AGENT_INACTIVE".
This also contradicts the higher-authority CLI contract, cli-contract.md:310-311: "This cleanup is best-effort and is skipped when the teardown could not be confirmed."
Consequence: a reader concludes crew deletes Agent rows precisely when teardown failed — i.e. when panes may still be live — which is the exact scenario the design refuses. A safety-relevant behavior stated backwards in a contract document. The wording was inherited from the loose comment at src/agents.ts:22-25; the intended sense is a failed launch's confirmed teardown.
MEDIUM
2. configuration.md:353-355 — "no variable can replace a tracked value or choose an executable" is contradicted by the same paragraph's own third bullet.
PATH does select which executable file crew spawns: src/which.ts:23-24 resolves a name to an absolute path, and src/which.ts:13 notes the resolved path is handed "to the spawn, so execvp performs no second PATH search of its own". src/process.ts:23 calls execFile with bare names (git, tmux — src/launcher/tmux.ts:25, src/worktree.ts:41), so PATH picks those too. The defensible claim — the one src/launcher/config.ts:40-42 and docs/design/security.md:40 actually make — is that tracked configuration cannot name an executable, and that no variable picks which tool crew runs.
3. configuration.md:179-180 — dangling cross-reference. The text points at "the whole-Crew --worktree/--no-worktree launch flags described above". In this document --no-worktree appears nowhere else and --worktree <branch> appears once, as a parenthetical in a table cell at :141. The flags are described in cli-contract.md:315-317. The sentence paraphrases cli-contract.md:84-87, whose pointer ("ADR-0011, under Roles and Teams") is valid intra-document there but mistranslates here.
4. configuration.md:176-178 — the three-command list reads as exhaustive but is not. "task start, task review, and task land create, check out, and remove one worktree per Task and one per reviewing Agent" omits task abandon, which removes the worktree (FR-W15, srs.md:1004-1007; src/tasks.ts:448-469), and task approve/task requeue, which check the review worktree back out to its resting base (FR-W10, srs.md:985-989; src/tasks.ts:389, :420). The CLI contract's equivalent sentence carries a pointer — cli-contract.md:93 "…one per reviewer (see Reviewed Tasks)" — which this rewrite drops, converting a summary into an apparently complete list.
5. configuration.md:349-351 — the precedence ladder still names only launcher.yaml. This PR adds .crew/config.yaml as a second tracked config file with no CLI-flag override at any layer (src/config.ts:108-112; no worker_worktrees flag in src/cli.ts:281-317). The one place in the document that answers "what beats what" now omits half the tracked configuration. One clause fixes it.
6. configuration.md:365-366 — HOME dropped from the setup consumer while the list claims to be exhaustive ("three narrow places"). setup reads it directly: src/setup/index.ts:56-58 (io.env.HOME ?? io.env.USERPROFILE in homeDir, feeding globalAbsPath) and :81. Bullet 1 now attributes HOME/USERPROFILE solely to the worktree base, so an auditor asking "who consumes this variable" — the exact use this rewrite exists to serve — will miss that HOME determines where crew setup writes global artifacts.
7. configuration.md:153 — the heading ## Workspace schema v1 collides with launcher.yaml's own workspace: block, documented 80 lines earlier in the same file (configuration.md:71-75, table rows :96-98; src/launcher/config.ts:35-36). Both have enabled and base_ref with the same HEAD default but different validation. A reader landing on the heading can reasonably read it as documentation of launcher.yaml's workspace: key and apply the wrong rule.
LOW
configuration.md:365-366— thePATHbullet omitsdoctor, which reads it viaisExecutableOnPathatsrc/doctor.ts:78and:96to emitDEPENDENCY_MISSINGfindings without ever spawning.configuration.md:361— "never whether one is created" is marginally overstated:managedWorktreeBasethrowsNOT_FOUNDwhen all three variables are unset (src/launcher/derive.ts:76-80), so the environment can prevent a worktree, it just can never cause one.configuration.md:358-361— the call-site enumeration is accurate but slightly short: the plan is also built bycrew team resume's drift check (src/launcher/resume.ts:97-98) and by the Console's Operator launch action (src/ui/actions.ts:345-346).configuration.md:44-45— drops the CLI contract's "today" qualifier on the reserved-verb set (cli-contract.md:352-353), so adding a thirdcrew team <verb>would falsify the paragraph rather than extend a growable set.configuration.md:189-191— the closing strict-parse paragraph restates the document preamble (:3-6) minus its 256 KiB cap, whichsrc/config.ts:111does enforce. Neither the Team nor the Launcher section repeats the preamble.configuration.md:44-47— the reserved-names paragraph separates the fields table from its own continuation ("When a member has replicas…",:49), which elaborates themembers[].replicasrow.
Not raised as a defect: configuration.md:164-165 scopes the file to "every Worker in the Workspace" while :177-178 describes a per-reviewing-Agent worktree. Both halves are inherited verbatim from cli-contract.md:82-88, so correcting the scoping belongs upstream, not here.
Verdict: correct in direction and admirably tight in scope, but not mergeable as-is. Finding 1 must be fixed. Findings 2–7 are cheap accuracy repairs worth taking in the same push. Finding 9's suggestion to extend the "Derived names and paths" table for Task/review worktree branch derivation is deliberately out of scope for #30.
Generated by Claude Code
dichovsky
left a comment
There was a problem hiding this comment.
Re-check of the fix pass (a95c9f2..ed935ef), changed areas only. Per .github/ISSUE_TRIAGE.md the implementer gets exactly one fix pass and there is no second round, so this is the final routine review.
Prior findings
| # | Status | Evidence |
|---|---|---|
| 1 (HIGH) — inverted reap condition | FIXED | configuration.md:370-373 now reads "Only after a teardown crew could confirm does that token scope the cleanup … an unconfirmed teardown skips the cleanup entirely", matching src/launcher/session.ts:604 (if (!tornDown) return false; guarding the sole reapByLaunchToken at :607) and cli-contract.md:305-314. |
| 2 — "choose an executable" | FIXED | :359-361 "Tracked configuration can never name an executable at all; PATH only decides which file on disk supplies a tool crew has already chosen" — true against src/which.ts:23-34 and security.md:38-40. |
| 3 — dangling cross-reference | FIXED | cli-contract.md:255 is ### Roles and Teams; the flags are at :316-317, inside it. |
| 4 — exhaustive-looking command list | FIXED | cli-contract.md:168 is ### Reviewed Tasks; task abandon at :205-220, approve/requeue switch-back at :245-247. |
| 5 — precedence ladder | FIXED | worker_worktrees appears only in src/config.ts (:17, :78-98), never in src/cli.ts; the added sentence's "no third layer above it" makes the same-layer phrasing defensible. |
6 — HOME / doctor attribution |
NEW DEFECT | See below. |
| 7 — heading collision | FIXED | No occurrence of workspace-schema or "Workspace schema" anywhere in docs/, docs-site/, web/, src/, tests/, .github/, README.md, CLAUDE.md. No anchor breaks. |
| 8 — "never whether one is created" | FIXED | :370-372 matches src/launcher/derive.ts:76-80, stated as a sufficient rather than exclusive condition, so the relative-XDG_DATA_HOME case is not misdescribed. |
| 9 — short call-site list | FIXED | The generalized wording covers all four callers: src/launcher/plan.ts:145, src/launcher/session.ts:389, src/tasks.ts:251, :529. |
| 10 — "today" qualifier | FIXED | :153-154 matches cli-contract.md:352-353. |
| 11 — redundant strict-parse paragraph | FIXED | Preamble :3-6 states all four rules and scopes them to tracked files under .crew/; enforcement confirmed via src/config.ts:111 → readManagedFile(…, MAX_CONFIG_BYTES) (src/fs-safe.ts:23) and src/config.ts:91-92 → loadYamlMapping (src/yaml-load.ts:139-156). No fact dropped. |
Scope is clean — only docs/design/configuration.md is touched, all hunks trace to the findings, and the four out-of-scope areas are untouched.
New finding introduced by the fix pass
HIGH — configuration.md:375-376: "doctor only reports what is missing and spawns nothing" is false.
runDoctor awaits collectVersionFloorFindings (src/doctor.ts:464), which calls probeVersion for every Participant target carrying a minimumVerifiedVersion (src/doctor.ts:114-127); that reaches io.runProcess(executable, target.versionArgs, { timeoutMs: VERSION_PROBE_TIMEOUT_MS }) at src/platforms/shared.ts:239. The function's own comment at src/doctor.ts:115-119 describes "five bounded 5s probes". It also contradicts docs/design/security.md:41-45, which documents exactly those probes spawning.
Consequence: a newly minted false invariant of the same class this PR exists to remove, in the security-adjacent environment section. The narrow claim is true of the PATH lookup itself, not of doctor. Dropping the clause, or narrowing it to "the PATH read itself only decides presence and spawns nothing", fixes it.
LOW
:376-377—HOME/USERPROFILEis attributed tosetupalone, butdoctorreads it too (src/doctor.ts:205collectArtifactDrift→src/setup/index.ts:404homeDir(io)).:376-377— "givessetupcrew's own home directory":src/setup/index.ts:57-58reads the user's home (HOME ?? USERPROFILE); crew has no home of its own.:369-370— "--print, which derives the path without touching the filesystem":src/launcher/plan.ts:144callsrealpathSync(root)first. The code's own comment says "a pure read … never invokes git"; "creates nothing on disk" would be exact.:176-182— the new[cli-contract.md](./cli-contract.md)link resolves and matches the](./file.md)idiom, but every comparable section-pointer indocs/design/carries an anchor (cli-contract.md:643,architecture.md:416,srs.md:13);#reviewed-tasks/#roles-and-teamswould make these clickable. The same sentence's mid-clause parenthetical plus the following "specified there" is the one spot where the patching shows a seam.
Verdict: one HIGH finding survives. The gating finding (the inverted reap condition) is correctly fixed and every other prior finding checks out, but the fix pass introduced a new false claim about doctor.
Per the routine's action limits the fix budget is spent, so this PR stays a draft and needs a human. The remaining work is one clause at configuration.md:375-376, plus the four optional LOWs above.
Generated by Claude Code
|
Flagging before this merges: the PR is now ready for review and
It is a clause I introduced in the fix pass, so merging as-is would land a new false invariant in a contract document — the same class of defect this PR exists to remove. CI being green does not catch it; nothing tests this prose. The fix is one clause: drop it, or narrow it to the thing that is actually true — "the I have also refreshed the PR body, which still described the pre-fix-pass state — the old Generated by Claude Code |
There was a problem hiding this comment.
Pull request overview
This PR updates docs/design/configuration.md to eliminate documented-schema drift versus the implementation and higher-authority docs (SRS / CLI contract), without changing runtime behavior.
Changes:
- Documents the previously undocumented workspace-level
.crew/config.yamlschema (worker_worktrees). - Corrects the “precedence / environment variables” narrative to match actual environment reads (e.g., worktree-base derivation and
CREW_LAUNCH_TOKEN). - Adds the missing Team-name reserved-word rule (
stop,resume) to the Team schema documentation.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
Fixed in The bullet now reads:
This keeps the read-only point where it is actually true — Two adjacent errors in the same sentence went with it, both from the re-check's LOW list, since I was editing that sentence anyway:
Verification, Node The three remaining LOW nits from the re-check are untaken and still listed there: Generated by Claude Code |
configuration.md is the doc README points at for which YAML a project may commit, but it never mentioned `.crew/config.yaml`, claimed environment variables are read only by `setup`, and omitted the reserved Team names — so a reader authoring config from this page hits rules it never states. Document the `.crew/config.yaml` schema (optional, `version: 1`, `worker_worktrees.enabled`/`base_ref`) alongside the Team and Launcher schemas, state that `stop` and `resume` are reserved Team names and that `crew teams` omits a reserved project stem, and replace the precedence claim with the environment reads that actually happen: the XDG/HOME worktree-base derivation, `CREW_LAUNCH_TOKEN`, and `PATH`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ETv61CYVQABvqvyCiR2Ev7
The reserved-name, worktree, and environment prose in configuration.md made several claims that the implementation does not support, so a reader auditing behavior from this document would draw the wrong conclusion. - The CREW_LAUNCH_TOKEN bullet had the reap condition inverted: the scoped cleanup runs only after a teardown crew could confirm and is skipped when it could not (src/launcher/session.ts guards the reap on `tornDown`), because panes may still be live. Restated to match cli-contract.md. - "No variable can choose an executable" is contradicted by PATH, which does select the file crew spawns (src/which.ts). Reframed to what is actually enforced: tracked configuration can never name an executable, and no variable picks which tool crew runs. - The whole-Crew --worktree/--no-worktree flags were said to be "described above"; they are described in cli-contract.md, not here. - The task-command list read as exhaustive while omitting task abandon (FR-W15) and task approve/requeue (FR-W10); it now carries a pointer instead. - The precedence ladder named only launcher.yaml; config.yaml is a second tracked file with no CLI-flag override at any layer. - HOME was dropped from the setup bullet while the list claimed to be exhaustive, doctor was missing as a PATH consumer, and setup's home lookup is crew's own home plus each target's fixed relative path, not a per-tool variable. - "Never whether one is created" overstated: an unset base makes managedWorktreeBase fail, so the environment can prevent a worktree. - The worktree-base call sites are also reached by team resume and the Console, so the enumeration is now generalized rather than extended. - "Workspace schema v1" collided with launcher.yaml's own workspace: block, which shares enabled/base_ref names; renamed to "Workspace settings". - Restored the "today" qualifier on the reserved-verb set so a future third verb extends the rule instead of falsifying the paragraph. - Trimmed the trailing strict-parse paragraph, which restated the document preamble that neither sibling schema section repeats. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ETv61CYVQABvqvyCiR2Ev7
The precedence section said `doctor` "only reports what is missing and spawns nothing". `runDoctor` awaits `collectVersionFloorFindings`, which probes each pinned Participant through `io.runProcess`, so the claim was false and contradicted security.md. The narrow true statement is about the PATH lookup itself, which only decides presence and starts nothing. The same sentence credited `HOME` to `setup` alone and called it crew's own home directory. `doctor` reads it too, through `collectArtifactDrift`, and the value is the user's home, not one crew owns. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ETv61CYVQABvqvyCiR2Ev7
476aef4 to
09368f8
Compare
Closes #30
What changed and why
docs/design/configuration.mdis the documentdocs/README.md:32points at for "exactly which YAML/Markdown input files may a project commit". It had three gaps against the code, all verified againstmain@d311767. Per the authority order indocs/README.md, the code and the higher-authority documents (SRS, CLI contract) are the correct side of each, so this is a documentation correction with no behavior change.docs/design/configuration.mdis the only file touched (+71 / −4 across two commits).1.
.crew/config.yamlwas entirely undocumented.grep -c 'config\.yaml' docs/design/configuration.mdreturned 0, whilesrc/config.tsstrict-loads it as a third tracked schema:CONFIG_KEYS = {version, worker_worktrees}(src/config.ts:17),versionmust be exactly1(:95-97),worker_worktrees.enableddefaults tofalseandbase_reftoHEAD(:78,:82), withassertValidBranchrun at load time (:87). It is already specified at SRS FR-W01–FR-W03 (srs.md:945-959) and in the CLI contract's "Workspace configuration" (cli-contract.md:73-100) — only the dedicated configuration-schema doc omitted it.A new
## Workspace settings schema v1section now sits between the Launcher schema and the Task brief, matching the two existing schema sections in shape: a YAML example, a| Field | Type/default | Rules |table, and prose covering the optional file and theversion: 1requirement;enabledand its relationship to the whole-Crew--worktree/--no-worktreeflags (ADR-0011 vs ADR-0015 — a Workspace may use either, both, or neither);base_refresolution and why a revision expression such asmain~1fails at config-load time rather than later attask start; and theINVALID_CONFIGoutcome for a document that breaks the parsing rules stated in the page preamble.2. The precedence section's env-var claim contradicted its own page. It read "In v1, environment variables never change project behavior. The only exceptions are the standard
HOMEandPATHvariables … all used only by the explicitsetupcommand." Contradicted bymanagedWorktreeBasereadingHOME/USERPROFILE/XDG_DATA_HOME(src/launcher/derive.ts:66-81) and bylaunchTokenFromreadingCREW_LAUNCH_TOKEN(src/agents.ts:27-30, consumed at:97-104) — and the same document already describes that XDG derivation atconfiguration.md:107-110.The three-item precedence list is untouched; a sentence after it places
.crew/config.yamlat the tracked layer with no flag-level override. The trailing paragraph now keeps the true underlying point — tracked configuration can never name an executable, and no variable overrides a tracked value or picks which tool crew runs — and then names the reads that do exist: the worktree-base derivation,CREW_LAUNCH_TOKEN, andPATHplus the home-directory read.3. The Team
namerule omitted the reserved names.src/teams.ts:38definesRESERVED_TEAM_NAMES = new Set(['stop', 'resume']), enforced as aUSAGEerror at:71-75(from both parse and name resolution) and applied again when listing project files at:252. The rule was incli-contract.md:352-356but not here, so a user authoringteams/stop.yamlstrictly per this document would be rejected by a rule it never stated. Thenamerow now says "not a reserved name", and a new paragraph states both halves — theUSAGErejection and the fact thatcrew teamsnever lists a project file whose filename stem is reserved — keeping the CLI contract's "today" qualifier on the verb set.One correction against the issue body
The issue attributes the
XDG_DATA_HOME/HOME/USERPROFILEread to "launch/--printtime" only.managedWorktreeBasehas a third class of caller:src/tasks.ts:251andsrc/tasks.ts:529, creating a Task worktree and a review worktree. Since this same change documentsworker_worktrees, the precedence bullet covers that read too rather than restating an incomplete claim. Relatedly,CREW_LAUNCH_TOKENis read only on a non---resumejoin (src/agents.ts:97short-circuits whenoptions.resume), so the wording is scoped to the Agent a join creates.Second commit (
ed935ef) — review fix passa95c9f2drew 1 HIGH and 6 MEDIUM findings in review;ed935efaddresses all of them. The HIGH: theCREW_LAUNCH_TOKENbullet stated the Agent-row reap as running on a failed teardown, whensrc/launcher/session.ts:604skips it unless teardown is confirmed — contradictingcli-contract.md:305-311. Also corrected: the "choose an executable" framing, a dangling "described above" cross-reference, an exhaustive-lookingtask start/review/landlist, the precedence ladder omittingconfig.yaml,HOMEdropped from thesetupattribution, and the## Workspace schema v1heading colliding withlauncher.yaml's ownworkspace:block.Warning
One HIGH finding is still open — see the re-check review on this PR.
configuration.md:375-376claims "doctoronly reports what is missing and spawns nothing", which is false:runDoctorawaitscollectVersionFloorFindings(src/doctor.ts:464), which spawns a bounded--versionprobe per Participant viaio.runProcess(src/platforms/shared.ts:239), and it contradictssecurity.md:41-45. The routine's one-fix-pass budget was spent, so this was left for a human.Verification
Docs-only change: no tests added or updated, claiming the docs-only exemption in
.github/ISSUE_TRIAGE.md. Nosrc/**orbin/**file changes, so coverage cannot move. The full gate was run on both commits, under Node24.18.0(the CI version), after rebasing ontoorigin/main.npm run typechecknpm run lintnpm run format:checkAll matched files use Prettier code style!npm run builddist/ui-assets/main.js 88.4kb)npm run test:coverageBoth failures are in
tests/integration/commands/doctor.test.ts("degrades a raw filesystem read failure in project roles instead of aborting doctor" and "degrades unreadable roles and teams directories to whole-listing warnings"). They were proven pre-existing by stashing the change, confirming a clean tree, and re-running the file — identical two failures, same names, same assertions. Root cause is environmental: the sandbox runs as uid 0, so the tests'chmod 000does not actually make the directories unreadable and the expectedINVALID_CONFIGwarnings never fire. A Markdown-only edit cannot influence them.Because vitest suppresses the coverage table on a failing run, the 95% thresholds were not evaluated locally. CI on GitHub-hosted runners is the authority for the gate, and it is green on both commits (
ubuntu / node 24.18.0andpublish dry-runsuccess), which confirms the two local failures were sandbox-only.Rebased onto
origin/main@d311767immediately before each push and the gate re-run afterward.Related open PRs
None. The two other open routine PRs touch disjoint files — #49 (
docs/design/srs.md) and #56 (README.md,CLAUDE.md,AGENTS.md,package.json,docs/design/srs.md,docs/design/product-spec.md). Neither touchesdocs/design/configuration.md.Out of scope
Deliberately untouched, each tracked separately:
resume_result,ui --jsonopener, implies-launch flags, error-code scopes) — CLI contract drift: doctor finding codes, resume_result, ui --json opener, implies-launch flags, error-code scopes #28.architecture.md§4.1 source-tree staleness — architecture.md §4.1 source tree is substantially stale #35.crew/task-…,crew/review-…) — beyond configuration.md drift: .crew/config.yaml undocumented, env-var precedence claim, reserved Team names #30's acceptance.configuration.md:164-165), which is inherited verbatim fromcli-contract.md:82-88and belongs upstream.