Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
12 changes: 10 additions & 2 deletions adopt-project.py
Original file line number Diff line number Diff line change
Expand Up @@ -691,7 +691,7 @@
return [*files.items(), (SHAPE_PIN, None)]


def _kept_pin_findings(plan: "Plan", tree: list,

Check failure on line 694 in adopt-project.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this function to reduce its Cognitive Complexity from 27 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=opensoft_openRepoShape&issues=AaFfVPtUoWDVO84mGUUR&open=AaFfVPtUoWDVO84mGUUR&pullRequest=1105
named_links: frozenset | set = frozenset()
) -> list[str]:
"""A FINDING for each pin file a path the plan keeps in the root already
Expand Down Expand Up @@ -740,7 +740,7 @@
under = "" if entry == path else f" under the entry {_spelled(entry)}"
where = "at" if at else "inside"
written = (_declared_pin_held(product) if product
else _shape_pin_held(mode if at else None))

Check warning on line 743 in adopt-project.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Extract this nested conditional expression into an independent statement.

See more on https://sonarcloud.io/project/issues?id=opensoft_openRepoShape&issues=AaFfVPtUoWDVO84mGUUS&open=AaFfVPtUoWDVO84mGUUS&pullRequest=1105
out.append(
f"FINDING {ROOT_HOLDS_PIN}: the plan keeps {_kind_of(mode)} "
f"{_spelled(path)} in the assembly root{under}, {where} "
Expand Down Expand Up @@ -6352,7 +6352,11 @@

Each follow-up is told the WHOLE list: the Makefile's names what runs
until it is merged, and that depends on whether `scripts/bootstrap.py`
went aside too (#493's review, F2).
went aside too (#493's review, F2). It is told which file GNU make reads
before the shape's `Makefile` as well, where the plan keeps a
`GNUmakefile` or a `makefile`, read as `_makefile_follow_up` reads it
(`GNU_MAKE_READS`, `_root_keeps`): the merge brings the shape's make
targets only where `make` reads the merged file (#1048).

`attributes_link` says the source's root `.gitattributes` is a symlink,
read from the source tree's mode as `execute` reads it from the index:
Expand All @@ -6362,8 +6366,12 @@
beside = {path: f"{COLLISION_DIR}/{path}"
for path in sorted(set(assembly_root_paths()))
if _root_keeps(surviving, path)}
reads_first = next((name for name in GNU_MAKE_READS
if name != SHAPE_MAKEFILE
and _root_keeps(surviving, name)), None)
return [collision_follow_up(path, actual, beside,
attributes_link=attributes_link)
attributes_link=attributes_link,
reads_first=reads_first)
for path, actual in beside.items()]


Expand Down
95 changes: 74 additions & 21 deletions scripts/shape_materialize.py
Original file line number Diff line number Diff line change
Expand Up @@ -768,9 +768,20 @@ def readme_trailing_shape_line(text: str) -> "re.Match | None":
ASSISTANT_INSTRUCTIONS = ("AGENTS.md", "CLAUDE.md")
#: The make targets of the shape's own `Makefile` that a person runs. A root
#: that kept the source's `Makefile` has none of them until the shape's copy
#: is merged into it (#480), and the collision follow-up says so.
#: is merged into it (#480); where the root also keeps a `GNUmakefile` or a
#: `makefile`, which GNU make reads first, the merge alone brings them only
#: where that file includes `Makefile` (#1048, #1105's review). The collision
#: follow-up says what each root takes.
#: `tests/test_adopt_e2e.py` holds this to the template's own targets.
SHAPE_MAKE_TARGETS = ("bootstrap", "validate", "pins", "park", "resume")
#: The names GNU make looks for when no `-f` is given, in its order: it reads
#: the first it finds, and only that one. `collision_follow_up` names them
#: (#1048), and so does the pinned `AGENTS-shape.md`, in this order.
#: `adopt-project.py` keeps the same tuple under the same name for
#: `_makefile_follow_up` (#977) and `_predict_collisions`, which picks the
#: file `reads_first` names;
#: `tests/test_adopt_collision_make_reads.py` holds the two tuples equal.
GNU_MAKE_READS = ("GNUmakefile", "makefile", "Makefile")


# ---------------------------------------------------------------------------
Expand Down Expand Up @@ -990,13 +1001,18 @@ def _tracked_at(target: Path, rel: str) -> str | None:

def collision_follow_up(intended: str, actual: str,
beside: dict | None = None, *,
attributes_link: bool = False) -> str:
attributes_link: bool = False,
reads_first: str | None = None) -> str:
"""The follow-up for ONE collision, in one place.

