Skip to content

feat(repo): guidance:overlay arm and overlay reach check - #338

Merged
napetrov merged 2 commits into
intel:mainfrom
napetrov:ce-overlay
Nov 4, 2026
Merged

napetrov merged 2 commits into
intel:mainfrom
napetrov:ce-overlay

Conversation

@napetrov

@napetrov napetrov commented Nov 1, 2026 •

Copy link
Copy Markdown
Contributor

Refs agentsmd/agents.md#185.

Summary

A harness profile names its agent with a new overlay key. The new arm keeps the base and that agent's overlay, and drops the rest:

 repo/
   AGENTS.md
+  AGENTS.claude.md       # kept for claude-code (overlay: claude)
-  AGENTS.codex.md        # removed by guidance:overlay under claude-code
-  sub/AGENTS.copilot.md  # removed
guidance:overlay (profile.overlay = claude)
  keep  AGENTS.md, every AGENTS.claude.md (any depth)
  drop  every other AGENTS.<name>.md
  refuse if the profile has no overlay, the base has no AGENTS.md or no AGENTS.claude.md,
           or a kept link names a removed overlay (the base would dangle)

dokimi repo reach now probes every AGENTS.<name>.md. A foreign overlay that a harness loaded is reported as a finding: line and in the overlays block of guidance_reach.v1 (cross_loaded). own_loaded says whether the harness loads its own overlay at all. The arm does not make a harness load its overlay; the probe measures that.

Evidence

  • make ci-local on the head commit: 0 failed, 30 passed, 7 skipped (docker, micromamba, latest-Python and push-only jobs).
  • tests/test_repo_overlay.py and tests/test_repo_reach.py: 122 passed offline. Includes the new regressions for a link reached through a directory link, a symlink loop in the base, a base link whose target a removed link also names, and a profile overlay value with a trailing newline.
  • Round 10 (2026-11-02): test_repo_overlay.py, test_repo_reach.py, test_repo_reach_links.py, test_repo_reach_edges.py, test_growth_ratchet.py 217 passed, 2 skipped; ruff and mypy clean on the touched files. Full ci-local not rerun for this round.
  • Self-review (pr-review local mode, 3 passes): every confirmed blocker was fixed. The last fixes were verified by tests and ci-local, not by a fresh review pass. The review record is in the author's local scratch, not in this PR.

Ambiguous overlay links

Kept as designed (maintainer decision, 2026-11-02). A load is pinned to an overlay link only when no other name of its file could have been read. Otherwise it is listed under ambiguous in overlays and is neither an own nor a cross load, when:

  • an own and a foreign overlay name one file, or a target has two overlay names;
  • a harness-native file (e.g. CLAUDE.md, which can @-import the target) is in the probe. Only native names present in the probe count, so a configured name with no file does not make a link ambiguous.

Effect: a foreign link to a file a native file may import is ambiguous in real runs, not cross_loaded. The rule is in _pinned_names and _own_overlay_note; docs/repo-evaluation-contract.md §7 states it.

Merge Danger

Door: two-way. The arm is new and the profile key is optional; reverting removes both with no data migration.

Blast radius: low for repos without AGENTS.<name>.md overlays.

  • The arm names are unchanged. guidance:none and guidance:as-skill cells now drop the AGENTS.<agent>.md overlays, so their results change for a repository that has them. Their cell ids stay the same; the changelog says older results of those two arms are not comparable.
  • The overlay key is added to the run id of the new overlay arm only, so existing cell ids are unchanged.
  • dokimi repo reach now probes overlay files too. Repos with AGENTS.<name>.md files will run more reach scenarios and cost more. Repos without overlays are unaffected.
  • Schema changes are widening only (harness_profile.v1: optional overlay; guidance_reach.v1: optional overlays).

Declined items

  • A check_ancestors guard that lacks overlay names could report an ancestor overlay as a false cross-load. Not probed.
  • overlay_of folds case, so AGENTS.CODEX.md counts as codex. A case-sensitive harness would not load it. Kept on purpose; tests assert it.
  • Alias attribution: a foreign-named symlink to the base AGENTS.md is reported as cross-loaded when only the base content was read. The docstring says a link counts as its target's overlay; say if you want it narrowed to reads by name.
  • Prior-round dismissals and the reasons are in the review record, not repeated here.

Open questions for maintainers

  1. The run id (batch_ledger) does not include the profile's overlay value. Editing it on a profile can reuse cells from a resumed batch built under the old overlay. Including it would change every claude-code cell id, so this PR does not change it.
  2. Should check_ancestors also guard AGENTS.<name>.md files above the workspace?

🤖 Generated with Claude Code

Copilot AI balanced review requested due to automatic review settings November 1, 2026 02:09
@napetrov
napetrov requested a review from a team as a code owner November 1, 2026 02:09

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.

🟡 Changes recommended

Critical symlink-validation and stale-inventory bugs can leave AGENTS.md dangling, retain foreign overlays, or cause cleanup to fail with FileNotFoundError.

2 open issues
What changed in this PR

2 new must-fix findings.

This PR adds per-agent overlay evaluation to dokimi’s repository workflow, allowing runs to isolate a harness’s own guidance and measure whether it reads other agents’ overlays.

Changes:

  • Adds guidance:overlay with profile-based selection and dangling-link checks.
  • Extends reach probing and reports with own-overlay and foreign-overlay loading results.
  • Updates built-in profiles, schemas, documentation, and regression tests.
File Description
dokimi/​tests/​test_repo_overlay.py Tests overlay filtering, validation, links, and reporting.
dokimi/​dokimi/​schemas/​harness_profile.v1.json Adds the optional overlay profile key.
dokimi/​dokimi/​schemas/​guidance_reach.v1.json Adds overlay reach results.
dokimi/​dokimi/​repos/​workspace.py Implements overlay workspace preparation.
dokimi/​dokimi/​repos/​reach.py Probes overlays and reports cross-loading.
dokimi/​dokimi/​repos/​profiles/​copilot.yaml Selects the Copilot overlay.
dokimi/​dokimi/​repos/​profiles/​copilot-review.yaml Selects the Copilot review overlay.
dokimi/​dokimi/​repos/​profiles/​codex.yaml Selects the Codex overlay.
dokimi/​dokimi/​repos/​profiles/​codex-review.yaml Selects the Codex review overlay.
dokimi/​dokimi/​repos/​profiles/​claude-code.yaml Selects the Claude overlay.
dokimi/​dokimi/​repos/​overlays.py Centralizes overlay filename recognition.
dokimi/​dokimi/​repos/​harness_profile.py Parses and validates overlay names.
dokimi/​dokimi/​commands/​repo_run.py Documents the new arm in CLI help.
dokimi/​docs/​repo-evaluation-contract.md Defines overlay arm and reach behavior.
dokimi/​docs/​preparing-a-repository.md Adds overlay evaluation guidance.
dokimi/​changelog.d/​20261031_120000_napetrov_ce-overlay.md Records the new arm and reach reporting.

Review effort: Balanced

Comment thread dokimi/dokimi/repos/workspace.py Outdated
Comment on lines +463 to +464
if arm.base == OVERLAY_ARM:
return _apply_overlay(ws, tracked, overlay or "")

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.

Fixed in ee38237. The overlay inventory now comes from the workspace (git ls-files --cached --others, existing paths only), not the snapshot index, so an overlay added by a setup patch is seen. Removal is checked against that inventory.

Comment thread dokimi/dokimi/repos/workspace.py Outdated
hop, seen = _canon(link), set()
while hop.is_symlink() and hop not in seen:
seen.add(hop)
hop = _canon(hop.parent / os.readlink(hop))

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.

Fixed in ee38237. _link_hits walks each path component, so a base link through a removed directory link is caught before _canon resolves it away. The arm refuses that base instead of leaving a dangling AGENTS.md. Test: test_overlay_arm_refuses_a_base_link_through_a_removed_directory_link.

@exai-bot

exai-bot commented Nov 1, 2026 •

Copy link
Copy Markdown

OpenCodeReview: Commit 1e28150 was not reviewed. This PR has reached its review limit (6 reviews).

To review this commit: re-request review from exai-bot in the Reviewers list.

Last review (a49a8be): 1 medium

Open threads from the bot: 2

Comment thread dokimi/dokimi/repos/reach.py Outdated
Comment on lines +136 to +140
for n in names.get(r["file"], ()):
if n in foreign:
cross.setdefault(n, {})[r["scenario"]] = r["class"]
if n in own_files:
own_loaded.add(n)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

bug · medium
Must-fix: a target's tokens do not establish which alias was read. With AGENTS.codex.md -> AGENTS.md and a harness that loads only AGENTS.md, resolve_links/build_workspace seed one shared token pair; this loop nevertheless reports AGENTS.codex.md as cross_loaded. An AGENTS.claude.md alias similarly falsely populates own_loaded. These are incorrect verdicts about overlay support/isolation, not merely alternative labels for the same content. Preserve ambiguous attribution rather than emitting a definitive finding, or collect path-specific access evidence; add a regression where base guidance shares its target with both own and foreign overlay aliases.

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.

Fixed in a52e675, with a follow-up in 4f33efc. A load of a file named by both a foreign and an own overlay is no longer attributed to either; it is listed under ambiguous. 4f33efc also covers a foreign target with an own alias, which was still counted as an own load. Tests: test_overlay_report_leaves_a_load_of_a_shared_target_unattributed, test_overlay_report_leaves_a_load_of_a_foreign_target_with_an_own_alias_unattributed.

if arm.base == "guidance:bridge" and not bridge:
name = profile.name if profile is not None else "no harness"
raise PrepError(f"guidance:bridge needs a harness profile with a `bridge` key; {name} has none")
overlay = profile.overlay if profile is not None else None

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

bug · medium
Must-fix: the workspace treatment now depends on profile.overlay, but batch_ledger.execution_id omits it and runner.run_cell reuses a completed result before calling prepare_workspace. Run guidance:overlay with a custom profile selecting claude, then change only overlay to codex and resume the same batch: the execution id and cell name are unchanged, so the claude result is returned as skipped instead of running the codex treatment. Validate/pin the overlay selection for overlay cells before reuse (or incorporate it into their identity), with a resume regression. This can be scoped to the new arm to preserve existing non-overlay cell identities.

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.

Fixed in f442d5d. An overlay cell's execution id now includes the overlay it keeps, so a completed result for another treatment is not reused. Plain-arm reruns still match in a batch holding overlay cells (both arm ids are checked). Test: test_an_overlay_cell_is_identified_by_the_overlay_it_keeps.

