Repository navigation
fix(repo): scan gitignored paths - #244
ndgrigorian wants to merge 15 commits into
Conversation
…solved (not failed) Guidance that warns about ignored files names paths the tree never Signed-off-by: Nikita Grigorian <nikita.grigorian@intel.com>
There was a problem hiding this comment.
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Copilot review overview
3 open issues
What changed in this PR
Adds .gitignore-aware handling to repo scanning so that missing paths which are intentionally ignored by the repository are treated as build outputs (unresolved) rather than renames (fail).
Changes:
- Introduces an L0
.gitignoreparser/matcher (GitIgnore) aligned withgitignore(5)semantics. - Updates L0 path resolution to downgrade missing-but-gitignored paths/directories/globs from
failtounresolved, including the ignoring rule in the “why”. - Adds tests validating matcher parity with
git check-ignoreand new scan outcomes for gitignored paths.
| File | Description |
|---|---|
| dokimi/dokimi/repos/l0/gitignore.py | New .gitignore parsing/matching implementation used by L0 scanning. |
| dokimi/dokimi/repos/l0/core.py | Integrates .gitignore checks into missing-path handling and caching. |
| dokimi/tests/test_repo_l0_gitignore.py | Adds parity tests against git check-ignore plus rule attribution tests. |
| dokimi/tests/test_repo_l0_cases.py | Adds cases asserting missing-but-ignored paths become unresolved and include rule provenance. |
| dokimi/docs/repo-evaluation-contract.md | Documents updated classification rules for missing gitignored paths. |
| dokimi/changelog.d/20261026_150000_ndgrigorian_scan-gitignored-paths.md | Changelog entry describing the behavioral change. |
| dokimi/tests/test_agent_profiles.py | Updates a string used by an API-name-guard test. |
Review effort: Lite
dpctl.tensor was removed previously, moved to dpnp Signed-off-by: Nikita Grigorian <nikita.grigorian@intel.com>
75ee694 to
9163ea3
Compare
antonwolfy
left a comment
There was a problem hiding this comment.
The matcher agrees with git check-ignore on the common forms (negation, nested files, **, anchoring, directory-only rules), but a legal .gitignore line can crash the scan, and two of the new unresolved paths depend on how the path is written. One bug, two risks and one nit, all inline.
What I ran, on Linux at 9163ea3:
make ci-local: 26 passed, 2 failed, 7 skipped; 8,591 tests; changed-line coverage 94%. The two failures are the gitleaks steps: the proxy returned 504 on the gitleaks download. Not a finding.- Reproductions for the findings through
scan_repo, with no network. - A differential run of
GitIgnore.ignored_byagainstgit check-ignore --no-indexover about 100 patterns (root and nested) and 41 paths. - 26 mutants of
gitignore.pyand_missing: 20 are killed.?crossing/, the#comment skip, the\#/\!escapes and trailing-space stripping survive the full suite.
Checked, no issues
- Git parity: no difference from git for
!under ignored directories,a/**,**/a/b,a/**/b,a**b,[]],[!a],[a-z], escaped trailing spaces, CRLF lines, and nested.gitignorefiles. - Unchanged cases: with no
.gitignorethe result staysfail; a path inside a submodule keeps its message; a tracked file under an ignored directory is stillok. - Growth baseline:
core.pystays under its cap andresolve_pathstays at its baseline complexity.
Signed-off-by: Anton Volkov <antonwolfy@gmail.com>
antonwolfy
left a comment
There was a problem hiding this comment.
I pushed a commit per nit, up to 38e1a82, which fix the nit from my review:
38e1a82:dokimi/dokimi/repos/l0/gitignore.py:100: the rule now keepsline(only unescaped trailing spaces removed) instead ofraw.strip(), so the reason shows the rule as git read it.
make ci-local at 38e1a82: 26 passed, 2 failed (gitleaks download 504, host network), 7 skipped.
Not approving yet. Still open: the bug and the two risks from my review.
A bracket body was copied into a Python character class, so a tracked line such as `[z-a].h` raised re.error and ended repo scan. Brackets now follow wildmatch (POSIX classes, escapes, `]` first, a `/` inside), and a line wildmatch aborts on (unclosed `[`, trailing `\`) matches nothing. Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>
Each `*` was an unbounded `[^/]*`, so a line like `*a*a*a*a*a*a*a*a*a*a*b` against a long missing name made repo scan hang. As in CPython's fnmatch, every star but the last now takes the fixed run after it atomically. Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>
Every gitignore case scanned a root AGENTS.md, so dropping the guidance file's directory from the glob probe in _glob left all tests green while a nested `sub/*.cxx` turned from unresolved to fail. Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>
|
antonwolfy
left a comment
There was a problem hiding this comment.
Review at 3e67c56. The bracket crash is fixed and the matcher now agrees with git check-ignore on the bracket forms from my earlier thread. Two risks from my earlier review are still open, and there is one nit inline.
What I ran, on Linux at 3e67c56:
make ci-local: 25 passed, 3 failed, 7 skipped; 8,596 tests; changed-line coverage passes. Two failures are the gitleaks install and the commit scan that needs it (the proxy returned 504 on the download); gitleaks run by hand over the PR's six commits found no leaks. The third istest_unbalanced_quote_in_hook_completes_quickly(1.7 s against a 1 s bound in the full run), which passes alone and failed only because other test suites ran on the machine at the same time. None of the three is a finding.- Reproductions through
scan_repofor the open threads. - A differential run of
GitIgnore.ignored_byagainstgit check-ignore --no-indexover about 10,800 generated patterns and 101 hand-picked cases, and 45 mutants ofgitignore.pyand_missing.
Still open from my earlier review:
dokimi/dokimi/repos/l0/core.py:309: a missing directory written without the trailing slash is stillfail. Guidance`pkg/dist`,pkg/.gitignore=dist/,pkg/a.cpresent givesfail("pkg/ exists but pkg/dist does not");`pkg/dist/`givesunresolved._missingstill tries only the file reading.dokimi/dokimi/repos/l0/core.py:331: onexinstance stands in for the whole glob. Stillfail:*.cxxwith!x.cxxand`pkg/*.cxx`;[0-9]*.pngwith`img/*.png`;gen.hwith`pkg/g*.h`;foo*.hwith!foox.hand`pkg/foo*.h`. The*.cxxcontrol without the!line givesunresolved, so the probe name decides the verdict.
I agree with Copilot that the x probe misclassifies globs under negation rules. Copilot's point on Rule.pattern does not apply: the rule now stores the line with only unescaped trailing spaces removed.
Checked, no issues
- Brackets:
[!]],[^]],[]],[z-a],[a\-z],[[:alpha:]]and the unclosed or unknown-class forms agree with git, and nothing raises; unusable lines match nothing. - Many stars:
*a*a*a*a*a*a*a*a*a*a*bagainst fiftyafinishes in milliseconds. - Unchanged cases: negation, anchoring, directory-only rules,
**and nested.gitignorefiles agree with git; the nested guidance file case is now pinned. - Repo rules: changelog fragment present, contract paragraph added, no generated artifacts.
Signed-off-by: Anton Volkov <antonwolfy@gmail.com>
antonwolfy
left a comment
There was a problem hiding this comment.
I pushed a commit per nit, up to 62e8eb6, which fix the nit from my review:
62e8eb6:dokimi/tests/test_repo_l0_gitignore.py:41: added the five suggested lines toIGNORESand the eight suggested names toPATHS.
make ci-local at 62e8eb6: 26 passed, 2 failed (host), 7 skipped.
Not approving yet. Still open: the two earlier threads of mine above nit level; @copilot-pull-request-reviewer on the glob probe substitutes x for every wildcard, so *.cxx with !x.cxx is reported fail although the glob still names ignored files.
vlad-perevezentsev
left a comment
There was a problem hiding this comment.
Review at 62e8eb6. The matcher now reads .gitignore lines as git does, and a bad line no longer crashes the scan. What is left is in core.py: when a missing directory or glob counts as ignored. I agree with @antonwolfy's two open threads there (pkg/dist vs dist/, and the x probe for globs). The rest is in the line comments.
What I ran, at 62e8eb6:
make ci-local --offline: 16 passed, 1 failed, 13 skipped (8,543 tests passed). The failure isschema_compat:repo_manifest.v1.jsonandtask_inspect.v1.jsondiffer on main, and this PR does not touch any schema.GitIgnore.ignored_bychecked on[z-a].o,[!]]x,[a-]w,[[:digit:]]x, an unclosed[\and*a*a*a*a*a*a*a*a*a*a*bagainst fiftya: the answers match git, nothing raises, and the many-star line returns at once.- The parity test, run with
XDG_CONFIG_HOMEpointing at an ignore file that has*.csv.
Checked, no issues
- Brackets and stars: ad212fb reads classes as wildmatch does, and a line git cannot use matches nothing. The atomic groups in 793fa29 need Python 3.11, and that is the package's minimum.
- Rule text in the reason: it is the line with only unescaped trailing spaces removed, so Copilot's
raw.strip()point no longer applies. - Repo rules: the new files have the SPDX header, the contract paragraph is there, and no run artifacts are committed.
antonwolfy
left a comment
There was a problem hiding this comment.
One more nit, found by running this branch on a real repository: the reason names the .gitignore line only when the path's parent directory exists. Nothing has changed since 62e8eb6, so my earlier threads stand as they were.
What I ran, on Linux at 62e8eb6:
repo scanon IntelPython/dpnp at0cf6730(8AGENTS.mdfiles, 245 claims), on main (b69b15f) and on this branch: fails drop from 5 to 1.- Minimal trees through
scan_repofor the case inline. - The suggested change, with
tests/test_repo_l0_cases.pyandtests/test_repo_l0_gitignore.py(117 passed) andscripts/check_growth.py(clean).
Checked, no issues
- Real tree: the four claims that moved are Sphinx outputs,
doc/reference/generated/anddoc/reference/*.incin the root file and the same two written relative todoc/indoc/AGENTS.md. Each is nowunresolvedand names.gitignore:15or:16. The one fail left isTestXxx, a placeholder. No other claim changed status. - Links: a Markdown link to an ignored file (
[API](doc/_build/html/index.html)withdoc/_build/ignored) staysfail. That looks right to me: the link is broken where the guidance is read on GitHub, and the contract paragraph covers code-span paths only.
Not approving yet: my two threads above nit level are still open.
* Fix x probe to be more general * Fix for missing directories when lacking trailing forward slash Signed-off-by: Nikita Grigorian <nikita.grigorian@intel.com>
A glob was judged ignored by one made-up file, its wildcards filled in with `x`, so the verdict depended on that letter. `*.cxx` with `!x.cxx`, `[0-9]*.png`, `gen.h` and `gen_[a-c].h` all gave `fail` although git ignores real matches. The probe now fills the wildcards two ways (`x`, `dokimi-probe`) and adds each name an ignoring rule's last segment matches (`gen.h`, `0x.png`, `gen_a.h`). It keeps only the names the glob matches and asks the exact matcher about each. A glob written with a trailing slash is now checked as a directory. A missing directory was judged by the made-up name `<dir>/file`, which a whitelist (`*`, `!*/`, `!*.py`) always ignores, so a renamed directory read as `unresolved`. A directory now counts as ignored when git ignores it, or would ignore every name probed inside it: `file` and each name a `!` line re-includes. The reason now names the `.gitignore` line on the fallbacks too, where the parent directory is missing (`pkg/resources/cmake/`, `pkg/include/pkg/*.h`). It also no longer says the path is "not meant to be tracked", only that it is likely generated. The git oracle in the parity test sets HOME and XDG_CONFIG_HOME, so a developer's own core.excludesFile cannot change the answer. Both whitelist test cases now include `!.gitignore`; without it the `.gitignore` was never tracked and the cases checked nothing. Signed-off-by: Nikita Grigorian <nikita.grigorian@intel.com>
|
@xaleryb @antonwolfy @vlad-perevezentsev |
napetrov
left a comment
There was a problem hiding this comment.
Request changes: one correctness bug.
- Blocking:
dokimi/repos/l0/core.py:386(insideprobe in_ignored_by). A missing path re-included by an anchored!line under a whitelist readsunresolved, notfail. Repro:pkg/.gitignore=*,!*/,!/.gitignore,!/setup.py,!/keep.txt; guidance namespkg/setup.py(absent). Result: unresolved. Expected: fail. The same happens for a missing directory with!/olddir/.git check-ignore --no-indexshows git trackspkg/setup.py. Theinsidecheck builds its probe children from the rule's last segment, so!/setup.pyprobessetup.py/setup.py, which the anchored rule does not match. Suggested fix: skip theinsidecheck when the path's last matching rule is a negation, and add tests for the anchored form next to the non-anchored whitelist cases.
Nits:
dokimi/repos/l0/gitignore.py:186strips all trailing whitespace. git strips only unescaped trailing spaces. A trailing tab andbar\\both differ fromgit check-ignore, and the differential tests do not cover them.core.py:321passes onlycands[0]to_missing, so the reason names no.gitignoreline forgen/out.hwhenpkg/gen/out.his the ignored candidate. Passing all candidates fixes it. This is the still-open Copilot thread.tests/test_repo_l0_gitignore.py:125: the 1 s timing assert may flake on a loaded box.tests/test_agent_profiles.py:445changesdpctl.tensor.empty()todpnp.tensor.empty()(commit 9163ea3). It is not in the PR description. Split it out or explain it. The Revert section saysrepo-evaluation-contact.md(should becontract) and omits this file.
Once item 1 is fixed, this is approvable.
antonwolfy
left a comment
There was a problem hiding this comment.
Review at b2444d8. The delta fixes my open points: a directory without its trailing slash, the single x probe for globs, a glob with a trailing slash, the reason on the fallback paths, and the git oracle's HOME. Two things still block: @napetrov's wrong verdict on an anchored re-include, which I reproduce, and a probe table no test pins.
I agree with @napetrov on the anchored ! re-include under a whitelist. New evidence: with *, !*/, !/setup.py, !/keep.txt in pkg/.gitignore, pkg/setup.py and pkg/keep.txt give unresolved, while git reports the ! lines.
I agree with @napetrov's points on the trailing-whitespace strip and the unrelated dpctl to dpnp change in test_agent_profiles.py.
Copilot's point on cands[0] does not apply to the verdict: the loop at core.py:315 already tries every candidate, and the fallback returns unresolved whether or not a rule matches, so only the reason text can differ.
Copilot's point on the git executable does not apply: the pre-existing _git_repo helper in test_repo_l0_cases.py already runs git unconditionally, so the PR adds no new kind of dependency.
Copilot's point on the one-second timing assertion does not apply: the input is 45 characters, a backtracking regression misses the bound by orders of magnitude, and the test passed in the full suite run.
What I ran, on a Linux review host at b2444d8:
make ci-local: 28 passed, 0 failed, 7 skipped; changed-line coverage passes.- Reproductions for the findings through
scan_repo, compared withgit check-ignore --no-index -v, with no network. - A mutation run: 46 mutants of the delta, 18 of which pass
test_repo_l0_cases.pyandtest_repo_l0_gitignore.py. Most change only the reason text or are equivalent; the one that changes a verdict is inline.
Checked, no issues
- Whitelists:
*,!*/,!*.ckeeps a missing file, a missing directory and a globfail, as in the newCASESrows. - Odd lines: a trailing backslash, an unclosed
[, an empty class and an unknown[[:foo:]]class give no names and no crash, and agree with git. - Unanchored negations:
build/*with!build/keep/,gen_*with!gen_keepand*.cxxwith!/keep.cxxagree with git. - Repo rules: changelog fragment present, contract paragraph matches the code, SPDX header on the new files, no schema or generated artifact touched.
vlad-perevezentsev
left a comment
There was a problem hiding this comment.
Review at b2444d8. Not ready yet: as @napetrov found and @antonwolfy confirmed, a whitelist .gitignore with anchored re-includes still hides a real rename. I reproduced it. The smaller points are in the line comments.
What I ran, at b2444d8:
- The @napetrov case through
scan_repo: with*,!*/,!/setup.py,!/olddir/inpkg/.gitignore, the missingpkg/setup.pyandpkg/olddir/getunresolved, with the reason "pkg/.gitignore:1(*) ignores it". Git does not ignore these paths, so they should stayfail, and a real rename is hidden.
… git does A missing path the last matching line re-includes (`!/setup.py`, `!/olddir/` under `*`, `!*/`) was probed as a directory, and the files probed inside it were all ignored, so it read `unresolved`. The inside probe now runs only for a directory no `!` line re-includes. .gitignore lines are read as git reads them: split at `\n` only with a `\r` before it dropped, a byte-order mark skipped, and only unescaped trailing spaces trimmed (a tab stays; `bs\\ ` loses its space). The git oracle reads paths with -z so such names compare unquoted. The reason for a path or glob tried at several bases now names the .gitignore line at any of them, not only at the root. Tests pin `_`, `-` and `.` as class members, and the changelog says fail counts can change for the same repository. Signed-off-by: Nikita Grigorian <nikita.grigorian@intel.com>
There was a problem hiding this comment.
Review: Approve
Fixes the gitignore matcher so it scans paths that are themselves gitignored, with new tests and a changelog fragment. Head 4dc53ec, merge base 59de48a.
Ran full CI locally: 24 passed / 4 failed / 7 skipped. All four ci.yml:test / Run tests reds are unrelated to this PR: two come from the ragas/langchain_core extras not being installed, and two are timing-sensitive assertions that pass standalone. The two static reds (schema_compat vs the PR base, pip-audit) are also unrelated: schema_compat flags schema files this PR does not touch (needs rebase against drift from main), and pip-audit's filelock CVEs come from a transitive dependency this PR does not touch.
Earlier review threads
| Status | Count | Threads |
|---|---|---|
| Fixed, with a test | 14 | #1-13, #18 |
| Accepted as-is, not a defect | 4 | #14-17 |
All 18 are GitHub-resolved; each was checked against head 4dc53ec and the described fix (or acceptance) is confirmed in code.
CI
| Check | Result | Cause |
|---|---|---|
ci.yml:test / Run tests (2) |
fail | pre-existing, missing ragas/langchain_core extras |
ci.yml:test / Run tests (2) |
fail | pre-existing, timing-sensitive under parallel load |
ci.yml:static / schema_compat vs the PR base |
fail | needs rebase, drift from main |
pip-audit.yml:pip-audit |
fail | pre-existing, transitive filelock CVE |
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #244 +/- ##
==========================================
+ Coverage 93.26% 93.31% +0.04%
==========================================
Files 272 273 +1
Lines 35117 35321 +204
Branches 5964 6003 +39
==========================================
+ Hits 32752 32959 +207
+ Misses 1547 1545 -2
+ Partials 818 817 -1
🚀 New features to boost your workflow:
|
|
tests/test_repo_l0_cases.py conflicted where this branch's gitignored-path test and main's pyargs-module test were both appended after the submodule test; both tests are kept, this branch's first. Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>
vlad-perevezentsev
left a comment
There was a problem hiding this comment.
LGTM
I checked 0a05364. 4dc53ec fixes the anchored re-include that @napetrov found: a path that a ! line re-includes stays a rename. My open points are fixed as well: the fallback reasons and the changelog note.
What I ran, at 0a05364:
make ci-local --offline: 17 passed, 0 failed, 13 skipped (10,166 tests passed).scan_repoon small trees. With*,!*/,!/.gitignore,!/setup.py,!/olddir/inpkg/.gitignore, a missingpkg/setup.pyandpkg/olddir/arefail, and a missingpkg/gen.hisunresolved.include/*still covers a missingpkg/include/. Frompkg/AGENTS.md,resources/*.handresources/cmake/are nowunresolvedand namepkg/.gitignore:1.
Checked, no issues
- Line trimming:
_trimdrops only unescaped trailing spaces. A tab stays,a\keeps its space, andbs\\loses it, as in git. - Merge of main: compared with main, the branch changes only the seven files of this PR.
check_filekeeps theversions.checkcall from #259. - Changelog: the fragment now says that the fail count and the L0 gate result can differ from an older scan of the same repository.
antonwolfy
left a comment
There was a problem hiding this comment.
Review at 0a05364. The delta fixes the anchored re-include that @napetrov found, and the trailing-whitespace, changelog and fallback-reason points. Two things are worth fixing before merge: a re-included directory whose contents stay ignored still reads fail, and three verdicts the new code decides have no test.
I agree with @xaleryb that a whitelist .gitignore (*, !*/, !.gitignore, !*.py) gives unresolved for a missing pkg/olddir without the slash and fail for pkg/olddir/. That question waits for the author.
What I ran, on a Linux review host at 0a05364:
make ci-local: 27 passed, 1 failed, 7 skipped; changed-line coverage passes. The failure ispip-audit, which reports advisories in the lock files; this PR does not touch them.- Reproductions through
scan_repo, compared withgit check-ignore --no-index -von real files, with no network. - A differential run of
GitIgnoreagainstgit check-ignore(about 12,500 generated files, plus hand-picked BOM, CR, tab and escaped-space lines) and mutants of_ignored_by,_globand_probes.
Checked, no issues
- Line cleaning:
_trim, the BOM skip and the split at\nwith one\rdropped agree with git ona\t,a\,bs\\, CRLF and lone CR lines; the new rows kill the mutants that drop the backslash handling, the BOM skip or the tab rule. - Directory rules:
reincludesand the directory form ofignored_byagree with git on!build/,*/!*//!*.pyand/*/!/srcin 15 trees. - Docs: each sentence of the contract paragraph and the changelog fragment matches what
scan_repodoes, except the slash point above. - Repo rules: SPDX headers, one fragment, no generated artifacts,
check_growth.pyclean; all 10 commits carry aSigned-off-by.
|
✅ OpenCodeReview: Review complete: 0 finding(s) across 7 selected item(s). Review details
|
- The API-name guard test now asserts each shape catches its own name (dotted call, bare call, keyword argument), so dropping or weakening one shape fails it. - The gitignore parity test compares the matcher with a pinned set of what `git check-ignore` printed, so it runs without git. A second test, skipped without git or on Windows, checks git still prints that set. It creates only the .gitignore files (--no-index matches names), runs `git init` with --template= and the isolated env. - The names() test is split into a class no candidate fits and a `!` rule, each with a readable line as a positive control, and checks names(..., negated=True). Signed-off-by: Nikita Grigorian <nikita.grigorian@intel.com>
A directory that a `!*/`-style line re-includes, with every file probed directly inside it still ignored (`out/*` or `tmp/**` with `!*/`), read `fail`: the probe of its contents ran only when no `!` line matched the directory. Now only a `!` line that names the path (`!/olddir/`, `!/setup.py`, `!*.py`) skips the probe; one whose last segment is all stars re-includes every directory and does not. `GitIgnore.reincludes` becomes `reincluded_by` and returns the rule. Under a whitelist (`*`, `!*/`, `!*.py`) a missing path written without a slash stays ignored as a file (`pkg/Makefile`, and `pkg/olddir`): the contract now says a directory needs its trailing slash there to be checked as a rename. New CASES rows pin the verdicts a mutation run showed were untested: the file reading of a path without a slash, `include/*` with one re-included file, and a directory glob under a whitelist. Signed-off-by: Nikita Grigorian <nikita.grigorian@intel.com>
`_probes` added a name from every ignoring rule and kept those the glob matched, so a last segment such as `*` or `*_*` matched an unrelated rule (`_skbuild`): `maintenance/*` named `.gitignore:1 (_skbuild)` in its reason, and `pkg/*_*` with `pkg/` present went from `fail` to `unresolved`. Rule names now count only when a literal starts or ends the segment (`g*.h`, `*.png`, `gen*`); the filled-in names are still tried. Signed-off-by: Nikita Grigorian <nikita.grigorian@intel.com>
…nores nothing
Covers the empty-input cases the matcher skipped: a blank or
whitespace-only line, and lines that reduce to no pattern ("/", "//",
"!", "!/"), each of which git check-ignore also ignores nothing for.
Also a tree with no .gitignore, where no file is read.
Signed-off-by: Nikita Grigorian <nikita.grigorian@intel.com>
|
@napetrov @antonwolfy |


Summary
This PR adds a feature to dokimi checking files provided in
AGENTS.mdfor presence in.gitignoreand ignores those files, as they are likely temporary or generated files likely not to be present in a given working tree.Slips in a fix of a typo referencing
dpctl.tensor, changing todpnp.tensor, asdpctl.tensorno longer existsChanges
Risk
Should be backwards-compatible and low-risk, only works on edge cases (i.e., actively in-use repo).
Testing
Tested against dpctl repo
Revert
Remove: test_repo_l0_gitignore.py, gitignore.py, 20261026_150000_ndgrigorian_scan-gitignored-paths.md
revert in: core.py, test_repo_l0_cases.py, repo-evaluation-contract.md, test_agent_profiles.py (slipped in typo correction)
Checklist
pytestpassesruff,mypy, and schema checks passAGENTS.md.github/.licenserc.yaml)