Skip to content

fix(repo): budget turns off an AGENTS.md beside its own CLAUDE* file - #366

Merged
antonwolfy merged 7 commits into
intel:mainfrom
antonwolfy:fix/budget-nested-claude
Nov 3, 2026
Merged

antonwolfy merged 7 commits into
intel:mainfrom
antonwolfy:fix/budget-nested-claude

Conversation

@antonwolfy

@antonwolfy antonwolfy commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Follow-up to the dokimi review of IntelPython/dpnp#3099, item 5 of its follow-ups: repo budget
models only a root CLAUDE.md, .claude/CLAUDE.md or CLAUDE.local.md turning AGENTS.md
off, while Claude Code also skips a subdirectory's AGENTS.md when that same directory holds its
own CLAUDE* file.

Measured with repo reach on Claude Code 2.1.316 (Haiku 4.5), on synthetic trees with one heading
and one line per file, with and without a root CLAUDE*. sub/AGENTS.md is not loaded beside
sub/CLAUDE.md, sub/CLAUDE.local.md or sub/.claude/CLAUDE.md, whether the session starts in
sub/ or reaches it from the root (read=sub). A CLAUDE.md in a parent below the root does not
turn a deeper AGENTS.md off on lazy loading (read=sub/deep loads sub/deep/AGENTS.md below
sub/CLAUDE.md). A root CLAUDE* (CLAUDE.local.md included) still turns every AGENTS.md
off. A lazily loaded nested CLAUDE.md does not turn off the root AGENTS.md loaded at start.

budget() on main (dokimi repo budget <tree>), against what the harness does:

Tree sub/AGENTS.md on main Claude Code 2.1.316 This PR
AGENTS.md, sub/AGENTS.md, sub/CLAUDE.md lazy, off total 0, no warning not loaded off
AGENTS.md, sub/AGENTS.md, sub/CLAUDE.local.md lazy, off total 0, no warning not loaded off
AGENTS.md, sub/AGENTS.md, sub/.claude/CLAUDE.md lazy, off total 0, no warning not loaded off
AGENTS.md, sub/AGENTS.md, sub/deep/CLAUDE.md lazy (deeper CLAUDE.md) lazy
AGENTS.md, sub/CLAUDE.md, sub/deep/AGENTS.md sub/deep/AGENTS.md lazy loaded on read=sub/deep lazy

On main, a displaced sub/AGENTS.md's own @ imports were counted as lazy too.

The rule is Claude Code's. It applies only with the claude-code profile, which sets the new
guidance.claude_displaces_agents flag. The Copilot profiles also list CLAUDE.md in their
guidance names, and their budget is unchanged (review of this PR: 4445841674,
4446226790).