Comment thread dokimi/dokimi/repos/workspace.py Outdated
Comment on lines +463 to +464
if arm.base == OVERLAY_ARM:
return _apply_overlay(ws, tracked, overlay or "")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

bug · medium
Must-fix: tracked comes from the snapshot's index, but setup patches have already been applied with git apply (without updating that index). With a base containing AGENTS.claude.md and a setup patch adding AGENTS.codex.md, the new foreign overlay is absent from tracked, survives this arm, and is committed by the subsequent git add -A. A setup patch deleting an existing foreign overlay instead leaves a stale entry that causes unlink() to raise FileNotFoundError. Enumerate the post-setup workspace files for this arm, or refresh the index before enumeration, and cover both additions and deletions.

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.

Fixed in ee38237. The overlay check reads the workspace after setup patches, not the snapshot index, so an overlay a patch creates is inventoried. Test: test_overlay_arm_reads_the_workspace_after_setup_patches_not_the_index.

Comment thread dokimi/dokimi/repos/workspace.py Outdated
Comment on lines +544 to +546
hop = _canon(hop.parent / os.readlink(hop))
if hop in gone:
return True

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

bug · medium
Must-fix: resolving the parent here erases removed overlays used as directory links. For example, with AGENTS.codex.md -> docs/, AGENTS.md -> AGENTS.codex.md/base.md, docs/base.md a regular file, and a regular AGENTS.claude.md, this walk jumps directly to docs/base.md and never matches gone. The arm then removes AGENTS.codex.md and leaves the kept AGENTS.md dangling, despite its link guard. Check symlink components before canonicalizing them (or reject foreign overlays that name directories), and add a regression for this case.

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.

Fixed in ee38237. The parent is no longer resolved through a removed overlay used as a directory link; the walk expands symlinks itself and stops on loops (_MAX_HOPS). Tests: test_overlay_arm_refuses_a_base_link_through_a_directory_link, test_overlay_arm_does_not_hang_on_a_symlink_loop_in_the_base.

Comment on lines +552 to +554
kept = sorted(p for p in tracked if overlay_of(p) == own)
if not kept:
raise PrepError(f"guidance:overlay: the base has no AGENTS.{own}.md to keep")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

bug · medium
Must-fix: a tracked own-overlay name is accepted even if it cannot supply a file. For example, keep a regular AGENTS.md and track AGENTS.claude.md -> missing.md: kept is nonempty, _link_hits finds no removed overlay, and preparation succeeds with only usable base guidance. A cyclic link or a link to a directory also passes. _checkout_tree preserves these links and no earlier preparation step validates their targets. Validate that kept overlays resolve to usable files inside the workspace and reject unusable targets with PrepError; add dangling/cyclic/directory own-overlay regressions (the current cycle test covers unrelated links only).

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.

Fixed in ee38237. A kept own overlay must now resolve to a regular file in the workspace, otherwise the arm raises PrepError ("does not resolve to a usable file in the workspace"). Test: test_overlay_arm_refuses_an_own_overlay_that_supplies_no_file.

@codecov-commenter

codecov-commenter commented Nov 1, 2026 •

Copy link
Copy Markdown

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

Codecov Report

❌ Patch coverage is 97.57785% with 7 lines in your changes missing coverage. Please review.
✅ Project coverage is 94.75%. Comparing base (51bbc4d) to head (1e28150).

Files with missing lines Patch % Lines
dokimi/dokimi/repos/guidance_arms.py 96.82% 1 Missing and 1 partial ⚠️
dokimi/dokimi/repos/overlay_paths.py 95.83% 1 Missing and 1 partial ⚠️
dokimi/dokimi/repos/workspace.py 96.96% 1 Missing and 1 partial ⚠️
dokimi/dokimi/repos/overlays.py 95.83% 0 Missing and 1 partial ⚠️
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #338      +/-   ##
==========================================
+ Coverage   94.73%   94.75%   +0.01%     
==========================================
  Files         348      352       +4     
  Lines       46284    46498     +214     
  Branches     7928     7969      +41     
==========================================
+ Hits        43848    44057     +209     
- Misses       1545     1547       +2     
- Partials      891      894       +3     
Files with missing lines Coverage Δ
dokimi/dokimi/commands/repo_run.py 95.21% <ø> (ø)
dokimi/dokimi/repos/batch_ledger.py 95.34% <100.00%> (+0.07%) ⬆️
dokimi/dokimi/repos/harness_profile.py 97.20% <100.00%> (+0.09%) ⬆️
dokimi/dokimi/repos/reach.py 98.70% <100.00%> (+0.07%) ⬆️
dokimi/dokimi/repos/reach_overlays.py 100.00% <100.00%> (ø)
dokimi/dokimi/repos/replay/build.py 98.17% <100.00%> (+0.01%) ⬆️
dokimi/dokimi/repos/runner.py 96.91% <100.00%> (+0.01%) ⬆️
dokimi/dokimi/repos/overlays.py 95.83% <95.83%> (ø)
dokimi/dokimi/repos/guidance_arms.py 96.82% <96.82%> (ø)
dokimi/dokimi/repos/overlay_paths.py 95.83% <95.83%> (ø)
... and 1 more
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment thread dokimi/dokimi/repos/reach.py Outdated
ambiguous: set[str] = set()
for target, aliases in links.items():
kinds = {"own" if overlay_of(n) == own else "foreign" for n in (target, *aliases) if overlay_of(n) is not None}
if aliases and (PurePosixPath(target).name in native or {"own", "foreign"} <= kinds):

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

bug · medium
Must-fix: native-name ambiguity is checked only on the target. With CLAUDE.md -> docs/shared.md and AGENTS.codex.md -> docs/shared.md, resolve_links supplies both names as aliases. A Claude harness loading only CLAUDE.md produces the shared tokens, but this branch attributes them to AGENTS.codex.md and reports a false cross-load. Check native names across (target, *aliases) and add this shared-native-alias regression; the existing native-target test does not cover it.

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.

Fixed in 3d7334b: a group of link names pins nothing when the target or an alias has a harness-native file name (_pinned_names), so a Claude load of docs/shared.md is not attributed to AGENTS.codex.md.
Test: test_overlay_report_does_not_pin_a_shared_target_read_through_a_native_alias (red on 4f33efc).

Comment thread dokimi/dokimi/repos/workspace.py Outdated
hops += 1
if hops > _MAX_HOPS:
return False
target = PurePosixPath(os.readlink(cand))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

bug · medium
Must-fix: os.readlink returns filesystem-native paths, but PurePosixPath does not split Windows backslashes. On Windows, with AGENTS.codex.md -> docs and a kept AGENTS.md -> AGENTS.codex.md\base.md, the entire target becomes one component. cand then names the regular base.md through the directory link, so the walker never checks the removed AGENTS.codex.md component. Preparation removes that link and leaves AGENTS.md dangling. Parse readlink results with native Path semantics and add a Windows regression for a backslash-separated target through a removed directory link.

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.

Fixed in 3d7334b: readlink targets are read with native Path semantics, so a Windows target such as AGENTS.codex.md\base.md splits into its names.
Test (Windows only, skipped here): test_overlay_arm_refuses_a_backslash_base_link_through_a_removed_directory_link. Not run on Windows in this round.

Comment thread dokimi/docs/repo-evaluation-contract.md Outdated
Comment on lines +1063 to +1064
line. `own_loaded` lists the harness's own overlays that loaded in some scenario;
when none did, the text report says so. `own` is the profile's `overlay`.

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 · low
Nit: document the alias-ambiguity exception here. With AGENTS.claude.md -> AGENTS.md and a Claude profile, reported tokens from AGENTS.md leave own_loaded empty but populate ambiguous; format_matrix deliberately suppresses the 'no overlay loaded' note. Thus an empty own_loaded does not always mean the own overlay was not loaded, and the text report does not always say so. Describe ambiguous and qualify both the own-load and cross-load claims so readers can distinguish an unobserved load from an unattributable one.

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.

Fixed in b880a3e. docs/repo-evaluation-contract.md section 7 now states the ambiguous set and when an own overlay gets the 'could not determine' note instead of 'not loaded'. Docs only; the rule is in _pinned_names and _own_overlay_note.

Comment thread dokimi/dokimi/repos/reach.py Outdated
for target, aliases in links.items():
kinds = {"own" if overlay_of(n) == own else "foreign" for n in (target, *aliases) if overlay_of(n) is not None}
# a native name anywhere in the group (the target or an alias) cannot tell which file a harness read
if aliases and (any(PurePosixPath(n).name in native for n in (target, *aliases)) or {"own", "foreign"} <= kinds):

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

bug · medium
Must-fix: foreign-only alias groups still produce unsupported per-path findings. For example, overlay_report([{"file": "docs/shared.md", "scenario": "cwd=.", "class": "loaded"}], "claude", {"docs/shared.md": ("AGENTS.codex.md", "AGENTS.copilot.md")}, native={"CLAUDE.md", "AGENTS.md"}) reports both aliases in cross_loaded. A harness reading only one alias produces exactly this shared-token record, so the other finding: is false. The same issue occurs with a foreign overlay target and another foreign overlay alias. Treat these competing overlay names as ambiguous (or report a group-level finding), and add regressions for both arrangements rather than attributing the load to every name.

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.

Fixed in b880a3e. _pinned_names treats a group with two overlay names as ambiguous, whatever their owner, and adds the target to ambiguous. Tests: test_overlay_report_leaves_a_load_of_two_foreign_aliases_unattributed and ..._foreign_target_with_a_foreign_alias_unattributed (test_repo_overlay.py).

Comment thread dokimi/dokimi/repos/reach.py Outdated
Comment on lines +490 to +491
if overlays.get("own_files") and not overlays.get("own_loaded") and not overlays.get("ambiguous"):
lines.append(f"note: no {overlays['own']} overlay ({', '.join(overlays['own_files'])}) was loaded in any scenario")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

bug · medium
Must-fix: this reports a negative loading verdict even when the probe has no usable evidence. For example, with a regular AGENTS.claude.md and every scenario timing out, classify() produces only error records, overlay_report() leaves own_loaded empty, and this prints “no claude overlay ... was loaded in any scenario.” Error records explicitly mean loading is unknown, not not_loaded. Gate this note on usable negative records for the own overlays (and qualify incomplete runs), or report that loading could not be determined. Add a regression for an all-error run.

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.

