Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
### Added

- **`repo budget` counts `.claude/rules`** — a `.claude/rules/**/*.md` file is a `rule` entry.
A rule in the root `.claude/rules/` whose `paths` frontmatter names no pattern is `start`; one
whose `paths` does, and every rule of a subdirectory's `.claude/rules/`, is `lazy`, unless a root `CLAUDE.md` or `AGENTS.md` imports it: then it is `start` whatever its `paths`. The
frontmatter is not counted, since Claude Code removes it before loading the rule, and an `@`
path in a rule is not followed as an import. A link to a directory under `.claude/rules` is a
warning: the rules behind it are not counted. A link under `.claude/rules` that does not resolve in
the tree is named too, whatever its suffix, since the tree cannot tell a dangling file from a directory; a
`CLAUDE.md` under `.claude/rules` is a rule, not guidance. Measured on Claude Code 2.1.304: a root rule
raised the first prompt by its size, `paths: []` included; a scoped or nested one loaded only
once the agent read a file it covers; a rule's `@extra.md` was not expanded. `start` was too
low by every root rule before; `repo_budget.v1` widens `kind` with `rule`. Rules are counted
for a harness profile with the new optional `guidance.claude_rules: true` (`claude-code`
only); the Codex and Copilot profiles leave it unset.
14 changes: 9 additions & 5 deletions dokimi/docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -298,12 +298,16 @@ next release.
non-empty body line, as Claude Code does. It warns on skills with no description or body text,
skills with near-duplicate descriptions (identical descriptions, or Jaccard index >= 0.5
after stop-word removal when both have 3+ words left), on AGENTS.md files turned off, on
unreadable files (non-UTF-8, dangling or out-of-tree link), and on imports past the harness's
hop limit, each of which also trips
`--fail-on-warning`. `start` is not an upper bound: it is too low by what it does not count
(`.claude/rules/*.md` files; only `@path.md` imports are followed, so `@notes.txt` adds nothing;
unreadable files (non-UTF-8, dangling or out-of-tree link), on imports past the harness's
hop limit, and on a link under `.claude/rules` to a directory or out of the tree (the rules behind
it are not counted), each of which also trips `--fail-on-warning`. For a profile with `guidance.claude_rules` (Claude Code),
`.claude/rules/**/*.md` files are `rule` entries: `start` in the root `.claude/rules/` when
`paths` names no pattern, `lazy` when it does or in a subdirectory's `.claude/rules/`, but `start` whatever its `paths` when a root CLAUDE.md or
AGENTS.md imports it, the frontmatter not counted. `start` is not an upper bound: it is too low by what it does not count
(only `@path.md` imports are followed, so `@notes.txt` adds nothing;
skills are read only from real `.claude/skills/*/SKILL.md` directories at the root, so a skill
behind a linked directory is not seen, and a nested `<subdir>/.claude/skills/` skill, which Claude
behind a linked directory is not seen, nor are the rules behind one (a warning names it), and a
nested `<subdir>/.claude/skills/` skill, which Claude
Code loads on the first read in that subdirectory, is not counted; and imports past the profile's `guidance.import_max_depth`,
four hops for Claude Code, which warn naming what was skipped). It is too high by whatever the
harness cuts from the skill listing: the listing is charged in full, and the harness cuts it past
Expand Down
10 changes: 7 additions & 3 deletions dokimi/docs/repo-evaluation-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,13 @@ SPDX-License-Identifier: Apache-2.0
> no LLM call): root CLAUDE.md files and their `@` imports plus the skill listing at
> start (AGENTS.md files and their imports too, unless a root CLAUDE*, `.claude/CLAUDE.md` or
> `CLAUDE.local.md` turns them off), nested guidance lazily, skill bodies on demand, and AGENTS.md
> files a root CLAUDE* turns off. The `start` total is not an upper bound: it is too low by what it does
> not count (`.claude/rules/*.md` files, non-`.md` imports, skills behind a linked directory, nested
> `.claude/skills/` skills, and
> files a root CLAUDE* turns off. For a profile with `guidance.claude_rules` (`claude-code`),
> `.claude/rules/**/*.md` files are `rule` entries: at start in the root `.claude/rules/` unless
> their frontmatter's `paths` names a pattern, lazily then and in a subdirectory's `.claude/rules/`; a rule a root CLAUDE.md or AGENTS.md
> imports is `start` whatever its `paths`;
> an `@` path in a rule is not followed. The `start` total is not an upper bound: it is too low by
Comment on lines +50 to +53

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

documentation · medium
Must-fix: these rules omit the case where guidance imports a rule. For example, with CLAUDE.md containing @.claude/rules/api.md and api.md containing paths: src/** frontmatter, repo budget reports the rule as start, not lazy (also asserted by test_a_rule_a_root_claude_md_imports_is_one_start_entry). If that rule contains @extra.md, _imports also follows it transitively. Qualify these statements to distinguish automatic rule discovery from guidance imports, or adjust the implementation if the unconditional no-import behavior is intended. The same missing start-classification exception appears in docs/architecture.md and the changelog; the changelog also repeats the unconditional no-import claim.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Waiting on a decision before I change this: is the harness's @ expansion inside a rule that a root CLAUDE.md imports part of the behaviour we document? Context: the code follows those @ paths transitively, as it does for any imported file, and counts them as start imports; the docs say an @ path in a rule is not followed, without the imported-rule case. Options: (a) keep the current behaviour and state it in the docs, with the start classification of imported rules; (b) stop expansion at an imported rule. My recommendation: measure it on Claude Code with repo reach before choosing; I can make the doc change in the next round once it is answered. The thread stays open until then.

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.

On the question in this thread: I would keep the current code (option a) and fix the docs. Claude Code's memory docs say an imported file can import more files, up to four hops; a file a CLAUDE.md @-imports is loaded as an import, whatever directory it is in. What 2.1.304 measured is a rule loaded as a rule (@extra.md not expanded), and that does not cover a rule a CLAUDE.md imports. With the PR code, CLAUDE.md = @.claude/rules/api.md, api.md with paths: src/** and see @../../extra.md gives api.md start and extra.md a start import, which matches the docs. So the docs should say: "an @ path in a rule is not followed, unless a CLAUDE.md imports the rule: then the rule loads at start and its imports are followed" (here, in architecture.md and in the changelog fragment). One thing this does not settle: when imported, the rule's frontmatter is probably not removed, but it is not counted now. repo reach on a probe repo would measure both (a few haiku calls, cents).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Waiting on a decision before I change this, still open. The reviewer's comment is one reading (keep following @ paths, and state it), not the author's decision.
Options: (a) keep following them, transitively and hop-limited, and state that in these docs, architecture.md and the changelog, as the comment proposes; (b) stop expansion at an imported rule.
My recommendation: (a), with the wording from the comment, after repo reach measures the imported-rule case on Claude Code. The comment also raises a new question for that measurement: whether an imported rule keeps its frontmatter. The count strips it on this route, as on the rule route, and that is not measured yet. The thread stays open until then.

> what it does not count (non-`.md` imports, skills behind a linked directory, rules behind one,
> which warns, nested `.claude/skills/` skills, and
> imports past the profile's `guidance.import_max_depth`, four hops for Claude Code, which warns
> naming what was skipped), and too high by whatever the harness cuts from the skill listing.
> Usage:
Expand Down
2 changes: 1 addition & 1 deletion dokimi/docs/what-it-measures.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ manifest it owns:

- `dokimi repo scan` (L0, no LLM): do the links, paths, identifiers, commands
and build labels the guidance names exist in the tree?
- `dokimi repo budget` (no LLM): how many characters of guidance and skill
- `dokimi repo budget` (no LLM): how many characters of guidance, `.claude/rules` and skill
listing every model call pays for at start-up, what loads lazily or on
demand, and which AGENTS.md files a CLAUDE.md turns off.
- `dokimi repo reach` (L1): which guidance files does a given harness actually
Expand Down
2 changes: 1 addition & 1 deletion dokimi/dokimi/commands/repo.py
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ def cmd_repo_budget(args: argparse.Namespace) -> None:
print(f"✗ {exc}", file=sys.stderr)
sys.exit(EXIT_USAGE)
_, tree = _source_tree(args)
payload = budget(tree, profile.guidance_names, profile.import_max_depth)
payload = budget(tree, profile.guidance_names, profile.import_max_depth, profile.reads_claude_rules)
if args.out:
try:
save_artifact("repo_budget", payload, Path(args.out))
Expand Down
Loading
Loading