fix: cross-platform audit Pass-1 (Windows) + C# support — v0.9.5 (#3)#4
Conversation
Reword RULE-25 to drop the fcntl pin (POSIX flock / Windows msvcrt). Add RULE-29 (portable import + lock), RULE-30 (utf-8 file I/O), RULE-31 (C# .cs Pass-1 analyzer) with proofs PROOF-44..52. Add skill_audit RULE-15 + PROOF-15 (python3->python->py -3 interpreter probe). Note C# deterministic Pass-1 coverage in supported_frameworks.md. Addresses #3. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Make scripts/audit/static_checks.py run on Windows, where purlin:audit was
completely broken:
- RULE-29: import fcntl under try/except ImportError with a _HAS_FCNTL flag;
add _lock_exclusive/_unlock that use fcntl.flock on POSIX and msvcrt.locking
on Windows (retry loop matches flock's blocking semantics). write_audit_cache
locks through the helpers.
- RULE-30: add encoding='utf-8' to every text-mode open(), and reconfigure
stdout/stderr to UTF-8 at startup so --load-criteria no longer dies with
UnicodeDecodeError (reading audit_criteria.md) or UnicodeEncodeError
(printing its ✓/⚠ glyphs) under a cp1252/ASCII locale.
- RULE-31: add check_csharp — parses [Trait("PurlinProof", ...)] markers, finds
each [Fact]/[Theory] body via a C#-aware brace scanner, and runs assert-true /
no-assertion detection (xUnit/NUnit/MSTest Assert.*, FluentAssertions .Should()).
Extract analyze_test_file() dispatch seam; route .cs to it.
- skill_audit RULE-15: SKILL.md documents a python3 -> python -> py -3 fallback.
Tests: PROOF-44/45 (AST guards), 46-49 (C#), 50 (Windows-sim msvcrt lock,
@integration), 51 (POSIX subprocess pipeline, @e2e), 52 (cp1252 locale repro
via --load-criteria, @e2e); skill_audit PROOF-15.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Building the fix surfaced a third Windows failure mode: --load-criteria prints criteria glyphs (✓/⚠) to an ASCII stdout and dies with UnicodeEncodeError even after the read-side encoding fix. RULE-30 now covers all text I/O — open() encoding AND stdout/stderr reconfigure — and PROOF-52 reproduces it via --load-criteria under PYTHONUTF8=0/LC_ALL=C. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bump VERSION and templates/config.json to 0.9.5. Fixes #3: purlin:audit now runs on Windows (fcntl optional + msvcrt lock fallback, UTF-8 text I/O, UTF-8 stdout) and gains deterministic C#/.NET Pass-1 coverage. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Tested on Windows — fix works ✅ (one false-positive to note)Tested this branch on Windows 11 / CPython 3.12 against a real .NET 7 (Blazor Server + xUnit) project. Installed this branch's
All three Windows encoding/
|
Note (separate from this PR's
|
…udit reachability) PR #4 follow-up: under `dotnet test`, TestCase.CodeFilePath is often null, so the xUnit logger emits proof entries with an empty test_file — which blocked purlin:audit from locating the .cs code for Pass 1 (scan) and Pass 2 (read). static_checks.py gains resolve_test_file_from_name() + a `--resolve-source <test_name>` CLI: it derives the declaring type from the fully-qualified test_name (segment before the final .method) and searches the project's source files (skipping bin/obj/node_modules/.git) for its class/struct/record/interface declaration, returning the repo-relative POSIX path of the best match (RULE-32, PROOF-56). skills/audit/SKILL.md uses this as a fallback when test_file is empty, and documents CollectSourceInformation + full PDBs as the native way to populate it (skill_audit RULE-16, PROOF-16). docs/testing-workflow-guide.md notes both. Also normalizes the pytest proof plugin's test_file to forward slashes (item.fspath.relto yields OS-native separators on Windows) — a no-op on POSIX — and normalizes the committed windows-tier proof file accordingly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Thanks for the thorough Windows pass and the two follow-up notes — both are addressed in a stacked follow-up PR: #5. Playwright C# assertions (your false-positive note): Empty Windows is now real CI, not simulation: RULE-29/30 are verified on a All 40 features VERIFIED at 100% integrity on the follow-up branch; the Windows Action is green. |
|
Verified on real Windows by the reporter and green on the |
… CI proofs Pass-1 C# no_assertions now recognizes Playwright fluent assertions — Expect(...)/Assertions.Expect(...) chained to a To*Async() matcher — so tests that assert solely through Playwright are no longer false-flagged HOLLOW (PR #4 review). A bare Expect(x) with no matcher chain is still flagged (RULE-31, PROOF-55). To keep the broader structural acceptance from masking weak tests, Pass-2 audit criteria gain a "presence/visibility-only assertion" WEAK check (audit_criteria Criteria-Version 16 -> 17). Windows RULE-29/30 are now verified for real on a windows-latest GitHub Actions runner (.github/workflows/windows-proofs.yml) via the new @windows proof tier — PROOF-53 (native msvcrt lock path) and PROOF-54 (--load-criteria under the native console codec) — committed back by CI, in addition to the host simulations (PROOF-50/52). The schema_proof_format anchor (RULE-4) and spec_format.md (Format-Version 8) admit the platform-gated windows tier; the feature-scoped overwrite is per (feature, tier), so host runs that skip these tests never clobber the CI-emitted proof file. Also bumps .claude-plugin/plugin.json to 0.9.5 to match VERSION (the manifest was not bumped during the v0.9.5 release; purlin_version caught the drift). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…udit reachability) PR #4 follow-up: under `dotnet test`, TestCase.CodeFilePath is often null, so the xUnit logger emits proof entries with an empty test_file — which blocked purlin:audit from locating the .cs code for Pass 1 (scan) and Pass 2 (read). static_checks.py gains resolve_test_file_from_name() + a `--resolve-source <test_name>` CLI: it derives the declaring type from the fully-qualified test_name (segment before the final .method) and searches the project's source files (skipping bin/obj/node_modules/.git) for its class/struct/record/interface declaration, returning the repo-relative POSIX path of the best match (RULE-32, PROOF-56). skills/audit/SKILL.md uses this as a fallback when test_file is empty, and documents CollectSourceInformation + full PDBs as the native way to populate it (skill_audit RULE-16, PROOF-16). docs/testing-workflow-guide.md notes both. Also normalizes the pytest proof plugin's test_file to forward slashes (item.fspath.relto yields OS-native separators on Windows) — a no-op on POSIX — and normalizes the committed windows-tier proof file accordingly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… C# test_file resolver (#5) Follow-up to #4. Recognizes Playwright fluent assertions in C# Pass-1 (RULE-31/PROOF-55) with a Pass-2 visibility-only WEAK criterion (audit_criteria v17); verifies Windows RULE-29/30 for real on a windows-latest Action via a new @windows proof tier (PROOF-53/54); resolves empty C# test_file from the fully-qualified test_name so purlin:audit can reach C# Pass-1/Pass-2 (RULE-32/PROOF-56, skill_audit RULE-16). Also bumps the plugin manifest to 0.9.5 to match VERSION.
Fixes #3 —
purlin:auditwas completely broken on Windows.What was broken (issue #3)
scripts/audit/static_checks.pyfailed on Windows for three independent reasons, each fixed here:import fcntl(POSIX-only) crashed the module on load → every subcommand died withModuleNotFoundError.open()calls omittedencoding=→UnicodeDecodeErrorreading the tool's own UTF-8 files under cp1252.--load-criteriaprinted✓/⚠glyphs to an ASCII stdout →UnicodeEncodeError(surfaced during this work).Plus: Pass 1 had no C#/.NET support — xUnit/NUnit/MSTest tests produced zero proofs.
What changed
fcntlimported undertry/except ImportErrorwith a_HAS_FCNTLflag;_lock_exclusive/_unlockusefcntl.flock(POSIX) ormsvcrt.locking(Windows). Cache serialization (RULE-25) preserved (static_checksRULE-29).encoding='utf-8'on every text-modeopen()+ stdout/stderr reconfigured to UTF-8 at startup (static_checksRULE-30).check_csharpanalyzer: parses[Trait("PurlinProof", …)]markers, C#-aware brace/string scanner, assert-true / no-assertion detection across xUnit/NUnit/MSTest/FluentAssertions;.csrouted via newanalyze_test_file()dispatch (static_checksRULE-31).skills/audit/SKILL.mddocuments apython3 → python → py -3interpreter fallback (skill_auditRULE-15).Verification
static_checksVERIFIED 29/29,skill_audit15/15.skill_auditPROOF-15. Independent audit: 7/7 behavioral proofs STRONG (100% integrity), 0 WEAK/HOLLOW.msvcrt) + a faithful cp1252 repro (--load-criteriaunderPYTHONUTF8=0/LC_ALL=C). Not yet a Windows CI runner — see release notes caveat.Tagged v0.9.5.
🤖 Generated with Claude Code