Fixed in b880a3e. An errored scenario no longer yields 'no overlay was loaded': _own_overlay_note prints that only with a usable not_loaded record and no error, otherwise 'could not determine whether ... was loaded'. Test: test_text_report_does_not_claim_not_loaded_for_an_all_error_run.

@antonwolfy antonwolfy 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 3d7334b. The arm and the link walk hold up under the cases I tried, and the earlier bot findings are fixed. Five points are worth fixing before merge: one wrong note in the reach text report, three gaps in tests that pin a reach verdict or the dangling-link refusal, and one mismatch between the contract and the arm.

What I ran, on a Linux review host at 3d7334b:

  • make ci-local: 28 passed, 2 failed, 7 skipped. The two failures are the gitleaks download (Install gitleaks, HTTP 504) and the Scan commits step that needs it. Neither touches this PR. Changed-line coverage passes.
  • tests/test_repo_overlay.py: 40 passed, 1 skipped (the Windows-only test). The tests write only under tmp_path.
  • Reproductions for the reach findings, with the LLM transport mocked.
  • Mutation runs against the new code: 13 mutants of reach.py, 2 of which pass the three reach/overlay test files; a set of workspace.py link-walk mutants, 6 of which pass test_repo_overlay.py.

Findings not anchored inline:

  1. The arm does not require a base AGENTS.md, but the contract says it keeps one. docs/repo-evaluation-contract.md (the overlay bullet, around line 318) says the workspace "keeps the base AGENTS.md" and that an overlay sits "beside an AGENTS.md". _apply_overlay only requires that an own overlay exists anywhere. A repo holding only AGENTS.claude.md and AGENTS.codex.md runs: the call returns (['AGENTS.codex.md'], {'arm_overlay': {'name': 'claude', 'kept': ['AGENTS.claude.md']}}). A repo with AGENTS.md, sub/AGENTS.claude.md and other/AGENTS.codex.md keeps the sub overlay although other/ has no base. The cell is then an overlay alone, which the contract sentence "so a cell cannot be the base alone by accident" does not cover. Suggestion: refuse when no AGENTS.md exists, or reword the contract and say an overlay alone is allowed. The PR description lists this as open question 2, so it needs a decision before merge.
Bot points I checked that no longer apply at this head (10)
  • Copilot's point on setup-patch additions and deletions missed by the index inventory does not apply: _workspace_files lists the workspace after the patches (git ls-files --cached --others, existing paths only), and test_overlay_arm_reads_the_workspace_after_setup_patches_not_the_index pins it.
  • Copilot's point on a base link through a removed directory link does not apply: _link_hits walks each path component before resolving, and the arm refuses AGENTS.md -> AGENTS.codex.md/base.md instead of leaving it dangling.
  • exai-bot's point on attributing a shared target to the wrong alias does not apply: a load of a file named by an own and a foreign overlay is listed under ambiguous, not as an own or cross load.
  • exai-bot's point on resuming a batch after changing only overlay does not apply: the overlay arm's execution id now carries the overlay, and plain cells keep their ids.
  • exai-bot's point on the index being stale after setup patches does not apply: the inventory comes from the workspace, as above.
  • exai-bot's point on the parent walk erasing a removed directory link does not apply: the walk expands symlinks itself and stops on loops.
  • exai-bot's point on an unusable own overlay does not apply: a kept overlay that is missing, cyclic or a directory link raises PrepError before anything is removed.
  • exai-bot's point on a shared native alias such as CLAUDE.md does not apply: _pinned_names pins nothing when the target or any alias has a native file name.
  • exai-bot's point on backslash readlink targets does not apply: targets are read with native Path semantics. The Windows test is skipped on Linux, so I could not run it.
  • exai-bot's point on foreign-only alias groups does not apply: with docs/shared.md named by AGENTS.codex.md and AGENTS.copilot.md, both are listed in cross_loaded, but a foreign overlay was read in either case, so the finding that isolation failed is right. Which alias was read is the documented alias attribution the description declines to narrow.

Comment thread dokimi/dokimi/repos/reach.py Resolved
Comment thread dokimi/dokimi/repos/reach.py Resolved
Comment thread dokimi/dokimi/repos/reach.py Resolved
Comment thread dokimi/dokimi/repos/workspace.py Resolved
Comment thread dokimi/dokimi/repos/reach.py Outdated
Comment on lines +123 to +124
def _pinned_names(links: dict[str, tuple[str, ...]], own: str | None,
native: set[str] | frozenset[str]) -> tuple[dict[str, tuple[str, ...]], set[str]]:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

maintainability · low
Nit: own is never read by _pinned_names; attribution depends only on the alias group and native names. Remove this parameter and change the sole caller to _pinned_names(links, native) so the helper's signature reflects its actual dependencies.

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.

Fixed in e761468: _pinned_names no longer takes own, and its one caller passes (links, native). No behaviour change.
Checked on the pushed head 6232e4d: test_repo_overlay.py, test_repo_reach.py, test_repo_reach_links.py, test_growth_ratchet.py pass; ruff clean.

Comment thread dokimi/dokimi/repos/workspace.py Outdated
for rel in removed:
if (ws / rel).is_dir() and not (ws / rel).is_symlink():
raise PrepError(f"guidance:overlay: {rel} is a directory, not an overlay file")
(ws / rel).unlink()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

security · high
Must-fix: validate the parent of each removed path stays inside the workspace before unlinking. A setup patch can replace a tracked directory sub/ (formerly containing sub/AGENTS.codex.md) with a symlink to an external directory. Because setup patches use git apply without updating the index, _workspace_files() still includes the cached sub/AGENTS.codex.md when that external file exists. With a valid root AGENTS.claude.md, this loop then deletes the external file on the host. The kept-overlay containment check does not protect foreign overlays. Reject removed paths whose resolved parent escapes ws (while still allowing the final component itself to be a symlink), and add a regression covering a directory-to-symlink setup patch.

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.

Fixed in 6232e4d. _apply_overlay now checks every removed overlay before anything is unlinked: if its parent resolves outside the workspace it raises PrepError, so the host file stays. The final name may still be a link, as you suggested, since unlink removes the link itself.
Regression test_overlay_arm_does_not_unlink_through_a_setup_patch_that_made_a_directory_a_link fails without the fix (no PrepError) and passes now.

Comment thread dokimi/dokimi/repos/workspace.py Outdated
gone = {_canon(ws / rel) for rel in removed}
removed_set = set(removed)
for rel in tracked:
if rel not in removed_set and (ws / rel).is_symlink() and _link_hits(ws / rel, gone):

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

bug · medium
Must-fix: check retained paths for removed symlinks in their ancestors too. A setup patch can replace an indexed directory AGENTS.codex.md/ with AGENTS.codex.md -> docs, where docs/AGENTS.claude.md exists. The stale indexed AGENTS.codex.md/AGENTS.claude.md passes the kept-file validation, but its final component is a regular file, so this condition skips it. Removing the foreign directory link then leaves a recorded kept overlay path dangling instead of refusing preparation. Walk the full workspace-relative path component by component; merely removing is_symlink() is insufficient because _link_hits resolves the parent before checking it. Add a regression for a retained regular file beneath a removed directory link.

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.

Fixed in e9a486f. Every path the arm keeps is now walked from the workspace root, component by component, before anything is removed. AGENTS.codex.md/AGENTS.claude.md under a removed AGENTS.codex.md link is refused ("resolves through an overlay the arm removes; the base would dangle"), and the codex link stays.
Test: test_overlay_arm_refuses_a_kept_overlay_under_a_removed_directory_link, red on 6232e4d (no refusal).

Comment thread dokimi/dokimi/repos/workspace.py Outdated
for rel in removed:
if (ws / rel).is_dir() and not (ws / rel).is_symlink():
raise PrepError(f"guidance:overlay: {rel} is a directory, not an overlay file")
(ws / rel).unlink()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

bug · medium
Must-fix: deduplicate deletion targets before unlinking. After a setup patch replaces indexed sub/ with sub -> real, _workspace_files can contain both the stale indexed sub/AGENTS.codex.md and real/AGENTS.codex.md. Both pass the containment checks and refer to the same file. This loop deletes it on the first iteration, then raises FileNotFoundError on the second, failing an otherwise valid overlay preparation. Deduplicate by _canon(ws / rel) (while preserving the removed-path metadata), and add a regression with an internal directory alias.

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.

Fixed in e9a486f. The removed list keeps one name per file, so sub/AGENTS.codex.md and real/AGENTS.codex.md are unlinked once. No FileNotFoundError.
Test: test_overlay_arm_removes_a_file_a_directory_alias_lists_twice_once, both alias orders, red on 6232e4d.
Local: overlay/reach/ratchet tests 193 passed, 2 skipped; ruff clean.

@antonwolfy antonwolfy 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 6232e4d. The delta since 3d7334b closes my three reach threads and the setup-patch escape, but two cases of the same setup-patch kind still break guidance:overlay. Both are in _apply_overlay and both reproduce. Two things are worth fixing before merge, and one earlier question is still open.

What I ran, on a Linux review host at 6232e4d:

  • make ci-local: 30 passed, 0 failed, 7 skipped; 12,054 tests passed; changed-line coverage passes
  • Reproductions for both findings, offline, in the repo's tests/ style.
  • Mutants of the new reach and workspace lines: 6 mutants, 2 of which pass the full overlay/reach suite. Both only change which "own overlay" note prints (dropping and "error" not in seen; matching only r["file"] instead of its alias group), so I did not raise them.

Still open from my earlier review: the contract (docs/repo-evaluation-contract.md, the overlay bullet around line 318) still says the workspace "keeps the base AGENTS.md", and _apply_overlay still only requires an own overlay. This waits for a decision: refuse when no AGENTS.md exists, or reword the contract.

Checked, no issues

  • Threads: the all-error note, the ambiguous exemption and the run_reach wiring are fixed and pinned by tests. The base-link refusal cases from my last review (absolute, .., ./x/.., chain, own overlay outside the workspace) now have tests.
  • Unlink escape: a removed overlay whose last component is a link to a file or directory outside the workspace is only unlinked; the target survives.
  • Commits: every non-merge commit carries a Signed-off-by.
