Skip to content

family.py and shape_materialize.py: init asks the scaffold's own manifest questions of family.yaml, so init, the scaffold and adopt refuse a validator that exits or answers with something that is not text, and a manifest that is not a mapping, in the same words, and pyflakes finds nothing in either file (#925, #923) - #1057

Merged
brettheap merged 2 commits into
mainfrom
l3-925-one-template-validator
Oct 20, 2026

Conversation

@brettheap

@brettheap brettheap commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Lane 3's change for #925 (U022), and for #923's U064 in scripts/family.py and scripts/shape_materialize.py only. The closing lines are at the end.

What changed

#925 (U022): one set of manifest questions, for project.yaml and family.yaml

scripts/family.py kept its own copy of every helper that asks a template's own YAML reader and validator about the values a manifest will hold (family_validator through refuse_unholdable_manifest). The scaffold and adopt-project.py ask the same questions through scripts/shape_materialize.py (manifest_validator through refuse_manifest_values). The two copies had drifted: #817's whole-manifest words reached only one of them (#929), and only the family's copy guarded against a validator that exits or answers with something that is not text. The issue's Direction is now done: there is one implementation, in shape_materialize.py, and family.py imports it.

  • TemplateManifest (in shape_materialize.py) holds everything that differs between the two manifests:

    • the manifest file and the templated YAML files;
    • the validator's path, and the template it is named under;
    • the module name the validator is loaded under, and what is asked of it;
    • the question put to the validator. For the project, that is _findings(manifest, policy, None); for the holder, it is Report() plus _check_envelope;
    • the code of the unusable-validator refusal;
    • whose reader and validator a sentence names (project or holder);
    • the FINDING_PLACEHOLDER and CARRIED_BY maps, and the remedies.

    PROJECT_MANIFEST describes the project's manifest. family.HOLDER_MANIFEST describes the holder's.

  • Signatures. Each shared function takes a manifest_kind argument, which defaults to the project's (it is keyword-only on public functions). So every public name and signature in shape_materialize is unchanged, and scaffold-project.py and adopt-project.py are not touched.

    • refuse_manifest_values calls manifest_value_problems exactly as it did before. Tests that point it at another template with functools.partial still work.
    • refuse_manifest_problems is the raising half of it, split out so that init can raise it with its own remedies.
  • family.py keeps its names. family_validator, family_manifest_misreads, family_manifest_invalid and refuse_unholdable_manifest now call the shared code. family.py still owns its own words: MANIFEST_FLAGS, FINDING_PLACEHOLDER, CARRIED_BY, the three remedies and KIND_REMEDIATIONS.

  • Order. The questions are asked in the same place as before: cmd_init asks them before the landing, the plan or any remote exists, and under --dry-run alike. Each validator is still loaded from its own template, and no shape copy is touched.

  • The loaded-validator cache is now shape_materialize's, shared by both manifests and keyed by the validator file. family_module() in tests/test_family_init_values.py empties it. Under tmp_path_retention_policy=failed, a test's tmp_path can be the path a test before it used, and the family's rows used to get a fresh cache with each fresh module.

Which words survive where the two copies differed

The words for every value are unchanged for all three tools. I checked this with an equivalence battery, which ran origin/main's helpers and this branch's in separate processes over the same inputs:

  • 51 values against every flag;
  • both the shipped templates and copies that write every placeholder bare;
  • reader-refused lines, comment lines and broken validators.

Of 12,024 answers, 11,724 are identical, and every one of family.py's answers is identical. The 300 that differ are all on the project's side, and all come from a broken copy of the standard. In each case the family's guard is the one that survives, for both manifests:

A template whose... project.yaml on main Now, for both manifests
validator calls sys.exit while it loads the run ended with the validator's status and printed no refusal <kind>-validator-unusable: "could not be loaded: SystemExit: 3"
validator's question calls sys.exit(0) the scaffold ended with exit 0, having made and said nothing "exited when it was asked: SystemExit: 0"
validator answers with a finding that is not text a traceback (AttributeError: 'int' object has no attribute 'startswith') "answered with a finding that is not text: its type is int"
validator answers with no list a traceback (TypeError) "raised when it was asked: TypeError: 'NoneType' object is not iterable"
project.yaml is not a mapping manifest-validator-unusable, "raised when it was asked: AttributeError: 'list' object has no attribute 'get'" <prefix>-manifest-invalid: "the manifest this run would write is not a mapping, which is what the project's own YAML reader makes of it."

Why the family's words win:

RV1057 re-ran the battery with seven rows of its own: 12,516 answers, 12,194 identical, and the 322 that differed were the five project rows (300) and the holder's WARNING and note lines (22), which the review round restores. That battery was not re-run at cc94c15a; the probe in the review round is the check.

Every refusal code is unchanged. New rows pin the project's words for all five rows, and family rows already held its words; two new rows pin the family's whole unusable-validator refusal and its whole misread refusal byte for byte.

#923 (U064): the pyflakes warnings in these two files

  • recorded_gitlink is no longer imported into family.py. No test or tool reads it off family.py: every module.recorded_gitlink hit is in a validate-*.py template or bootstrap.py.
  • The repeated YamlError/parse_yaml imports are gone from both files.
  • family.py's importlib.util, PLACEHOLDER_RE, render and _unread_sentences imports are gone too, since only the old copy used them.

Review round

RV1057's review of 556a65b4 was NOT READY on one MUST. The round is one commit on top, cc94c15a, pushed as a fast-forward.

  • MUST-1, done. _validator_notes dropped a line that starts with WARNING or note for both manifests. That filter is the project's: its validator's own main counts those lines as notes. The holder's own main fails on every entry in report.findings (validate-family.py), and main's family copy refused every one. So a holder validator whose only finding was a WARNING line went through init, and now it does not.
    • TemplateManifest.passed_over (shape_materialize.py:5126) holds the prefixes of the lines that are not findings. It is ("WARNING", " note ") in PROJECT_MANIFEST (:5153) and () in family.HOLDER_MANIFEST (family.py:3578). _validator_notes tests note.startswith(manifest_kind.passed_over) (:5515), which is never true of ().
    • tests/test_family_init_values.py has a new row, test_a_holder_validator_whose_only_finding_is_a_warning_is_refused. A holder validator whose only finding is WARNING family-id: the id is wrong (or the note line) is refused as family-manifest-invalid, with that whole line as the sentence.
    • tests/test_scaffold_manifest_values.py has a new project row, test_a_project_validators_warnings_and_notes_are_notes_and_no_finding. A WARNING and a note line are still passed over, and a FINDING beside them is still one.
    • RV1057's warnprobe.py, each tree in its own process: at 556a65b4 it printed PASSED: no refusal; init would go on and write the holder; at cc94c15a it prints REFUSED family-manifest-invalid :: WARNING family-id: the id is wrong, as main did.
    • This restored guard is the one other change since the first push. The five rows in the table above are the only intended behaviour change of the whole pull request.
  • NIT-2, done. The docstrings of manifest_misreads, manifest_invalid, _validator_notes, manifest_judged and manifest_value_problems named the project where the function now serves either manifest. They say "the manifest of manifest_kind" (the project's, by default).
  • NIT-1, skipped on purpose. The not-a-mapping sentence keeps the family's words, as lane 5's review of family.py init: where the holder's own YAML reader refuses the manifest it would write, family-manifest-invalid names the value on the line it refuses, --org is '@x', and gives the reader's remedy, not the validator's (#929) #960 (NIT 3) settled; lane 3 files the follow-up.
  • The Tests section says that test_chain_pin_sources.py, test_reader_refusals_named.py, test_adopt_plan_yaml_quoting.py and test_scaffold_leg_path_collisions.py now pass at the head (RV1057's run), and gives this round's runs.

Left out of scope

Tests

Each file ran alone, with a stub gh first on PATH that exits 97, GH_TOKEN=bogus, an empty GH_CONFIG_DIR and EMAIL set:

timeout 2400 /usr/bin/python3 -m pytest -q -p no:cacheprovider --basetemp=<scratch>/bt-<label> -o tmp_path_retention_policy=failed <file>

At this head, cc94c15a (on main 1a47ed07), this review round's runs:

File Exit Result
tests/test_family_init_values.py 0 110 passed, 19 skipped in 76.99s
tests/test_family_init_yaml_words.py 0 9 passed in 1.37s
tests/test_scaffold_manifest_values.py 0 112 passed in 94.10s
tests/test_adopt_manifest_values.py 0 79 passed in 180.97s
tests/test_reader_refusals_named.py 0 44 passed in 20.12s
tests/test_no_auto_maintenance.py 0 60 passed in 10.46s

At the first head, 556a65b4 (W925f's runs, and RV1057's where marked):

File Exit Result
tests/test_chain_pin_sources.py (RV1057) 0 52 passed
tests/test_reader_refusals_named.py (RV1057) 0 44 passed
tests/test_adopt_plan_yaml_quoting.py (RV1057) 0 517 passed in 505.94s
tests/test_scaffold_leg_path_collisions.py (RV1057) 0 104 passed
tests/test_cli_reference.py 0 23 passed in 32.85s
tests/test_agents_md_codes.py 0 47 passed in 2.27s
tests/test_update_add_codes_documented.py 0 6 passed in 0.49s
tests/test_repo_hygiene.py 0 100 passed in 2.57s
tests/test_shape_doctor.py 0 445 passed, 3 skipped in 680.61s
  • The four files RV1057 ran (test_chain_pin_sources.py, test_reader_refusals_named.py, test_adopt_plan_yaml_quoting.py, test_scaffold_leg_path_collisions.py) passed only at the old base b0babe18 before the rebase, and now pass at 556a65b4. The rows of this round change no refusal code or word, so the first-head table stands for the files not re-run at cc94c15a; GitHub's CI runs the whole suite on this PR.
  • The 19 skips in test_family_init_values.py are by design: 16 rows (:229) and 3 rows (:301) run only against a template that writes the placeholder bare, and skip on one that quotes it.
  • The 3 skips in test_shape_doctor.py run only on a Windows host (:10489).

pyflakes 4.0.0, /usr/bin/python3 -m pyflakes scripts/family.py scripts/shape_materialize.py:

  • at this head, cc94c15a: no output, exit 0;
  • at main 1a47ed07: U064's five warnings (family.py:181, two at family.py:198, two at shape_materialize.py:4971), exit 1.

Mutants

Each mutant breaks one guard of the shared code in scripts/shape_materialize.py. Then a row that drives family.py init and a row that drives the scaffold run against it, and the file is put back byte for byte, checked by sha256. A baseline M0 ran every row first on the unbroken file: all 17 passed, and each selected at least one test.

Mutant Verdict Row Result
M1 load catches Exception only KILLED tests/test_family_init_values.py -k 'test_a_validator_that_cannot_be_asked_is_refused_by_name and exits-on-import' 1 failed, 126 deselected in 0.61s
M1 load catches Exception only KILLED tests/test_family_init_values.py -k 'test_a_validator_that_cannot_be_asked_says_so_in_the_holders_words' 1 failed, 126 deselected in 0.58s
M1 load catches Exception only KILLED tests/test_scaffold_manifest_values.py -k 'test_a_validator_that_cannot_be_asked_is_a_named_refusal_not_a_traceback and exits-at-import' 1 failed, 110 deselected in 0.66s
M1 load catches Exception only KILLED tests/test_scaffold_manifest_values.py -k 'test_a_validator_that_cannot_be_asked_says_so_in_the_projects_words' 1 failed, 110 deselected in 0.30s
M2 ask lets SystemExit through KILLED tests/test_family_init_values.py -k 'test_a_validator_that_cannot_be_asked_is_refused_by_name and exits-when-asked' 1 failed, 126 deselected in 0.65s
M2 ask lets SystemExit through KILLED tests/test_family_init_values.py -k 'test_a_validator_that_exits_is_a_refusal_at_the_command_line_and_not_a_zero' 1 failed, 126 deselected in 0.64s
M2 ask lets SystemExit through KILLED tests/test_scaffold_manifest_values.py -k 'test_a_validator_that_cannot_be_asked_is_a_named_refusal_not_a_traceback and exits-when-asked' 1 failed, 110 deselected in 0.69s
M3 no check that each finding is text KILLED tests/test_family_init_values.py -k 'test_a_validator_that_cannot_be_asked_is_refused_by_name and finding-is-not-text' 1 failed, 126 deselected in 0.67s
M3 no check that each finding is text KILLED tests/test_scaffold_manifest_values.py -k 'test_a_validator_that_cannot_be_asked_is_a_named_refusal_not_a_traceback and finding-is-not-text' 1 failed, 110 deselected in 0.75s
M4 no not-a-mapping sentence KILLED tests/test_family_init_values.py -k 'test_a_manifest_that_is_not_a_mapping_is_a_sentence_and_not_a_trace' 1 failed, 126 deselected in 0.80s
M4 no not-a-mapping sentence KILLED tests/test_family_init_yaml_words.py -k 'test_a_manifest_that_is_not_a_mapping_keeps_its_kind_and_remedy' 1 failed, 8 deselected in 0.27s
M4 no not-a-mapping sentence KILLED tests/test_scaffold_manifest_values.py -k 'test_a_manifest_that_is_not_a_mapping_is_said_and_not_a_validator_defect' 1 failed, 110 deselected in 0.81s
M5 the reader's refusal takes its own code KILLED tests/test_family_init_yaml_words.py -k 'test_init_names_org_with_the_readers_remedy' 4 failed, 5 deselected in 1.23s
M5 the reader's refusal takes its own code KILLED tests/test_reader_refusals_named.py -k 'test_the_scaffold_names_org_with_the_readers_remedy' 2 failed, 42 deselected in 0.53s
M6 the validator's words lose own KILLED tests/test_family_init_yaml_words.py -k 'test_a_finding_of_the_validator_keeps_the_validators_remedy' 1 failed, 8 deselected in 0.22s
M6 the validator's words lose own KILLED tests/test_scaffold_init_empty_values.py -k 'test_a_flag_passed_empty_is_refused_by_name and family' 3 failed, 1 passed, 32 deselected in 1.10s
M6 the validator's words lose own KILLED tests/test_scaffold_init_empty_values.py -k 'test_a_flag_passed_empty_is_refused_by_name and scaffold' 6 failed, 3 passed, 27 deselected in 2.08s

Every mutant is killed both by an init row and by a scaffold row.

This round's two mutants, each on scripts/shape_materialize.py, which was put back and compared with cmp:

Mutant Verdict Row Result
R1 the hard-coded tuple is restored: note.startswith(("WARNING", " note ")) for both manifests KILLED tests/test_family_init_values.py -k warning (test_a_holder_validator_whose_only_finding_is_a_warning_is_refused, both lines) 2 failed, 127 deselected in 0.37s
R2 PROJECT_MANIFEST passes over nothing: passed_over=() KILLED tests/test_scaffold_manifest_values.py -k warnings_and_notes 1 failed, 111 deselected in 0.37s

Merges

The review round (cc94c15a) changes only scripts/family.py, scripts/shape_materialize.py, tests/test_family_init_values.py and tests/test_scaffold_manifest_values.py, the same four files as the first commit, and its merge-tree has not been re-run yet. The results below are at the first head, 556a65b4 (W925f's and RV1057's runs).

git merge-tree --write-tree of 556a65b4 with:

Closes #925
Part of #923

Lane: openreposhape-3 (openRepoShape-3)
🤖 Generated with Claude Code

…nifest questions of `family.yaml`, so `init`, the scaffold and adopt refuse a validator that exits or answers with something that is not text, and a manifest that is not a mapping, in the same words, and pyflakes finds nothing in either file (#925, #923)

`family.py` kept a copy of every helper that asks a template's own YAML
reader and validator about a manifest's values, and the two copies had
drifted (#925, #459's U022). They are one implementation now, in
`shape_materialize.py`, and `family.py` imports it:

- `TemplateManifest` holds what differs between `project.yaml` and
  `family.yaml`: the file, the validator and the question put to it, the
  code of the unusable-validator refusal, whose reader and validator a
  sentence names, the finding and carried-by maps, and the remedies.
  `PROJECT_MANIFEST` is the project's; `family.HOLDER_MANIFEST` the
  holder's.
- Every shared function takes `manifest_kind`, keyword-only where it is
  public, defaulting to the project's, so the scaffold, adopt and every
  public name and signature of `shape_materialize` are unchanged.
  `refuse_manifest_problems` is the refusal `refuse_manifest_values` raises,
  split out so `init` raises it with its own remedies.
- `family.py` keeps its names (`family_validator`,
  `family_manifest_misreads`, `family_manifest_invalid`,
  `refuse_unholdable_manifest`) as calls of the shared ones.

Every word `init` says is unchanged, and so is every word the scaffold and
adopt say of a value. Where the copies differed, the family's guards
survive for both: a validator that exits as it loads or when asked, one
that answers with something that is not text or not a list, and a
manifest that is not a mapping. The project's copy let the first two end
the run with the validator's status, gave the third a traceback, and
called the validator broken for the fourth.

U064 (#923): `recorded_gitlink`, which nothing read off `family.py`, and
the repeated `YamlError`/`parse_yaml` imports in both files are gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@brettheap brettheap added this to the v1.2.0 milestone Oct 20, 2026
@sourcery-ai

sourcery-ai Bot commented Oct 20, 2026

Copy link
Copy Markdown

Reviewer's Guide

The PR consolidates the duplicated project and family manifest-questioning logic into shape_materialize's parameterized implementation, making init, scaffold, and adopt consistently reject unusable validators and malformed manifests with owner-specific wording, while removing pyflakes warnings and expanding regression coverage.

Sequence diagram for manifest validation and refusal

sequenceDiagram
    participant Command as init scaffold or adopt
    participant Shared as shape_materialize
    participant Reader as manifest YAML reader
    participant Validator as template validator
    participant Refusal as Refusal
    Command->>Shared: manifest_value_problems(...)
    Shared->>Reader: parse_yaml(rendered manifest)
    alt manifest is not a mapping
        Reader-->>Shared: non-mapping result
        Shared->>Refusal: refuse_manifest_problems(...)
    else manifest is readable
        Shared->>Validator: manifest_validator(...)
        Validator-->>Shared: validator module
        Shared->>Validator: findings(...)
        alt validator exits, raises, or returns non-text finding
            Validator-->>Shared: unusable response
            Shared->>Refusal: _validator_unusable(...)
        else validator returns findings
            Shared->>Refusal: refuse_manifest_problems(...) if needed
        end
    end
Loading

File-Level Changes

Change Details Files
Unified project and family manifest validation behind a parameterized shared implementation.
  • Introduced TemplateManifest configurations for project.yaml and family.yaml, including validators, reader wording, placeholder mappings, and remedies.
  • Moved family validation helpers to delegate to shape_materialize while preserving family-facing names and behavior.
  • Added shared handling for validator load/request failures, SystemExit, non-text findings, and non-mapping manifests.
  • Shared the validator cache across both manifest types and keyed it by validator path.
scripts/shape_materialize.py
scripts/family.py
Standardized refusal wording and behavior for malformed manifests and unusable validators.
  • Preserved existing public project helper signatures while adding keyword-only manifest selection.
  • Ensured reader refusals, validator findings, and remediation codes identify the correct manifest owner.
  • Kept validation ordering before creation, planning, remotes, and during dry runs.
scripts/shape_materialize.py
scripts/family.py
Removed unused imports and added regression coverage for shared behavior and failure guards.
  • Removed recorded_gitlink, duplicate YAML imports, and imports used only by the deleted family implementation.
  • Added tests for validator exits, non-text or missing findings, non-mapping manifests, shared helper identity, cache isolation, and exact refusal wording.
  • Verified pyflakes reports no warnings in either changed script.
scripts/family.py
tests/test_family_init_values.py
tests/test_family_init_yaml_words.py
tests/test_scaffold_manifest_values.py

Assessment against linked issues

Issue Objective Addressed Explanation
#925 Replace the duplicated project.yaml and family.yaml manifest-reader and validator helpers with one shared implementation, parameterized by each template’s manifest, validator, questions, and wording, without touching shape copies. ✅
#925 Preserve the existing refusal codes and wording, ask each validator from its own template before anything is created, and keep the family-specific remedies and behavior for family.yaml. ✅

Possibly linked issues

  • #unknown: The PR routes family init through shared manifest checks for holder.yaml, refusing validator findings and YAML misreads before creation.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@brettheap

Copy link
Copy Markdown
Contributor Author

(lane openRepoShape-3, 2026-10-20T11:27:59Z) RV1057, lane 3's independent review of #1057 at 556a65b: NOT READY, one MUST.

MUST-1. scripts/shape_materialize.py:5505: _validator_notes drops findings that start with "WARNING" or " note " for BOTH manifests. That filter is the project's: its validator's main counts those as notes. The holder's main fails on any entry in report.findings (templates/family-root/scripts/validate-family.py:3979), and main's family copy refused every one. Probe, each tree in its own process, with a holder validator whose only finding is 'WARNING family-id: the id is wrong':

  main 1a47ed07: REFUSED family-manifest-invalid :: WARNING family-id: the id is wrong
  head 556a65b4: PASSED: no refusal; init would go on and write the holder

No shipped validator reaches it (Report.finding always writes "FINDING ..."), but it is a lost guard that lets a manifest through, and the body's "the only answers that change are the five rows" is not true while it stands. Fix: add a TemplateManifest field (e.g. passed_over), ("WARNING", " note ") in PROJECT_MANIFEST and () in HOLDER_MANIFEST (family.py:3580), and test note.startswith(manifest_kind.passed_over). Add a row in tests/test_family_init_values.py: a holder validator whose only finding is a WARNING line is refused as family-manifest-invalid, with that line as the sentence.

NIT-1. shape_materialize.py:5486: the not-a-mapping sentence carries the invalid-value remedy ("choose a value ... accepts"), but no typed value reaches it: of 51 values on the shipped and bare templates, none did; only broken list templates do. The family's words were settled by #960's NIT 3, so keep them here; a follow-up could give it the defect-of-the-standard remedy for both manifests.
NIT-2. Docstrings still say project only: shape_materialize.py:5295, :5362, :5515.

Equivalence: W925's battery plus 7 rows of mine, old against new in separate processes: 12516 answers, 12194 identical, 322 differ: W925's five project rows (300) and MUST-1 (22). None on a shipped or bare template.
Cache: keyed by template_dir / the validator's own file, so the two manifests and two templates never share an entry. In one process, a real family.py init --dry-run loads only the family-root validator; holder then project gives 2 modules; one directory holding both validators gives 2 keys. The same path after its file changed still answers from the cache, as main did per copy; family_module() empties it in tests.
Names: public names and signatures of shape_materialize are kept (manifest_kind added, defaulted). refuse_manifest_values still calls manifest_value_problems(values, policy, what, misreads=misreads). Nothing reads a removed name off family.py. cmd_init is untouched. pyflakes: head clean; main shows U064's five.
Tests at 556a65b, one file at a time under the stub gh: test_chain_pin_sources 52 passed, test_reader_refusals_named 44 passed, test_adopt_plan_yaml_quoting 517 passed, test_scaffold_leg_path_collisions 104 passed, test_family_init_values 108 passed 19 skipped, test_scaffold_manifest_values 111 passed.
Mutants: baseline 8 rows pass; M2 3/3 and M4 3/3 KILLED; M7 (mine, _unread_sentences ignores carried_by) KILLED by test_init_names_org_with_the_readers_remedy and by test_the_scaffold_names_org_with_the_readers_remedy. File restored, sha256 e2f2a23a0dfa.
Merges: merge-tree clean with origin/main 8a05721 (tree 2fbc1d8b), #1041, #1051, #1052, #1058 and #1025. No open PR touches the five files.
Body: closingIssuesReferences [925]; ends Closes #925 / Part of #923 / Lane / robot line. Update the Tests section: the four files that passed only before the rebase now pass at the head.

Lane: openreposhape-3 (openRepoShape-3)

…NG or note line is a finding again, so `init` refuses it as `family-manifest-invalid` as it did before #925, and the docstrings say the manifest of `manifest_kind` where they said the project (#925)

RV1057's MUST-1 on #1057. `_validator_notes` dropped a line that starts
with "WARNING" or "  note " for BOTH manifests. That filter is the
project's: its validator's own `main` counts those lines as notes. The
holder's own `main` fails on every entry of `report.findings`, and main's
family copy refused every one, so the merged code let a holder validator
whose only finding is a WARNING line through `init`.

- `TemplateManifest.passed_over` holds the prefixes of the lines that are
  not findings: `("WARNING", "  note ")` in `PROJECT_MANIFEST` and `()` in
  `HOLDER_MANIFEST`; `_validator_notes` tests
  `note.startswith(manifest_kind.passed_over)`, which is never true of `()`.
- tests/test_family_init_values.py: a holder validator whose only finding
  is `WARNING family-id: the id is wrong` (or a `  note ` line) is refused
  as `family-manifest-invalid`, with that line as the sentence.
- tests/test_scaffold_manifest_values.py: a project validator's WARNING and
  `  note ` lines are still notes, and a FINDING beside them is still one.
- NIT-2: the docstrings of `manifest_misreads`, `manifest_invalid`,
  `_validator_notes`, `manifest_judged` and `manifest_value_problems` name
  the manifest of `manifest_kind` where they said the project.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

@brettheap

Copy link
Copy Markdown
Contributor Author

(lane openRepoShape-3, 2026-10-20T11:57:18Z) RV1057d, lane 3's independent delta review of #1057's fix round at cc94c15 (one commit over 556a65b): READY. MUST-1 is fixed; one new NIT.

MUST-1, fixed. TemplateManifest.passed_over (scripts/shape_materialize.py:5126) is ("WARNING", " note ") in PROJECT_MANIFEST (:5153) and () in HOLDER_MANIFEST (scripts/family.py:3578), and _validator_notes tests note.startswith(manifest_kind.passed_over) (:5515). Each matches its validator's own main: validate-manifest.py:1398 passes over exactly those two prefixes, and validate-family.py:3977-3985 fails on any entry of report.findings. RV1057's warnprobe.py, unchanged, at cc94c15: REFUSED family-manifest-invalid :: WARNING family-id: the id is wrong (main 1a47ed0's answer; 556a65b had passed it). In the diff's neighbours nothing else treats the two manifests alike where their mains differ: family passes no ignore, _sentences_of refuses any remaining line (right for both mains), both mains refuse a non-mapping, and only the project asks manifest_judged.

NIT-3 (the rest of NIT-2). scripts/shape_materialize.py:5389-5394, manifest_invalid's docstring, still says a WARNING "is a note and never a refusal, exactly as the project's own exit code treats it, and so is its note", plus "as the project asks it" and "the project's own YAML reader". family_manifest_invalid calls it with HOLDER_MANIFEST, where a WARNING is now a refusal, so for the holder the paragraph is false. Fix here or in a follow-up: "A line manifest_kind.passed_over names is a note and never a refusal, as that validator's own exit code treats it: the project's WARNING and note (#990); the holder passes over none (#925)", and make :5389-5390 neutral.

New rows: test_a_holder_validator_whose_only_finding_is_a_warning_is_refused[warning|note] pins passed_over == (), family_manifest_invalid == [line] and Refusal.detail == line (the whole line), code family-manifest-invalid. test_a_project_validators_warnings_and_notes_are_notes_and_no_finding pins _validator_notes == [the whole FINDING line], manifest_invalid's whole sentence, manifest_judged {PROJECT_ID}, and [] / frozenset() with the finding ignored.
Tests at cc94c15, one file at a time under the stub gh: test_family_init_values 110 passed 19 skipped; test_scaffold_manifest_values 112 passed; test_no_auto_maintenance 60 passed. pyflakes on family.py and shape_materialize.py: clean.
Mutants (baseline 2 + 1 passed first): R1 the hard-coded tuple again KILLED (2 failed: [warning], [note]); R2 PROJECT_MANIFEST passed_over=() KILLED (1 failed); R3 (mine) _validator_notes filters nothing KILLED by the project row's behaviour assert (1 failed). File restored, cmp OK, sha256 414bbbec2d5640b5.
Merges: merge-tree --write-tree of cc94c15 is clean with origin/main 833af14 (tree 27eeed23), #1051 93dacf8 (fe13d14f) and #1052 dff9aac (4eed65a4). The branch is on 1a47ed0, so it is behind main (#954, #1041) and needs update-branch before landing.
Body: closingIssuesReferences [925]; it ends Closes #925 / Part of #923 / Lane / robot line; no other closing keyword; it names the restored guard as the one other change. Its Merges paragraph is stale (merge-tree "not re-run yet", results at 556a65b, "the same four files as the first commit", which touched five). cc94c15's trailer names Sonnet 5.5; the squash message sets the final one.

Lane: openreposhape-3 (openRepoShape-3)

@brettheap
brettheap marked this pull request as ready for review October 20, 2026 13:19
@sourcery-ai

sourcery-ai Bot commented Oct 20, 2026

Copy link
Copy Markdown

Sorry @brettheap, you've used your own review budget of 250,000 diff characters for the last 7 days.

You can request another review in 3 days and 11 hours by commenting @sourcery-ai review. Upgrade to get a review now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant