Skip to content

Commit afd4f77

Browse files
committed
chore(ci): restore green CI matrix (tool-version drift + targeted fixes)
The CI matrix had been red on two pre-existing causes (predating the v3 work) plus a few introduced by it; this restores all steps to green: - Ruff format (3.12 job): the lockfile pins ruff 0.15.12 but the tree was formatted under an older ruff; reformatted src+scripts to 0.15.12 (whitespace only) and fixed 9 import-sort (I001) findings. - Mypy (3.12 job): fixed 20 type errors — 6 introduced by v3 (incl. a dual module-name from importing the manuscript producer as src.X) and 14 pre-existing in argument_utils/unified_parser/mcp/gui/lsp (behavior-preserving narrowings, casts, and removed redundant casts/unused ignores). - Repository terminology: reworded a comment that used a banned term. - Pytest (3.11/3.13 jobs): test_gridworld_render_execute_analyze_visualize_strict asserts (not skips) when the optional Julia backend packages are absent, which CI lacks; marked it pytest.mark.pipeline so the lightweight 'not pipeline' run excludes it (consistent with the other heavy cross-framework pipeline tests). Verified locally with the frozen toolchain: ruff format --check, ruff check, all four doc audits, and mypy src all pass; the lightweight pytest set passes (the Julia test is now deselected).
1 parent 9211009 commit afd4f77

51 files changed

Lines changed: 692 additions & 278 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

scripts/check_manuscript_tokens.py

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,7 @@
4242

4343

4444
def _section_files(manuscript_dir: Path) -> list[Path]:
45-
return [
46-
p
47-
for p in sorted(manuscript_dir.glob("*.md"))
48-
if p.name not in _EXCLUDED
49-
]
45+
return [p for p in sorted(manuscript_dir.glob("*.md")) if p.name not in _EXCLUDED]
5046

5147

5248
def _strip_code(text: str) -> str:
@@ -57,8 +53,12 @@ def _strip_code(text: str) -> str:
5753

5854

5955
def main() -> int:
60-
parser = argparse.ArgumentParser(description="Manuscript token/citation integrity gate")
61-
parser.add_argument("--strict", action="store_true", help="treat hard-coded counts as failures")
56+
parser = argparse.ArgumentParser(
57+
description="Manuscript token/citation integrity gate"
58+
)
59+
parser.add_argument(
60+
"--strict", action="store_true", help="treat hard-coded counts as failures"
61+
)
6262
args = parser.parse_args()
6363

6464
manuscript_dir = _PROJECT_ROOT / "manuscript"
@@ -108,7 +108,9 @@ def main() -> int:
108108
continue
109109
for value, key in config_counts.items():
110110
if re.search(rf"(?<!\d){re.escape(value)}(?!\d)", line):
111-
config_hardcoded.append(f"config.yaml: {stripped.split(':')[0]} hard-codes {value} (use a description without the number; {{{{{key}}}}} does not resolve in config.yaml)")
111+
config_hardcoded.append(
112+
f"config.yaml: {stripped.split(':')[0]} hard-codes {value} (use a description without the number; {{{{{key}}}}} does not resolve in config.yaml)"
113+
)
112114

113115
unknown_tokens: list[str] = []
114116
dangling_cites: list[str] = []
@@ -130,7 +132,9 @@ def main() -> int:
130132
no_tokens = _TOKEN_RE.sub("", body)
131133
for value, key in hardcode_targets.items():
132134
if re.search(rf"(?<!\d){re.escape(value)}(?!\d)", no_tokens):
133-
hardcoded.append(f"{path.name}: literal {value} should be {{{{{key}}}}}")
135+
hardcoded.append(
136+
f"{path.name}: literal {value} should be {{{{{key}}}}}"
137+
)
134138

135139
print(f"Sections checked: {len(_section_files(manuscript_dir))}")
136140
print(f"Known tokens: {len(known_tokens)} | Bib keys: {len(bib_keys)}")
@@ -159,7 +163,9 @@ def main() -> int:
159163
if ok and not hardcoded:
160164
print("\n✅ Manuscript token/citation integrity: clean")
161165
elif ok:
162-
print("\n✅ No unknown tokens or dangling citations (hard-coded warnings above)")
166+
print(
167+
"\n✅ No unknown tokens or dangling citations (hard-coded warnings above)"
168+
)
163169
else:
164170
print("\n❌ Manuscript integrity gate FAILED")
165171
return 0 if ok else 1

scripts/check_pomdp_gridworld_outputs.py

Lines changed: 123 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
from pathlib import Path
1818
from typing import Any
1919