Bot points I checked that no longer apply at this head (12)
  • Copilot's point on setup-patch additions and deletions missed by the index inventory does not apply: _workspace_files lists the workspace after the patches (git ls-files --cached --others, existing paths only).
  • Copilot's point on a base link through a removed directory link does not apply: _link_hits walks each path component before resolving, and the arm refuses AGENTS.md -> AGENTS.codex.md/base.md.
  • exai-bot's point on attributing a shared target to the wrong alias does not apply: a load of a file named by an own and a foreign overlay is listed under ambiguous.
  • exai-bot's point on resuming a batch after changing only overlay does not apply: the overlay arm's execution id carries the overlay.
  • exai-bot's point on the index being stale after setup patches does not apply: the inventory comes from the workspace.
  • exai-bot's point on the parent walk erasing a removed directory link does not apply: the walk expands symlinks itself and stops on loops.
  • exai-bot's point on an unusable own overlay does not apply: a kept overlay that is missing, cyclic or a directory link raises PrepError.
  • exai-bot's point on a shared native alias such as CLAUDE.md does not apply: _pinned_names pins nothing when the group holds a native file name.
  • exai-bot's point on backslash readlink targets does not apply: targets are read with native Path semantics (the Windows test is skipped on Linux, so I could not run it).
  • exai-bot's point on foreign-only alias groups does not apply: a group with two overlay names is now ambiguous, and ambiguous holds the target.
  • exai-bot's point on the all-error note does not apply: _own_overlay_note prints "could not determine" unless a not_loaded record exists and none errored.
  • exai-bot's point on unlinking through a setup patch that made a directory a link out of the workspace does not apply: _apply_overlay raises PrepError before unlinking, and the regression fails with the check removed.

Comment thread dokimi/dokimi/repos/workspace.py Resolved
Comment thread dokimi/dokimi/repos/workspace.py Resolved
Comment thread dokimi/dokimi/repos/overlays.py Outdated
@functools.lru_cache(maxsize=1)
def native_names() -> frozenset[str]:
"""Guidance names a builtin harness loads by its own convention (``AGENTS.override.md``): never an overlay."""
return frozenset(n for name in builtin_profiles() for n in load_profile(name).guidance_names)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

bug · medium
Must-fix: these names do not necessarily load the bundled profiles. load_profile() first checks for a file of that name in the current working directory. For example, with a plain executable or text file named codex in the working directory, overlay_of("AGENTS.claude.md") raises ProfileError while trying to parse that file as a profile. Because reach calls overlay_of() for every repository file, even probing Claude Code can now fail on this unrelated local file. Load each bundled YAML by its explicit PROFILE_DIR path here, and add a regression with a same-named file in the working directory.

@napetrov napetrov Nov 2, 2026 •

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.

Already on head: native_names() loads each bundled profile by its explicit PROFILE_DIR path, so codex in the cwd is not read. Pinned by test_overlay_of_reads_the_bundled_profiles_not_a_same_named_file_in_the_cwd in 7e46426.

Comment thread dokimi/dokimi/repos/reach.py Outdated
Comment on lines +136 to +137
else:
names[target] = group

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

bug · medium
Must-fix: a single overlay alias does not establish that the overlay name was read. For example, let CLAUDE.md import @docs/shared.md and let AGENTS.codex.md symlink to docs/shared.md. Claude can load the target through the import without loading the foreign overlay, but this branch attributes its tokens to AGENTS.codex.md and emits a cross-load finding. The reproducible report input is overlay_report([{"file": "docs/shared.md", "scenario": "cwd=.", "class": "loaded"}], "claude", {"CLAUDE.md": (), "docs/shared.md": ("AGENTS.codex.md",)}, native={"CLAUDE.md"}). The same attribution problem affects own_loaded and canonical-target tool reads classified as fetched. Preserve evidence identifying the name read, or leave these alias loads ambiguous when that evidence is unavailable; add a regression for a directly imported non-native target. (Read scenarios request REACH_PROBE.txt, not the canonical guidance target, so they do not themselves force the target read.)

@napetrov napetrov Nov 2, 2026 •

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.

Fixed in 7e46426. A link target is pinned to an overlay alias only when no native file is in the probe, since CLAUDE.md can import the target. With one present the alias is ambiguous. Test: test_overlay_report_leaves_a_link_load_unattributed_when_a_native_file_may_import_it (your docs/shared.md example, red before). A probe with no native file keeps the pin, as before.

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.

Maintainer decision (2026-11-02): keep the ambiguous rule. A link load is pinned only when no native file can import the target, else it is listed under ambiguous. Unchanged in d70dd57, except that only native names present in the probe count. The PR body has a section on it.

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.

A consequence of the kept rule: codex and copilot declare imports: null, yet a repo with overlays nearly always has an AGENTS.md (native for every builtin profile), so every overlay that is a link is ambiguous for every harness and never a finding:. If intended, the contract could say so; otherwise apply the rule only to profiles with guidance.imports set.

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.

Kept the rule as the contract states, and documented the consequence in aa9fc49: codex and copilot have no imports, so with their AGENTS.md in the probe every overlay link is ambiguous.
Open for the maintainer: scope the rule to profiles with guidance.imports?

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.

Maintainer decision: keep the ambiguous rule. A link load is pinned only when no native file can import it. No change.

Comment thread dokimi/dokimi/repos/reach.py Outdated
Comment on lines +479 to +480
if "not_loaded" in seen and "error" not in seen:
return f"note: no {overlays['own']} overlay ({files}) was loaded in any scenario"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

bug · medium
Must-fix: this treats a refusal as evidence that the overlay was not loaded. Reproduce with the existing fake harness's FAKE_MODE=refuse, an AGENTS.claude.md file, and profile.overlay='claude': the successful but token-free refusal is classified as not_loaded, so this prints a definitive negative even though format_matrix also warns that the scenario's prose makes its negative cells untrustworthy. Check the relevant scenario rows for suspect prose before making this claim, and emit the indeterminate note when the negative evidence is unusable. Add a regression using the refusal mode.

@napetrov napetrov Nov 2, 2026 •

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.

Fixed in 7e46426. A scenario that errored or answered with prose over PROSE_LIMIT gives no usable not_loaded record, so the note says it could not be determined. Test: test_text_report_does_not_claim_not_loaded_from_a_refusal, red before.

Comment thread dokimi/dokimi/repos/workspace.py Outdated
Comment on lines +609 to +612
for rel in removed:
if (ws / rel).is_dir() and not (ws / rel).is_symlink():
raise PrepError(f"guidance:overlay: {rel} is a directory, not an overlay file")
(ws / rel).unlink()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

bug · medium
Must-fix: deletion aliases can become inaccessible during this loop. With AGENTS.codex.md -> real, a usable AGENTS.claude.md, and inventory entries AGENTS.codex.md, AGENTS.codex.md/AGENTS.copilot.md, and real/AGENTS.copilot.md, _overlay_targets retains the nested alias for the copilot file. This loop removes the codex directory symlink first, then raises FileNotFoundError when unlinking its child. Setup patches can produce this inventory through stale indexed descendants, and the dangling-path guard skips both paths because they are foreign overlays. Unlink via the canonical parent paths computed before deletion, or choose aliases/order that remain accessible; add a regression where both the directory link and its descendant are removal targets.

@napetrov napetrov Nov 2, 2026 •

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.

Fixed in 7e46426: same change as the thread above. Children are unlinked before their alias; regression test added.

@antonwolfy antonwolfy 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 e9a486f. The two fixes since my last review hold for the cases they name, but the dedup in the unlink fix leaves a sibling of the same crash. One bug inline, plus one nit.

What I ran, on a Linux review host at e9a486f:

  • make ci-local: 30 passed, 0 failed, 7 skipped; changed-line coverage passes.
  • A reproduction for the bug below, offline, in the repo's tests/ style.
  • Mutants of _apply_overlay, _overlay_targets, _path_hits and _workspace_files: 34 mutants, 7 of which pass test_repo_overlay.py. Four are equivalent or only reorder a refusal; the other three (lexists to exists, the realpath(ws) normalisation, the "is a directory" refusal) are test gaps on rare paths, so I did not raise them.

Still open from my earlier review:

  • dokimi/docs/repo-evaluation-contract.md (the overlay bullet, around line 318): the contract says the workspace "keeps the base AGENTS.md", and _apply_overlay still only requires an own overlay. This waits for a decision: refuse when no AGENTS.md exists, or reword the contract. The refusal list in the same bullet also names one refusal while the arm has several more.
  • dokimi/dokimi/repos/workspace.py:612: the removal-order variant of the alias crash, inline.

Checked, no issues

  • Kept overlay under a removed directory link: AGENTS.codex.md -> docs with the stale index entry AGENTS.codex.md/AGENTS.claude.md is refused before anything is unlinked, and _path_hits covers chains, .., absolute targets and loops.
  • Unlink escape: a removed overlay whose parent resolves outside the workspace is refused; a final-name link out of the workspace is only unlinked.
  • Commits: every non-merge commit carries a Signed-off-by.
Bot points I checked that no longer apply at this head (16)
  • Copilot's point on setup-patch additions and deletions missed by the index inventory does not apply: _workspace_files lists the workspace after the patches (git ls-files --cached --others, existing paths only).
  • Copilot's point on a base link through a removed directory link does not apply: _path_hits walks each path component before resolving, and the arm refuses AGENTS.md -> AGENTS.codex.md/base.md.
  • exai-bot's point on attributing a shared target to the wrong alias does not apply: a load of a file named by an own and a foreign overlay is listed under ambiguous.
  • exai-bot's point on resuming a batch after changing only overlay does not apply: the overlay arm's execution id carries the overlay.
  • exai-bot's point on the index being stale after setup patches does not apply: the inventory comes from the workspace.
  • exai-bot's point on the parent walk erasing a removed directory link does not apply: the walk expands symlinks itself and stops on loops.
  • exai-bot's point on an unusable own overlay does not apply: a kept overlay that is missing, cyclic or a directory link raises PrepError.
  • exai-bot's point on a shared native alias such as CLAUDE.md does not apply: _pinned_names pins nothing when the group holds a native file name.
  • exai-bot's point on backslash readlink targets does not apply: targets are read with native Path semantics (the Windows test is skipped on Linux, so I could not run it).
  • exai-bot's point on foreign-only alias groups does not apply: a group with two overlay names is ambiguous.
  • exai-bot's point on the all-error note does not apply: _own_overlay_note prints "could not determine" unless a not_loaded record exists and none errored.
  • exai-bot's point on unlinking through a setup patch that made a directory a link out of the workspace does not apply: _apply_overlay raises PrepError before unlinking.
  • exai-bot's point on a retained file under a removed directory link does not apply: every kept path is walked from the workspace root and refused.
  • exai-bot's point on deduplicating deletion targets does not apply to the two-name case: sub/AGENTS.codex.md and real/AGENTS.codex.md are unlinked once. The nested-alias variant is the inline comment.
  • exai-bot's point on a single overlay alias of a non-native target does not apply as a blocker: the false finding: needs a hand-built alias group, and the description declines narrowing alias attribution on purpose. The matrix names the alias, so a reader can check it.
  • exai-bot's point on the refusal-mode run does not apply: the "no overlay was loaded" note is printed together with the existing warning: ... read its trace before trusting its not_loaded cells line, which the same run emits.

