You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(polish): tutorial code-fence static check (Phase 4 of polish-fact-check) (#37)
Phase 4 adds a static analysis pass over Python code fences in
polished tutorials. Every ```python fence is extracted, parsed
with ast.parse for syntax errors, and type-checked with
`mypy --strict` as a subprocess per fence. Findings land in the
same `## Unresolved references` block as Phase 1 fact-check
findings.
Scope is limited to `docs/tutorials/*.md`. Other doc kinds may
carry code samples but the reader-follow-along expectation is
highest for tutorials, so they're the highest-value target.
New module: src/attune_author/fact_check/tutorial_static_check.py
- _FENCE_PATTERN regex extracts ```python ... ``` bodies plus
line offsets so findings map back to absolute tutorial lines
- _run_mypy: subprocess wrapper with 10s timeout; handles
TimeoutExpired, FileNotFoundError, and unexpected exit codes
by returning an empty findings list. The static check never
blocks the polish pipeline.
- _parse_mypy_output: regex parse of `<path>:<line>: <sev>:
<msg>` lines, rewriting mypy line numbers to absolute file
position via `base_line + mypy_line - 1`
- _strip_skip_directive: honors `# attune-author: skip-mypy`
as the first non-blank line of a fence (only first-line is
recognized; trailing directives are intentionally preserved)
- strip_skip_directives_in_file: removes the directive from
the written tutorial so readers don't see it
Wiring:
- check_tutorial_static toggle on FactCheckConfig (default True)
- check_polished_file routes to tutorial_static_check.check only
when is_tutorial_path(polished_path) returns True
- apply_polish_results calls strip_skip_directives_in_file on
the final content of tutorial-kind templates before writing
Phase 4.2 (sample EXECUTION — running the code, not just
type-checking it) is explicitly OUT OF SCOPE for this PR. Static
analysis only. Execution-tier design tracked as a follow-up.
Tests: 16 new tests covering syntax-error path, mypy happy path
with mocked subprocess, skip directive (in-fence + file-level
strip), tutorial-path routing in check_polished_file, and every
subprocess failure mode. Full suite: 942 passed (was 926), 37
pre-existing skips.
Real-fixture acceptance tests (4.8, 4.9, 4.11) require the
ops-dashboard tutorial fixture + a real mypy install. Unit-level
coverage with mocked subprocess + syntax-error path is in place;
the integration test lands alongside Phase 2/3's live-LLM run.
Spec: docs/specs/polish-fact-check/
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: docs/specs/polish-fact-check/tasks.md
+19-17Lines changed: 19 additions & 17 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -136,26 +136,28 @@ plumbing.
136
136
137
137
| # | Task | Layer | Status | Notes |
138
138
|---|------|-------|--------|-------|
139
-
| 4.1 | Add `tutorial_static_check.check(polished_path, project_root)` to `fact_check/` package | attune-author |todo|Operates only on `docs/tutorials/*.md`|
140
-
| 4.2 | Code-fence extractor: pull all ```python fences with line numbers | attune-author |todo|Skip fences with `# attune-author: skip-mypy` first line|
141
-
| 4.3 |`ast.parse` each fence; collect syntax errors as findings | attune-author |todo|Cheap pre-check before invoking mypy|
142
-
| 4.4 | Run `mypy --strict --no-error-summary` per fence | attune-author |todo|Subprocess; timeout 10s; capture stderr|
143
-
| 4.5 | Parse mypy output into findings | attune-author |todo|Map line numbers back to original fence position |
144
-
| 4.6 | Strip `# attune-author: skip-mypy` directives before publication | attune-author |todo|Apply only to the file written; preserve in source if any|
| 4.13 | Add design.md follow-up section on Phase 4.2 execution tiers | attune-author |todo|Reference the security + perf walkthrough from spec discussion|
139
+
| 4.1 | Add `tutorial_static_check.check(polished_path, project_root)` to `fact_check/` package | attune-author |**done**|`is_tutorial_path(...)` heuristic gates routing in `check_polished_file` so only `docs/tutorials/` files run the static check|
140
+
| 4.2 | Code-fence extractor: pull all ```python fences with line numbers | attune-author |**done**|`_FENCE_PATTERN` regex; line numbers derived from `_line_of_offset`|
141
+
| 4.3 |`ast.parse` each fence; collect syntax errors as findings | attune-author |**done**|SyntaxError caught with the `exc.lineno` mapped back to absolute file line|
142
+
| 4.4 | Run `mypy --strict --no-error-summary` per fence | attune-author |**done**|`_run_mypy` via temp file + subprocess; 10s timeout; handles TimeoutExpired + FileNotFoundError + unexpected exit codes by returning `[]`|
143
+
| 4.5 | Parse mypy output into findings | attune-author |**done**|`_parse_mypy_output` regex; line numbers rewritten to absolute file position via `base_line + mypy_line - 1`|
144
+
| 4.6 | Strip `# attune-author: skip-mypy` directives before publication | attune-author |**done**|`strip_skip_directives_in_file` invoked in `apply_polish_results` for `tutorial` depth only; first-line directive only, trailing directives intentionally preserved|
145
+
| 4.7 | Add `[tool.attune-author.fact-check.tutorial_static]` config | attune-author |**partial**|Top-level `check_tutorial_static` toggle on `FactCheckConfig` (defaults True). Sub-table for `mypy_args` / `timeout_seconds` deferred — current constants match the spec; expose only when a real consumer needs the knob.|
146
+
| 4.8 | Test: pre-fix `tutorials/ops-dashboard.md` flags `_readers` + `_models` imports | attune-author |deferred|Requires the ops-dashboard regression fixture + real mypy run. Unit-level coverage via mocked mypy + syntax-error path is in place; the integration test lands alongside the live-LLM acceptance run.|
147
+
| 4.9 | Test: post-fix version produces zero errors | attune-author |deferred | Same gate as 4.8|
148
+
| 4.10 | Test: `skip-mypy` directive is honored and stripped from output | attune-author |**done**|`test_check_skips_fence_with_directive` + `test_strip_skip_directive_first_line`|
149
+
| 4.11 | Test: total static-check time per tutorial < 10s | attune-author |deferred|Enforced indirectly via the 10s mypy subprocess timeout; a real bench lands with 4.8|
150
+
| 4.12 | Update CHANGELOG + README | attune-author |**done**|CHANGELOG under Unreleased; README adds "Tutorial static check (Phase 4)" subsection. 4.2 execution explicitly noted as out of scope.|
0 commit comments