Changes

  • repos/budget.py: a new helper, _displaced, decides which AGENTS.md files a CLAUDE* file
    turns off: every one while a root CLAUDE* resolves (as before). Otherwise, with
    claude_displaces_agents, it is a nested one whose own directory holds a CLAUDE.md,
    CLAUDE.local.md or .claude/CLAUDE.md that resolves (a dangling link does not count, as at
    the root). One in a parent below the root, a sibling or a deeper directory does not count, and
    neither do the root files AGENTS.md and .claude/AGENTS.md: only a root CLAUDE* turns those
    off. budget() uses it both where it filters the AGENTS.md files whose imports count and
    where it classes each entry, so the root rule's exceptions carry over: the file stays lazy
    when one of those CLAUDE* files is a link to it, or a loaded file @-imports it. Since the
    off decision is taken from every path that reaches a file, a file that is also reached as an
    AGENTS.md in a directory with no CLAUDE* (zzz/AGENTS.md -> ../sub/AGENTS.md) is not off.
    A displaced nested AGENTS.md adds no imports of its own.
  • The per-directory rule is gated on a profile flag, not on the guidance names:
    guidance.claude_displaces_agents, an optional boolean added in place to harness_profile.v1
    (default false), read by HarnessProfile.claude_displaces_agents and set only in
    claude-code.yaml. budget() takes it as the keyword claude_displaces_agents (default
    false). repo budget and the reach receipt (_print_receipt) pass the profile's value.
  • CLAUDE_ROOT (CLAUDE.md, CLAUDE.local.md, .claude/CLAUDE.md) moves from repos/receipt.py
    to repos/budget.py, and _displaced builds the names beside an AGENTS.md from it.
    receipt.py imports it from budget, which it already imported, so the two cannot drift.
  • The off warning, now built by _off_warnings, names the CLAUDE* file for each nested case:
    sub/CLAUDE.md turns AGENTS.md loading off in sub/ and no CLAUDE.md imports sub/AGENTS.md, so Claude Code does not read it unless the agent opens it (import it with @AGENTS.mdfrom a CLAUDE.md beside it;repo reach measures it). The root-case warning is unchanged.
  • budget() loses one branch to _off_warnings: its entry in scripts/growth_baseline.json
    goes from 21 to 20.
  • repos/profiles/claude-code.yaml guidance.notes: the rule measured on 2.1.316. Started in a
    chain with a CLAUDE* file, no AGENTS.md loads. On lazy loading, a directory's own CLAUDE*
    file turns that directory's AGENTS.md off, a CLAUDE* in a parent below the root does not,
    and a root CLAUDE* file (CLAUDE.local.md included) turns every AGENTS.md off for the whole
    session. The 2.1.286, 2.1.241 and 2.1.250 notes stay.
  • Docs: the budget.py module docstring (a new line after the off bullet, and the checks
    paragraph), repo budget --help, docs/repo-evaluation-contract.md (the repo budget
    paragraph, the profile example with the new key, the receipt's off row, and the Precedence
    paragraph: a nested-only displacement has no root winner, so precedence_observed lists none)
    and docs/architecture.md.
  • New tests in tests/test_repo_budget_nested.py (tests/test_repo_budget.py is at 996 of its
    1,000 lines).
  • Changelog fragment (Fixed).

Risk

With --harness claude-code (the default), repo budget output changes for trees with no root
CLAUDE* file and a nested AGENTS.md beside its own CLAUDE.md, CLAUDE.local.md or
.claude/CLAUDE.md. That AGENTS.md moves from lazy to off, its imports drop out of
lazy, and a new warning appears, so repo budget --fail-on-warning now exits 1 on such a tree
where it exited 0. The effective-instructions receipt reads budget's when, so such a file's
expected becomes false in claude-code runs, which is what the harness does. The same budget
as before for a tree with a root CLAUDE* file, for .claude/AGENTS.md, and for the Codex and
Copilot profiles. A direct caller of budget() that does not pass claude_displaces_agents
also gets the old budget. harness_profile.v1 gains one optional key, in place: every existing
profile still validates.

Testing