Comment thread dokimi/dokimi/repos/workspace.py Resolved
Comment thread dokimi/dokimi/repos/overlays.py Resolved
Comment thread dokimi/dokimi/repos/reach.py Outdated
Comment on lines +503 to +504
if overlays.get("own_files") and not overlays.get("own_loaded") and not overlays.get("ambiguous"):
lines.append(_own_overlay_note(payload, overlays))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

bug · medium
Must-fix: this suppresses the own-overlay diagnostic whenever any file has an ambiguous load, even if none of those names belongs to the own overlay. For example, with AGENTS.claude.md classified as not_loaded and AGENTS.codex.md classified as loaded with a CLAUDE.md alias, ambiguous contains only AGENTS.codex.md and CLAUDE.md, yet no note is printed about the unambiguously unloaded claude overlay. Limit this guard to ambiguity involving own_files, and add a regression combining an own-overlay negative record with an unrelated ambiguous foreign load.

Suggestion:

Suggested change
if overlays.get("own_files") and not overlays.get("own_loaded") and not overlays.get("ambiguous"):
lines.append(_own_overlay_note(payload, overlays))
if (overlays.get("own_files") and not overlays.get("own_loaded")
and not set(overlays["own_files"]).intersection(overlays.get("ambiguous", ()) )):
lines.append(_own_overlay_note(payload, overlays))

@napetrov napetrov Nov 2, 2026 •

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.

Fixed in 7e46426. The own-overlay note is held back only when an own file is itself ambiguous. Test: test_text_report_still_says_not_loaded_for_an_own_overlay_next_to_an_unrelated_ambiguous_load, red before.

@antonwolfy antonwolfy 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.

I pushed a commit per nit, up to ba6dbfd, which fix the nit from my review:

  • ba6dbfd: dokimi/dokimi/repos/overlays.py:25: native_names loads each built-in profile by its path under PROFILE_DIR, so a file named after a profile in the working directory is not read.

make ci-local at ba6dbfd: 30 passed, 0 failed, 7 skipped.

Not approving yet. Still open: the bug from my review; the earlier thread of mine above nit level.

Comment thread dokimi/dokimi/repos/reach.py Outdated
"scenarios": scenario_rows,
"records": records,
"overlays": overlay_report(records, profile.overlay, {s.path: s.aliases for s in seeded},
{PurePosixPath(n).name for n in profile.guidance_names}),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

bug · medium
Must-fix: this passes configured native names, not native files actually present in the probe. _pinned_names treats any nonempty native set as evidence of a possible import, so a profile with guidance.names: [CLAUDE.md] probing only AGENTS.codex.md -> docs/shared.md marks a loaded target ambiguous and leaves cross_loaded empty even though no CLAUDE.md exists to import it. The same problem suppresses own_loaded for an own-overlay link. Restrict the native set to names present among seeded targets and aliases, and add a regression with configured native names but no native files. Keep the existing native-import/native-alias regressions.

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.

Fixed in d70dd57. The native set is now the configured names present in the probe (native_in_probe), so CLAUDE.md with no CLAUDE.md file or link no longer makes a link group ambiguous. Test: test_native_in_probe_counts_only_the_native_names_a_probed_file_or_link_carries. Existing native-import/native-alias regressions unchanged.

Comment thread dokimi/dokimi/repos/workspace.py Outdated
if rel not in listed_set and _path_hits(ws, rel, gone):
raise PrepError(f"guidance:overlay: {rel} resolves through an overlay the arm removes; "
"the base would dangle")
for rel in reversed(removed): # a name under a link sorts after the link: unlink it first, or its path is gone

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

bug · medium
Must-fix: reverse lexical order still breaks chained directory aliases, so the earlier deletion-order fix does not cover all dependencies. With AGENTS.z.md -> real, AGENTS.a.md -> AGENTS.z.md, a regular AGENTS.claude.md, and listed entries AGENTS.a.md/AGENTS.copilot.md and real/AGENTS.copilot.md, deduplication keeps the nested AGENTS.a.md/... name. This loop removes AGENTS.z.md first, then unlinking that nested name raises FileNotFoundError. Such a stale indexed entry can remain after a setup patch replaces a directory with a symlink; an otherwise valid overlay cell consequently fails after partially modifying the workspace. Unlink through the prevalidated canonical paths (captured before deletion), or order by actual symlink dependencies rather than spelling, and extend the deletion regression to this chained-alias inventory.

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.

Fixed in d70dd57. Each removed overlay is unlinked by the canonical path captured before any unlink, after all the checks, so order no longer matters. With AGENTS.z.md -> real, AGENTS.a.md -> AGENTS.z.md and both listings, the nested name and real/AGENTS.copilot.md collapse to one entry. Test: test_overlay_arm_removes_a_chained_directory_alias_listing_without_failing (red on 7e46426).

Comment thread dokimi/docs/repo-evaluation-contract.md Outdated
Comment on lines +1095 to +1097
`ambiguous` lists the names of a load that could have been read under another
name: a file reached through an alias group that holds a native guidance name
or two overlay names. Those loads are neither own nor cross loads. The text

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 · low
Nit: this says a native guidance name must belong to the alias group, but _pinned_names marks a linked load ambiguous whenever any harness-native file is present anywhere in the probe. For example, with separate CLAUDE.md and AGENTS.codex.md -> docs/shared.md, a loaded docs/shared.md is ambiguous even though its alias group has no native name. Document the probe-wide condition so readers can correctly interpret missing cross_loaded findings; the implementation's conservative rule can remain unchanged.

Suggestion:

Suggested change
`ambiguous` lists the names of a load that could have been read under another
name: a file reached through an alias group that holds a native guidance name
or two overlay names. Those loads are neither own nor cross loads. The text
`ambiguous` lists the names of a load that could have been read under another
name: a file with aliases when any harness-native guidance file is present in
the probe (it could import the target), or when the alias group holds two overlay
names. Those loads are neither own nor cross loads. The text

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.

Already true at 53aeecd: the ambiguous paragraph names the probe-wide native-file condition. No further text change here; pushed in 19195e0.

Comment thread dokimi/dokimi/repos/reach.py Outdated
Comment on lines +491 to +492
if "not_loaded" in negative and "error" not in seen:
return f"note: no {overlays['own']} overlay ({files}) was loaded in any scenario"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

bug · medium
Must-fix: a usable negative in one scenario masks an indeterminate refusal in another. For example, with own-overlay records not_loaded for both cwd=. and read=sub, prose counts 0 and 40 respectively, negative contains not_loaded and seen contains no error, so this prints “was loaded in any scenario” despite read=sub providing no evidence about loading. The all-refusal test covers only the case where negative is empty. Require all relevant scenarios to provide usable negative evidence before making the all-scenarios claim, or qualify the note to cover only usable scenarios; add a mixed negative/refusal regression.

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.

Fixed in 19195e0. The "not loaded" note now needs every scenario that probed the own overlay to give a usable answer, so a refusal in one scenario prints "could not determine". Regression: test_text_report_does_not_claim_not_loaded_when_another_scenario_was_a_refusal.

@antonwolfy antonwolfy 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 d70dd57. Both open threads are fixed, but the own-overlay note still overstates in a mixed run, and the base AGENTS.md question from my earlier review is still open. One bug and one nit inline.

What I ran, on a Linux review host at d70dd57:

  • make ci-local: 30 passed, 0 failed, 7 skipped; changed-line coverage passes.
  • Reproductions for the note finding, offline, with a hand-built reach payload.
  • Mutants of the new workspace.py and reach.py lines: 19 of the canonical-path unlink, 3 of which pass the suite (2 are equivalent), plus a set for the reach note and pinning logic, a few of which also pass. I did not raise them: they change a printed note or a rare path.

Still open from my earlier review:

  • dokimi/docs/repo-evaluation-contract.md:347: the contract says the workspace "keeps the base AGENTS.md", and _apply_overlay still only requires an own overlay. A repo with only AGENTS.claude.md and AGENTS.codex.md runs under claude: the call returns (['AGENTS.codex.md'], {'arm_overlay': {'name': 'claude', 'kept': ['AGENTS.claude.md']}}), so the cell is an overlay with no base. This waits for a decision: refuse when no AGENTS.md exists, or reword the contract. preparing-a-repository.md has the same "keep only the base and the own overlay" wording.

Checked, no issues

  • Threads: the nested and chained directory aliases no longer crash. Removed overlays are keyed by canonical path and unlinked once, after every refusal check. A final-name link out of the workspace is only unlinked, and the target survives.
  • Resume ids: execution_id adds the overlay only for overlay-arm cells, so existing cell ids are unchanged and the golden ledger test passes.
  • Schemas: overlay and overlays are optional, so both changes widen.
  • Commits: all 14 non-merge commits carry a Signed-off-by.
