Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
e21c8c0
shape-doctor.py and shape_advisory.py: a `.git` or an agent file in t…
Oct 21, 2026
eb3f098
tests/test_doctor_unsearchable_links.py: a `.git` that is a gitdir fi…
Oct 21, 2026
85e314d
tests/test_doctor_unsearchable_links.py: rows for the seven mutants t…
Oct 22, 2026
89486f6
tests/test_doctor_unsearchable_links.py: the two composite asserts ar…
Oct 22, 2026
0f9aff0
shape_advisory.py and shape-doctor.py: a leg's `AGENTS.md` this user …
Oct 22, 2026
d65db6a
tests/test_doctor_unsearchable_links.py: control rows hold `what is h…
Oct 22, 2026
34ed0a9
shape_advisory.py: where git cannot give a commit's `AGENTS.md`, a fo…
Oct 22, 2026
e356013
shape-doctor.py: `the way in` over a leg's `AGENTS.md` this user cann…
Oct 22, 2026
0ca3361
tests/test_doctor_unsearchable_links.py: the row for a leg known by i…
Oct 22, 2026
66e68f9
tests/test_doctor_unsearchable_links.py: the docstring of the control…
Oct 22, 2026
5b38c3a
Merge branch 'main' into fix/1110-doctor-advisory-unsearchable-git
brettheap Nov 8, 2026
9d93487
Merge #1144's final head 5b38c3a3 into #1110c's branch
Nov 8, 2026
ab31e2e
Merge #1144's squash into #1110c's branch (ours for #1144's files)
Nov 8, 2026
5370b01
Merge main into #1110c's branch
Nov 8, 2026
60c4152
shape_advisory.py: a leg's `AGENTS.md` committed as a symlink is read…
Nov 8, 2026
b4b5b1e
tests/test_advisory_agents_md_symlink.py: rows for a leg's `AGENTS.md…
Nov 8, 2026
2abd817
shape_advisory.py: `_leg_by_agents_md` asks `Tree.is_link` and reads …
Nov 8, 2026
9e4aab8
Merge branch 'main' into fix/1110-leg-agents-md-unreadable
brettheap Nov 8, 2026
6c4c813
Merge #1159's final head 9e4aab8c into #1163's branch
Nov 8, 2026
7029934
Merge #1159's squash into #1163's branch (ours for #1159's files)
Nov 8, 2026
7121e22
shape_advisory.py: a leg's `AGENTS.md` committed as a link spelled wi…
Nov 8, 2026
0f8dae6
tests/test_advisory_agents_md_symlink.py: a control `[not-utf-8]`, a …
Nov 8, 2026
8e54c32
tests/test_advisory_agents_md_symlink.py and shape_advisory.py: a lin…
Nov 8, 2026
b24382a
Merge branch 'main' into fix/1163-agents-md-symlink
brettheap Nov 10, 2026
1261655
shape_advisory.py: a `project.yaml` or `family.yaml` committed as a s…
Nov 10, 2026
16bee38
Merge #1270's head b24382a87 (fix/1163-agents-md-symlink, with main 9…
Nov 10, 2026
0c3999d
tests/test_advisory_record_symlink.py: the docstring is true of the c…
Nov 10, 2026
0c19a3c
Merge branch 'main' into fix/1163-agents-md-symlink
brettheap Nov 10, 2026
c6d9a9a
Merge #1270's final head 0c19a3c77 (fix/1163-agents-md-symlink, with …
Nov 10, 2026
129f4dc
Merge #1270's squash 3bf35b22e into fix/1272-record-symlink: scripts/…
Nov 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 53 additions & 4 deletions scripts/shape_advisory.py
Original file line number Diff line number Diff line change
Expand Up @@ -473,8 +473,10 @@ class Tree:

`text()` reads a symlink in a commit as the blob it is, the PATH it
holds, as before. `text_through()` reads it as the disk's is read,
through it (#1163), where `is_link()` says it is one; only a leg's
`AGENTS.md` is read so (`_leg_by_agents_md`).
through it (#1163), where `is_link()` says it is one. A leg's
`AGENTS.md` is read so (`_leg_by_agents_md`), and so are `project.yaml`
and `family.yaml` (`_read_mapping`, #1272). `single-repository.yaml` is
not: a link there is named as one and never followed (`read_record`).
"""

def __init__(self, root: Path, commit: str | None = None):
Expand Down Expand Up @@ -538,7 +540,7 @@ def text_through(self, rel: str) -> str | None:
commit, followed as git follows it there (`git cat-file
--batch-check --follow-symlinks`): each link from the directory it is
in, through a linked folder too, up to 40 links. `rel` is a name at
the root, as `AGENTS.md` is.
the root, as `AGENTS.md` and the two manifests are.

GIT 2.43 FOLLOWS NO `.` SEGMENT (#1163), so it is not quite a
checkout's `open()`: it answers `dangling` for a link spelled
Expand Down Expand Up @@ -583,9 +585,26 @@ def _read_mapping(tree: Tree, rel: str):
three this file expects: a manifest nested past the recursion limit is
as unreadable as one that does not parse, and the ratified rule is that
the advisory never changes an exit status.

A SYMLINK IN THE COMMIT IS READ THROUGH, AS THE DISK'S IS (#1272). In a
commit a link is a blob that holds the PATH it leads to, and that path
read as YAML was no manifest: `adopt-project.py plan` and `check` advised
an assembly root whose `project.yaml` is a link, or a family holder whose
`family.yaml` is one, while the doctor, which reads the disk through the
link, named the root or the holder. So a link in the commit
(`Tree.is_link`) is followed inside it (`Tree.text_through`), as a leg's
`AGENTS.md` is (#1163). NOTHING THAT IS A FILE INSIDE THE COMMIT IS A
MANIFEST THIS READING SEES, by the rule above: a link that dangles,
loops, runs through a file or leads to a directory gives None, and one
that leads OUT of the commit raises `OutOfTheCommit`, also None here.
The plan is written against the commit, and a manifest the commit does
not hold declares nothing in it; the link's own path is never read as
the manifest. A regular file, and every reading of the disk, is read as
before.
"""
try:
text = tree.text(rel)
text = (tree.text_through(rel) if tree.is_link(rel)
else tree.text(rel))
data = parse_yaml(text.lstrip(BOM)) if text is not None else None
except Exception: # noqa: BLE001 - see the docstring
return None
Expand Down Expand Up @@ -838,6 +857,18 @@ def _workspace(root: Path, names: list[str], policy: NamingPolicy | None,
RECORD_SILENCES = "silences"
RECORD_SILENCES_NOTHING = "silences-nothing"

#: Why a `single-repository.yaml` the commit holds as a SYMLINK silences
#: nothing, whatever it leads to (#1272). Its blob is the PATH the link
#: holds, not the record's text: read as YAML, it said `it could not be read
#: (YamlError)` of a record that can be read, and a path spelled as a
#: record's `kind:` line silenced the advisory. A later ruling to follow a
#: link inside the commit narrows "a symlink in the commit" here to the
#: links it still does not follow.
RECORD_LINK_NOT_FOLLOWED = ("it is a symlink in the commit, which is not "
"followed for this record, so whether it "
"records that this project stays a single "
"repository is not known here")


def record_schema(shape: Path = SHAPE_ROOT) -> dict:
"""The record's schema, as data. A refusal when it cannot be read: it
Expand Down Expand Up @@ -917,6 +948,22 @@ def read_record(root, shape: Path = SHAPE_ROOT,

`root` is a path, or a `Tree` -- which is how `adopt-project.py` asks
this of the commit it plans rather than of the working tree.

A SYMLINK IN THE COMMIT IS NOT FOLLOWED, AND IS NAMED AS ONE (#1272). Its
blob is the PATH it leads to, and read as YAML that said `it could not
be read (YamlError)` of a record that can be read, where the disk, read
through the link, says `recorded-single`. Following the link inside the
commit, as `_read_mapping` follows a manifest's, would also change
`adopt-project.py`'s record question (`ask_about_a_record_only`, #528): a
linked record would take the record's rule and its default `drop`, a
ruling not yet made. So every link in the commit (`Tree.is_link`)
silences nothing with `RECORD_LINK_NOT_FOLLOWED`, a sentence that names
no exception, and `ask_about_a_record_only` asks the general question of
it, as it did of every link whose path did not parse as a record.
`is_link` is never True of the disk, which the doctor reads, nor of a
leg's INDEX (`Tree(mount, "")`, the doctor's leg audit), where `git
ls-tree` has no commit to list and fails, which `is_link` takes for no
link: both read as before.
"""
tree = root if isinstance(root, Tree) else Tree(Path(root), commit)
try:
Expand All @@ -925,6 +972,8 @@ def read_record(root, shape: Path = SHAPE_ROOT,
return RECORD_ABSENT, [], []
if kind != "file":
return RECORD_SILENCES_NOTHING, ["it is not a file"], []
if tree.is_link(RECORD_FILE):
return RECORD_SILENCES_NOTHING, [RECORD_LINK_NOT_FOLLOWED], []
data = parse_yaml(tree.text(RECORD_FILE).lstrip(BOM))
except Exception as exc: # noqa: BLE001 - see the docstring
return RECORD_SILENCES_NOTHING, [f"it could not be read "
Expand Down
Loading
Loading