Skip to content

Commit 3939f7c

Browse files
ptr727claude
andcommitted
Anchor a Loose File on Its Own Parent, Not on Where the Caller Stands
The no-repository fallback anchored a file argument on `.`, which is the caller's directory. Scanning a loose file from inside an operational repository therefore exempted it, which is this PR's own defect in miniature, reintroduced by the fix for the previous round's regression. A file now anchors on its own parent. A bare filename still anchors on `.` and that stays correct, since `.` is the directory holding it. The test meant to guard this passed a directory, so it never reached the branch that broke. It passes the file now, with `operational_checkout` mocked to treat `.` as operational, which is the arrangement that fails on the old behaviour. A second case keeps the directory path covered. Verified live as well: a loose file scanned from an operational checkout reports the planted violation, and the multi-file invocation outside a repository still exits 0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 492957a commit 3939f7c

2 files changed

Lines changed: 24 additions & 6 deletions

File tree

‎scripts/prose_lint.py‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1122,12 +1122,14 @@ def main(argv: list[str] | None = None) -> int:
11221122
file=sys.stderr)
11231123
return2
11241124
# With one repository in play, the model is that repository's own.
1125-
# With none, anchor the way discovery does, so the rule set is decided over the tree that will actually be read.
1125+
# With none, a file anchors on its own parent rather than on `.`, which is where the caller stands.
1126+
# Anchoring a loose file on `.` reintroduces this defect in miniature, exempting a file the caller's repository does not contain.
1127+
# A bare filename still anchors on `.`, correctly, since that is the directory holding it.
11261128
ifgit_roots:
11271129
scan_root=Path(next(iter(git_roots)))
11281130
else:
11291131
first=Path(scan_paths[0])
1130-
scan_root=firstiffirst.is_dir() elsePath('.')
1132+
scan_root=firstiffirst.is_dir() elsefirst.parent
11311133

11321134
# An operational repository's runbook carries the literal path an operator types.
11331135
# That is the repository's own content, not an agent quoting an environment it observed.

‎scripts/test_prose_lint.py‎

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1881,19 +1881,35 @@ def test_one_repository_plus_a_loose_path_is_not_a_conflict(self) -> None:
18811881
[str(self.release), str(loose), '--check', 'home-path']))
18821882
self.assertNotIn('more than one repository', self.err.getvalue())
18831883

1884-
deftest_a_path_under_no_repository_falls_back_to_itself_rather_than_the_caller(self) ->None:
1884+
deftest_a_loose_file_anchors_on_its_own_parent_rather_than_on_the_caller(self) ->None:
18851885
"""The fallback must not reintroduce the dependency the fix removes.
18861886
1887-
`repo_root` returns '' for a path git cannot place, and resolving that to `.` would put the
1888-
caller's directory back in charge of the verdict.
1887+
A *file* argument is the case that matters. Anchoring it on `.` puts the caller's directory
1888+
back in charge, so scanning a loose file from inside an operational repository exempts it.
1889+
Passing a directory here would not exercise that branch at all, which is how the earlier
1890+
version of this test let the regression through.
18891891
"""
18901892
loose=self.tmp/'loose'
18911893
loose.mkdir()
18921894
bait=loose/'notes.md'
18931895
bait.write_text(f'Deploy into {NIX_HOME}/stack here.\n', encoding='utf-8')
1896+
# The caller stands somewhere operational; the scanned file's own directory does not.
18941897
withmock.patch.object(prose_lint, 'repo_root', return_value=''), \
1898+
mock.patch.object(prose_lint, 'operational_checkout',
1899+
side_effect=lambdaroot: Path(root) ==Path('.')), \
1900+
mock.patch.object(prose_lint, 'discover', return_value=[bait]):
1901+
self.assertEqual(1, prose_lint.main([str(bait), '--check', 'home-path']))
1902+
self.assertNotIn('operational repository', self.err.getvalue())
1903+
1904+
deftest_a_loose_directory_anchors_on_itself(self) ->None:
1905+
loose=self.tmp/'loose'
1906+
loose.mkdir()
1907+
bait=loose/'notes.md'
1908+
bait.write_text(f'Deploy into {NIX_HOME}/stack here.\n', encoding='utf-8')
1909+
withmock.patch.object(prose_lint, 'repo_root', return_value=''), \
1910+
mock.patch.object(prose_lint, 'operational_checkout',
1911+
side_effect=lambdaroot: Path(root) ==Path('.')), \
18951912
mock.patch.object(prose_lint, 'discover', return_value=[bait]):
1896-
# Not a repository, so it carries no operational payload and the rule stays on.
18971913
self.assertEqual(1, prose_lint.main([str(loose), '--check', 'home-path']))
18981914
self.assertNotIn('operational repository', self.err.getvalue())
18991915

0 commit comments

Comments
 (0)