Bot points I checked that no longer apply at this head (21)
  • Copilot's point on setup-patch additions and deletions missed by the index inventory does not apply: _workspace_files lists the workspace after the patches, not the snapshot index.
  • Copilot's point on a base link through a removed directory link does not apply: _path_hits walks each path component before resolving, and the arm refuses such a base.
  • exai-bot's point on attributing a shared target to the wrong alias does not apply: a load of a file named by an own and a foreign overlay is listed under ambiguous.
  • exai-bot's point on resuming a batch after changing only overlay does not apply: the overlay arm's execution id carries the overlay, and plain cells keep their ids.
  • exai-bot's point on the stale index after setup patches does not apply: the inventory comes from the workspace.
  • exai-bot's point on the parent walk erasing a removed directory link does not apply: the walk expands symlinks itself and stops on loops.
  • exai-bot's point on an unusable own overlay does not apply: a kept overlay that is missing, cyclic or a directory link raises PrepError.
  • exai-bot's point on a shared native alias does not apply: a link group is ambiguous when a native file is in the probe.
  • exai-bot's point on backslash readlink targets does not apply: targets are read with native Path semantics (the Windows test is skipped on Linux, so I could not run it).
  • exai-bot's point on foreign-only alias groups does not apply: a group with two overlay names is ambiguous.
  • exai-bot's point on the all-error note does not apply: _own_overlay_note prints "could not determine" unless a usable not_loaded record exists and none errored.
  • exai-bot's point on unlinking through a setup patch that made a directory a link out of the workspace does not apply: _overlay_targets raises PrepError before any unlink.
  • exai-bot's point on a retained file under a removed directory link does not apply: every kept path is walked from the workspace root and refused.
  • exai-bot's point on deduplicating deletion targets does not apply: removed overlays are keyed by canonical path and unlinked once.
  • exai-bot's point on a profile-named file in the working directory does not apply: native_names loads each bundled profile by its PROFILE_DIR path.
  • exai-bot's point on attributing an alias load does not apply: a link load is left ambiguous when a native file is in the probe, which is the maintainer's stated rule.
  • exai-bot's point on treating a refusal as a negative does not apply to a single refusal scenario: it gives no usable not_loaded record, so the note says loading could not be determined.
  • exai-bot's point on a directory link and its nested alias does not apply: unlinking is by canonical path.
  • exai-bot's point on suppressing the own-overlay note does not apply: the note is held back only when an own file is itself ambiguous.
  • exai-bot's point on configured native names does not apply: native_in_probe counts only the native names a probed file or link carries.
  • exai-bot's point on chained directory aliases does not apply: canonical paths have real directories as parents, so unlink order does not matter.

Comment thread dokimi/dokimi/repos/reach.py Resolved
Comment thread dokimi/docs/repo-evaluation-contract.md Resolved

@antonwolfy antonwolfy 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.

I pushed a commit per nit, up to 53aeecd, which fix the nit from my review:

  • 53aeecd: dokimi/docs/repo-evaluation-contract.md:1095: the ambiguous sentence now says a file with aliases is listed when any harness-native guidance file is in the probe, or when its alias group holds two overlay names; guidance_reach.v1.json is reworded the same way.

make ci-local at 53aeecd: 30 passed, 0 failed, 7 skipped.

Not approving yet. Still open: the bug from my review.

@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 53aeecd. The arm and the link walk hold up on Linux: removed overlays are unlinked once, by canonical path, after every refusal check, and reach follows the kept ambiguity rule. Still open: the guidance:none baseline keeps the overlays, two tests fail on Windows, and the mixed refusal note (the fix commit 19195e0 is not on the PR yet).

Agreed with @antonwolfy: the "not loaded in any scenario" note next to a refusal in another scenario; the arm still does not require a base AGENTS.md, though the contract says it keeps one (a repo with only AGENTS.claude.md and AGENTS.codex.md gives kept: ['AGENTS.claude.md'] and no refusal).

Agreed with @exai-bot on the same mixed refusal note.

What I ran, at 53aeecd:

  • make ci-local --offline: 19 passed, 0 failed, 13 skipped (network and Docker steps).
  • The CI external-catalog-smoke job (docker-oracle, docker-probe): both pass, isolation probe closed.
  • Offline repros for the note, the guidance:none fixture, overlay_of names, a codex link load, a base with no AGENTS.md, and _apply_overlay on 50,000 files (1.8 s).
  • On Windows (Server 2025, Python 3.13): pytest tests/test_repo_overlay.py tests/test_repo_reach_links.py: 63 passed, 2 failed. The backslash test that is skipped on Linux passes; the two failures are in the line comment on workspace.py:572.

Checked, no issues

  • Unlinking: all checks run before the first unlink, and a link out of the workspace is only unlinked.
  • Resume ids: only overlay-arm cells add the overlay to execution_id, so existing cell ids and the golden ledger stay the same.
  • Schemas: overlay in harness_profile.v1 and overlays in guidance_reach.v1 are optional, so both widen.
  • Docs and changelog: the ambiguous text matches _pinned_names; the fragment is in Added and names the new key and the refusals.

`AGENTS.<agent>.md` beside an `AGENTS.md`, at any depth. The workspace keeps
the base `AGENTS.md` and the `AGENTS.<overlay>.md` files and removes every
other `AGENTS.<name>.md` (not the harness's own names such as
`AGENTS.override.md`, nor files under `.claude`). The files are recognised by

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.

With the usual **/AGENTS.md globs, guidance:none keeps every AGENTS.<agent>.md, so the "no guidance" baseline holds more overlay guidance than guidance:overlay.

On this PR's test fixture, guidance:none removes AGENTS.md and sub/AGENTS.md and leaves AGENTS.claude.md, AGENTS.codex.md and sub/AGENTS.copilot.md. A none / overlay / raw comparison then counts overlays as "none".

Suggestion: have guidance:none (and rev:) also drop the files overlay_of recognises, or document that a repo with overlays needs **/AGENTS.*.md in guidance.globs.

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.

Documented in aa9fc49: none and rev: match only guidance.globs, so a repo with overlays lists **/AGENTS.*.md there.
Open for the maintainer: should none also drop the overlays overlay_of recognises?

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.

Maintainer decision: keep. guidance:none and rev: match only guidance.globs, as the contract says. No change.

Comment thread dokimi/dokimi/repos/workspace.py Outdated
hops += 1
if hops > _MAX_HOPS:
return False
target = Path(os.readlink(cand)) # the native separators: a Windows target splits on backslashes

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.

Two tests in test_repo_overlay.py fail on Windows (Server 2025, Python 3.13).

  • ...however_it_is_written[absolute]: no PrepError. os.readlink returns \\?\C:\... there, while gone holds realpath paths without the prefix, so cand in gone never matches.
  • test_overlay_arm_keeps_a_base_link_whose_target_a_removed_link_also_names: OSError [Errno 22]. A Windows link made with symlink_to("docs/shared.md") does not open; this one is the test.

Suggestion: strip the \\?\ prefix after os.readlink on nt, and use Path("docs", "shared.md") in the test.

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.

Fixed in aa9fc49: a link target read on Windows drops the \\?\ prefix, and the Windows test link is Path("docs", "shared.md"). Pinned by test_a_link_target_with_the_windows_long_path_prefix_is_read_without_it.


from .harness_profile import PROFILE_DIR, builtin_profiles, load_profile

OVERLAY_RE = re.compile(r"AGENTS\.([A-Za-z0-9][A-Za-z0-9-]*)\.md")

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.

OVERLAY_RE takes any AGENTS.<word>.md as an agent overlay. overlay_of gives ja for AGENTS.ja.md, local for AGENTS.local.md, template for docs/AGENTS.template.md, and override for AGENTS.Override.md (the native check is case-sensitive). The arm deletes them, and repo reach reports them as foreign-overlay findings.

