Add ARCHITECTURE.md: agent-facing reference doc - #812
Conversation
|
The Coverage Enforcement failure on this run is a pre-existing test flake, unrelated to this PR: This is an This PR only touches I don't have permission to trigger a job rerun from here ( Generated by Claude Code |
Task index, module map, Mermaid diagrams for module structure and the adr-bot data flow, extension seams, conventions, and known pitfalls, sourced from a full pass over the codebase.
Corrects the module dependency diagram (it claimed no cycles; runner, generation, and config actually cycle back through tailoring and cli), fills in two TerminalIO methods that have no default impl, replaces a misleading coverage command, fixes a backwards anyhow/Result convention claim, and documents the branding and model_cache modules that were missing entirely.
…fork Groups nodes into the 5 actual SyncPhase subgraphs instead of a flat 9-box chain, nesting Signals inside Fetch where it actually runs. Shortens each node to a title plus one function name (the verbose cache-key/retry detail already lives in the prose notes below) and replaces the misleading straight-line G step with a real fork/join for the V1 (tailored) vs V2 (deterministic, no LLM) ADR paths.
c32756b to
e6e7d1f
Compare
judyks
left a comment
There was a problem hiding this comment.
Review: ARCHITECTURE.md
I checked the doc's claims against the source at the head commit rather than skimming it. It's unusually accurate — the hard, checkable claims all hold (exact struct fields, the 13-variant exit-code count, all three dependency cycles, the 81% test-block figure, the 9 coverage --test binaries). Two things are wrong, and merging as-is leaves the repo's docs contradicting each other.
Non-blocking — the doc is a clear net win.
Must fix
1. TerminalIO::select_one signature is wrong (line 302)
- fn select_one(&self, prompt: &str, items: &[String], default: usize) -> Result<usize, ActualError>;
+ fn select_one(&self, prompt: &str, items: &[String], default: Option<usize>) -> Result<usize, ActualError>;Source is default: Option<usize> (src/cli/ui/terminal.rs). This one matters more than a normal typo: the PR description says select_files/select_one were added to §7 because "an agent following the doc alone would have hit a compile error implementing a new terminal backend." With the wrong parameter type, that compile error is still there — the fix didn't quite land.
2. The login.rs sentence says the opposite of what CI does (line 364)
cli/commands/login.rsis excluded from the coverage ignore-filename-regex for a different reason
.github/workflows/coverage.yml:75 puts cli/commands/login\.rs inside the --ignore-filename-regex. It's excluded from coverage by that regex — not excluded from the regex. Suggested:
cli/commands/login.rsis excluded from coverage via that same--ignore-filename-regex, for a different reason: its real browser OAuth + loopback listener can't run in CI.
The new doc is right, but two existing docs are now stale
ARCHITECTURE.md is correct in both cases below. The problem is that shipping it makes three files disagree.
3. Coverage instructions conflict — and one is a live bug
Line 33 says don't run cargo llvm-cov --workspace. But CONTRIBUTING.md:52-60 and docs/LOCAL_DEVELOPMENT.md:98-120 both instruct exactly that, and their regex omits cli/commands/login\.rs, which CI's has. A contributor following either existing doc gets a false coverage failure on login.rs today.
Fix is one entry, in 3 places (CONTRIBUTING.md ×1, docs/LOCAL_DEVELOPMENT.md ×2):
-'(src/main\.rs|tests/|real_terminal\.rs|sync_kb_poller\.rs|tui/renderer\.rs|pty\.rs|session\.rs|test_support\.rs)'
+'(src/main\.rs|tests/|real_terminal\.rs|sync_kb_poller\.rs|tui/renderer\.rs|pty\.rs|session\.rs|test_support\.rs|cli/commands/login\.rs)'One nit on the framing: "skips the file exclusions CI relies on" is a little unfair to those docs — they do pass a regex, it's just one entry out of date. The substance is right.
4. anyhow guidance conflicts with CONTRIBUTING.md
CONTRIBUTING.md:91 says "Prefer anyhow::Result in binary code, typed errors in library code." That's not what the code does — anyhow appears in exactly five files, all under src/analysis/signals/, and in neither main.rs nor lib.rs. §8 of the new doc describes the real convention; CONTRIBUTING.md should be corrected to match.
Recommendation: fold both into this PR. Four one-line doc edits, zero code risk, no CI impact. Merging a doc that contradicts two others — one with a regex that actively misfires — trades a slightly wider PR for a repo that's self-inconsistent the moment it lands.
Discoverability
Nothing in the repo references ARCHITECTURE.md. For an agent-facing reference that's the difference between useful and dead weight.
Note the obvious targets are traps: CLAUDE.md and AGENTS.md are generated from docs/adr/ by this tool (per the doc's own §10), so a link added there gets overwritten on the next adr-bot run. Link from the hand-maintained files instead — README.md (~line 183, beside the existing CONTRIBUTING.md link) and CONTRIBUTING.md §Project Structure.
Nitpicks
- Line 370 frames the
tests/common/mod.rs↔src/testutil.rsduplication as avoidable ("instead of sharing one implementation"). It isn't:src/lib.rs:18declares#[cfg(test)] pub(crate) mod testutil;, so integration-test binaries — a separate crate, compiled withoutcfg(test)— can't reachactual_cli::testutilat all. Calling it forced duplication is the more useful note; as written it invites someone to try to merge them. - Lines 356-357 hardcode volatile numbers. They're exact today (
#[cfg(test)]starts at line 1408 → 5,960 of 7,367 = 80.9%; 16 top-level fns), but they rot on every commit to that file, and there's no CI guard — no markdown lint or link check in any workflow. "Roughly 80% is its own test module" would age better. - §10 omits
docs/LOCAL_DEVELOPMENT.md, the closest sibling doc. Project layout is now documented in three places. - Lines 353-359 tell agents an ADR rule's accept criteria are wrong. True —
benches/doesn't exist and rule29aaf503runstest -d benches— butCLAUDE.mdsays "Compliance is not optional," so a standing exception in a doc nothing links to is the weaker fix. Correcting the rule itself seems better.
Verified correct
Listing what was actually checked, not assumed:
- All three dependency cycles are real and correctly described:
config→cli(src/config/dotpath.rs:3),runner↔tailoring(all 5 backends),generation↔tailoring(both directions confirmed). - Exactly 5 production
TailoringRunnerimpls, as listed. Trait signature exact. - §6: every struct field matches source —
RepoAnalysis,Project,ToolMatch,CanonicalIR,TailoringOutput,FileOutput,AdrSection,Adr,is_v2(). error.rs: exactly 13 variants map to exit code 2; theClaudeNotFoundhint string is verbatim.anyhowconfined to exactly the five namedanalysis/signalsfiles.DEFAULT_API_URLduplicated identically in both named files.SERVICE_KEYhardcoded and documented as a write-only counter token — there's even a test asserting it never reaches log output.- The
integrationfeature gates exactly one test, and it is#[ignore]d. coverage.ymluses--libplus exactly 9--testbinaries with a 100% per-file gate.pipeline.rsis the largest file in the repo; the 81% figure is exact.- All TOC anchors resolve against the doc's own headers.
CI is green across all 10 checks on the head commit — the coverage flake you documented has cleared.
Generated by Claude Code
Source audit of the new doc turned up two wrong claims, plus two existing docs that it contradicts. ARCHITECTURE.md: - TerminalIO::select_one took `default: usize`; the real trait (src/cli/ui/terminal.rs) takes `Option<usize>`, and both impls agree. The snippet exists so an agent can implement a new terminal backend from the doc alone, so the wrong type defeated its purpose. - The login.rs note said it was "excluded from the coverage ignore-filename-regex". coverage.yml puts cli/commands/login.rs *inside* that regex — it is excluded from coverage by it. Reworded. - Reframed the tests/common/mod.rs vs src/testutil.rs duplication as forced rather than an oversight: testutil is #[cfg(test)] + pub(crate), so integration-test binaries cannot reach it. - Explained why ADR 29aaf503's benches/ requirement is a stale artifact (the source ADR cites benches/analysis_baseline.rs; Cargo.toml still has autobenches = false; .gitignore still ignores benches/*_results*.json) and warned against both "fixes" — creating benches/, or hand-editing the generated .claude/rules/ file. - Replaced hardcoded line counts with a rounded figure so they do not rot. - Cross-referenced docs/LOCAL_DEVELOPMENT.md. CONTRIBUTING.md and docs/LOCAL_DEVELOPMENT.md: - Their --ignore-filename-regex omitted cli/commands/login.rs, which CI's has, so anyone following either doc got a false coverage failure on that file. All three occurrences now match coverage.yml byte for byte. - "Prefer anyhow::Result in binary code" did not match the code: anyhow appears only in the five analysis/signals files, and in neither main.rs nor lib.rs. Updated to describe the actual convention. ARCHITECTURE.md was unreferenced by any file; linked it from README.md and CONTRIBUTING.md. The generated CLAUDE.md/AGENTS.md are deliberately not used, since adr-bot overwrites them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BBV61d1yJX4nZhS3UtJJPd
Summary
Adds
ARCHITECTURE.md, a reference doc structured for an agent to consult mid-task rather than a human onboarding narrative: a task index ("I need to... -> touch these files"), a module dependency diagram, the fullactual adr-botdata flow, core data structure shapes, the three extension-seam traits, coding conventions, and known pitfalls/constraints.Scope grew during review: fixing the doc surfaced two existing docs that contradict it, one of them with a live bug. Those are fixed here too, so the repo is self-consistent when this lands. Four files, all markdown — no Rust source is touched.
Pass 1: initial source audit
The doc was cross-checked against the current source after an initial draft turned up real inaccuracies:
configcycles back throughcli(config/dotpath.rscalls intocli::commands::models), and bothrunnerandgenerationcycle back throughtailoring(they consumeTailoringOutput, whichtailoringalso depends on them for). Diagram redrawn to showtailoringas a dependency hub with the real edges, and the false "no cycles" claim removed.TerminalIOextension-seam snippet was missing two methods (select_files,select_one) that have no default implementation, an agent following the doc alone would have hit a compile error implementing a new terminal backend.cargo llvm-cov --workspace) would have pulled incrates/tui-test's own tests andtests/tui_e2e.rs, which CI's own comments say hangs under instrumentation. Replaced with a pointer to the real invocation incoverage.yml.anyhow/Result<_, ActualError>split backwards; corrected and named the real exception (analysis::signals).tailoring/'s file table pointed at the wrong file forfilter_minor_changes; fixed and disambiguated from the similarly-namedpre_filter_rejected.lib.rsbut never documented:branding/andmodel_cache.rs(the latter is a real, sizeable dependency ofconfig/dotpath.rsandcli/commands/models.rs).Pass 2: review fixes
A second audit caught two claims that were still wrong, and traced them outward:
ARCHITECTURE.mdTerminalIO::select_onewas documented as takingdefault: usize; the real trait takesOption<usize>, and both impls agree. Pass 1 added the method precisely so a backend could be implemented from the doc alone — with the wrong type, that compile error was still there.login.rsnote said it was "excluded from the coverage ignore-filename-regex".coverage.ymlputscli/commands/login.rsinside that regex — it is excluded from coverage by it. The sentence asserted the opposite of what CI does.tests/common/mod.rsvssrc/testutil.rsduplication as forced rather than an oversight:testutilis#[cfg(test)]+pub(crate), so integration-test binaries can't reach it. As written it invited someone to "fix" it.29aaf503'sbenches/requirement is a stale artifact (the source ADR citesbenches/analysis_baseline.rs;Cargo.tomlstill carriesautobenches = false;.gitignorestill ignoresbenches/*_results*.json) and warned off both wrong reactions — creatingbenches/, or hand-editing the generated.claude/rules/file.pipeline.rs.docs/LOCAL_DEVELOPMENT.md.CONTRIBUTING.mdanddocs/LOCAL_DEVELOPMENT.md— both contradicted the new doc, and both were the stale side:--ignore-filename-regexomittedcli/commands/login.rs, which CI's has. Anyone following either doc got a false coverage failure on that file. All three occurrences now matchcoverage.ymlbyte for byte.anyhow::Resultin binary code" didn't match the code:anyhowappears only in the fiveanalysis/signalsfiles, and in neithermain.rsnorlib.rs. Updated to describe the actual convention.Discoverability —
ARCHITECTURE.mdwas referenced by no file in the repo. Linked fromREADME.mdandCONTRIBUTING.md. Deliberately not from the generatedCLAUDE.md/AGENTS.md, sinceadr-botoverwrites those.Test plan
--ignore-filename-regexoccurrences byte-compared against.github/workflows/coverage.ymlselect_onesnippet byte-compared againstsrc/cli/ui/terminal.rsgit diff --name-onlyconfirms four markdown files changed and nosrc/pathsGenerated by Claude Code