Both callers say it: `adopt-project.py plan` PREDICTS the collisions from
the plan's surviving root paths, and `execute` reports the ones that
actually happened. Two spellings of the same instruction is how the plan a
human approves stops matching the commit message they read afterwards.
`adopt-project.py plan` says it: `_predict_collisions` PREDICTS the
collisions from the plan's surviving root paths, the plan stores each
follow-up in `follow_ups:`, and `execute` carries those items into the
split commit's message, so the plan a human approves and the commit
message they read afterwards hold one spelling of each. `execute` names
the collisions that actually happened by path only: its `beside` lines,
and the split message's "WRITTEN BESIDE" list. `Materialized.follow_ups()`
would say this of them, and nothing calls it (#1105's review, NIT-4).

A `.gitattributes` THAT IS A SYMLINK gets no block (`_append_eol_block`),
so `attributes_link` says which of two things happened, and the follow-up
Expand All @@ -1005,15 +1021,37 @@ def collision_follow_up(intended: str, actual: str,
its place needs (#471's review, SHOULD-2). `plan` knows it from the
source tree's mode.

THE MAKEFILE'S MERGE IS WHAT BRINGS THE SHAPE'S MAKE TARGETS (#480). Until
it is made, `make bootstrap` in the root exits 2 with `No rule to make
target 'bootstrap'`, so the follow-up says that, and what runs meanwhile:
`python3 scripts/bootstrap.py`, unless that script went aside as well.
`beside` maps each shape file written beside one the source kept to where
it went, the whole list, and only the Makefile's follow-up reads it.
Where the root's `scripts/bootstrap.py` is the source's own, that command
runs the source's script and exits 0, so the follow-up names no command,
and says both merges come first (#493's review, F2).
THE MAKEFILE'S MERGE IS WHAT BRINGS THE SHAPE'S MAKE TARGETS (#480), WHERE
`make` READS THE MERGED FILE. Until it is made, `make bootstrap` in the
root exits 2 with `No rule to make target 'bootstrap'`, so the follow-up
says that, and what runs meanwhile: `python3 scripts/bootstrap.py`, unless
that script went aside as well. `beside` maps each shape file written
beside one the source kept to where it went, the whole list, and only the
Makefile's follow-up reads it. Where the root's `scripts/bootstrap.py` is
the source's own, that command runs the source's script and exits 0, so
the follow-up names no command, and says both merges come first (#493's
review, F2).

BUT GNU MAKE READS THE FIRST OF `GNUmakefile`, `makefile` AND `Makefile`
IT FINDS, AND ONLY THAT ONE (#1048, measured on #1046's review). Where
the root keeps a `GNUmakefile` or a `makefile` beside the source's own
`Makefile`, `make` reads that file, so the merge alone brings the targets
only where that file includes `Makefile`: after `cat shape/Makefile >>
Makefile`, `make -n bootstrap` at the root still exits 2 with the same
`No rule` where it does not, and exits 0 where it ends in `include
Makefile` (#1105's review, SHOULD-1). `reads_first` names that file, the
one `adopt-project.py`'s `_makefile_follow_up` names (#977), as its
caller reads it from the plan's root paths; the follow-up then says
`make` reads it, that the targets reach `make` there only once it has
them or includes a Makefile that does, and, as before, what runs
meanwhile, in the words `execute`'s NEXT uses (#1047). It says nothing of
the merge alone, which turns on that file's own lines. Where
`scripts/bootstrap.py` went aside as well, it says `python3
scripts/bootstrap.py` runs the shape's bootstrap once that script is
merged, and not that `make bootstrap` does. With none, the Makefile's
follow-up is what it was, byte for byte: a root whose only Makefile is
the source's is one where `make` reads the merged file. It is read by no
other collision.

AND A PINNED COPY'S FOLLOW-UP SAYS WHAT HAPPENS TO ITS ROW (#502). The
shape pin has a row for a copied file at the path it landed on, so once
Expand Down Expand Up @@ -1065,15 +1103,30 @@ def collision_follow_up(intended: str, actual: str,
return merge
targets = ", ".join(f"`{target}`" for target in SHAPE_MAKE_TARGETS)
script = (beside or {}).get(BOOTSTRAP)
if reads_first is None:
arrive = (f"The shape's make targets ({targets}) arrive only with "
"that merge")
joint = ", and"
meanwhile = ("`python3 scripts/bootstrap.py` is what `make "
"bootstrap` runs")
until = "before both merges are made"
else:
listed = _listed([f"`{name}`" for name in GNU_MAKE_READS])
arrive = (
f"GNU make at the root reads the source's `{reads_first}`, the "
f"first of {listed} it finds, so the shape's make targets "
f"({targets}) reach `make` there only once `{reads_first}` has "
"them, or includes a Makefile that does")
joint = "; and"
meanwhile = ("`python3 scripts/bootstrap.py` is what the shape's "
"`make bootstrap` runs")
until = f"until then, and then `python3 {BOOTSTRAP}` runs it"
if script is None:
return (f"{merge}. The shape's make targets ({targets}) arrive only "
"with that merge; until then `python3 scripts/bootstrap.py` "
"is what `make bootstrap` runs")
return (f"{merge}. The shape's make targets ({targets}) arrive only with "
f"that merge, and the root's {BOOTSTRAP} is the source's "
return f"{merge}. {arrive}; until then {meanwhile}"
return (f"{merge}. {arrive}{joint} the root's {BOOTSTRAP} is the source's "
f"own until {script} is merged into it as well, so neither `make "
f"bootstrap` nor `python3 {BOOTSTRAP}` runs the shape's "
"bootstrap before both merges are made")
f"bootstrap {until}")


def _attributes_link_follow_up(intended: str, actual: str,
Expand Down
16 changes: 11 additions & 5 deletions templates/assembly-root/AGENTS-shape.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,17 @@ ruled and measured on the MedxEHR conversion: `contracts/` moved to the spec
leg, and the code leg's tooling now takes `CONTRACTS_DIR` from the environment
rather than each script guessing at `../`. Where this root was ADOPTED, the
shape's Makefile exports `CONTRACTS_DIR` once, as the `contracts/` of the spec
leg at the path `project.yaml` mounts it, and `make contracts-dir` prints it.
Where the source kept a Makefile of its own, that block waits in
`shape/Makefile` until the two are merged. A SCAFFOLDED root's Makefile exports
none: its legs start from the scaffold's seed, with no `contracts/`, so
nothing reads across them yet.
leg at the path `project.yaml` mounts it, and `make contracts-dir` prints it
wherever `make` reads that Makefile. GNU make reads the first of `GNUmakefile`,
`makefile` and `Makefile` it finds, and only that one. Where the source kept a
Makefile of its own, that block waits in `shape/Makefile` until the two are
merged. Where the source kept a `GNUmakefile` or a `makefile`, beside a
`Makefile` or instead of one, `make` at the root reads the first of them, and
not the shape's Makefile unless that file includes it, so `make contracts-dir`
has no rule there until that file holds the block, or includes a Makefile that
does. A SCAFFOLDED
root's Makefile exports none: its legs start from the scaffold's seed, with no
`contracts/`, so nothing reads across them yet.

## The five commands

Expand Down
Loading
Loading