Suggestion: treat as foreign only known agent names (the builtin profiles' overlay keys), or require an AGENTS.md beside the file, as the docstring says.

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.

Fixed in aa9fc49: AGENTS.<name>.md is an overlay only for a builtin profile's overlay key (claude, codex, copilot); ja, local, template and Override are ordinary files. Pinned in test_overlay_names_are_agent_files_beside_agents_md.
Open for the maintainer: should overlay names of profiles loaded from outside the builtin set count too?

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.

Maintainer decision: keep. Only the builtin profile overlay keys are overlays; other AGENTS..md files are ordinary files. No change.

targets = _overlay_targets(ws, listed)
gone = set(targets)
listed_set = set(listed)
for rel in tracked:

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.

Nit: _path_hits walks every workspace path even when there is no foreign overlay to remove. On 50,000 files with only AGENTS.md and AGENTS.claude.md, _apply_overlay takes 1.8 s per cell, all in this loop.

Suggestion: return early when targets is empty.

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.

Fixed in aa9fc49: the path walk runs only when an overlay is removed. Pinned by test_the_path_walk_runs_only_when_an_overlay_is_removed.

Comment thread dokimi/docs/preparing-a-repository.md Outdated
> **Status: recommendations.** §1–§7 apply today. The measurement loop in §8 runs
> today for guidance arms (`guidance:none`, `guidance:raw`, `guidance:bridge`).
> today for guidance arms (`guidance:none`, `guidance:raw`, `guidance:bridge`,
> `guidance:overlay`).

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.

Nit: Two arm lists still name only none/raw/bridge: docs/benchmarking-and-comparison.md:280 and the _parse_guidance docstring at dokimi/dokimi/repos/workspace.py:146. (Anchored here because neither line is in the diff.)

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.

Fixed in aa9fc49: the arm list in docs/benchmarking-and-comparison.md and the _parse_guidance docstring now name guidance:overlay.

Comment thread dokimi/docs/repo-evaluation-contract.md Outdated
Comment on lines +356 to +357
a profile loaded from outside the builtin set has no recognised overlay name, so
its arm is refused. A base with no `AGENTS.<overlay>.md` is a

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 · low
Nit: External profiles are not categorically refused. For example, copying claude-code.yaml to a custom path retains overlay: claude, and prepare_workspace accepts it when AGENTS.claude.md exists: recognition checks the overlay value against builtin names, not the profile's origin. Clarify that an external profile cannot introduce a new overlay name, but can reuse a builtin one.

Suggestion:

Suggested change
a profile loaded from outside the builtin set has no recognised overlay name, so
its arm is refused. A base with no `AGENTS.<overlay>.md` is a
an external profile can reuse one of those names but cannot introduce a new
recognised overlay name. A base with no `AGENTS.<overlay>.md` is a

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.

Fixed in a49a8be: the contract now says an external profile can reuse a builtin overlay name; a non-builtin key is still refused.
Pinned by test_external_copy_of_a_builtin_profile_keeps_its_builtin_overlay.

@napetrov

napetrov commented Nov 3, 2026

Copy link
Copy Markdown
Contributor Author

Open for the maintainer (antonwolfy's base AGENTS.md point, 11-02 review): refuse the overlay arm when no AGENTS.md exists, or reword the contract to drop the base-AGENTS.md promise? Not changed yet.

Comment thread dokimi/dokimi/repos/workspace.py Outdated
"""The target of a symbolic link as a path. On Windows ``os.readlink`` gives an absolute target with the ``\\\\?\\``
long-path prefix, which the canonical paths of the workspace do not carry, so the prefix is dropped."""
target = os.readlink(link)
return Path(target[len("\\\\?\\"):] if target.startswith("\\\\?\\") else target)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

bug · medium
Must-fix: stripping \\?\ from a UNC target such as \\?\UNC\server\share\ws\AGENTS.codex.md produces UNC\server\share\ws\AGENTS.codex.md, which Windows Path treats as relative. With a workspace at \\server\share\ws, a valid AGENTS.claude.md, and AGENTS.md linking absolutely to that foreign overlay, _path_hits misses the removal target and preparation deletes AGENTS.codex.md, leaving AGENTS.md dangling. Normalize the \\?\UNC\ prefix to \\ before handling the ordinary \\?\ prefix, and add a Windows UNC-target regression; the current long-path test covers only the ordinary prefix.

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.

Fixed in 87dd6b5: _link_target maps \\?\UNC\server\share\x to \\server\share\x, not the relative UNC\.... Test: test_a_link_target_on_a_windows_unc_share_keeps_the_server_name.

@napetrov

napetrov commented Nov 3, 2026

Copy link
Copy Markdown
Contributor Author

Maintainer decision: the overlay arm refuses a base with no usable AGENTS.md, with a clear error. Done in 87dd6b5.

@napetrov

napetrov commented Nov 3, 2026

Copy link
Copy Markdown
Contributor Author
Should `guidance:none` also drop the `AGENTS..md` overlays? It removes only the manifest globs today, so an overlay stays (vlad-perevezentsev, 53aeecd review).

Already fixed in 19195e0: the mixed-refusal note.

@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.

LGTM

I checked 8080f70. Every point from my earlier review is settled at 0ae5a97, and the one nit left there is fixed in this branch.

Merge: safe now: it merges cleanly into current main, 7 commits behind, and its overlay, reach, docs-refs and schema-compat tests pass on the merge. Once it is in, #333, #367, #363, #368 and #359 conflict with it (workspace.py, repo-evaluation-contract.md, reach.py, profiles/codex.yaml, preparing-a-repository.md, repo_run.py) and need a rebase.

What I ran, at 8080f70:

  • make ci-local --offline: 18 passed, 1 failed, 13 skipped. The failure is schema_compat against current main: it reports headroom.v1.json removed, a schema #279 added on main after this branch's base. Not from this PR.
  • On the merge with current main: the overlay, reach, docs-refs and schema-compat tests, 294 passed, 1 skipped.
  • On Windows (Server 2025, Python 3.13), at 0ae5a97: pytest tests/test_repo_overlay.py tests/test_repo_reach_links.py: 75 passed, none skipped, including the long-path and backslash link tests.

Checked, no issues

  • Mixed refusal note: "not loaded" now needs every scenario that probed the own overlay to be usable.
  • Windows link targets: _link_target drops \\?\ and maps \\?\UNC\server\share to \\server\share.
  • Base AGENTS.md: a base without one, or whose AGENTS.md does not resolve to a file, is refused before anything is unlinked.
  • Refactor: overlay_paths.py moves the link checks unchanged; it has the SPDX header.

Fixed in this branch, up to 8080f70:

  • A profile with a non-builtin overlay was told its overlay file was missing when it was there: 8080f70 refuses such a profile by its name (overlay 'gemini' is not a builtin overlay name (claude, codex, copilot)) before it reads the base; the test fails on the old message.

@antonwolfy antonwolfy 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 8080f70. The delta since 53aeecd holds up: the base AGENTS.md is now required, the mixed-refusal note needs every probed scenario to be usable, overlay names are limited to the builtin profiles, and the link checks moved to overlay_paths.py unchanged apart from the Windows prefix handling. One risk is left, inline: the refusal for an AGENTS.md that resolves to no file has no test.

What I ran, on a Linux review host at 8080f70:

  • make ci-local: 30 passed, 0 failed, 7 skipped; 12,616 tests; changed-line coverage 98%.
  • Mutants of the new lines: probed <= usable, the builtin-name restriction and refusal, the missing-AGENTS.md refusal, the UNC branch of _link_target and the if targets: skip. All are killed except the usable-file check on AGENTS.md (inline).
  • A reproduction for the finding, offline, with no model call.

Still open from my earlier review: none. The mixed-refusal note and the base AGENTS.md question are both fixed at this head.

Checked, no issues

  • Refusal order: every new refusal comes before the first unlink, so a refused cell leaves the workspace as it was.
  • Early skip: skipping the dangling-base walk when no overlay is removed is sound, since an empty gone set can never hit; AGENTS.md is still checked.
  • Schemas: overlay and overlays are optional, so both changes widen; no new schema version is needed.
  • Commits: all 21 non-merge commits carry a Signed-off-by.
Bot points I checked that no longer apply at this head (23)
  • Copilot's point on setup-patch additions and deletions missed by the index inventory does not apply: _workspace_files lists the workspace after the patches, not the snapshot index.
  • Copilot's point on a base link through a removed directory link does not apply: _path_hits walks each path component before resolving, and the arm refuses such a base.
  • exai-bot's point on attributing a shared target to the wrong alias does not apply: a load of a file named by an own and a foreign overlay is listed under ambiguous.
  • exai-bot's point on resuming a batch after changing only overlay does not apply: the overlay arm's execution id carries the overlay, and plain cells keep their ids.
  • exai-bot's point on the stale index after setup patches does not apply: the inventory comes from the workspace.
  • exai-bot's point on the parent walk erasing a removed directory link does not apply: the walk expands symlinks itself and stops on loops.
  • exai-bot's point on an unusable own overlay does not apply: a kept overlay that is missing, cyclic or a directory link raises PrepError.
  • exai-bot's point on a shared native alias does not apply: a link group is ambiguous when a native file is in the probe.
  • exai-bot's point on backslash readlink targets does not apply: targets are read with native Path semantics, and the Windows tests were run by another reviewer.
  • exai-bot's point on foreign-only alias groups does not apply: a group with two overlay names is ambiguous.
  • exai-bot's point on the all-error note does not apply: _own_overlay_note prints "could not determine" unless a usable not_loaded record exists and none errored.
  • exai-bot's point on unlinking through a setup patch that made a directory a link out of the workspace does not apply: _overlay_targets raises PrepError before any unlink.
  • exai-bot's point on a retained file under a removed directory link does not apply: every kept path is walked from the workspace root and refused.
  • exai-bot's point on deduplicating deletion targets does not apply: removed overlays are keyed by canonical path and unlinked once.
  • exai-bot's point on a profile-named file in the working directory does not apply: native_names and overlay_names load each bundled profile by its PROFILE_DIR path.
  • exai-bot's point on attributing an alias load does not apply: a link load is left ambiguous when a native file is in the probe, which is the maintainer's stated rule, now documented in the contract.
  • exai-bot's point on treating a refusal as a negative does not apply: a refusing scenario gives no usable not_loaded record, so the note says loading could not be determined.
  • exai-bot's point on a directory link and its nested alias does not apply: unlinking is by canonical path.
  • exai-bot's point on suppressing the own-overlay note does not apply: the note is held back only when an own file is itself ambiguous.
  • exai-bot's point on configured native names does not apply: native_in_probe counts only the native names a probed file or link carries.
  • exai-bot's point on chained directory aliases does not apply: canonical paths have real directories as parents, so unlink order does not matter.
  • exai-bot's point on a usable negative masking a refusal in another scenario does not apply: the "not loaded" note now needs probed <= usable, and the mutation check kills both dropping and flipping that condition.
  • exai-bot's point on a \\?\UNC\... link target does not apply: _link_target maps it to \\server\share\..., and a test with a mocked os.readlink pins it.

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

@antonwolfy antonwolfy 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 04ee7ce. The delta since 8080f70 is two commits: a test that pins the refusal of an AGENTS.md that supplies no file, and guidance:none now also deleting the overlays. The first is right. The second changes an existing arm, and it has test gaps, an unannounced change to existing cells and a replay side effect. Three points are worth fixing before merge, two inline and one below.

What I ran, on a Linux review host at 04ee7ce:

  • make ci-local: 30 passed, 0 failed, 7 skipped; 13,109 tests; changed-line coverage passes.
  • Mutants of the new _apply_arm lines against the overlay, reach, arm and runner tests (1,454 tests): 7 of 10 are killed. Three survive (inline).
  • Reproductions for the findings, offline, with no model call.

Finding not anchored inline:

  1. A replayed PR that edits an overlay can no longer apply under guidance:none. By code reading, not run end to end: _left_out in replay/build.py keeps a review-patch hunk unless the file matches guidance_globs or the eval dir. contract §"replay" says the none arm deletes those files, so a hunk on one would not apply. none now also deletes AGENTS.<agent>.md, which the globs do not match. A PR that changes AGENTS.claude.md keeps its hunk, the file is gone before git am, and the cell fails with review patch ... does not apply. Before this commit the file existed and the patch applied. Suggestion: have _left_out also leave out files overlay_of recognises, or say in the contract that such a task cannot use guidance:none.

Still open from my earlier review: none.

Checked, no issues

  • Thread on the AGENTS.md refusal: test_overlay_arm_refuses_a_base_that_supplies_no_file covers a dangling, a cyclic and a directory AGENTS.md, and disabling the check fails all three parameters.
  • rev: code path: the dropped list is empty for rev:, so its files are as before; only the test gap below.
  • Growth ratchet: workspace.py is 767 lines and test_repo_overlay.py 702, both inside their limits.
  • Schemas: overlay and overlays are optional, so both changes widen.

@napetrov's DCO point waits for the author: commit c257c7e has no Signed-off-by, so the DCO check will fail on GitHub; ci-local does not run it.

Bot points I checked that no longer apply at this head (23)
  • Copilot's point on setup-patch overlays missed by the index inventory does not apply: the inventory is read from the workspace after the patches.
  • Copilot's point on a base link through a removed directory link does not apply: the link walk checks every path component and the arm refuses such a base.
  • exai-bot's point on attributing a shared target to an alias does not apply: such a load is listed under ambiguous.
  • exai-bot's point on resuming a batch after changing only overlay does not apply: the overlay cell's execution id carries the overlay.
  • exai-bot's point on the stale index after setup patches does not apply: the overlay arm reads the workspace, not the index.
  • exai-bot's point on the parent walk erasing a removed directory link does not apply: the walk expands links itself and stops on loops.
  • exai-bot's point on an unusable own overlay does not apply: a kept overlay that supplies no file raises PrepError.
  • exai-bot's point on a shared native alias does not apply: a link group with a native name in the probe is left ambiguous.
  • exai-bot's point on backslash readlink targets does not apply: targets are read with native Path semantics.
  • exai-bot's point on foreign-only alias groups does not apply: a group with two overlay names is ambiguous.
  • exai-bot's point on the all-error note does not apply: the note says loading could not be determined unless a usable not_loaded record exists.
  • exai-bot's point on unlinking through a setup-patch directory link does not apply to the overlay arm: it refuses before the first unlink.
  • exai-bot's point on a kept file under a removed directory link does not apply: every kept path is walked from the workspace root and refused.
  • exai-bot's point on deduplicating deletion targets does not apply: removed overlays are keyed by canonical path and unlinked once.
  • exai-bot's point on a profile-named file in the working directory does not apply: bundled profiles load by their PROFILE_DIR path.
  • exai-bot's point on attributing an alias load does not apply: a link load with a native file in the probe is ambiguous, which the maintainer kept on purpose.
  • exai-bot's point on treating a refusal as a negative does not apply: a refusing scenario gives no usable not_loaded record.
  • exai-bot's point on a directory link and its nested alias does not apply: unlinking is by canonical path.
  • exai-bot's point on suppressing the own-overlay note does not apply: the note is held back only when an own file is itself ambiguous.
  • exai-bot's point on configured native names does not apply: only native names present in the probe count.
  • exai-bot's point on chained directory aliases does not apply: canonical paths have real directories as parents, so order does not matter.
  • exai-bot's point on a usable negative masking a refusal does not apply: the note needs every probed own-overlay scenario to be usable.
  • exai-bot's point on a \\?\UNC\ link target does not apply: the prefix maps to \\server\share.

Comment thread dokimi/tests/test_repo_overlay.py Resolved
@xaleryb

xaleryb commented Nov 4, 2026

Copy link
Copy Markdown
Contributor
  • Ran on Python 3.13, at the head and before each push: the full suite, ruff, mypy, bandit, schema and schema_compat, the comment-block, growth and changelog checks, the benchmark gate and the CLI snapshot.
  • Pushed:
    • 2eabc12: guidance:none read overlays from the index. A setup patch that deletes AGENTS.codex.md gave FileNotFoundError, and one that turns sub/ into a link out of the workspace deleted the outside file. It now goes through _workspace_files and _overlay_targets, like guidance:overlay.
    • cf78b80: when a replayed PR edits an overlay, the none and overlay cells failed git am. Replay now leaves overlays out of change.patch; the contract says so.
    • 245ab2c: tests that pin p not in matched and rev: keeping the overlays.
    • a0ab4dd: a ### Changed changelog entry; none cells keep their id while their content changes.
    • 7cde135: merged main (6 files, both sides kept). conformance_profiles/codex.yaml gets overlay: codex so it mirrors the built-in profile again.
    • 9cc346e: the format and as-skill arms move to repos/guidance_arms.py, so workspace.py stays under the 800-line cap; _apply_arm and format_matrix get helpers for the complexity cap.
    • 9e5f2a0: guidance:as-skill kept the overlays, which benchmarking-and-comparison.md says it removes as guidance:none does. It now drops them too; the changelog says older as-skill results are not comparable.
  • Left for the author: DCO on c257c7e8 (git rebase --signoff), and the PR body: its "Blast radius" says existing runs are unaffected, but guidance:none and guidance:as-skill cells now drop AGENTS.<agent>.md overlays, so their results change for a repository that has them.

@antonwolfy antonwolfy 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.

The merge waits for the author. @napetrov's DCO point is open: commit c257c7e has no Signed-off-by, so the DCO check will fail on GitHub, and @xaleryb's point on the PR description, which still says existing runs are unaffected, is open too. The delta since 04ee7ce is sound. Both test gaps from my earlier threads are closed, and the changelog now announces that guidance:none drops overlays. One nit inline.

What I ran, on a Linux review host at 9e5f2a0:

  • make ci-local: 30 passed, 0 failed, 7 skipped; 14,293 tests; changed-line coverage passes.
  • Mutants of the new lines in _apply_removal, _dropped_overlays, replay/build.py and overlay_paths.py (10 mutants, against the overlay, replay, arm, format and runner tests): all killed.
  • Reproductions of the replay case under every builtin profile and guidance arm, with no model call.

Waiting for the author:

  • Commit c257c7e needs git rebase --signoff; ci-local does not run the DCO check.
  • The "Blast radius" section of the description says existing runs are unaffected. guidance:none and guidance:as-skill cells now drop the AGENTS.<agent>.md overlays, so their results change for a repository that has them. This is @xaleryb's point; the changelog fragment already says it.

Checked, no issues

  • rev:: the dropped map is empty for rev:, so its overlays stay; test_guidance_rev_keeps_the_overlays fails if rev: drops them.
  • Globs that also match overlays: guidance:none with **/AGENTS.*.md removes each overlay once; the test fails without the p not in matched filter.
  • Refusals: a setup patch that turns sub/ into a link out of the workspace is refused under guidance:none before the first unlink, and the file outside the workspace survives.
  • Replay: an overlay hunk is left out of change.patch and the task applies under guidance:none; the test fails without the new overlay_of clause.
  • Refactor: the format and as-skill arms moved to guidance_arms.py with the same behaviour.
Bot points I checked that no longer apply at this head (23)
  • Copilot's point on setup-patch overlays missed by the index inventory does not apply: the inventory is read from the workspace after the patches.
  • Copilot's point on a base link through a removed directory link does not apply: the link walk checks every path component and the arm refuses such a base.
  • exai-bot's point on attributing a shared target to an alias does not apply: such a load is listed under ambiguous.
  • exai-bot's point on resuming a batch after changing only overlay does not apply: the overlay cell's execution id carries the overlay.
  • exai-bot's point on the stale index after setup patches does not apply: the overlay arm reads the workspace, not the index.
  • exai-bot's point on the parent walk erasing a removed directory link does not apply: the walk expands links itself and stops on loops.
  • exai-bot's point on an unusable own overlay does not apply: a kept overlay that supplies no file raises PrepError.
  • exai-bot's point on a shared native alias does not apply: a link group with a native name in the probe is left ambiguous.
  • exai-bot's point on backslash readlink targets does not apply: targets are read with native Path semantics.
  • exai-bot's point on foreign-only alias groups does not apply: a group with two overlay names is ambiguous.
  • exai-bot's point on the all-error note does not apply: the note says loading could not be determined unless a usable not_loaded record exists.
  • exai-bot's point on unlinking through a setup-patch directory link does not apply to the overlay arm: it refuses before the first unlink.
  • exai-bot's point on a kept file under a removed directory link does not apply: every kept path is walked from the workspace root and refused.
  • exai-bot's point on deduplicating deletion targets does not apply: removed overlays are keyed by canonical path and unlinked once.
  • exai-bot's point on a profile-named file in the working directory does not apply: bundled profiles load by their PROFILE_DIR path.
  • exai-bot's point on attributing an alias load does not apply: a link load with a native file in the probe is ambiguous, which the maintainer kept on purpose.
  • exai-bot's point on treating a refusal as a negative does not apply: a refusing scenario gives no usable not_loaded record.
  • exai-bot's point on a directory link and its nested alias does not apply: unlinking is by canonical path.
  • exai-bot's point on suppressing the own-overlay note does not apply: the note is held back only when an own file is itself ambiguous.
  • exai-bot's point on configured native names does not apply: only native names present in the probe count.
  • exai-bot's point on chained directory aliases does not apply: canonical paths have real directories as parents, so order does not matter.
  • exai-bot's point on a usable negative masking a refusal does not apply: the note needs every probed own-overlay scenario to be usable.
  • exai-bot's point on a \\?\UNC\ link target does not apply: the prefix maps to \\server\share.

@antonwolfy antonwolfy 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.

I pushed a commit per nit, up to 1de4127, which fix the nit from my review:

  • 1de4127: dokimi/changelog.d/20261031_120000_napetrov_ce-overlay.md:12: the old replay task now fails under guidance:none and guidance:as-skill, and under guidance:overlay when the edited overlay is another agent's.

make ci-local at 1de4127: 30 passed, 0 failed, 7 skipped.

Not approving yet. Still open: @napetrov on commit c257c7e has no Signed-off-by (DCO); sign off the commit; @xaleryb on PR description 'Blast radius' still says existing runs are unaffected; guidance:none and as-skill cells now drop overlays.

@napetrov

napetrov commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor Author

Findings are the inline threads, fixed in ee38237; answered there.

Empty run marker: this review has no finding. Its findings are the inline threads, each answered there.

Empty run marker: this review has no finding. Its findings are the inline threads, each answered there.

Empty run marker: this review has no finding. Its findings are the inline threads, each answered there.

Fixed in 3d7334b, b880a3e and 6232e4d; see the threads.

Empty run marker: this review has no finding. Its findings are the inline threads, each answered there.

Empty run marker: this review has no finding. Its findings are the inline threads, each answered there.

Both setup-patch cases are fixed in 6232e4d and e9a486f.

Empty run marker: this review has no finding. Its findings are the inline threads, each answered there.

Bug fixed in d70dd57, nit in ba6dbfd.

Empty run marker: this review has no finding. Its findings are the inline threads, each answered there.

Thanks, ba6dbfd is in this head.

Empty run marker: this review has no finding. Its findings are the inline threads, each answered there.

Empty run marker: this review has no finding. Its findings are the inline threads, each answered there.

Fixed in 19195e0 and 87dd6b5; the nit is in 53aeecd.

Thanks, 53aeecd is in this head.

Fixed in df57fb3, aa9fc49, 19195e0; your 8080f70 approval covers them.

Empty run marker: this review has no finding. Its findings are the inline threads, each answered there.

Empty run marker: this review has no finding. Its findings are the inline threads, each answered there.

The points hold at this head: 8080f70, 87dd6b5 and 19195e0.

Replay hunk fixed in 41760da, changelog in fc75851, tests in a983d5b.

Signed off in this push (c257c7e is now 7cebbf6); PR body corrected.

Thanks, da46c29 is in this head.

Bot notice about its review limit, not a finding. No change this round.

Codecov app notice, not a finding. No change this round.

PR body fixed: Blast radius now says guidance:none and as-skill drop overlays.

Squash of the ce-overlay branch onto main 51bbc4d: the old history
carried copies of main commits from before main was rewritten, and
their author did not match the sign-off (DCO).

Co-authored commit from the review: ba6dbfd (Anton Volkov).

Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
main has 40 docs since docs/rule-ledger.md (intel#336), so the benchmark job
fails its sanity bound on every branch that merges main.

Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
@napetrov
napetrov merged commit 5ce3207 into intel:main Nov 4, 2026
21 checks passed
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.

7 participants