Add plan.md capturing an eight-phase plan to reorganise the repo so Claude Code (and humans) can find the right rule, write to the right place, and keep documentation in sync with code. The plan introduces a `docs/` parent for requirements and rationale, single-source-of-truth files for configuration and CLI surface, sync checks to catch drift, and a preflight phase plus recovery appendix to make phase-by-phase execution safe. Each subsequent commit on this branch should execute one phase. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
28 KiB
Plan: Align Bear with agent workflows
This plan reorganises the repo so that an LLM agent (or a human) can find the right rule, write to the right place, and not be misled by inconsistencies between sibling documents. It also adds two pieces that are currently missing: a persistent home for design rationale, and a single source of truth for configuration and CLI surface that cannot silently drift from the code.
Execute the tasks in phase order. Tasks within a phase can be done in any order unless a dependency is called out.
Guiding principle: three roles for documentation
The end state separates three roles, each with one home and one drift-control mechanism. No piece of information lives in two places.
| Role | Where | Drift control |
|---|---|---|
| Contract -- what the user can expect | docs/requirements/*.md |
Requirements: tag in tests, check-requirements-coverage.sh |
| Concrete user-facing surface -- config keys, CLI flags | docs/configuration.md, generated man page |
check-config-coverage.sh, check-man-page.sh |
| Rationale -- why we chose this | docs/rationale/NNNN-*.md |
Linked from the requirement(s) it supports |
Requirements stay contract-only. Concrete keys and flags do not appear in requirement bodies; they live in the surface documents and the source code, which the sync checks keep aligned. Rationale documents are the only home for design reasoning that would previously have been crammed into a Notes section or lost in a PR thread.
This principle drives Phases 3, 4, and 5. The earlier inconsistency
where some requirements had an ## Implementation details section
and others did not is resolved by removing that section everywhere,
not by codifying it.
Conventions for the executing agent
- Follow
CLAUDE.mdpre-commit rules:cargo fmt --check,cargo clippy --all-targets -- -D warnings,cargo testmust pass before any commit. Run them after each phase, not just at the end. - ASCII only in every file written or edited (no em dashes, no smart quotes, no unicode bullets).
- Use
git mvfor renames so history follows. Never copy + delete. - Land each phase as its own commit, conventional-commit style:
docs(meta): ...,chore(scripts): .... Branch name suggestion:agent-workflow-restructure. - After each phase, update the routing tables in
CLAUDE.mdfiles so the repo never sits in a half-renamed state. - Run one phase per agent invocation. Each phase is designed to produce a single coherent commit that a human can review before the next phase starts. Do not chain phases inside one run.
- If a phase's instructions and the current repo state disagree (e.g. a file the plan says to move has already been moved), stop and report rather than guessing. Appendix C describes how to recover from common partial states.
Phase 0: Preflight checks
Intent
Catch problems before any change lands: a dirty working tree, a broken baseline, or an out-of-date branch will compound errors through subsequent phases. This phase makes no commits.
Changes
- Run
git status --short. Working tree must be clean. If not, stop and ask the human. - Run the existing pre-commit triple on the starting branch and
record the result:
cargo fmt --checkcargo clippy --all-targets -- -D warningscargo testIf any fails on the baseline, stop and report. The plan assumes a green baseline.
- Confirm the branch is
agent-workflow-restructure(or another branch dedicated to this work). Never execute onmasteror on a release branch like4.1.4-rc. - Read
CLAUDE.md(root),requirements/CLAUDE.md, and Appendix A and Appendix B at the bottom of this plan. These are the inputs the later phases assume you have in mind.
Acceptance
git status --shortproduces no output.- The pre-commit triple exits 0.
- The current branch is the dedicated restructure branch.
Phase 1: Restructure the documentation tree
Intent
Today, requirements/ sits at the repo root and is the only place
for project-level documents. The plan introduces docs/ as the
parent for all human- and agent-readable documentation, with sibling
folders for the two kinds of long-form content the repo needs:
contracts (requirements) and rationale (decisions and research).
Repo-hygiene scripts move into a sibling scripts/ folder so they
are easy to discover and not confused with the documents they check.
Changes
- Create directory
docs/. - Rename
requirements/todocs/requirements/usinggit mv. All existing.mdfiles keep their names. - Create directory
docs/rationale/. - Create directory
scripts/. - Move
requirements/check-coverage.shtoscripts/check-requirements-coverage.sh. Update every literalrequirementspath inside the script:- Header comment "Run from the repo root:
./requirements/..." ->./scripts/check-requirements-coverage.sh. - Existence guard
${repo_root}/requirements->${repo_root}/docs/requirements. - Error message that names the same path.
- Glob
${repo_root}/requirements/*.md->${repo_root}/docs/requirements/*.md. Thescript_dir/..computation still gives the repo root after the move, so no logic change is needed there.
- Header comment "Run from the repo root:
- Add
docs/README.mdthat lists what each subfolder contains, in two or three lines, and links to the man page underman/for the user-facing CLI reference.
Acceptance
ls docs/showsrequirements/,rationale/,README.md(plus any files added in later phases).ls scripts/showscheck-requirements-coverage.sh.scripts/check-requirements-coverage.shruns from the repo root with exit code 0 (or the same exit code as before the move).requirements/no longer exists at the repo root.
Phase 2: Define the rationale (ADR) system
Intent
Today there is no home for design rationale or vendor research that informed a decision but is not part of the user-visible contract. Notes sections of requirements become cramped when the rationale is substantial, and issue threads rot. Architecture Decision Records solve this with a small, stable format.
Changes
- Create
docs/rationale/CLAUDE.mddescribing:- When to write a rationale document: when a design decision is non-obvious, when vendor research informed it, or when a future reader will ask "why didn't we do X instead?"
- File naming:
NNNN-short-kebab-case-title.mdwith a monotonically increasing four-digit number. - Required sections: Context, Decision, Consequences, References.
- Linking: a rationale document links to the requirement(s) it supports. The requirement's Notes section links back.
- Create
docs/rationale/_template.mdwith the four sections above and one-line guidance under each. - Create
docs/rationale/0001-document-restructure.md. This is the first rationale entry and records why this plan exists: the inconsistencies it resolves and the trade-offs chosen (e.g. ADRs over an in-requirement rationale dump). Link to the resulting structure.
Acceptance
docs/rationale/CLAUDE.mdexists and answers the "when to write one" question in plain language.docs/rationale/_template.mdexists with the four sections.docs/rationale/0001-document-restructure.mdexists and follows the template.
Phase 3: Tighten the requirement template to contract-only
Intent
requirements/CLAUDE.md defines a requirement template, but the
repo's actual practice has drifted: some requirements (e.g.
output-path-format.md) carry an ## Implementation details
section with literal YAML config keys, while others
(e.g. output-compilation-entries.md) do not. With docs/configuration.md
(Phase 4) and the generated man page (Phase 5) becoming the single
sources of truth for concrete user-facing surface, the in-requirement
Implementation details section becomes a duplicate place that can
drift from those sources. The right resolution is to remove it
everywhere, codify requirements as contract-only, and add an
explicit ## Rationale section whose only job is to link to ADRs.
Changes
- Edit
docs/requirements/CLAUDE.md(formerlyrequirements/CLAUDE.md):- State that requirements are contract-only: they describe what the user can expect, not where the bits live.
- Forbid embedding literal config keys, CLI flag names, or
schema fragments in a requirement body. Reference behaviour
instead (e.g. "a configuration option toggles inlining"), and
let
docs/configuration.mdname the key. - Remove any guidance that implies an
## Implementation detailssection should exist. - Add
## Rationaleas an optional section, placed after## Notes. Its body is a list of links todocs/rationale/NNNN-*.mddocuments that motivated the requirement. No prose beyond a one-line label per link.
- Audit the requirements listed in Appendix B (the exact set of
files that currently contain
## Implementation details). Migrate the content of each section as follows. Becausedocs/configuration.mddoes not exist until Phase 4, the audit writes its findings to a staging filedocs/configuration.draft.mdthat Phase 4 consumes and deletes:- Literal config keys, default values, YAML examples -> append
to
docs/configuration.draft.mdwith a header naming the source requirement, then remove from the requirement body. - Algorithm walkthroughs, error-handling tables, and other "how" content -> code comments next to the implementation, or delete if already obvious from the code.
- Design choices, trade-offs, vendor research -> a new
rationale document under
docs/rationale/, linked from the requirement's## Rationalesection. - When the section is empty after migration, remove the heading.
- Do not change acceptance criteria during this audit; the audit is structural only.
- Literal config keys, default values, YAML examples -> append
to
- Always create
docs/configuration.draft.mdeven if every audited section ends up routed to rationale or code. An empty staging file with a single placeholder line is acceptable; Phase 4 needs a known file to consume. - Worked example --
output-path-format.md(one of the entries in Appendix B). Its## Implementation detailsopens with a YAML block describingformat.paths.directoryandformat.paths.file. That YAML and the prose around it go todocs/configuration.draft.md. The subsequent "Strategy details" table describing how each strategy resolves paths is algorithm description; it goes to a code comment in the relevant module underbear/src/output/(or is deleted if the code is already self-evident). The platform-constraint paragraph about Windows\\?\prefix stripping is rationale tied to GitHub issue #683 and belongs in a new rationale document. After migration, the## Implementation detailsheading is removed; the requirement body keeps only Intent, Acceptance criteria, Non-functional constraints, Testing, Notes, and the new## Rationalelink.
Acceptance
docs/requirements/CLAUDE.mdstates the contract-only rule and describes the## Rationalesection.- No file under
docs/requirements/contains an## Implementation detailssection. - For each existing requirement touched, the commit message lists the destinations of migrated content (configuration entry, rationale document, or code comment).
cargo testandscripts/check-requirements-coverage.shboth pass.
Phase 4: Configuration reference and sync check
Intent
Bear's configuration schema lives in bear/src/config/types.rs. The
public surface is currently documented (when it is documented at all)
inside scattered Implementation details sections and the man page.
There is no single document a user or agent can read to find every
option, its type, its default, and the requirement that governs it.
And nothing in the build catches a new option that is added without
matching documentation. This phase establishes docs/configuration.md
as the sole home for that surface, and migrates the YAML examples
that Phase 3 strips out of requirement files into it.
Changes
- Create
docs/configuration.mdwith one entry per public field of the configuration schema. Each entry contains:- Field path in dotted notation (e.g.
format.paths.directory). - Type and accepted values.
- Default value.
- One-line description from the user's perspective.
- Link to the governing requirement under
docs/requirements/.
- Field path in dotted notation (e.g.
- Populate the document from two inputs:
- The current state of
bear/src/config/types.rs(the schema). docs/configuration.draft.md, the staging file produced by the Phase 3 audit. Fold each entry into the appropriate field indocs/configuration.md, editing for consistency, then delete the staging file in the same commit. This is a one-time snapshot; the sync check below keeps it honest.
- The current state of
- Add
scripts/check-config-coverage.sh. Scope it narrowly to keep the implementation tractable:- Walks
bear/src/config/types.rsand collects only struct fields with a#[serde(rename = "...")]or implicit serde name, reachable from the top-level config struct. - For each collected field, looks for its dotted path in
docs/configuration.md. Reports missing entries. - Out of scope for v1: enum variants (tagged enums like
Intercept), elements ofVec<T>collections beyond their parent field name, and#[serde(flatten)]fields. Document these limitations at the top of the script and indocs/configuration.md. A future improvement can replace the shell script with asyn-based Rust binary that handles them fully; the script is sufficient to catch the common case of a newly added struct field that nobody documented.
- Walks
- Wire the check into the pre-commit triple. Update
CLAUDE.md(root) to list it alongsidecargo fmt --check,cargo clippy --all-targets -- -D warnings,cargo test. - Document the convention in
docs/configuration.mditself: "Adding or removing a public config field requires updating this file in the same commit. The sync check enforces this." - Smoke-test the script before committing: temporarily add a dummy
struct field
__plan_smoke: Option<()>tobear/src/config/types.rs, runscripts/check-config-coverage.sh, confirm it exits non-zero and names the dummy field. Revert the field withgit checkout -- bear/src/config/types.rs. Do not commit the dummy.
Acceptance
docs/configuration.mdexists and lists every option present inbear/src/config/types.rsat the time of writing.docs/configuration.draft.mdhas been deleted; its contents are folded intodocs/configuration.md.scripts/check-config-coverage.shexits 0 on the current tree.CLAUDE.mdlists the check as part of the mandatory pre-commit set.
(Drift detection can be verified manually by temporarily adding a
dummy field to types.rs and re-running the check; do not commit
the dummy field.)
Phase 5: CLI reference and sync with clap
Intent
bear/src/args.rs defines the CLI via clap derive macros. man/bear.1.md
is the user-facing reference and man/bear.1 is its generated form.
Today the man page is hand-written, and bear/CLAUDE.md:48 notes
that clap_mangen generation is "not yet implemented". This is the
same drift risk as the config file, with the same answer: generate
from the source of truth and check that the generated artefact is
up to date.
Changes
- Add
clap_mangenas a dev-dependency to thebearcrate. - Add a small binary
bear/src/bin/generate-man.rsthat takes the clapCommandfromargs.rsand writes its troff output to a temporary path (e.g.target/man/bear.1). - Leave
man/bear.1.mdandman/bear.1as the human-authored sources of truth. No literal inclusion of generated content into either file; this keeps splicing rules and pandoc behaviour untouched. The generator's role is to produce a reference artefact that the check script compares against. - Add
scripts/check-man-page.sh:- Runs the generator to produce the reference artefact.
- Extracts the option list from both the reference artefact and
man/bear.1.md(e.g. by collecting every long flag of the form--<name>). - Compares the two sets. Exits non-zero if either side has flags the other lacks.
- Does not enforce ordering, surrounding prose, or formatting -- only flag-set parity.
- Document the workflow in
man/CLAUDE.md: "After editingargs.rs, updateman/bear.1.mdso its option list still matches whatcargo run --bin generate-manproduces. The sync check enforces parity." - Add the check to the pre-commit set in root
CLAUDE.md. - Smoke-test the script before committing: temporarily add a dummy
clap argument such as
#[arg(long = "plan-smoke")] _smoke: boolto the top-levelArgsstruct inbear/src/args.rs, runscripts/check-man-page.sh, confirm it exits non-zero and names--plan-smoke. Revert withgit checkout -- bear/src/args.rs. Do not commit the dummy.
Acceptance
cargo run --bin generate-manproduces output whose synopsis and option list matchman/bear.1.md.scripts/check-man-page.shexits 0 on the current tree.man/CLAUDE.mddocuments the regeneration step.
(Drift detection can be verified manually by temporarily adding a
clap argument to args.rs and re-running the check; do not commit
the change.)
Phase 6: Routing and scope updates
Intent
After Phases 1 through 5, several paths have changed and several new
rules exist. The routing tables in every CLAUDE.md file must point
at the new locations, and the always-loaded CLAUDE.md at the root
must mention the new pre-commit checks. Subdirectory CLAUDE.md files
should also surface the "write a requirement first for new features"
rule, which currently lives only at the root.
Changes
-
Update the routing table in root
CLAUDE.md:- Add rows for
docs/configuration.md,docs/rationale/,docs/requirements/,scripts/. - Update existing rows that pointed at
requirements/. - Add a row for "Add or change a CLI flag" pointing at both
bear/src/args.rsandman/CLAUDE.md.
- Add rows for
-
Update every
CLAUDE.mdlisted in Appendix A so its references torequirements/now readdocs/requirements/. Verify by runninggrep -rn "requirements/" --include='CLAUDE.md' .from the repo root after the edits; the only hits should be the newdocs/requirements/path. -
Add a one-line reminder near the top of each subdirectory
CLAUDE.mdthat touches user-visible behaviour: "New features require a requirement underdocs/requirements/before implementation. See rootCLAUDE.mdDecision protocol." Apply this at minimum tobear/CLAUDE.md,intercept-preload/CLAUDE.md, andbear/interpreters/CLAUDE.md. -
Update the pre-commit section of root
CLAUDE.mdto list:cargo fmt --checkcargo clippy --all-targets -- -D warningscargo testscripts/check-requirements-coverage.shscripts/check-config-coverage.shscripts/check-man-page.sh
These checks are advisory: they are run by humans or agents before committing, not enforced by a
.git/hooks/pre-commitgate. The CLAUDE.md listing is the contract; a future phase can add a composite script or CI gate if drift becomes a problem in practice.
Acceptance
- No
CLAUDE.mdfile references the oldrequirements/path. - The root
CLAUDE.mdrouting table includes entries for every document and script added in Phases 1 through 5. - Every subdirectory
CLAUDE.mdwhose code can introduce a new feature points back at the Decision protocol. - Running all six pre-commit checks from the root succeeds.
Phase 7: Back-fill the response-file inlining rationale
Intent
The 4.1.4-rc branch added output-response-file-inlining.md with a
brief Notes section that gestures at vendor differences. The full
research (GCC keeps @file literal on missing files, Clang errors,
MSVC has no nesting, tokenization differs per family, nvcc uses
--options-file instead) was gathered during the design discussion
and currently lives only in this conversation. Capturing it as a
rationale document validates the new docs/rationale/ folder with a
realistic example and preserves the receipts for any future revisit.
This phase runs only after 4.1.4-rc has been merged or rebased onto
the restructure branch, so the requirement file is in its final
location.
Changes
-
Create
docs/rationale/0002-response-file-inlining-design.md, following the Phase 2 template. -
Context section -- include the following per-compiler matrix verbatim. The executing agent should not have to re-derive it.
Compiler @filesyntaxMissing file Nested @fileGCC @file, GNU quotingkept literal, no error recursive Clang / Apple Clang @file, GNU quotingerror recursive clang-cl @file, Windows quotingerror recursive MSVC (cl.exe) @file, Windows quotingerror not supported flang / icx / armclang @file, LLVM-basederror recursive nvcc uses --options-file/-optfinsteadn/a n/a IBM XL uses -qoptfile=fileinsteadn/a n/a Tokenization differences: GCC/Clang-family use whitespace separators, single or double quotes, backslash-escape any character. MSVC family uses Windows command-line rules: only double quotes group, backslash escaping is positional.
Cite sources: GCC manual ("Overall Options"), LLVM CommandLine docs, Microsoft "@ (Specify a Compiler Response File)", NVIDIA CUDA Compiler Driver NVCC.
-
Decision section -- record the four chosen behaviours and the one-sentence reason each diverges from at least one compiler's own behaviour:
- Opt-in by default: preserves the current contract documented
in
output-compilation-entries. - Warn-and-keep on missing file: matches GCC, deliberately diverges from Clang/MSVC so a stale build artefact does not fail the whole database.
- Always recurse: matches GCC/Clang, deliberately exceeds MSVC's non-nesting limit because a successful build had already resolved any nesting.
- Per-family tokenization: honours the only genuinely non-normalisable difference between compilers.
- Opt-in by default: preserves the current contract documented
in
-
Consequences section -- nvcc
--options-fileand IBM XL-qoptfileare out of scope for v1; if a user requests them later, add new acceptance criteria to the requirement rather than re-opening this rationale. -
References section -- link issue #701, the requirement file
docs/requirements/output-response-file-inlining.md, and the four compiler documentation pages cited in step 2. Do not name any specific source file or test in the Bear codebase -- those paths may change before this rationale is read. -
Edit the Notes section of
docs/requirements/output-response-file-inlining.mdto link to the rationale document and remove text duplicated by it.
Acceptance
docs/rationale/0002-response-file-inlining-design.mdexists and follows the template from Phase 2.docs/requirements/output-response-file-inlining.mdlinks to it from its Notes section.scripts/check-requirements-coverage.shstill passes.
Phase 8: Decommission this plan
Intent
plan.md is a one-time execution document. Once the phases are
complete, leaving it at the root invites confusion about whether it
is still authoritative. Either preserve it as a rationale entry or
remove it.
Changes
- If the plan's narrative still has historical value, move it to
docs/rationale/0003-documentation-restructure-plan.mdand edit the heading to past tense ("How Bear's documentation was restructured"). Otherwise, deleteplan.md. - Update any branch description or PR body that referenced
plan.mdat the root.
Acceptance
plan.mdno longer exists at the repo root.- Either it has been archived under
docs/rationale/with a meaningful number, or it has been deleted and the commit message records the deletion.
Dependency summary
- Phase 0 runs first, before anything else. No commit; aborts if the baseline is dirty or red.
- Phases 1 and 2 are independent and can run in either order, but Phase 1 first is cleaner because Phase 2 writes into directories Phase 1 creates.
- Phase 3 depends on Phase 1 (paths) and Phase 2 (rationale folder
exists, so migrated content has a destination and the new
## Rationalesection in the template can link somewhere). - Phase 4 depends on Phase 3 (the audit's migrated config content
feeds into
docs/configuration.md). - Phase 5 is independent of Phase 4 and depends only on Phases 1 and 3.
- Phase 6 depends on Phases 1 through 5 being landed.
- Phase 7 depends on Phase 2 and on
4.1.4-rcbeing merged. - Phase 8 is last.
Out of scope for this plan
- Changing the build pipeline beyond adding
clap_mangenand the three check scripts. - Splitting the
bearcrate, renaming any source modules, or changing the workspace layout. - Introducing a documentation site generator (mdBook, Antora,
etc.). Plain Markdown under
docs/is sufficient for now; a site generator can be a future rationale entry of its own.
Appendix A: CLAUDE.md inventory
These are the CLAUDE.md files that exist in the repo at the time
this plan was written. Phase 6 step 2 must update every one whose
content references requirements/. Phase 6 step 3 surfaces the
Decision protocol in the subset marked with a *.
CLAUDE.md(root)bear/CLAUDE.md*bear/interpreters/CLAUDE.md*bear-codegen/CLAUDE.mdbear-completions/CLAUDE.mdintercept-preload/CLAUDE.md*integration-tests/CLAUDE.mdman/CLAUDE.mdplatform-checks/CLAUDE.mdrequirements/CLAUDE.md(becomesdocs/requirements/CLAUDE.mdin Phase 1)
If find . -name CLAUDE.md -not -path './target/*' -not -path './.git/*'
returns a file not in this list, the plan is out of date with the
repo: stop and report rather than guessing what the new file
should say.
Appendix B: Requirements with Implementation details sections
These are the exact files Phase 3 step 2 must audit. They were
identified by grep -l '^## Implementation details' requirements/*.md at the time this plan was written. Any other
requirement file is out of scope for migration.
requirements/output-json-compilation-database.mdrequirements/output-path-format.mdrequirements/output-source-directory-filter.mdrequirements/interception-compiler-env-with-flags.mdrequirements/interception-preload-mechanism.mdrequirements/interception-wrapper-mechanism.mdrequirements/interception-wrapper-recursion.mdrequirements/output-append.mdrequirements/output-atomic-write.mdrequirements/output-duplicate-detection.md
(After Phase 1, these paths begin with docs/requirements/.)
If the audit finds a ## Implementation details heading in a
requirement file not on this list, the plan is out of date: stop
and report. If a file on this list no longer has the heading by
the time Phase 3 runs, skip it silently and note this in the
commit message.
Appendix C: Recovery from partial states
If a phase aborts partway, the safe action is to revert the working tree and start the phase over rather than try to patch forward. Specific recoveries:
- Phase 1 partly applied (some files moved, some not): run
git status --short; if both old and new paths exist for the same file,git restorethe affected files and re-run Phase 1 from scratch. - Phase 3 produced
docs/configuration.draft.mdbut Phase 4 did not consume it: the file is harmless to keep across invocations. Phase 4 step 2 will pick it up. Do not delete it manually. - A check script (Phase 4 or 5) returns non-zero on the smoke test but no dummy field is present: the script itself has a bug. Fix the script in the same commit; do not commit a passing smoke-test if the underlying detection is wrong.
- Routing edits in Phase 6 left a stale
requirements/path in someCLAUDE.md: re-run the grep verifier from Phase 6 step 2 and patch the remaining file in a follow-up commit. - Pre-commit triple fails after a phase commit: revert the
commit (
git reset --hard HEAD~1is acceptable on the dedicated restructure branch), fix the failure, re-commit. Do not chain phases on top of a red baseline.