On a Linux review host, at 50d006c (on main 93ff3a4):

  • make ci-local: 30 passed, 0 failed, 7 skipped (external-catalog-smoke, which needs Docker and
    the network; test-latest, docs.yml:deploy and pip-audit.yml:report, whose job conditions
    are false on a PR; cli-surface / Package the result, an expression the local runner does not
    evaluate; conda-forge-deps, no micromamba; semgrep secrets, which runs on the PR).
    12561 tests passed, 5 skipped, 6 xfailed; changed-line coverage 100% of 31 measured lines.
    The matrix legs on Python 3.11, 3.12 and 3.14 were not run.
  • The repro above: on main, the three own-directory trees give sub/AGENTS.md lazy with
    off 0. Here they give off, with the warning naming the CLAUDE* file. The deeper and parent
    trees stay lazy. With --harness copilot and copilot-review, AGENTS.md, sub/AGENTS.md,
    sub/CLAUDE.md gives sub/AGENTS.md lazy 7 on main, off 7 at 279275a, and lazy 7
    here. .claude/AGENTS.md beside .claude/CLAUDE.local.md (or .claude/.claude/CLAUDE.md) is
    start 5 on main, off at 279275a, and start 5 here.
  • tests/test_repo_budget_nested.py has 19 tests. Against main's budget.py, the three
    own-directory cases and the displaced-import test fail. The 8 that pin what main already gets
    right (deeper, parent and sibling CLAUDE*, a non-Claude profile, a dangling link, the import
    and alias exceptions, a second AGENTS.md path) pass. Against 279275a, the 6 tests this round
    adds fail: Copilot (2), no flag, .claude/ (2), and one shared CLAUDE* set. The other 13
    pass.
  • Mutants, each run on a copy of the committed tree; every one fails at least one new test:
    • a deeper CLAUDE* counts; a parent's CLAUDE* counts;
    • d/.claude/CLAUDE.md not counted; CLAUDE.local.md not counted; a dangling CLAUDE* link
      counts;
    • the import exception dropped; the alias exception dropped in the off decision; the alias
      exception dropped in the import filter;
    • a displaced AGENTS.md keeps its imports; the off decision taken from the shown path only;
      the warning names the AGENTS.md instead of the CLAUDE* file.
    • This round, 8 more: the profile flag ignored in _displaced; repo budget does not pass the
      flag; claude-code.yaml does not set it; the profile property always false; always true;
      _displaced does not skip root files; _displaced spells the CLAUDE* names out again;
      receipt.py defines its own CLAUDE_ROOT.
  • Merged together with feat(repo): reach --scenarios shadow adds a CLAUDE.local.md #367 (repo reach --scenarios shadow) on main 93ff3a4, at 279275a:
    pytest -n auto 12575 passed, 5 skipped, 6 xfailed; mypy and ruff clean. Not yet run merged
    together with the other follow-up branches at this round's head.

Revert

A plain revert. Nothing is stored in a new format. The one schema key is optional.

Checklist

  • Tests added/updated and pytest passes
  • ruff, mypy, and schema checks pass
  • Docs updated (or N/A) — see the docs-drift checklist in CONTRIBUTING.md
  • Self-reviewed per "Before opening a PR" in AGENTS.md
  • No ad-hoc run artifacts committed
  • New files carry the SPDX license header (or are exempt in .github/.licenserc.yaml)

Settles item 5 of the follow-ups from the dokimi review of
IntelPython/dpnp#3099: `repo budget` modelled only a root CLAUDE.md,
.claude/CLAUDE.md or CLAUDE.local.md turning AGENTS.md off. Claude Code
2.1.316 also skips a subdirectory's AGENTS.md when that same directory
holds its own CLAUDE.md, CLAUDE.local.md or .claude/CLAUDE.md, also when
the agent reaches the directory lazily from the root; budget counted
that AGENTS.md, and its imports, as lazy.

A new helper, _displaced, decides which AGENTS.md files a CLAUDE* file
turns off: every one while a root CLAUDE* resolves (unchanged), else a
nested one whose own directory holds a resolvable CLAUDE* file. One in a
parent or deeper directory does not count. The existing exceptions
apply: a CLAUDE* alias of the file, or an import from a loaded file,
keeps it lazy, and so does the same file reached as an AGENTS.md in a
directory with no CLAUDE*. A displaced nested AGENTS.md adds no imports
of its own. The off warning names the CLAUDE* file for each nested one.

The claude-code profile notes, the module docstring, `repo budget
--help`, the repo evaluation contract and architecture.md describe the
nested rule. budget() drops one branch to _off_warnings, so its growth
baseline shrinks from 21 to 20.

Signed-off-by: Anton Volkov <antonwolfy@gmail.com>
@antonwolfy
antonwolfy requested a review from a team as a code owner November 2, 2026 13:40
Copilot AI balanced review requested due to automatic review settings November 2, 2026 13:40
@exai-bot

exai-bot commented Nov 2, 2026 •

Copy link
Copy Markdown

✅ OpenCodeReview: Review complete: 0 finding(s) across 11 selected item(s).

