← All Pull Requests

Review component quality before placement and cache optional MPN models #3

Closed opened by John Lauer 2026-09-16

Boards can reach placement with incomplete component identities, weak 3D models or board-specific catalog placeholders. This adds an early components step before models and placement, and a working components --etch ask|off|on command that inventories the actual PCB into a reusable AI review register.

The printed workflow requires wiki-first sourcing, then adom-parts-search/manufacturer/distributor sources and Pup for browser-only downloads. It separates global reusable parts from project-only definitions; reviews dimensions, terminals, pin-1, materials and top/bottom/oblique views; prefers redistribution-permitted manufacturer assets; and keeps restricted CAD private for comparison. Extraction, recolouring and etching do not clear source restrictions. Optional MPN marking preserves the plain STEP and caches additional marked STEP/GLB/thumbnails with hashes, parameters, provenance and native-view evidence. ask is not consent.

The command preserves review notes on rerun, marks reviews stale if properties/footprint/model references change, and leaves malformed existing JSON untouched. It is an AI-owned review register, not an automated identity, geometry or rights certification. It cannot detect an external STEP file changing behind an unchanged path; content hashes in the review cache must be checked by the reviewing AI.

Validation: rebuilt the release binary; cargo test --workspace passed; the CLI integration test passed on a disposable run of the 149-footprint ESC, checking early plan order, complete unique references, default ask, explicit on/off, preference persistence, review preservation, stale-source invalidation and malformed-register preservation. No desktop or delivered run was modified by these tests. Test command: python3 tests/component-workflow.py target/release/adom-aiflow /home/adom/aiflow-esc-astra/final-native/esc-g431-astra.kicad_pcb /home/adom/aiflow-esc-astra/spec.json.

Evidence: https://wiki.adom.inc/adom/esc-g431/files/docs/astra/component-quality/README.md . Three independent nominal models and optional marked variants were rendered in native KiCad. Manufacturer drawings/private source CAD provided dimensional checks; the Ultra Librarian download stopped at CAPTCHA, so no completed UL comparison is claimed.

Supersedes PR #2. The current wiki source was re-read before submission; the main.rs diff contains only this command and workflow printing. The published source snapshot still differs from the installed release command surface, so the isolated rebuilt binary was tested without replacing the installed production tool. Please integrate and publish through the maintainer's complete release tree for all users.

Diff Skip to comments (1)

--- a/crates/adom-aiflow/src/main.rs+++ b/crates/adom-aiflow/src/main.rs@@ -1,1094 +1,1138 @@⋯ 27 unchanged lines ⋯ enum Cmd {     /// Start a run: copies the board, stamps the prompt time     Start { #[arg(long)] board: String, #[arg(long)] spec: String, #[arg(long)] engine: String, #[arg(long)] prompt_time: Option<String>, #[arg(long)] target: Option<String>, #[arg(long)] remote_board: Option<String>, #[arg(long)] force: bool },+    /// Inventory every board reference for the early component quality review; optional MPN marking is an explicit preference.+    Components { #[arg(long, value_parser = ["ask", "off", "on"])] etch: Option<String> },     /// The stages this board needs and who can take each     Plan,     /// Record who takes a stage: route=ai pour=binary⋯ 459 unchanged lines ⋯             let _ = TURN.set((dir.clone(), idx));             ok(&format!("run started for engine {engine} at {}; the clock started at the prompt, {pt}", dir.display()), &[format!("Next: adom-aiflow plan --run {} (the stages this board needs and who can take each).", dir.display()), "Then name the run's page once: `report --page <owner/slug> --user-machine <the human's box> --push --refresh` (or `--surface webview` when you run inside Adom Hydrogen and the human watches a workspace tab). From then on the run's own README (steps, times, returns, every step's clip and screenshots) rebuilds, pushes and reloads in the human's browser at every step change, by itself.".into(), "Record the paste time exactly: --prompt-time 2026-09-14T15:04:05Z when you start late.".into()]);         }+        Cmd::Components { etch } => {+            thread(&cli);+            let mut r = load_run(&cli);+            let b = board_of(&r);+            let path = dir.join("components.json");+            let old: Value = if path.exists() {+                let text = std::fs::read_to_string(&path).unwrap_or_else(|e| err(&format!("read components register: {e}"), &[]));+                serde_json::from_str(&text).unwrap_or_else(|e| err(&format!("invalid components register (left untouched): {e}"), &[]))+            } else { json!({}) };+            let preference = etch.as_deref().or_else(|| old["mpnMarking"].as_str()).unwrap_or("ask");+            let mut entries = Vec::new();+            for fp in b.root.find_all("footprint") {+                let props: BTreeMap<String, String> = fp.find_all("property").filter_map(|p| Some((p.atom(1)?.into(), p.atom(2)?.into()))).collect();+                let Some(reference) = props.get("Reference") else { continue; };+                let mut entry = old["components"].as_array().and_then(|a| a.iter().find(|e| e["reference"].as_str() == Some(reference.as_str()))).cloned().unwrap_or_else(|| json!({"reference": reference, "review": {"status": "pending", "classification": "unreviewed", "wikiPage": null, "geometryEvidence": [], "visualEvidence": [], "redistributionEvidence": [], "variants": []}}));+                let models = json!(fp.find_all("model").filter_map(|n| n.value()).collect::<Vec<_>>());+                if entry.get("properties").is_some() && (entry["properties"] != json!(props) || entry["footprint"] != json!(fp.value()) || entry["modelReferences"] != models) {+                    entry["review"]["status"] = json!("stale");+                    entry["review"]["staleReason"] = json!("Board properties, footprint or model references changed; re-review retained evidence.");+                }+                entry["properties"] = json!(props);+                entry["footprint"] = json!(fp.value());+                entry["modelReferences"] = models;+                entries.push(entry);+            }+            let report = json!({"schemaVersion": 1, "board": b.path, "mpnMarking": preference, "components": entries,+                "qualityGate": "AI review required; this inventory does not certify identities, geometry, redistribution rights or native rendering"});+            std::fs::write(&path, serde_json::to_string_pretty(&report).unwrap()).unwrap_or_else(|e| err(&format!("write component register: {e}"), &[]));+            r.data["components"] = json!({"register": path, "mpnMarking": preference, "reviewStatus": "pending"});+            r.save().unwrap_or_else(|e| err(&e, &[]));+            let flow: Value = serde_json::from_str(include_str!("../../../flows/board.json")).unwrap();+            let step = flow["steps"].as_array().unwrap().iter().find(|s| s["name"] == "components").unwrap();+            let mut hints: Vec<String> = step["workflow"].as_array().unwrap().iter().filter_map(Value::as_str).map(str::to_owned).collect();+            hints.push(format!("MPN marking preference: {preference}. 'ask' means offer the option, not consent; 'off' preserves plain models; 'on' requests additional reviewed marked variants. Existing review notes are preserved on rerun; re-review when source properties or geometry changes."));+            ok(&format!("component register: {} ({} references); AI review is pending", path.display(), report["components"].as_array().unwrap().len()), &hints);+        }         Cmd::Plan => {             let mut r = load_run(&cli);             // the flow is a file: the steps in order, who does each, what the binary offers, what comes later⋯ 20 unchanged lines ⋯             lines.push(format!("flow \"{}\": {}", flow["name"].as_str().unwrap_or(""), flow["scope"].as_str().unwrap_or("")));             for st in flow["steps"].as_array().cloned().unwrap_or_default() {                 lines.push(format!("  step {:<10} {:<7} {}", st["name"].as_str().unwrap_or(""), st["who"].as_str().unwrap_or(""), st["does"].as_str().unwrap_or("")));+                if let Some(workflow) = st["workflow"].as_array() {+                    for (i, action) in workflow.iter().filter_map(Value::as_str).enumerate() {+                        lines.push(format!("    {}. {}", i + 1, action));+                    }+                }             }             lines.push(format!("  later: {}", flow["later"].as_array().map(|a| a.iter().filter_map(|x| x["name"].as_str()).collect::<Vec<_>>().join(", ")).unwrap_or_default()));             lines.push("stages the binary offers:".into());⋯ 567 unchanged lines ⋯         }     } }+--- a/flows/board.json+++ b/flows/board.json@@ -1,162 +1,186 @@⋯ 9 unchanged lines ⋯       "record": "nothing on screen yet: the clip is the board opening on the test box (capture open) and the spec being read"     },     {+      "name": "components",+      "who": "ai",+      "does": "Audit wiki component identity, reusable CAD quality and redistribution evidence before placement; offer cached optional MPN-marked variants.",+      "binary": [+        "components"+      ],+      "workflow": [+        "Inventory every reference from the actual board and schematic: manufacturer, MPN, supplier code, package, value and ratings; group repeated exact parts and record their references. Do not invent an MPN or silently substitute a similar value/package. Distinguish populated parts, DNP parts and bare copper/mechanical features; a bare test pad needs no purchased component or fictitious 3D body.",+        "Search the wiki FIRST for each exact manufacturer/MPN and supplier code; reuse the matching component page and inspect its symbol, footprint and STEP/WRL assets. A page existing is not proof its CAD bundle is complete. Record the page URL and missing assets per reference.",+        "For missing identities or assets, use adom-parts-search next, then original manufacturer and distributor websites. Use Pup to navigate and download through the automated browser when curl/fetch is blocked or the site requires JavaScript; do not treat a blocked fetch as proof the part is unavailable. Read the relevant parts-search and Pup skills for current commands.",+        "Verify manufacturer, exact ordering code, package dimensions, pin numbering and required electrical ratings against source evidence before accepting CAD or a candidate part. Preserve source URLs and provenance. Label generated or approximate models as such; never present an approximate body as a verified vendor model.",+        "Global component pages are shared resources for ALL ADOM USERS, never a board-specific BOM dump. Create one only for a distinct reusable manufacturer part or independently specified reusable custom component, after searching for duplicates. Write a part-focused page with portable verified assets and provenance, and improve existing pages. Keep board references, unresolved identities, one-off land patterns, bare copper test pads and project-only assemblies inside the board project wiki page; do not create catalog placeholders or rename a one-off feature to make it appear global.",+        "Use portable project/library model paths, preserve placement and routing, rerun kicad_model_check and inspect the saved board in native KiCad 3D. Report reference coverage, reused/created page URLs, unresolved identities and missing CAD separately. A successful download or wiki publication alone does not close a missing-model finding.",+        "Judge quality per component, not per board screenshot: publish a linked visual register with top, bottom and oblique views. Check dimensions, units, terminal count and pitch, pin-1/polarity, body/pad alignment, standoff, materials and visible details. File resolution alone is not quality. Record pass, needs-work, reference-only or unknown with evidence and limits.",+        "Prefer manufacturer CAD when the source permits the intended redistribution. Keep third-party/Ultra Librarian downloads as private reference-only inputs unless redistribution is explicitly permitted. Compare independently generated models against manufacturer drawings and permitted reference views; retain dimensional deviations and source hashes. Converting, extracting, recoloring, or etching a restricted model does not make it independently authored or license-cleared.",+        "Offer optional MPN marking using adom/adom-chip-laser or the current adom-step2glb laser-etch service. Ask whether to enable it; default to unmarked models until the user chooses. Check the shared page cache first. Preserve the plain STEP and publish an additional marked STEP, derived GLB and reviewed thumbnails only for assets eligible for redistribution. Keep the mark clear of pin-1, polarity, terminals and optical/mechanical features. Marking is an identification aid, not evidence of actual factory top-marking, especially on tiny passives.",+        "Cache reviewed artifacts on the existing global component page: source/plain STEP, optional MPN STEP, GLB, thumbnails and machine-readable provenance. Record input hash, generator/tool version, parameters, units, transforms, marking text/mode, reference evidence and review results. Cache keys must change when geometry, text or generator parameters change. Keep board-only transforms and mappings in the board project. Never claim cross-EDA parity without rendering the variant in the named native viewers."+      ]+    },+    {       "name": "models",       "who": "ai",-      "does": "every footprint on the board has its 3D model resolved (kicad_model_check): fetch the vendor STEP, build one, or fix the path, so the board renders as it will be built; a bare footprint in the 3D view is a missing model",+      "does": "audit every board component and missing symbol, footprint or 3D asset; resolve exact identities on the wiki first, then adom-parts-search and manufacturer/supplier sites (Pup when browser access is needed); publish missing component pages, repair portable model paths, and verify the native 3D board with kicad_model_check",       "binary": [         "models"       ],-      "record": "nothing to film: the model check's list and the fixes; the 3D walkthrough later is the proof"+      "record": "nothing to film: the model check's list and the fixes; the 3D walkthrough later is the proof",+      "workflow": [+        "Use the components register and its reviewed plain or explicitly selected marked variants; fix portable model paths, run kicad_model_check, then inspect the native board render. Do not substitute a model merely to make the missing-file gate pass."+      ]     },     {       "name": "placement",⋯ 137 unchanged lines ⋯   "clips": "every `step <name>` stops the previous step's clip and starts a new window recording tagged with the step, when the board is open on a test box; run.json captures[] carries one entry per clip with its step, start, stop and file, and deliver lists them; the final video is cut from these clips, one segment per step, so two engines' videos line up step for step; a return (step <name> --back --why) is a new visit and gets its own clip, tagged <step>-<visit> with the reason, so the rework is on camera and the final cut can show the loop",   "screenshots": "every step visit gets two background screenshots of the editor window, at its start and at its end (shot-<step>-<visit>-start.png, shot-<step>-<visit>-end.png), logged as artifacts, so a run's own README has a picture for every step without anyone taking one" }+--- a/SKILL.md+++ b/SKILL.md@@ -1,83 +1,100 @@⋯ 80 unchanged lines ⋯ - `finish` is the only thing that ends a run. A run without `finish` is not a result, and its minutes are still counting. - KiCad drops pour islands that touch nothing: a heat spreader drawn across dense routing on the other layer fills as fragments and reads small in `measure`. Put the copper where the layer is actually free (the analysis numbers say when it is not). - 0.1's analyses are conservative heuristics (IPC-2221 for tracks, presence, connection style and vias for pours and tabs); they say so in their output. 0.2 computes cross-sections through the filled copper.++## Component coverage before placement++The early `components` step audits component identity, library coverage and model quality before `models` and placement. Run `plan` to read its ordered sourcing workflow. Inventory every reference on the actual board and schematic, and track exact manufacturer/MPN, supplier code, package and required ratings. Reuse exact wiki component pages first; check their assets rather than assuming a page contains a complete CAD bundle. Use `adom-parts-search` next and manufacturer/distributor sites for gaps. Pup is the browser fallback for JavaScript or blocked curl/fetch downloads; read its skill for current commands.++Publish missing identified component pages with verified symbol/footprint/model assets and provenance; improve existing pages instead of duplicating them. Do not invent identities for generic land patterns, DNP parts or bare copper test pads, or silently substitute a similar part. Record unresolved identities and missing assets separately. Generated/approximate bodies must be labelled. Keep model paths portable, preserve layout, rerun `kicad_model_check`, and visibly inspect native KiCad 3D before calling model coverage complete. The AI performs this sourcing audit; these instructions do not add an automatic sourcing or component-identity gate.++Global component pages serve ALL ADOM USERS. Publish only distinct reusable manufacturer parts or independently specified reusable custom components, after duplicate checking. Keep page titles and assets part-focused and portable. Board reference mappings, unresolved identities, one-off footprints, bare copper features and project-only assemblies belong in the board project wiki page, not new component catalog entries. Do not turn project-specific placeholders into generic-looking pages merely by renaming them.++## Early component quality and optional MPN marking++Before placement, declare `step components` and run `components` with your `--run` and `--ai-thread`. It writes `components.json`, inventories every board reference and preserves review notes on rerun. This is an AI review register, not an automated geometry or licensing certification. Record wiki links, classification, dimensions, terminals, pin-1/polarity, plain/marked variants, top/bottom/oblique visual evidence and redistribution evidence. Unknowns remain unknown.++Use manufacturer drawings and permitted CAD as reference checks. Third-party downloads may be private reference-only; never redistribute them without permission, or call extracted/recolored/etched geometry independently authored. Compare independently generated geometry with dimensional and visual evidence. Publish only reusable assets on global pages; board-only definitions stay in the project.++Offer `components --etch on` or `--etch off` (initial default `ask` is not consent). Discover adom/adom-chip-laser and the current STEP engine. Keep the plain model; cache a separate MPN-marked STEP, GLB and reviewed thumbnails when enabled, with input hash, generator version/parameters, text/mode and native-render evidence. Protect pin-1, polarity and functional features. Marking is an identification aid, not a claim of real factory markings. Do not regenerate a valid shared cache, and do not claim KiCad/Fusion/Altium parity until each named viewer was checked.+--- a/skills/adom-aiflow/SKILL.md+++ b/skills/adom-aiflow/SKILL.md@@ -1,83 +1,100 @@⋯ 80 unchanged lines ⋯ - `finish` is the only thing that ends a run. A run without `finish` is not a result, and its minutes are still counting. - KiCad drops pour islands that touch nothing: a heat spreader drawn across dense routing on the other layer fills as fragments and reads small in `measure`. Put the copper where the layer is actually free (the analysis numbers say when it is not). - 0.1's analyses are conservative heuristics (IPC-2221 for tracks, presence, connection style and vias for pours and tabs); they say so in their output. 0.2 computes cross-sections through the filled copper.++## Component coverage before placement++The early `components` step audits component identity, library coverage and model quality before `models` and placement. Run `plan` to read its ordered sourcing workflow. Inventory every reference on the actual board and schematic, and track exact manufacturer/MPN, supplier code, package and required ratings. Reuse exact wiki component pages first; check their assets rather than assuming a page contains a complete CAD bundle. Use `adom-parts-search` next and manufacturer/distributor sites for gaps. Pup is the browser fallback for JavaScript or blocked curl/fetch downloads; read its skill for current commands.++Publish missing identified component pages with verified symbol/footprint/model assets and provenance; improve existing pages instead of duplicating them. Do not invent identities for generic land patterns, DNP parts or bare copper test pads, or silently substitute a similar part. Record unresolved identities and missing assets separately. Generated/approximate bodies must be labelled. Keep model paths portable, preserve layout, rerun `kicad_model_check`, and visibly inspect native KiCad 3D before calling model coverage complete. The AI performs this sourcing audit; these instructions do not add an automatic sourcing or component-identity gate.++Global component pages serve ALL ADOM USERS. Publish only distinct reusable manufacturer parts or independently specified reusable custom components, after duplicate checking. Keep page titles and assets part-focused and portable. Board reference mappings, unresolved identities, one-off footprints, bare copper features and project-only assemblies belong in the board project wiki page, not new component catalog entries. Do not turn project-specific placeholders into generic-looking pages merely by renaming them.++## Early component quality and optional MPN marking++Before placement, declare `step components` and run `components` with your `--run` and `--ai-thread`. It writes `components.json`, inventories every board reference and preserves review notes on rerun. This is an AI review register, not an automated geometry or licensing certification. Record wiki links, classification, dimensions, terminals, pin-1/polarity, plain/marked variants, top/bottom/oblique visual evidence and redistribution evidence. Unknowns remain unknown.++Use manufacturer drawings and permitted CAD as reference checks. Third-party downloads may be private reference-only; never redistribute them without permission, or call extracted/recolored/etched geometry independently authored. Compare independently generated geometry with dimensional and visual evidence. Publish only reusable assets on global pages; board-only definitions stay in the project.++Offer `components --etch on` or `--etch off` (initial default `ask` is not consent). Discover adom/adom-chip-laser and the current STEP engine. Keep the plain model; cache a separate MPN-marked STEP, GLB and reviewed thumbnails when enabled, with input hash, generator version/parameters, text/mode and native-render evidence. Protect pin-1, polarity and functional features. Marking is an identification aid, not a claim of real factory markings. Do not regenerate a valid shared cache, and do not claim KiCad/Fusion/Altium parity until each named viewer was checked.+--- a/docs/component-sourcing.md+++ b/docs/component-sourcing.md@@ -0,0 +1,18 @@+# Wiki-first component and CAD coverage++## Component coverage before placement++The early `components` step audits component identity, library coverage and model quality before `models` and placement. Run `plan` to read its ordered sourcing workflow. Inventory every reference on the actual board and schematic, and track exact manufacturer/MPN, supplier code, package and required ratings. Reuse exact wiki component pages first; check their assets rather than assuming a page contains a complete CAD bundle. Use `adom-parts-search` next and manufacturer/distributor sites for gaps. Pup is the browser fallback for JavaScript or blocked curl/fetch downloads; read its skill for current commands.++Publish missing identified component pages with verified symbol/footprint/model assets and provenance; improve existing pages instead of duplicating them. Do not invent identities for generic land patterns, DNP parts or bare copper test pads, or silently substitute a similar part. Record unresolved identities and missing assets separately. Generated/approximate bodies must be labelled. Keep model paths portable, preserve layout, rerun `kicad_model_check`, and visibly inspect native KiCad 3D before calling model coverage complete. The AI performs this sourcing audit; these instructions do not add an automatic sourcing or component-identity gate.++Global component pages serve ALL ADOM USERS. Publish only distinct reusable manufacturer parts or independently specified reusable custom components, after duplicate checking. Keep page titles and assets part-focused and portable. Board reference mappings, unresolved identities, one-off footprints, bare copper features and project-only assemblies belong in the board project wiki page, not new component catalog entries. Do not turn project-specific placeholders into generic-looking pages merely by renaming them.++## Early component quality and optional MPN marking++Before placement, declare `step components` and run `components` with your `--run` and `--ai-thread`. It writes `components.json`, inventories every board reference and preserves review notes on rerun. This is an AI review register, not an automated geometry or licensing certification. Record wiki links, classification, dimensions, terminals, pin-1/polarity, plain/marked variants, top/bottom/oblique visual evidence and redistribution evidence. Unknowns remain unknown.++Use manufacturer drawings and permitted CAD as reference checks. Third-party downloads may be private reference-only; never redistribute them without permission, or call extracted/recolored/etched geometry independently authored. Compare independently generated geometry with dimensional and visual evidence. Publish only reusable assets on global pages; board-only definitions stay in the project.++Offer `components --etch on` or `--etch off` (initial default `ask` is not consent). Discover adom/adom-chip-laser and the current STEP engine. Keep the plain model; cache a separate MPN-marked STEP, GLB and reviewed thumbnails when enabled, with input hash, generator version/parameters, text/mode and native-render evidence. Protect pin-1, polarity and functional features. Marking is an identification aid, not a claim of real factory markings. Do not regenerate a valid shared cache, and do not claim KiCad/Fusion/Altium parity until each named viewer was checked.+--- a/tests/component-workflow.py+++ b/tests/component-workflow.py@@ -0,0 +1,26 @@+"""Exercise early component inventory, optional marking and review-note preservation."""+import pathlib,subprocess,tempfile,sys,json+binary,board,spec=map(lambda p:str(pathlib.Path(p).resolve()),sys.argv[1:])+with tempfile.TemporaryDirectory(prefix='aiflow-components-') as d:+ base=[binary,'--ai-thread','ESC AI Flow Astra','--run',d]+ subprocess.run(base+['start','--board',board,'--spec',spec,'--engine','component-workflow-test'],check=True,capture_output=True,text=True)+ text=subprocess.run(base+['plan'],check=True,capture_output=True,text=True).stdout+ assert text.index('step components')<text.index('step placement')+ assert text.index('Search the wiki FIRST')<text.index('use adom-parts-search next')+ for phrase in ['ALL ADOM USERS','private reference-only','optional MPN marking','Cache keys']:+  assert phrase in text,phrase+ result=subprocess.run(base+['components'],check=True,capture_output=True,text=True)+ p=pathlib.Path(d)/'components.json';v=json.load(open(p));assert len(v['components'])==149;assert v['mpnMarking']=='ask'+ refs=[r['reference'] for r in v['components']];assert len(set(refs))==149+ v['components'][0]['review']['notes']='Preserve this independent review';p.write_text(json.dumps(v))+ subprocess.run(base+['components','--etch','on'],check=True,capture_output=True,text=True)+ v=json.load(open(p));assert v['mpnMarking']=='on';assert v['components'][0]['review']['notes']=='Preserve this independent review'+ subprocess.run(base+['components'],check=True,capture_output=True,text=True);assert json.load(open(p))['mpnMarking']=='on'+ subprocess.run(base+['components','--etch','off'],check=True,capture_output=True,text=True);assert json.load(open(p))['mpnMarking']=='off'+ v=json.load(open(p));v['components'][0]['properties']['Value']='deliberately stale source value';v['components'][0]['review']['status']='pass';p.write_text(json.dumps(v))+ subprocess.run(base+['components'],check=True,capture_output=True,text=True)+ v=json.load(open(p));assert v['components'][0]['review']['status']=='stale';assert v['components'][0]['review']['notes']=='Preserve this independent review'+ p.write_text('{invalid');r=subprocess.run(base+['components'],capture_output=True,text=True);assert r.returncode!=0;assert p.read_text()=='{invalid'+ print(result.stdout)+ print('PASS: early plan order, 149 references, explicit marking preference, preserved review notes, stale source invalidation, malformed register untouched.')+

Comments

John Lauer 2026-09-16

Superseded by consolidated PR #5; please review that version.

Log in to comment.