20-
2120
ALL_RENDER_TARGETS: tuple[str, ...] = (
2221
"pymdp",
2322
"rxinfer",
@@ -104,7 +103,9 @@ def _first_mapping_value(mapping: dict[str, Any]) -> dict[str, Any]:
104103

105104

106105
def _check_pipeline_summary(output_dir: Path, report: ContractReport) -> None:
107-
summary_path = output_dir / "00_pipeline_summary" / "pipeline_execution_summary.json"
106+
summary_path = (
107+
output_dir / "00_pipeline_summary" / "pipeline_execution_summary.json"
108+
)
108109
summary = _load_json(summary_path, report)
109110
if not isinstance(summary, dict):
110111
return
@@ -116,20 +117,31 @@ def _check_pipeline_summary(output_dir: Path, report: ContractReport) -> None:
116117
)
117118

118119
steps = summary.get("steps", [])
119-
report.require(isinstance(steps, list) and len(steps) >= 25, "Pipeline summary must include all 25 numbered steps.")
120+
report.require(
121+
isinstance(steps, list) and len(steps) >= 25,
122+
"Pipeline summary must include all 25 numbered steps.",
123+
)
120124
if isinstance(steps, list):
121125
bad_steps: list[str] = []
122126
for step in steps:
123127
if not isinstance(step, dict):
124128
continue
125-
step_name = str(step.get("script_name") or step.get("step") or step.get("name") or "unknown")
129+
step_name = str(
130+
step.get("script_name")
131+
or step.get("step")
132+
or step.get("name")
133+
or "unknown"
134+
)
126135
exit_code = step.get("exit_code")
127136
status = str(step.get("status", "")).lower()
128137
if exit_code not in (0, 2, None) or status in {"failed", "error"}:
129-
bad_steps.append(f"{step_name}: status={status!r} exit_code={exit_code!r}")
138+
bad_steps.append(
139+
f"{step_name}: status={status!r} exit_code={exit_code!r}"
140+
)
130141
report.require(
131142
not bad_steps,
132-
"Pipeline summary contains non-success/non-warning steps: " + "; ".join(bad_steps),
143+
"Pipeline summary contains non-success/non-warning steps: "
144+
+ "; ".join(bad_steps),
133145
)
134146
report.note("pipeline summary")
135147

@@ -138,10 +150,20 @@ def _check_parse_outputs(output_dir: Path, report: ContractReport) -> None:
138150
summary_path = output_dir / "3_gnn_output" / "gnn_processing_summary.json"
139151
summary = _load_json(summary_path, report)
140152
if isinstance(summary, dict):
141-
report.require(summary.get("total_files") == 1, "Step 3 should process exactly one GridWorld model.")
142-
report.require(summary.get("successful_parses") == 1, "Step 3 should parse the GridWorld model successfully.")
143-
parsed_candidates = sorted((output_dir / "3_gnn_output").glob(f"**/{GRIDWORLD_STEM}*_parsed.json"))
144-
report.require(bool(parsed_candidates), "Step 3 parsed GridWorld JSON artifact is missing.")
153+
report.require(
154+
summary.get("total_files") == 1,
155+
"Step 3 should process exactly one GridWorld model.",
156+
)
157+
report.require(
158+
summary.get("successful_parses") == 1,
159+
"Step 3 should parse the GridWorld model successfully.",
160+
)
161+
parsed_candidates = sorted(
162+
(output_dir / "3_gnn_output").glob(f"**/{GRIDWORLD_STEM}*_parsed.json")
163+
)
164+
report.require(
165+
bool(parsed_candidates), "Step 3 parsed GridWorld JSON artifact is missing."
166+
)
145167
report.note("parse outputs")
146168

147169

@@ -151,31 +173,56 @@ def _check_render_outputs(output_dir: Path, report: ContractReport) -> None:
151173
if not isinstance(summary, dict):
152174
return
153175

154-
report.require(summary.get("total_files") == 1, "Step 11 should render exactly one GridWorld model.")
155-
report.require(summary.get("successful_files") == 1, "Step 11 should mark the GridWorld file successful.")
176+
report.require(
177+
summary.get("total_files") == 1,
178+
"Step 11 should render exactly one GridWorld model.",
179+
)
180+
report.require(
181+
summary.get("successful_files") == 1,
182+
"Step 11 should mark the GridWorld file successful.",
183+
)
156184
report.require(
157185
not summary.get("failed_framework_renderings"),
158186
"Step 11 has failed framework renderings: "
159187
+ json.dumps(summary.get("failed_framework_renderings"), sort_keys=True),
160188
)
161189

162-
file_result = _first_mapping_value(summary.get("file_results", {}) if isinstance(summary.get("file_results"), dict) else {})
190+
file_result = _first_mapping_value(
191+
summary.get("file_results", {})
192+
if isinstance(summary.get("file_results"), dict)
193+
else {}
194+
)
163195
framework_results = file_result.get("framework_results", {})
164-
report.require(isinstance(framework_results, dict), "Step 11 summary lacks framework_results mapping.")
196+
report.require(
197+
isinstance(framework_results, dict),
198+
"Step 11 summary lacks framework_results mapping.",
199+
)
165200
if isinstance(framework_results, dict):
166201
missing = [name for name in ALL_RENDER_TARGETS if name not in framework_results]
167-
report.require(not missing, f"Step 11 did not attempt all render targets: {missing}")
202+
report.require(
203+
not missing, f"Step 11 did not attempt all render targets: {missing}"
204+
)
168205
failed = [
169206
f"{name}: {result.get('message', '')}"
170207
for name, result in framework_results.items()
171-
if name in ALL_RENDER_TARGETS and isinstance(result, dict) and not result.get("success")
208+
if name in ALL_RENDER_TARGETS
209+
and isinstance(result, dict)
210+
and not result.get("success")
172211
]
173-
report.require(not failed, "Step 11 render target failures: " + "; ".join(failed))
212+
report.require(
213+
not failed, "Step 11 render target failures: " + "; ".join(failed)
214+
)
174215

175216
for framework in ALL_RENDER_TARGETS:
176217
framework_dir = render_dir / GRIDWORLD_STEM / framework
177-
report.require(framework_dir.exists(), f"Missing Step 11 framework directory: {framework_dir}")
178-
report.require(any(framework_dir.glob("*")), f"Step 11 framework directory is empty: {framework_dir}")
218+
report.require(
219+
framework_dir.exists(),
220+
f"Missing Step 11 framework directory: {framework_dir}",
221+
)
222+
report.require(
223+
any(framework_dir.glob("*")),
224+
f"Step 11 framework directory is empty: {framework_dir}",
225+
)
179226
report.note("render outputs")
180227

181228

@@ -196,23 +243,42 @@ def _simulation_payloads(output_dir: Path, framework: str) -> list[dict[str, Any
196243

197244

198245
def _check_execute_outputs(output_dir: Path, report: ContractReport) -> None:
199-
summary_path = output_dir / "12_execute_output" / "summaries" / "execution_summary.json"
246+
summary_path = (
247+
output_dir / "12_execute_output" / "summaries" / "execution_summary.json"
248+
)
200249
summary = _load_json(summary_path, report)
201250
if isinstance(summary, dict):
202251
encoded = json.dumps(summary, sort_keys=True).lower()
203252
report.require(
204-
any(token in encoded for token in ("succeeded", "failed", "skipped", "success_count", "failure_count")),
253+
any(
254+
token in encoded
255+
for token in (
256+
"succeeded",
257+
"failed",
258+
"skipped",
259+
"success_count",
260+
"failure_count",
261+
)
262+
),
205263
"Step 12 summary should include explicit success/skipped/error accounting.",
206264
)
207265

208266
for framework in STRICT_EXECUTION_TARGETS:
209267
payloads = _simulation_payloads(output_dir, framework)
210-
report.require(payloads, f"Missing Step 12 collected simulation results for {framework}.")
268+
report.require(
269+
payloads, f"Missing Step 12 collected simulation results for {framework}."
270+
)
211271
if not payloads:
212272
continue
213273
payload = payloads[0]
214-
report.require(payload.get("success") is True, f"{framework} simulation payload is not successful.")
215-
report.require(payload.get("num_timesteps") == 15, f"{framework} should report 15 timesteps.")
274+
report.require(
275+
payload.get("success") is True,
276+
f"{framework} simulation payload is not successful.",
277+
)
278+
report.require(
279+
payload.get("num_timesteps") == 15,
280+
f"{framework} should report 15 timesteps.",
281+
)
216282
report.require(
217283
payload.get("model_parameters", {}).get("B_shape") == [9, 9, 5],
218284
f"{framework} should preserve GridWorld B shape [9, 9, 5].",
@@ -226,7 +292,9 @@ def _check_execute_outputs(output_dir: Path, report: ContractReport) -> None:
226292

227293
def _check_analysis_outputs(output_dir: Path, report: ContractReport) -> None:
228294
analysis_dir = output_dir / "16_analysis_output"
229-
manifest_path = analysis_dir / "cross_framework" / "gridworld_analysis_manifest.json"
295+
manifest_path = (
296+
analysis_dir / "cross_framework" / "gridworld_analysis_manifest.json"
297+
)
230298
manifest = _load_json(manifest_path, report)
231299
if isinstance(manifest, dict):
232300
frameworks = sorted(manifest.get("frameworks", []))
@@ -239,16 +307,31 @@ def _check_analysis_outputs(output_dir: Path, report: ContractReport) -> None:
239307
"GridWorld analysis manifest should confirm matching matrix provenance.",
240308
)
241309
outputs = manifest.get("outputs", {})
242-
report.require(bool(outputs.get("png")), "GridWorld analysis manifest should list PNG outputs.")
243-
report.require(len(outputs.get("gif", [])) >= 7, "GridWorld analysis manifest should list GridWorld GIF outputs.")
244-
report.require(bool(outputs.get("statistics")), "GridWorld analysis manifest should list statistics outputs.")
310+
report.require(
311+
bool(outputs.get("png")),
312+
"GridWorld analysis manifest should list PNG outputs.",
313+
)
314+
report.require(
315+
len(outputs.get("gif", [])) >= 7,
316+
"GridWorld analysis manifest should list GridWorld GIF outputs.",
317+
)
318+
report.require(
319+
bool(outputs.get("statistics")),
320+
"GridWorld analysis manifest should list statistics outputs.",
321+
)
245322

246323
pngs = sorted(analysis_dir.glob("**/*.png"))
247324
gifs = sorted(analysis_dir.glob("**/*.gif"))
248325
report.require(bool(pngs), "Step 16 PNG analysis outputs are missing.")
249326
report.require(bool(gifs), "Step 16 GIF animation outputs are missing.")
250-
report.require(all(path.stat().st_size > 0 for path in pngs[:10]), "One or more Step 16 PNG outputs are empty.")
251-
report.require(all(path.stat().st_size > 0 for path in gifs), "One or more Step 16 GIF outputs are empty.")
327+
report.require(
328+
all(path.stat().st_size > 0 for path in pngs[:10]),
329+
"One or more Step 16 PNG outputs are empty.",
330+
)
331+
report.require(
332+
all(path.stat().st_size > 0 for path in gifs),
333+
"One or more Step 16 GIF outputs are empty.",
334+
)
252335
report.note("analysis outputs")
253336

254337

@@ -267,9 +350,17 @@ def _check_report_and_website_outputs(output_dir: Path, report: ContractReport)
267350
)
268351

269352
website_dir = output_dir / "20_website_output"
270-
required_pages = ("index.html", "pipeline.html", "reports.html", "analysis.html", "visualization.html")
353+
required_pages = (
354+
"index.html",
355+
"pipeline.html",
356+
"reports.html",
357+
"analysis.html",
358+
"visualization.html",
359+
)
271360
for page in required_pages:
272-
report.require((website_dir / page).exists(), f"Step 20 website page is missing: {page}")
361+
report.require(
362+
(website_dir / page).exists(), f"Step 20 website page is missing: {page}"
363+
)
273364
website_pages = [website_dir / page for page in required_pages]
274365
report.require(
275366
any(_text_contains_marker(path) for path in website_pages),

scripts/generate_pipeline_container_plan.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,10 @@ def main() -> int:
6262
if findings:
6363
print(f"Security review found {len(findings)} issue(s):", file=sys.stderr)
6464
for f in findings:
65-
print(f" [{f.severity}] {f.code} ({f.spec_name}): {f.message}", file=sys.stderr)
65+
print(
66+
f" [{f.severity}] {f.code} ({f.spec_name}): {f.message}",
67+
file=sys.stderr,
68+
)
6669
return 1
6770

6871
print("Security review: clean (0 findings).")

scripts/manuscript_build_figures.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,10 @@ def main() -> int:
3434
variables_json = _PROJECT_ROOT / "output" / "data" / "manuscript_variables.json"
3535
if not variables_json.is_file():
3636
subprocess.run(
37-
[sys.executable, str(_PROJECT_ROOT / "scripts" / "z_generate_manuscript_variables.py")],
37+
[
38+
sys.executable,
39+
str(_PROJECT_ROOT / "scripts" / "z_generate_manuscript_variables.py"),
40+
],
3841
cwd=str(_PROJECT_ROOT),
3942
check=False,
4043
)

scripts/manuscript_fig_backend_matrix.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,11 @@ def main() -> None:
107107
nosub_bg = "#f6d4d4"
108108
stripe_bg = "#eef2f6"
109109

110-
for (r, c), cell in table.get_cells().items() if hasattr(table, "get_cells") else table.get_celld().items():
110+
for (r, c), cell in (
111+
table.get_cells().items()
112+
if hasattr(table, "get_cells")
113+
else table.get_celld().items()
114+
):
111115
cell.set_edgecolor("#9aa6b2")
112116
cell.set_linewidth(0.6)
113117
if r == 0:

0 commit comments

Comments
 (0)