Review details
  • Commit reviewed: 3875150 against merge base 3258d6c
  • Coverage: 11 files in 3 groups
  • Engine: OpenCodeReview v1.13.3, us.openai.gpt-6.1-sol
  • Usage: 154k tokens (73k from cache), 20s
  • Tool calls: 8 (6 file_read, 1 code_search, 1 file_read_diff)
  • Nested repository budget handling, receipts, and documentation: 7 files
  • Claude Code harness profile and schema: 3 files
  • Growth baseline update: 1 file

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

No active review issues require changes to the nested AGENTS.md displacement update.

No open issues 🎉

What changed in this PR

0 new findings; nothing to fix.

This PR aligns repo budget with measured Claude Code behavior: a nested AGENTS.md is reported as off when its own directory contains a resolving CLAUDE* file, while preserving import and alias exceptions.

Changes:

  • Applies nested displacement to file classification and import counting, with warnings naming the responsible file.
  • Adds regression tests for directory boundaries, dangling links, imports, aliases, and non-Claude profiles.
  • Updates CLI help, profile notes, documentation, and the complexity baseline.
File Description
dokimi/​tests/​test_repo_budget_nested.py Tests nested displacement and its exceptions.
dokimi/​scripts/​growth_baseline.json Lowers the recorded complexity of budget().
dokimi/​dokimi/​repos/​profiles/​claude-code.yaml Documents measured startup and lazy-loading rules.
dokimi/​dokimi/​repos/​budget.py Implements nested displacement and targeted warnings.
dokimi/​dokimi/​commands/​repo.py Updates budget help to describe nested displacement.
dokimi/​docs/​repo-evaluation-contract.md Clarifies budget classification and receipt behavior.
dokimi/​docs/​architecture.md Updates the budget model description.
dokimi/​changelog.d/​20261102_120000_antonwolfy_budget-nested-claude.md Records the fix and preserved exceptions.

Review effort: Balanced

@antonwolfy antonwolfy self-assigned this Nov 2, 2026
@codecov-commenter

codecov-commenter commented Nov 2, 2026 •

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.39%. Comparing base (3258d6c) to head (3875150).
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #366      +/-   ##
==========================================
- Coverage   94.40%   94.39%   -0.01%     
==========================================
  Files         313      313              
  Lines       40216    40240      +24     
  Branches     6822     6828       +6     
==========================================
+ Hits        37966    37986      +20     
- Misses       1435     1438       +3     
- Partials      815      816       +1     
Files with missing lines Coverage Δ
dokimi/dokimi/commands/repo.py 96.22% <100.00%> (ø)
dokimi/dokimi/repos/budget.py 100.00% <100.00%> (ø)
dokimi/dokimi/repos/harness_profile.py 96.29% <100.00%> (+0.06%) ⬆️
dokimi/dokimi/repos/receipt.py 98.78% <100.00%> (-0.01%) ⬇️

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@vlad-perevezentsev vlad-perevezentsev left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review at 279275a. The per-directory rule is right for Claude Code, and the alias and import exceptions hold. It also reaches the Copilot profiles, which list CLAUDE.md, while the changelog says Copilot is unchanged; that and two smaller gaps are in the line comments.

What I ran, at 279275a:

  • make ci-local --offline: 0 failed, 19 passed, 13 skipped (network-only jobs).
  • A budget repro on this head and on the base: with Copilot's names, sub/AGENTS.md beside sub/CLAUDE.md goes from lazy to off; .claude/AGENTS.md beside .claude/CLAUDE.local.md goes from start to off.

Checked, no issues

  • Root CLAUDE trees:* _displaced returns every AGENTS.md for the root CLAUDE*, as before, and the single warning text is the same.
  • Exceptions: a CLAUDE* link to the AGENTS.md, an import from a loaded file, and an alias reached as an AGENTS.md in another directory keep it lazy; imports of an off file are no longer counted.
  • Docs and changelog: CLI help, architecture.md and the contract describe the rule; the fragment is under Fixed.

Comment thread dokimi/dokimi/repos/budget.py Resolved
Comment thread dokimi/dokimi/repos/budget.py Resolved

@napetrov napetrov left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One blocking: the new same-directory rule also fires on the Copilot profiles (inline). Unverified: the new import-cut warning on an off file (sub/d.md: import of sub/AGENTS.md is past the 4-hop limit) may just be noise.

Coverage: plain + edge passes, budget probes on base and head; no live harness, so actual Claude Code loading was not measured.

Comment thread dokimi/dokimi/repos/budget.py Resolved
Comment thread dokimi/dokimi/repos/budget.py Resolved
…AUDE*

Review of intel#366 (comments 4445841674, 4446226790): the same-directory rule
keyed on any CLAUDE* name in the profile's guidance names, and copilot.yaml
and copilot-review.yaml list CLAUDE.md. A Copilot budget of a tree with
AGENTS.md, sub/AGENTS.md and sub/CLAUDE.md reported sub/AGENTS.md off 7,
with a Claude Code warning, where main reported it lazy, while the
changelog says Copilot gets the same budget as before.

The rule is now gated on a profile flag, guidance.claude_displaces_agents,
an optional boolean added in place to harness_profile.v1 and set only in
claude-code.yaml. budget() takes it as the keyword claude_displaces_agents
(default false) and both repo budget and the reach receipt pass the
profile's value. The root rule is unchanged. Tests run repo budget
--harness copilot and copilot-review on the reviewer's tree (lazy) and
--harness claude-code on it (off).

Signed-off-by: Anton Volkov <antonwolfy@gmail.com>
Review of intel#366 (comments 4445841688, 4446226802): _is_root treats
.claude/AGENTS.md as a root file, read at start, but _displaced treated it
as nested, so .claude/CLAUDE.local.md or .claude/.claude/CLAUDE.md beside it
turned it from start 5 (main) to off, with a new warning. The comment "at
the root a CLAUDE* would have taken the branch above" did not hold for it.

_displaced now skips every file _is_root accepts: only a root CLAUDE*
turns AGENTS.md or .claude/AGENTS.md off. A test pins the tree with only
.claude/AGENTS.md and .claude/CLAUDE.local.md (or .claude/.claude/CLAUDE.md)
at start, with nothing off and no warning.

Signed-off-by: Anton Volkov <antonwolfy@gmail.com>
Review of intel#366 (comment 4445841700): the notes said a CLAUDE* in a parent
directory does not turn a lazily reached AGENTS.md off, while budget turns
every AGENTS.md off under a root CLAUDE.md, the root being a parent of
every directory. The dropped sentence about one CLAUDE.md turning
AGENTS.md off for the whole session was what explained it.

Both cases were measured on Claude Code 2.1.316 (Haiku 4.5), with and
without a root CLAUDE*. guidance.notes now say a CLAUDE* in a parent below
the root does not turn the AGENTS.md off, and a root CLAUDE* file
(CLAUDE.local.md included) turns every AGENTS.md off for the whole
session. The contract, docs/architecture.md, the budget.py docstring and
the changelog fragment say "a parent below the root" too. No code change.

Signed-off-by: Anton Volkov <antonwolfy@gmail.com>
Review of intel#366 (comment 4445841710): _displaced spelled out CLAUDE.md,
CLAUDE.local.md and .claude/CLAUDE.md again, beside receipt.CLAUDE_ROOT,
so a location added to one could miss the other.

CLAUDE_ROOT moves to budget.py, which receipt.py already imports (budget
imports nothing from receipt, so no cycle), and _displaced builds the
names beside an AGENTS.md from it. A test checks that the receipt reads the
same object and that the nested rule follows the constant.

Signed-off-by: Anton Volkov <antonwolfy@gmail.com>
@antonwolfy

antonwolfy commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor Author

Round for @vlad-perevezentsev's and @napetrov's reviews, at 50d006c (CI green, OpenCodeReview 0 findings):

  • 28ff756: the same-directory rule is gated on a profile flag, guidance.claude_displaces_agents (optional, added to harness_profile.v1), set only in claude-code.yaml. Copilot budgets are back to main's, as the changelog says.
  • 5fdbb9d: .claude/AGENTS.md is left to the root rule; a .claude/CLAUDE.local.md beside it no longer turns it off.
  • 2b4bc1e: the notes, contract, architecture page and fragment say "a CLAUDE* in a parent below the root does not", and keep the root rule.
  • 50d006c: one CLAUDE_ROOT in budget.py, imported by receipt.py.

@napetrov's note on the hop-limit warning for an off file: checked and kept. In sub/CLAUDE.md -> d1 -> d2 -> d3 -> d.md -> @AGENTS.md, sub/AGENTS.md is off only because that import is past the 4-hop limit (one hop shorter, it is lazy with no warning), so the warning is what tells the author their @AGENTS.md is not followed. main warns the same way in the root case.

Not changed here, same class as the Copilot point but already on main: the root rule also keys on CLAUDE* in the guidance names, so with --harness copilot a root AGENTS.md beside a root CLAUDE.md reads off. Tracked in #372.

@napetrov napetrov left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review of 279275a..50d006c: the profile flag fixes the Copilot budget and the .claude/ case. One nit inline; ready otherwise.

Comment thread dokimi/dokimi/repos/budget.py Resolved

@napetrov napetrov left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving at 50d006c: CI green, the profile flag fixes the Copilot budget. The broken-link warning nit can go in a follow-up.

Conflicts:
- dokimi/scripts/growth_baseline.json: both sides changed neighbouring
  lines. Kept this branch's dokimi/repos/budget.py::budget at 20 (the
  nested rule moved into a helper) and main's
  dokimi/repos/environment/__init__.py::inspect_repo at 22. Every other
  entry is main's. scripts/check_growth.py passes on the merged tree.

docs/reference/cli-interface.json merged without a conflict;
scripts/cli_surface.py dump regenerates it unchanged.

Signed-off-by: Anton Volkov <antonwolfy@gmail.com>
Settles the review point that a nested sub/AGENTS.md which is a broken
link, beside a real sub/CLAUDE.md, came out `off` with the warning that
Claude Code does not read it, on top of the not-readable warning, while
the base budget gave it `lazy` with the not-readable warning only.

_displaced now applies the same-directory CLAUDE* rule only to an
AGENTS.md that resolves (_is_resolvable): a dangling or out-of-tree link
keeps the status and the single warning the base gives it. A readable
AGENTS.md beside a CLAUDE* file is still `off`. The root rule is
unchanged. The module docstring, the contract and the changelog fragment
say "that resolves".

Signed-off-by: Anton Volkov <antonwolfy@gmail.com>
@antonwolfy
antonwolfy merged commit c17f630 into intel:main Nov 3, 2026
21 checks passed
@antonwolfy
antonwolfy deleted the fix/budget-nested-claude branch November 3, 2026 13:59
antonwolfy added a commit to antonwolfy/dokimi that referenced this pull request Nov 3, 2026
Brings in intel#366 (budget turns off an AGENTS.md beside its own CLAUDE*
file), which adds the profile key guidance.claude_displaces_agents in
the same places where this branch adds guidance.shadow_file. Each
conflict was two additions at the same spot; both are kept:

- dokimi/dokimi/repos/harness_profile.py: both properties,
  HarnessProfile.shadow_file and then claude_displaces_agents.
- dokimi/dokimi/repos/profiles/claude-code.yaml: both keys under
  guidance, each with its comment (shadow_file: CLAUDE.local.md, then
  claude_displaces_agents: true), and main's reworded notes.
- dokimi/dokimi/schemas/harness_profile.v1.json: both optional
  properties under guidance.
- dokimi/docs/repo-evaluation-contract.md: both lines in the §6 example
  profile (shadow_file, then claude_displaces_agents); main's other
  contract changes merged without conflict.

docs/reference/cli-interface.json, regenerated with
scripts/cli_surface.py dump, is unchanged.

Signed-off-by: Anton Volkov <antonwolfy@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants