From 76d182281be95825a87e993871a781db1ce4f409 Mon Sep 17 00:00:00 2001 From: rlabarca Date: Thu, 25 Jun 2026 12:46:13 -0400 Subject: [PATCH 1/5] fix(static_checks): recognize Playwright C# assertions + real Windows CI proofs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- .claude-plugin/plugin.json | 2 +- .github/workflows/windows-proofs.yml | 46 ++++++++++++ RELEASE_NOTES.md | 4 +- dev/test_schema_proof_format.py | 5 +- dev/test_static_checks.py | 29 ++++++++ dev/test_windows_native.py | 86 ++++++++++++++++++++++ references/audit_criteria.md | 5 +- references/formats/proofs_format.md | 4 +- references/formats/spec_format.md | 5 +- references/supported_frameworks.md | 2 +- scripts/audit/static_checks.py | 17 +++-- specs/_anchors/schema_proof_format.md | 4 +- specs/audit/static_checks.md | 9 ++- specs/audit/static_checks.proofs-unit.json | 9 +++ 14 files changed, 207 insertions(+), 20 deletions(-) create mode 100644 .github/workflows/windows-proofs.yml create mode 100644 dev/test_windows_native.py diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 25c08ec4..976c8164 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "purlin", - "version": "0.9.4", + "version": "0.9.5", "description": "Rule-proof spec-driven development — specs define rules, tests prove them, sync_status shows coverage", "author": { "name": "Rich LaBarca", diff --git a/.github/workflows/windows-proofs.yml b/.github/workflows/windows-proofs.yml new file mode 100644 index 00000000..8b29de3d --- /dev/null +++ b/.github/workflows/windows-proofs.yml @@ -0,0 +1,46 @@ +name: windows-proofs + +# Runs the Windows-native proofs for static_checks (RULE-29/30) on a real windows-latest +# runner and commits the emitted `windows`-tier proof file back to the branch, so +# purlin:verify/purlin:status can read genuine Windows verification — not host simulation. + +on: + push: + # Skip the runner's own proof commit (loop guard, paired with [skip ci]). + paths-ignore: + - '**/*.proofs-windows.json' + workflow_dispatch: + +permissions: + contents: write + +jobs: + windows-native: + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: true + + - uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Install pytest + run: pip install pytest + + - name: Run Windows-native proofs + run: python -m pytest dev/test_windows_native.py -v + + - name: Commit Windows proof file back to the branch + shell: bash + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add specs/audit/static_checks.proofs-windows.json + if git diff --cached --quiet; then + echo "No proof-file changes to commit." + else + git commit -m "test(static_checks): windows-native proofs from windows-latest [skip ci]" + git push origin HEAD:${GITHUB_REF_NAME} + fi diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 9e3240ff..03d2c416 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -11,12 +11,12 @@ ### Added -- **Deterministic Pass-1 coverage for C#/.NET tests.** Pass 1 previously recognized only pytest/Jest/Shell markers, so xUnit/NUnit/MSTest tests carrying `[Trait("PurlinProof", "feature:PROOF-N:RULE-N:tier")]` produced zero proofs and the structural checker silently no-op'd on `.cs` files. The new `check_csharp` analyzer parses those trait markers, locates each `[Fact]`/`[Theory]` method body via a C#-aware brace/string scanner, and runs assert-true / no-assertion detection — recognizing xUnit `Assert.*`, NUnit `Assert.That`, MSTest `Assert.*`, and FluentAssertions `.Should()` as assertions (`static_checks` RULE-31). The dispatch was extracted into `analyze_test_file()` and now routes `.cs`. `references/supported_frameworks.md` documents the new coverage. +- **Deterministic Pass-1 coverage for C#/.NET tests.** Pass 1 previously recognized only pytest/Jest/Shell markers, so xUnit/NUnit/MSTest tests carrying `[Trait("PurlinProof", "feature:PROOF-N:RULE-N:tier")]` produced zero proofs and the structural checker silently no-op'd on `.cs` files. The new `check_csharp` analyzer parses those trait markers, locates each `[Fact]`/`[Theory]` method body via a C#-aware brace/string scanner, and runs assert-true / no-assertion detection — recognizing xUnit `Assert.*`, NUnit `Assert.That`, MSTest `Assert.*`, FluentAssertions `.Should()`, and Playwright fluent assertions (`Expect(...)`/`Assertions.Expect(...)` chained to a `To*Async()` matcher) as assertions (`static_checks` RULE-31). A bare `Expect(x)` with no matcher chain is still flagged `no_assertions`. The dispatch was extracted into `analyze_test_file()` and now routes `.cs`. `references/supported_frameworks.md` documents the new coverage. To keep this broader structural acceptance from masking weak tests, the Pass-2 audit criteria gained a "presence/visibility-only assertion" WEAK check (`references/audit_criteria.md` → Criteria-Version 17). - **Portable interpreter guidance in `purlin:audit`.** `skills/audit/SKILL.md` documents a `python3 → python → py -3` fallback for invoking `static_checks.py`, since stock Windows does not put `python3` on PATH (`skill_audit` RULE-15). ### Testing -- New proofs: `static_checks` PROOF-44/45 (AST structural guards — no unconditional `fcntl` import; `encoding='utf-8'` on every text-mode `open()`), PROOF-46–49 (C# assert-true / no-assertion / multi-framework assertion recognition / `.cs` dispatch), PROOF-50 (Windows lock path driven through an injected fake `msvcrt`, `@integration`), PROOF-51 (full CLI pipeline via real subprocess, `@e2e`), PROOF-52 (the cp1252 failure reproduced by running `--load-criteria` under `PYTHONUTF8=0`/`LC_ALL=C`, `@e2e`); `skill_audit` PROOF-15. Windows behavior is verified by simulation (fake `msvcrt`) plus a faithful ASCII-locale subprocess repro — not yet a Windows CI runner. Independent audit of the new proofs: 7/7 behavioral proofs STRONG (100% integrity), 0 WEAK/HOLLOW. `static_checks` VERIFIED at 29/29, `skill_audit` at 15/15. +- New proofs: `static_checks` PROOF-44/45 (AST structural guards — no unconditional `fcntl` import; `encoding='utf-8'` on every text-mode `open()`), PROOF-46–49 (C# assert-true / no-assertion / multi-framework assertion recognition / `.cs` dispatch), PROOF-50 (Windows lock path driven through an injected fake `msvcrt`, `@integration`), PROOF-51 (full CLI pipeline via real subprocess, `@e2e`), PROOF-52 (the cp1252 failure reproduced by running `--load-criteria` under `PYTHONUTF8=0`/`LC_ALL=C`, `@e2e`); `skill_audit` PROOF-15. PROOF-55 covers the Playwright assertion recognition (and the bare-`Expect` negative). Windows behavior is now verified for real on a `windows-latest` GitHub Actions runner — `.github/workflows/windows-proofs.yml` runs PROOF-53 (native `msvcrt` lock path, `_HAS_FCNTL` genuinely False) and PROOF-54 (`--load-criteria` under the native console codec) in the dedicated `@windows` proof tier and commits the results back — in addition to the host simulation (fake `msvcrt`) and ASCII-locale subprocess repro. Independent audit of the new proofs: STRONG at 100% integrity, 0 WEAK/HOLLOW. ## v0.9.4 — Plugin-bundled MCP server & e2e proof quality diff --git a/dev/test_schema_proof_format.py b/dev/test_schema_proof_format.py index 49b995e5..008f40a8 100644 --- a/dev/test_schema_proof_format.py +++ b/dev/test_schema_proof_format.py @@ -161,7 +161,7 @@ class TestProofFormatConventions: @pytest.mark.proof("schema_proof_format", "PROOF-4", "RULE-4") def test_standard_tiers_documented(self): - valid_tiers = {'unit', 'integration', 'e2e'} + valid_tiers = {'unit', 'integration', 'e2e', 'windows'} # Verify all existing proof files only use valid tiers proof_files = glob.glob(os.path.join(PROJECT_ROOT, 'specs', '**', '*.proofs-*.json'), recursive=True) @@ -177,12 +177,13 @@ def test_standard_tiers_documented(self): assert entry_tier in valid_tiers, \ f"Invalid entry tier '{entry_tier}' in {entry.get('id')} of {path}" - # Verify format reference documents all 3 standard tiers + # Verify format reference documents the standard tiers + the platform-gated windows tier with open(os.path.join(PROJECT_ROOT, 'references', 'formats', 'spec_format.md')) as f: fmt = f.read() assert '@integration' in fmt assert '@e2e' in fmt + assert '@windows' in fmt @pytest.mark.proof("schema_proof_format", "PROOF-6", "RULE-6") def test_proof_files_not_gitignored(self): diff --git a/dev/test_static_checks.py b/dev/test_static_checks.py index 5fe75629..613ff019 100644 --- a/dev/test_static_checks.py +++ b/dev/test_static_checks.py @@ -1467,3 +1467,32 @@ def test_dispatch_routes_cs_to_check_csharp(self): os.unlink(other) finally: os.unlink(path) + + @pytest.mark.proof("static_checks", "PROOF-55", "RULE-31") + def test_recognizes_playwright_fluent_assertions(self): + """A C# test asserting only via Playwright's Expect(...).To*Async() is recognized + as an assertion (status=pass), but a bare Expect(x) with no matcher is still flagged.""" + # Mirrors the PR #4 repro: Nav_ShowsUser_AndSignOut asserts only through Playwright. + playwright = ( + 'await Assertions.Expect(page.Locator("text=" + user)).ToBeVisibleAsync();\n' + 'await Assertions.Expect(page.Locator("a[href=\'logout\']")).ToBeVisibleAsync();' + ) + path = self._cs(playwright) + try: + results = check_csharp(path, "csfeat") + assert len(results) == 1 + assert results[0]['status'] == 'pass', \ + f"Playwright assertion not recognized — {results[0]}" + finally: + os.unlink(path) + + # Bare Expect(x) with no To*Async() matcher chain is NOT an assertion. + bare = self._cs("Expect(result);") + try: + results = check_csharp(bare, "csfeat") + assert len(results) == 1 + assert results[0]['status'] == 'fail' + assert results[0]['check'] == 'no_assertions', \ + f"bare Expect(x) should be no_assertions — {results[0]}" + finally: + os.unlink(bare) diff --git a/dev/test_windows_native.py b/dev/test_windows_native.py new file mode 100644 index 00000000..1c17e796 --- /dev/null +++ b/dev/test_windows_native.py @@ -0,0 +1,86 @@ +"""Windows-native proofs for static_checks (RULE-29/30). + +These verify the real Windows code paths — the native `msvcrt.locking` file lock and +UTF-8 text I/O under the native console codec — on an actual `windows-latest` runner, +not the host simulations (PROOF-50 fake msvcrt, PROOF-52 simulated ASCII locale). + +They are gated to Windows and emit the dedicated `windows` proof tier +(`specs/audit/static_checks.proofs-windows.json`). The tier keeps host runs — where +these tests are skipped — from clobbering the CI-emitted proof file, since the pytest +proof plugin overwrites per (feature, tier) only for tiers it actually collected. + +Run on Windows: python -m pytest dev/test_windows_native.py +""" + +import os +import subprocess +import sys +import tempfile + +import pytest + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'scripts', 'audit')) + +import static_checks +from static_checks import write_audit_cache, read_audit_cache + +_STATIC_CHECKS_PY = os.path.join( + os.path.dirname(__file__), '..', 'scripts', 'audit', 'static_checks.py' +) +_REPO_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) + +pytestmark = pytest.mark.skipif( + sys.platform != "win32", + reason="Windows-native proofs run only on a windows-latest CI runner", +) + + +def _audit_entry(assessment, feature, proof_id, rule_id): + return { + "assessment": assessment, + "criterion": "matches rule intent", + "why": "test exercises the rule correctly", + "fix": "none", + "feature": feature, + "proof_id": proof_id, + "rule_id": rule_id, + "priority": "LOW", + "cached_at": "2026-04-01T00:00:00+00:00", + } + + +@pytest.mark.proof("static_checks", "PROOF-53", "RULE-29", tier="windows") +def test_real_msvcrt_lock_path(): + """On real Windows, fcntl is genuinely absent so write_audit_cache uses the native + msvcrt.locking path with no fake. The cache must round-trip and the adjacent + audit_cache.json.lock must be created.""" + assert static_checks._HAS_FCNTL is False, ( + "expected fcntl unavailable on Windows — the native msvcrt path would not be exercised" + ) + with tempfile.TemporaryDirectory() as tmpdir: + write_audit_cache(tmpdir, { + "h1": _audit_entry("STRONG", "feat_a", "PROOF-1", "RULE-1"), + "h2": _audit_entry("WEAK", "feat_a", "PROOF-2", "RULE-2"), + }) + after = read_audit_cache(tmpdir) + assert len(after) == 2, f"entries lost on native Windows lock path: {list(after)}" + assert "h1" in after and "h2" in after + lock_path = os.path.join(tmpdir, '.purlin', 'cache', 'audit_cache.json.lock') + assert os.path.exists(lock_path), "msvcrt lock file was not created adjacent to the cache" + + +@pytest.mark.proof("static_checks", "PROOF-54", "RULE-30", tier="windows") +def test_load_criteria_native_console(): + """Under the native Windows console codec (no PYTHONUTF8/LC_ALL/LANG overrides), + --load-criteria must read the tool's own non-ASCII audit_criteria.md and print it + without UnicodeDecodeError (read) or UnicodeEncodeError (stdout).""" + env = dict(os.environ) + for var in ('PYTHONUTF8', 'PYTHONIOENCODING', 'LC_ALL', 'LANG'): + env.pop(var, None) + with tempfile.TemporaryDirectory() as tmpdir: + r = subprocess.run( + [sys.executable, _STATIC_CHECKS_PY, '--load-criteria', '--project-root', tmpdir], + cwd=_REPO_ROOT, env=env, capture_output=True, text=True, encoding='utf-8', + ) + assert r.returncode == 0, f"--load-criteria failed under native console codec: {r.stderr}" + assert any(ord(ch) > 127 for ch in r.stdout), "no non-ASCII glyph in --load-criteria output" diff --git a/references/audit_criteria.md b/references/audit_criteria.md index e10d16bf..3f8cbdce 100644 --- a/references/audit_criteria.md +++ b/references/audit_criteria.md @@ -1,4 +1,4 @@ -> Criteria-Version: 16 +> Criteria-Version: 17 # Proof Audit Criteria @@ -78,6 +78,7 @@ Structural proofs are **excluded from the audit**. They are assessed as EXCLUDED - **Missing negative test for constraint rules** — when the rule describes rejection or constraint behavior ("reject passwords under 8 characters", "block after 5 failed attempts"), the test only checks the happy path (valid password accepted). STRONG requires at least one negative test proving the constraint rejects what it should - **Catch-all assertions** — `assert resp.json()` (truthy check) instead of checking specific fields - **String containment instead of equality** — `assert "error" in resp.text` when the proof says "verify error message is 'invalid_credentials'" +- **Presence/visibility-only assertion** — the test asserts only that an element exists, is visible, or is attached (e.g. Playwright `Expect(locator).ToBeVisibleAsync()`, `.ToBeAttachedAsync()`; or a DOM `getByText(...)` truthy check) when the rule requires a specific value, text, count, or state. Visible ≠ correct content. STRONG requires asserting the value/text/count the rule describes (`ToHaveTextAsync`, `ToHaveValueAsync`, `ToHaveCountAsync`, or an equality check), not mere presence. The WEAK heuristics in this section apply to fluent / `Expect(...)`-style assertion chains the same as to `assert`/`expect()` — judge what the chain actually verifies, not its surface form - **Time-dependent tests without mocked clock** — `assert elapsed < 1.0` depends on machine speed, not code correctness - **Tautological escape hatch** — the assertion contains an OR/||/or branch that always evaluates to True, typically a comparison between test constants, fixture data, or literal values that makes the assertion pass regardless of what the code under test returns. Examples: Python `assert func() > 0 or CONSTANT not in OTHER_CONSTANT`, JavaScript `expect(result > 0 || FIXTURE === FIXTURE).toBe(true)`, Go `if result > 0 || expectedConst != "" { t.Log("ok") }`. The escape hatch may not be obvious — look for OR branches where both operands are defined in test setup rather than derived from code-under-test output - **Assertion validates test data, not code output** — the test's primary assertions check properties of the test's own setup data (constants, fixtures, mock return values) rather than output from the system under test. The test proves the test is correctly set up, not that the code works @@ -147,7 +148,7 @@ When reporting, group findings by tier. When fixing (manually or via builder), w **Pass 2 (LLM) tier mapping by criterion:** - HIGH: any WEAK with "missing" in criterion, "only happy path", "only checks", deep mocking, missing negative test for constraint rules, tier mismatch, source-constant assertion -- LOW: assertion farming, catch-all assertions, string containment instead of equality, time-dependent tests +- LOW: assertion farming, catch-all assertions, string containment instead of equality, time-dependent tests, presence/visibility-only assertion ## Audit Caching diff --git a/references/formats/proofs_format.md b/references/formats/proofs_format.md index 5059176b..319ee084 100644 --- a/references/formats/proofs_format.md +++ b/references/formats/proofs_format.md @@ -51,7 +51,7 @@ Proof files live in the same directory as their spec. The proof plugins resolve | Field | Type | Description | |-------|------|-------------| -| `tier` | string | Test tier: `"unit"`, `"integration"`, `"e2e"`, etc. | +| `tier` | string | Test tier: `"unit"`, `"integration"`, `"e2e"`, `"windows"` (platform-gated), etc. Freeform — a proof tagged `@` lives in `.proofs-.json`. | | `proofs[].feature` | string | Feature name (matches spec filename stem) | | `proofs[].id` | string | Proof ID matching `## Proof` section: `PROOF-1`, `PROOF-2`, etc. | | `proofs[].rule` | string | Rule ID this proof covers: `RULE-1`, `RULE-2`, etc. | @@ -71,6 +71,8 @@ When proof plugins write a proof file, they: This means each test run replaces only its own feature's entries, preserving proofs from other features that share the same tier file. This is the "feature-scoped overwrite" pattern. +The overwrite is scoped per `(feature, tier)`: a run only rewrites the tier files it actually collected proofs for. A platform-gated tier (e.g. `windows`, emitted only by a CI runner where those tests run, and skipped elsewhere) is therefore never clobbered by a host run that skips those tests — so a CI-committed `.proofs-windows.json` survives subsequent local runs and is read by `sync_status`/`purlin:verify` like any other tier. + ## Proof Markers by Framework ### pytest diff --git a/references/formats/spec_format.md b/references/formats/spec_format.md index b59e531a..601ccf5d 100644 --- a/references/formats/spec_format.md +++ b/references/formats/spec_format.md @@ -1,4 +1,4 @@ -> Format-Version: 7 +> Format-Version: 8 # Spec Format @@ -130,6 +130,9 @@ Append a tier tag to proofs that aren't unit: | `@integration` | Needs database, network, filesystem, or external service | | `@e2e` | Needs browser, full app stack, or UI rendering | | `@manual` | Requires human judgment | +| `@windows` | Needs a real Windows runner (e.g. `windows-latest` in CI); skipped on POSIX hosts | + +Tier tags are not limited to this set: any `@` is parsed as a tier, and its proofs are read from the matching `.proofs-.json` file. The tags above are the common ones. ### Manual proofs diff --git a/references/supported_frameworks.md b/references/supported_frameworks.md index 30e68452..890eb61b 100644 --- a/references/supported_frameworks.md +++ b/references/supported_frameworks.md @@ -26,7 +26,7 @@ Shipped plugins that `purlin:init` does not yet auto-detect or scaffold — wire |-----------|-------------|-----------|------------|-----------|---------------|------| | **xUnit** | xunit (.NET) | C#, F#, VB.NET | `scripts/proof/xunit_purlin.cs` | `*.csproj` or `*.sln` present | `[Trait("PurlinProof", "feature:PROOF-1:RULE-1:unit")]` test trait | `specs/proof/proof_plugins_xunit.md` | -> **C# deterministic Pass-1 coverage:** the audit Pass-1 static checker (`scripts/audit/static_checks.py`) parses `.cs` test files directly — it reads the `[Trait("PurlinProof", ...)]` markers above, associates each with its `[Fact]`/`[Theory]` method body, and runs assert-true / no-assertion detection (recognizing xUnit `Assert.*`, NUnit `Assert.That`, MSTest `Assert.*`, and FluentAssertions `.Should()`). This is independent of the runtime proof plugin, which still records pass/fail during the actual test run. +> **C# deterministic Pass-1 coverage:** the audit Pass-1 static checker (`scripts/audit/static_checks.py`) parses `.cs` test files directly — it reads the `[Trait("PurlinProof", ...)]` markers above, associates each with its `[Fact]`/`[Theory]` method body, and runs assert-true / no-assertion detection (recognizing xUnit `Assert.*`, NUnit `Assert.That`, MSTest `Assert.*`, FluentAssertions `.Should()`, and Playwright `Expect(...).To*Async()` as assertions). This is independent of the runtime proof plugin, which still records pass/fail during the actual test run. > **Vitest version support:** the Vitest reporter (`vitest_purlin.ts`) collects proofs in the `onFinished(files)` hook, whose shape is stable across Vitest 2.x → 4.x (tested on 2.x and 3.x). Earlier `onTaskUpdate`-based collection broke silently on Vitest 2+ and is no longer used. Note that `jest_purlin.js` is **not** a drop-in for Vitest — Vitest does not call Jest's `onTestResult`/`onRunComplete` hooks, so Vitest projects use `vitest_purlin.ts`. diff --git a/scripts/audit/static_checks.py b/scripts/audit/static_checks.py index e7713f45..7793918e 100755 --- a/scripts/audit/static_checks.py +++ b/scripts/audit/static_checks.py @@ -737,7 +737,7 @@ def check_csharp(filepath, feature_name, rule_descs=None): Parses `[Trait("PurlinProof", "feature:PROOF-N:RULE-N:tier")]` markers, finds each marked test method's body, and applies assert-true / no-assertion detection. Recognizes xUnit `Assert.*`, NUnit `Assert.That`, MSTest `Assert.*`, - and FluentAssertions `.Should()` as assertions. + FluentAssertions `.Should()`, and Playwright `Expect(...).To*Async()` as assertions. """ with open(filepath, encoding='utf-8') as f: content = f.read() @@ -769,14 +769,21 @@ def check_csharp(filepath, feature_name, rule_descs=None): # no_assertions: no recognized assertion call in the body. # xUnit/NUnit/MSTest `Assert.`, FluentAssertions `.Should(`, Moq `.Verify(`. - if (not re.search(r'\bAssert\s*\.', body) - and not re.search(r'\.\s*Should\s*\(', body) - and not re.search(r'\.\s*Verify\s*\(', body)): + has_assert = re.search(r'\bAssert\s*\.', body) + has_should = re.search(r'\.\s*Should\s*\(', body) + has_verify = re.search(r'\.\s*Verify\s*\(', body) + # Playwright fluent assertions: Expect(...)/Assertions.Expect(...) chained to a + # ToAsync() call (ToBeVisibleAsync, ToHaveTextAsync, ToContainTextAsync, ...). + # Both tokens are required so a bare Expect(x) with no matcher is still flagged, and a + # plain LINQ `.ToListAsync()` (no Expect) is not mistaken for an assertion. + has_playwright = (re.search(r'\bExpect\s*\(', body) + and re.search(r'\.\s*To\w+Async\s*\(', body)) + if not (has_assert or has_should or has_verify or has_playwright): results.append({ 'proof_id': proof_id, 'rule_id': rule_id, 'test_name': test_name, 'status': 'fail', 'check': 'no_assertions', - 'reason': 'test method has no Assert./.Should()/.Verify() call', + 'reason': 'test method has no Assert./.Should()/.Verify()/Expect(...).To*Async() call', }) continue diff --git a/specs/_anchors/schema_proof_format.md b/specs/_anchors/schema_proof_format.md index a9be29ff..29db53ec 100644 --- a/specs/_anchors/schema_proof_format.md +++ b/specs/_anchors/schema_proof_format.md @@ -9,7 +9,7 @@ - RULE-1: Proof files are named `.proofs-.json` and live in the same directory as their spec - RULE-2: The JSON schema has a top-level `tier` string and a `proofs` array. Each proof entry contains: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier` - RULE-3: `status` is either `"pass"` or `"fail"` — no other values -- RULE-4: `tier` values are: `"unit"`, `"integration"`, `"e2e"` — no other automated tiers +- RULE-4: `tier` values are: `"unit"`, `"integration"`, `"e2e"`, and `"windows"` (a platform-gated tier proven only on a Windows runner) — no other automated tiers - RULE-5: Proof plugins use feature-scoped overwrite: load existing file, purge entries matching current feature, append new entries, write merged result - RULE-6: Proof files are committed to git — they are project records, not ephemeral build artifacts - RULE-7: Manual proofs are stamped inline in the spec's `## Proof` section as `@manual(, , )`, not in proof JSON files @@ -19,7 +19,7 @@ - PROOF-1 (RULE-1): Create a proof file at `specs/test/foo.proofs-unit.json`; run sync_status; verify it reads the file and associates it with the `foo` spec @integration - PROOF-2 (RULE-2): Validate a proof JSON file against the required fields; verify all 7 fields (`feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`) are present in each proof entry - PROOF-3 (RULE-3): Create a proof entry with `status: "error"`; run sync_status; verify it is not counted as a passing proof @integration -- PROOF-4 (RULE-4): Scan all real `*.proofs-*.json` files; verify every tier value (top-level and per-entry) is in the valid set {unit, integration, e2e}. Verify `spec_format.md` documents @integration and @e2e +- PROOF-4 (RULE-4): Scan all real `*.proofs-*.json` files; verify every tier value (top-level and per-entry) is in the valid set {unit, integration, e2e, windows}. Verify `spec_format.md` documents @integration, @e2e, and @windows - PROOF-5 (RULE-5): Pre-seed a proof file with feature B entries; run the real pytest proof plugin for feature A via subprocess; verify feature B entries are preserved and feature A entries are added @integration - PROOF-6 (RULE-6): Grep `.gitignore` for `*.proofs-*.json`; verify no gitignore rule excludes proof files - PROOF-7 (RULE-7): Grep `references/formats/proofs_format.md` for `@manual`; verify manual stamp format is `@manual(, , )` diff --git a/specs/audit/static_checks.md b/specs/audit/static_checks.md index 6af03e6e..0d7a0fb8 100644 --- a/specs/audit/static_checks.md +++ b/specs/audit/static_checks.md @@ -31,9 +31,9 @@ - RULE-26: `--write-cache` CLI flag reads a JSON dict of cache entries from stdin and merges them into the existing audit cache via write_audit_cache, printing a JSON status response with `status: "merged"` and entry count - RULE-27: check_js detects tautological assertions (`expect(true).toBe(true)`) and JS/TS test bodies with no `expect()` calls, returning the same JSON shape (proof_id, rule_id, test_name, status, reason) as check_python - RULE-28: check_js parses JS/TS test files with a brace-balancing tokenizer that (a) matches test titles containing apostrophes regardless of quote style, and (b) captures full test bodies containing nested braces — options objects, destructured parameters, type assertions — without truncating at the first inner `}` -- RULE-29: static_checks.py imports and runs on Windows as well as POSIX — no Unix-only module is imported unconditionally at module load. `fcntl` is imported under `try/except ImportError` and a module-level flag (`_HAS_FCNTL`) selects between `fcntl.flock` and an `msvcrt.locking` fallback, so write_audit_cache acquires and releases its exclusive lock through that flag and the Windows lock path is exercisable without `fcntl` -- RULE-30: static_checks.py performs all text I/O as UTF-8 regardless of the OS locale: every text-mode `open()` specifies `encoding='utf-8'`, and stdout/stderr are reconfigured to UTF-8 at startup, so the tool's own UTF-8 specs/proofs/criteria/config/cache files both parse (read) and print (stdout) correctly even when the platform codec is cp1252/ASCII (e.g. on Windows). Without this, `--load-criteria` raises `UnicodeDecodeError` reading `audit_criteria.md` and `UnicodeEncodeError` printing its glyphs -- RULE-31: For `.cs` test files, Pass 1 parses `[Trait("PurlinProof", "feature:PROOF-N:RULE-N:tier")]` traits, associates each with its `[Fact]`/`[Theory]` method body, and applies assert-true and no-assertion detection — returning the same JSON shape (proof_id, rule_id, test_name, status, reason) as check_python/check_js. xUnit `Assert.*`, NUnit `Assert.That`, MSTest `Assert.*`, and FluentAssertions `.Should()` all count as assertions +- RULE-29: static_checks.py imports and runs on Windows as well as POSIX — no Unix-only module is imported unconditionally at module load. `fcntl` is imported under `try/except ImportError` and a module-level flag (`_HAS_FCNTL`) selects between `fcntl.flock` and an `msvcrt.locking` fallback, so write_audit_cache acquires and releases its exclusive lock through that flag and the Windows lock path is exercisable without `fcntl`. The native lock path is verified for real on a `windows-latest` GitHub Actions runner (where `_HAS_FCNTL` is genuinely False and `msvcrt` is real), not only by host simulation +- RULE-30: static_checks.py performs all text I/O as UTF-8 regardless of the OS locale: every text-mode `open()` specifies `encoding='utf-8'`, and stdout/stderr are reconfigured to UTF-8 at startup, so the tool's own UTF-8 specs/proofs/criteria/config/cache files both parse (read) and print (stdout) correctly even when the platform codec is cp1252/ASCII (e.g. on Windows). Without this, `--load-criteria` raises `UnicodeDecodeError` reading `audit_criteria.md` and `UnicodeEncodeError` printing its glyphs. This is verified for real under the native Windows console codec on a `windows-latest` GitHub Actions runner, not only by host locale simulation +- RULE-31: For `.cs` test files, Pass 1 parses `[Trait("PurlinProof", "feature:PROOF-N:RULE-N:tier")]` traits, associates each with its `[Fact]`/`[Theory]` method body, and applies assert-true and no-assertion detection — returning the same JSON shape (proof_id, rule_id, test_name, status, reason) as check_python/check_js. xUnit `Assert.*`, NUnit `Assert.That`, MSTest `Assert.*`, FluentAssertions `.Should()`, and Playwright fluent assertions — `Expect(...)`/`Assertions.Expect(...)` chained to a `To*Async()` matcher (`ToBeVisibleAsync`, `ToHaveTextAsync`, `ToContainTextAsync`, …) — all count as assertions. A bare `Expect(x)` with no matcher chain is not an assertion (still flagged no_assertions) ## Proof @@ -87,3 +87,6 @@ - PROOF-50 (RULE-29): With `fcntl` forced unavailable (`_HAS_FCNTL=False`) and a recording fake `msvcrt` injected into `sys.modules`, call write_audit_cache; verify the cache round-trips intact AND the fake `msvcrt.locking` was invoked with `LK_LOCK` then `LK_UNLCK` @integration - PROOF-51 (RULE-29): e2e: Invoke the real static_checks.py CLI as a subprocess on the host (`--write-cache` via stdin, then `--read-cache`, then Pass-1 on a hollow `.cs` fixture); verify valid JSON is emitted on stdout at each step and `.purlin/cache/audit_cache.json` is written to disk @e2e - PROOF-52 (RULE-30): e2e: Invoke the real static_checks.py CLI `--load-criteria` as a subprocess with `env PYTHONUTF8=0`, `LC_ALL=C`, and `LANG=C` (simulating the Windows cp1252/ASCII default), reading the tool's own `references/audit_criteria.md` which contains non-ASCII glyphs; verify it exits 0 (no `UnicodeDecodeError` on read, no `UnicodeEncodeError` on stdout) and the printed criteria text contains a non-ASCII character @e2e +- PROOF-55 (RULE-31): Run check_csharp on a `.cs` test whose only assertion is `await Assertions.Expect(page.Locator(...)).ToBeVisibleAsync();`; verify status=pass (recognized as an assertion, NOT flagged no_assertions). Also run on a body containing a bare `Expect(x)` with no `To*Async()` matcher and no Assert/Should/Verify; verify status=fail check=no_assertions +- PROOF-53 (RULE-29): On a real `windows-latest` runner, assert `_HAS_FCNTL` is False (POSIX `fcntl` genuinely absent, real `msvcrt` in use), then call write_audit_cache with no fake/monkeypatch into a temp dir; read the cache back and verify the entries round-trip intact and the `audit_cache.json.lock` file was created adjacent to the cache — exercising the native `msvcrt.locking` path on real Windows @windows +- PROOF-54 (RULE-30): On a real `windows-latest` runner under the native console codec (no `PYTHONUTF8`/`LC_ALL`/`LANG` overrides), invoke the real static_checks.py CLI `--load-criteria` as a subprocess over the tool's own non-ASCII `references/audit_criteria.md`; verify it exits 0 (no `UnicodeDecodeError` on read, no `UnicodeEncodeError` on stdout) and the printed criteria text contains a non-ASCII character @windows diff --git a/specs/audit/static_checks.proofs-unit.json b/specs/audit/static_checks.proofs-unit.json index b4931263..5186d275 100644 --- a/specs/audit/static_checks.proofs-unit.json +++ b/specs/audit/static_checks.proofs-unit.json @@ -621,6 +621,15 @@ "test_name": "test_dispatch_routes_cs_to_check_csharp", "status": "pass", "tier": "unit" + }, + { + "feature": "static_checks", + "id": "PROOF-55", + "rule": "RULE-31", + "test_file": "dev/test_static_checks.py", + "test_name": "test_recognizes_playwright_fluent_assertions", + "status": "pass", + "tier": "unit" } ] } From 28c475c0af26d2eb00c7039e6b05fbe2a2566023 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 25 Jun 2026 16:47:41 +0000 Subject: [PATCH 2/5] test(static_checks): windows-native proofs from windows-latest [skip ci] --- specs/audit/static_checks.proofs-windows.json | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 specs/audit/static_checks.proofs-windows.json diff --git a/specs/audit/static_checks.proofs-windows.json b/specs/audit/static_checks.proofs-windows.json new file mode 100644 index 00000000..6e2dda20 --- /dev/null +++ b/specs/audit/static_checks.proofs-windows.json @@ -0,0 +1,23 @@ +{ + "tier": "windows", + "proofs": [ + { + "feature": "static_checks", + "id": "PROOF-53", + "rule": "RULE-29", + "test_file": "dev\\test_windows_native.py", + "test_name": "test_real_msvcrt_lock_path", + "status": "pass", + "tier": "windows" + }, + { + "feature": "static_checks", + "id": "PROOF-54", + "rule": "RULE-30", + "test_file": "dev\\test_windows_native.py", + "test_name": "test_load_criteria_native_console", + "status": "pass", + "tier": "windows" + } + ] +} From f0cf3db3b24a469e0e8ece61729fc72f06d75c16 Mon Sep 17 00:00:00 2001 From: rlabarca Date: Thu, 25 Jun 2026 12:50:14 -0400 Subject: [PATCH 3/5] verify: [Complete:static_checks] features=1/1 vhash=68530817 static_checks 29/29 rules proved across unit/integration/e2e + the new @windows tier (PROOF-53/54 from windows-latest CI). All 85 proofs pass. Co-Authored-By: Claude Opus 4.8 (1M context) --- .purlin/report-data.js | 2 +- specs/audit/static_checks.receipt.json | 185 +++++++++++++------------ 2 files changed, 101 insertions(+), 86 deletions(-) diff --git a/.purlin/report-data.js b/.purlin/report-data.js index b3fb9740..9f6712f2 100644 --- a/.purlin/report-data.js +++ b/.purlin/report-data.js @@ -1 +1 @@ -const PURLIN_DATA = {"timestamp":"2026-06-05T14:37:28.809015+00:00","project":"purlin","version":"0.9.2","docs_url":"https://github.com/rlabarca/purlin/blob/main/docs/index.md","summary":{"total_features":35,"verified":35,"passing":0,"partial":0,"failing":0,"untested":0},"features":[{"name":"config_engine","category":"mcp","type":"feature","is_global":false,"source_url":null,"pinned":null,"description":"Two-file configuration system that separates shared team defaults (`config.json`, committed) from per-user overrides (`config.local.json`, gitignored). Resolution merges both files so framework updates remain visible while respecting local preferences.","source_path":null,"stack":"python/stdlib, json","ext_status":null,"proved":15,"total":15,"deferred":0,"status":"VERIFIED","vhash":"3d48c34c","receipt":{"commit":"6cd4518e322d351ea3c91a7b877f1e59f90f2492","timestamp":"2026-04-07T13:46:48.452182+00:00","stale":false},"rules":[{"id":"RULE-1","description":"`find_project_root` returns `PURLIN_PROJECT_ROOT` env var when set and the directory exists","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Set PURLIN_PROJECT_ROOT to a temp dir with .purlin/ inside; call find_project_root; verify it returns that dir without climbing","test_file":"dev/test_config_engine.py","test_name":"test_env_var_takes_precedence","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-10","description":"`update_config` uses atomic replacement (write to .tmp, then os.replace) to prevent partial writes","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-10","description":"Call update_config; verify no .tmp file remains and os.replace is used in source","test_file":"dev/test_config_engine.py","test_name":"test_atomic_replacement","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-2","description":"`find_project_root` climbs from the start directory looking for a `.purlin/` marker directory","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Create /tmp/a/b/c with .purlin/ in /tmp/a; call find_project_root(start_dir=\"/tmp/a/b/c\"); verify returns /tmp/a","test_file":"dev/test_config_engine.py","test_name":"test_climbs_to_purlin_marker","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-3","description":"`find_project_root` falls back to cwd if no `.purlin/` marker is found in any ancestor","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Call find_project_root on a dir with no .purlin/ ancestor; verify returns cwd","test_file":"dev/test_config_engine.py","test_name":"test_falls_back_to_cwd","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-4","description":"`resolve_config` merges config.json (base) with config.local.json (overrides) \u2014 local keys win for duplicates, base keys are preserved when absent from local","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Create config.json with {\"team\": \"default\", \"shared\": \"base\"} and config.local.json with {\"shared\": \"override\", \"local_only\": true}; call resolve_config; verify result is {\"team\": \"default\", \"shared\": \"override\", \"local_only\": true} \u2014 base key preserved, shared key overridden, local-only key included","test_file":"dev/test_config_engine.py","test_name":"test_merge_base_with_overrides","tier":"unit","status":"pass","audit":""},{"id":"PROOF-11","description":"Create config.json with {\"report\": true, \"version\": \"0.9.0\"} and config.local.json with {\"pre_push\": \"strict\"}; call resolve_config; verify result has all three keys \u2014 framework key \"report\" visible despite not being in local. This is the key scenario: framework adds a new default, existing user keeps their overrides, new default is visible","test_file":"dev/test_config_engine.py","test_name":"test_framework_new_key_visible_through_existing_local","tier":"unit","status":"pass","audit":""},{"id":"PROOF-4","description":"Create config.json with {\"team\": \"default\", \"shared\": \"base\"} and config.local.json with {\"shared\": \"override\", \"local_only\": true}; call resolve_config; verify result is {\"team\": \"default\", \"shared\": \"override\", \"local_only\": true} \u2014 base key preserved, shared key overridden, local-only key included","test_file":"dev/test_config_engine.py","test_name":"test_local_only_keys_included","tier":"unit","status":"pass","audit":""},{"id":"PROOF-4","description":"Create config.json with {\"team\": \"default\", \"shared\": \"base\"} and config.local.json with {\"shared\": \"override\", \"local_only\": true}; call resolve_config; verify result is {\"team\": \"default\", \"shared\": \"override\", \"local_only\": true} \u2014 base key preserved, shared key overridden, local-only key included","test_file":"dev/test_config_engine.py","test_name":"test_empty_local_returns_shared","tier":"unit","status":"pass","audit":""},{"id":"PROOF-4","description":"Create config.json with {\"team\": \"default\", \"shared\": \"base\"} and config.local.json with {\"shared\": \"override\", \"local_only\": true}; call resolve_config; verify result is {\"team\": \"default\", \"shared\": \"override\", \"local_only\": true} \u2014 base key preserved, shared key overridden, local-only key included","test_file":"dev/test_config_engine.py","test_name":"test_cli_dump","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-5","description":"`resolve_config` returns config.json contents when config.local.json does not exist","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Create config.json with {\"key\": \"val\"} and no config.local.json; call resolve_config; verify returns {\"key\": \"val\"} and config.local.json was NOT created","test_file":"dev/test_config_engine.py","test_name":"test_shared_only_no_copy","tier":"unit","status":"pass","audit":""},{"id":"PROOF-5","description":"Create config.json with {\"key\": \"val\"} and no config.local.json; call resolve_config; verify returns {\"key\": \"val\"} and config.local.json was NOT created","test_file":"dev/test_config_engine.py","test_name":"test_local_without_shared","tier":"unit","status":"pass","audit":""},{"id":"PROOF-5","description":"Create config.json with {\"key\": \"val\"} and no config.local.json; call resolve_config; verify returns {\"key\": \"val\"} and config.local.json was NOT created","test_file":"dev/test_config_engine.py","test_name":"test_cli_key","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-6","description":"`resolve_config` ignores config.local.json and warns to stderr when it contains invalid JSON, returning config.json only","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Create config.local.json with invalid JSON and config.json with {\"key\": \"fallback\"}; call resolve_config; verify returns {\"key\": \"fallback\"} and stderr contains warning","test_file":"dev/test_config_engine.py","test_name":"test_malformed_local_falls_back","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-7","description":"`resolve_config` returns empty dict when neither config file exists","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Call resolve_config in a dir with no config files; verify returns {}","test_file":"dev/test_config_engine.py","test_name":"test_no_configs_returns_empty","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-8","description":"`update_config` writes only to config.local.json, never to config.json","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-12","description":"Call update_config to set \"report\" to false; verify config.json is untouched and config.local.json now has \"report\": false; call resolve_config; verify merged result has \"report\": false (local override wins)","test_file":"dev/test_config_engine.py","test_name":"test_local_override_wins_in_merge","tier":"unit","status":"pass","audit":""},{"id":"PROOF-8","description":"Create config.json with {\"team\": \"v1\"}; call update_config(root, \"user_pref\", \"dark\"); verify config.json still has {\"team\": \"v1\"} unchanged and config.local.json has {\"user_pref\": \"dark\"}","test_file":"dev/test_config_engine.py","test_name":"test_writes_only_to_local","tier":"unit","status":"pass","audit":""},{"id":"PROOF-8","description":"Create config.json with {\"team\": \"v1\"}; call update_config(root, \"user_pref\", \"dark\"); verify config.json still has {\"team\": \"v1\"} unchanged and config.local.json has {\"user_pref\": \"dark\"}","test_file":"dev/test_config_engine.py","test_name":"test_update_creates_local_if_missing","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-9","description":"`update_config` preserves existing keys in config.local.json when adding or updating a key","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-9","description":"Create config.local.json with {\"existing\": \"keep\"}; call update_config(root, \"added\", \"new\"); verify config.local.json has both {\"existing\": \"keep\", \"added\": \"new\"}","test_file":"dev/test_config_engine.py","test_name":"test_preserves_existing_keys","tier":"unit","status":"pass","audit":""},{"id":"PROOF-9","description":"Create config.local.json with {\"existing\": \"keep\"}; call update_config(root, \"added\", \"new\"); verify config.local.json has both {\"existing\": \"keep\", \"added\": \"new\"}","test_file":"dev/test_config_engine.py","test_name":"test_update_overwrites_existing_key","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-1","description":"FORBIDDEN \u2014 No `eval()` or `exec()` calls in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Grep `scripts/` for `eval(` and `exec(` in `.py` files; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_eval_or_exec","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-2","description":"FORBIDDEN \u2014 No `subprocess` calls with `shell=True` in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Grep `scripts/**/*.py` for `shell\\s*=\\s*True`; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_shell_true","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-3","description":"FORBIDDEN \u2014 No `os.system()` calls in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Grep `scripts/**/*.py` for `os\\.system\\s*\\(`; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_os_system","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-4","description":"FORBIDDEN \u2014 No hardcoded credential assignments (password/secret/api_key/token literals) in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Grep `scripts/` for assignment patterns `(password|secret|api_key|token)\\s*=\\s*[\"'][^\"']+[\"']` in `.py`, `.sh`, `.js` files, excluding test files and comments; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_hardcoded_credentials","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-5","description":"All subprocess calls use list-mode arguments (not string commands)","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Grep `scripts/**/*.py` for `subprocess.run(` and `subprocess.call(`; verify each call's first argument is a list literal `[`, not a string","test_file":"dev/test_security.py","test_name":"test_subprocess_uses_list_args","tier":"unit","status":"pass","audit":""}]}],"audit":null},{"name":"dashboard_visual","category":"_anchors","type":"anchor","is_global":false,"source_url":null,"pinned":null,"description":"Defines the visual constants \u2014 colors, typography, status indicators, and spacing \u2014 for the Purlin dashboard. When required by other specs, it ensures the design system is enforced in one place.","source_path":null,"stack":null,"ext_status":null,"proved":11,"total":11,"deferred":0,"status":"VERIFIED","vhash":"2b240c30","receipt":{"commit":"4aa2a2e5db4c6232f1bf92169401ac2f3d68409f","timestamp":"2026-04-06T17:45:20.297645+00:00","stale":false},"rules":[{"id":"RULE-1","description":"Dark theme background is #0f172a, card background is #1e293b","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Load dashboard in Playwright with dark theme; verify computed background-color of body is #0f172a and card element is #1e293b","test_file":"dev/test_purlin_report.py","test_name":"test_dark_theme_backgrounds","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-10","description":"Integrity color coding: green at 80%+, amber at 50-79%, red below 50%, gray when null","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-10","description":"Load dashboard with features at integrity 90%, 60%, and 30%; verify green, amber, and red color classes respectively","test_file":"dev/test_purlin_report.py","test_name":"test_integrity_color_coding","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-11","description":"All colors and visual constants are defined as CSS custom properties in :root or theme selectors, not hardcoded in component styles","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-11","description":"Grep purlin-report.html for hardcoded hex colors outside of CSS custom property definitions; verify none exist in component style rules","test_file":"dev/test_purlin_report.py","test_name":"test_no_hardcoded_hex_outside_custom_properties","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-2","description":"Light theme background is #f1f5f9, card background is #ffffff","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Load dashboard in Playwright with light theme; verify computed background-color of body is #f1f5f9 and card element is #ffffff","test_file":"dev/test_purlin_report.py","test_name":"test_light_theme_backgrounds","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-3","description":"Status green is #22c55e, amber is #f59e0b, red is #ef4444, teal accent is #2dd4bf","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Grep purlin-report.html CSS for --green: #22c55e, --amber: #f59e0b, --red: #ef4444, --teal: #2dd4bf; verify all present","test_file":"dev/test_purlin_report.py","test_name":"test_status_colors_in_css","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-4","description":"Font stack is -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Grep purlin-report.html for the sans-serif font stack; verify it includes -apple-system and Roboto","test_file":"dev/test_purlin_report.py","test_name":"test_sans_serif_font_stack","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-5","description":"Monospace font stack is \"SF Mono\", \"Fira Code\", \"Cascadia Code\", \"JetBrains Mono\", Consolas, monospace","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Grep purlin-report.html for the monospace font stack; verify it includes \"SF Mono\" and Consolas","test_file":"dev/test_purlin_report.py","test_name":"test_mono_font_stack","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-6","description":"VERIFIED status badge is solid green pill with white text","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Load dashboard with a VERIFIED feature in Playwright; verify .sb-ready element has background-color matching --green and white text","test_file":"dev/test_purlin_report.py","test_name":"test_ready_badge_style","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-7","description":"PARTIAL status badge is amber outline pill (transparent background, amber border)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Load dashboard with a partial feature in Playwright; verify .sb-partial element has transparent background and amber border","test_file":"dev/test_purlin_report.py","test_name":"test_partial_badge_style","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-8","description":"FAIL status badge is solid red pill with white text","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-8","description":"Load dashboard with a FAIL feature in Playwright; verify .sb-fail element has background-color matching --red","test_file":"dev/test_purlin_report.py","test_name":"test_fail_badge_style","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-9","description":"UNTESTED status badge is gray pill with amber text; no-proofs badge (generic) is gray pill at reduced opacity","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-9","description":"Load dashboard with an UNTESTED feature in Playwright; verify .sb-untested element has gray background and amber text color; verify a separate no-proofs element (.sb-none) has reduced opacity","test_file":"dev/test_purlin_report.py","test_name":"test_untested_badge_and_no_proofs_opacity","tier":"unit","status":"pass","audit":""}]}],"audit":null},{"name":"drift","category":"mcp","type":"feature","is_global":false,"source_url":null,"pinned":null,"description":"Structured change summary tool for the purlin:drift skill. Resolves a \"since\" anchor, classifies changed files, detects spec drift, and returns machine-readable JSON for the drift skill to interpret and format.","source_path":null,"stack":"python/stdlib, json, subprocess (list-only), hashlib","ext_status":null,"proved":29,"total":29,"deferred":0,"status":"VERIFIED","vhash":"eb81ccd9","receipt":{"commit":"011ccbaa30dc0bae1ab95724e96609a5e91ecaba","timestamp":"2026-04-08T15:36:25.098091+00:00","stale":false},"rules":[{"id":"RULE-1","description":"Resolves the \"since\" anchor from: explicit argument (integer or date), most recent `verify:` commit, most recent tag, or smart fallback based on commits since Purlin initialization","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Call with explicit since=\"5\"; verify HEAD~5. Call with verify commit in log; verify that SHA","test_file":"dev/test_mcp_server.py","test_name":"test_since_anchor_resolution","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"RULE-10","description":"Detects broken scope paths \u2014 files referenced in `> Scope:` that no longer exist on disk \u2014 and returns them in a `broken_scopes` array","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-10","description":"Create spec with non-existent scope path; verify broken_scopes entry","test_file":"dev/test_mcp_server.py","test_name":"test_broken_scope_detection","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"RULE-11","description":"Returns a spec-from-code recommendation when no verification anchor exists and >= 30 commits have been made since Purlin initialization","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-11","description":"Create a temp repo with 50+ commits, no verify or tags; call drift; verify spec-from-code recommendation. Create repo with <30 commits; verify normal drift","test_file":"dev/test_mcp_server.py","test_name":"test_large_repo_recommends_spec_from_code","tier":"unit","status":"pass","audit":"STRONG"},{"id":"PROOF-11","description":"Create a temp repo with 50+ commits, no verify or tags; call drift; verify spec-from-code recommendation. Create repo with <30 commits; verify normal drift","test_file":"dev/test_mcp_server.py","test_name":"test_small_repo_returns_normal_drift","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"RULE-12","description":"When an externally-referenced anchor has both external and local rules, drift detects staleness when the external source advances \u2014 returning an external_anchor_drift entry with status stale and the remote SHA","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-12","description":"e2e: Create bare git repo; create mixed anchor pinned to initial SHA; advance repo; run drift; verify external_anchor_drift entry with status=stale","test_file":"dev/test_drift.py","test_name":"test_mixed_anchor_stale_when_external_source_advances","tier":"e2e","status":"pass","audit":"STRONG"},{"id":"PROOF-17","description":"e2e: Create external anchor pinned to initial SHA; advance repo; run drift; verify stale entry with remote SHA","test_file":"dev/test_drift.py","test_name":"test_stale_entry_includes_remote_sha","tier":"e2e","status":"pass","audit":"STRONG"}]},{"id":"RULE-13","description":"When the external source advances AND the local anchor file is modified, drift surfaces both: an external_anchor_drift entry with status stale AND a spec_changes entry showing the new rule","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-14","description":"e2e: Advance external AND modify local anchor; run drift; verify both external_anchor_drift stale AND spec_changes with new_rules","test_file":"dev/test_drift.py","test_name":"test_external_advance_plus_local_modification_surfaces_both","tier":"e2e","status":"pass","audit":"STRONG"}]},{"id":"RULE-14","description":"drift returns the anchor name in external_anchor_drift matching the anchor's spec name, not the external repo name or path","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-16","description":"e2e: Create anchor named local_security; advance repo; verify drift returns anchor=local_security","test_file":"dev/test_drift.py","test_name":"test_anchor_name_in_drift_matches_spec_name_not_repo_path","tier":"e2e","status":"pass","audit":"STRONG"}]},{"id":"RULE-15","description":"Detects unpinned state when an anchor has `> Source:` but no `> Pinned:`, returning an external_anchor_drift entry with status `unpinned`","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-17","description":"e2e: Create external anchor pinned to initial SHA; advance repo; run drift; verify stale entry with remote SHA","test_file":"dev/test_mcp_server.py","test_name":"test_unpinned_anchor_detected","tier":"unit","status":"pass","audit":"STRONG"},{"id":"PROOF-18","description":"e2e: Create anchor with Source but no Pinned; run drift; verify unpinned status","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"RULE-16","description":"Returns a `rule_details` object for each spec with CHANGED_BEHAVIOR files, containing per-rule ID, description, and proof status (pass/fail/unproved), plus the list of changed scope files","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-19","description":"Create spec with 3 rules and `> Scope:` pointing to a source file; add proofs for 2 of 3 rules; modify the scope file and commit; call drift; verify rule_details contains the spec with 3 rule entries, 2 with proof_status=pass, 1 with proof_status=unproved, and the changed file in changed_files","test_file":"dev/test_mcp_server.py","test_name":"test_rule_details_for_changed_behavior","tier":"integration","status":"pass","audit":"STRONG"}]},{"id":"RULE-2","description":"Classifies changed files into categories: CHANGED_SPECS, TESTS_ADDED, CHANGED_BEHAVIOR, NO_IMPACT, NEW_BEHAVIOR","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Create changed spec, test file, and README; verify CHANGED_SPECS, TESTS_ADDED, NO_IMPACT classifications","test_file":"dev/test_mcp_server.py","test_name":"test_file_classification","tier":"unit","status":"pass","audit":"STRONG"},{"id":"PROOF-13","description":"e2e: Modify local anchor file (add rule); commit; run drift; verify anchor classified as CHANGED_SPECS","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"RULE-3","description":"Returns JSON with `since`, `commits`, `files`, `spec_changes`, and `proof_status` fields","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Call drift; verify all 5 top-level JSON keys present","test_file":"dev/test_mcp_server.py","test_name":"test_drift_json_structure","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"RULE-4","description":"proof_status entries include a `structural_checks` count of structural proofs, and `proved`/`total` fields only count behavioral proofs","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Create spec with grep-based proof passing; verify proved=1 and total=1 in proof_status","test_file":"dev/test_mcp_server.py","test_name":"test_drift_structural_only_flag","tier":"unit","status":"pass","audit":"WEAK"}]},{"id":"RULE-5","description":"proof_status totals include required and global anchor rules, not just own rules","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Create feature requiring anchor with 2 rules; verify proof_status total includes anchor rules","test_file":"dev/test_mcp_server.py","test_name":"test_drift_includes_required_in_total","tier":"unit","status":"pass","audit":"STRONG"},{"id":"PROOF-15","description":"e2e: Create feature requiring stale anchor; add all proofs; verify proof_status total=4 proved=4 despite staleness","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"RULE-6","description":"Classifies files in `skills/`, `agents/`, and `.claude/agents/` as NEW_BEHAVIOR when not in scope \u2014 never NO_IMPACT","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Create changed file at skills/new/SKILL.md with no scope; verify classified as NEW_BEHAVIOR","test_file":"dev/test_mcp_server.py","test_name":"test_skill_md_not_no_impact","tier":"unit","status":"pass","audit":"STRONG"},{"id":"PROOF-6","description":"Create changed file at skills/new/SKILL.md with no scope; verify classified as NEW_BEHAVIOR","test_file":"dev/test_mcp_server.py","test_name":"test_agent_md_not_no_impact","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"RULE-7","description":"Scope matching supports directory prefix: `> Scope: src/api/` matches `src/api/login.js`","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Create spec with `> Scope: src/api/` and changed file src/api/login.js; verify CHANGED_BEHAVIOR","test_file":"dev/test_mcp_server.py","test_name":"test_scope_prefix_matching","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"RULE-8","description":"File entries include `behavioral_gap: true` when the file's spec has zero proved rules (total > 0 but proved == 0) and category is CHANGED_BEHAVIOR","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-8","description":"Create spec with zero proofs and changed scope file; verify behavioral_gap: true","test_file":"dev/test_mcp_server.py","test_name":"test_coverage_gap_drift_flag","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"RULE-9","description":"Returns a `drift_flags` array summarizing features with zero proved rules (coverage gap) that have changed files","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-9","description":"Same setup; verify drift_flags array entry with reason behavioral_gap_with_code_change","test_file":"dev/test_mcp_server.py","test_name":"test_drift_flags_array","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"schema_spec_format/RULE-1","description":"Every spec MUST contain exactly two required sections: `## Rules`, `## Proof`","label":"required","source":"schema_spec_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Parse `references/formats/spec_format.md`; find the `## Required Sections` area; verify both sections (`## Rules`, `## Proof`) appear within it","test_file":"dev/test_schema_spec_format.py","test_name":"test_format_documents_two_sections","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"schema_spec_format/RULE-2","description":"Rules are numbered sequentially as `RULE-N` (RULE-1, RULE-2, ...). Unnumbered lines under `## Rules` trigger a sync_status WARNING","label":"required","source":"schema_spec_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Create a spec with an unnumbered line under `## Rules` (e.g., `- some constraint without RULE-N prefix`); run sync_status; verify it reports a WARNING about unnumbered rules","test_file":"dev/test_schema_spec_format.py","test_name":"test_unnumbered_rule_triggers_warning","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"schema_spec_format/RULE-3","description":"Proofs are numbered as `PROOF-N (RULE-N)` \u2014 each proof references the rule it covers","label":"required","source":"schema_spec_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Grep `specs/**/*.md` for lines under `## Proof`; verify every proof line matches the pattern `PROOF-\\d+ \\(RULE-\\d+\\)`","test_file":"dev/test_schema_spec_format.py","test_name":"test_all_proof_lines_match_pattern","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"schema_spec_format/RULE-4","description":"Every RULE must have at least one corresponding PROOF","label":"required","source":"schema_spec_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Create a spec with RULE-1 but no PROOF referencing RULE-1; run sync_status; verify it reports the rule as uncovered","test_file":"dev/test_schema_spec_format.py","test_name":"test_rule_without_proof_shows_uncovered","tier":"unit","status":"pass","audit":"WEAK"}]},{"id":"schema_spec_format/RULE-5","description":"`> Requires:` metadata, when present, is a comma-separated list of anchor names whose rules also apply to this feature","label":"required","source":"schema_spec_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Create a spec with `> Requires: base_anchor`; run sync_status; verify the report includes `base_anchor/RULE-1` in the coverage output","test_file":"dev/test_schema_spec_format.py","test_name":"test_requires_includes_referenced_rules","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"schema_spec_format/RULE-6","description":"`> Scope:` metadata, when present, is a comma-separated list of source file paths the feature touches","label":"required","source":"schema_spec_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Create a spec with `> Scope: scripts/mcp/purlin_server.py`; verify sync_status parses and displays the scope files","test_file":"dev/test_schema_spec_format.py","test_name":"test_scope_metadata_parsed","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"schema_spec_format/RULE-7","description":"Feature specs use heading `# Feature: `, anchor specs use `# Anchor: `","label":"required","source":"schema_spec_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Grep `specs/**/*.md` for first-level headings; verify each starts with `# Feature:` or `# Anchor:`","test_file":"dev/test_schema_spec_format.py","test_name":"test_spec_headings_use_correct_prefix","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"schema_spec_format/RULE-8","description":"`> Description:` metadata, when present, is a plain-language summary of the spec; supports multi-line via `>` continuation lines that do not match a metadata field pattern (`> FieldName:`)","label":"required","source":"schema_spec_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-8","description":"Create a spec with `> Description: First line\\n> second line\\n> Scope: src/`; parse description; verify it equals \"First line second line\" (continuation joined, Scope not included)","test_file":"dev/test_schema_spec_format.py","test_name":"test_multiline_description_continuation","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"security_no_dangerous_patterns/RULE-1","description":"FORBIDDEN \u2014 No `eval()` or `exec()` calls in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Grep `scripts/` for `eval(` and `exec(` in `.py` files; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_eval_or_exec","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"security_no_dangerous_patterns/RULE-2","description":"FORBIDDEN \u2014 No `subprocess` calls with `shell=True` in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Grep `scripts/**/*.py` for `shell\\s*=\\s*True`; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_shell_true","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"security_no_dangerous_patterns/RULE-3","description":"FORBIDDEN \u2014 No `os.system()` calls in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Grep `scripts/**/*.py` for `os\\.system\\s*\\(`; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_os_system","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"security_no_dangerous_patterns/RULE-4","description":"FORBIDDEN \u2014 No hardcoded credential assignments (password/secret/api_key/token literals) in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Grep `scripts/` for assignment patterns `(password|secret|api_key|token)\\s*=\\s*[\"'][^\"']+[\"']` in `.py`, `.sh`, `.js` files, excluding test files and comments; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_hardcoded_credentials","tier":"unit","status":"pass","audit":"WEAK"}]},{"id":"security_no_dangerous_patterns/RULE-5","description":"All subprocess calls use list-mode arguments (not string commands)","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Grep `scripts/**/*.py` for `subprocess.run(` and `subprocess.call(`; verify each call's first argument is a list literal `[`, not a string","test_file":"dev/test_security.py","test_name":"test_subprocess_uses_list_args","tier":"unit","status":"pass","audit":"STRONG"}]}],"audit":{"integrity":94,"strong":15,"weak":1,"hollow":0,"manual":0,"findings":[{"proof_id":"PROOF-4","rule_id":"RULE-4","level":"WEAK","priority":"HIGH","criterion":"missing assertion for structural_checks count field","fix":"add assertion: assert \"structural_checks\" in data[\"proof_status\"][\"refs\"] to verify the field exists in the proof_status entry"}]}},{"name":"figma_web","category":"workflows","type":"feature","is_global":false,"source_url":null,"pinned":null,"description":"End-to-end workflow proof that the exact 5-message sequence from docs/examples/figma-web-app.md produces a working web UI with high visual fidelity to the Figma design. Uses the modal-test Figma file as the design reference. Each step is a real `claude -p` invocation \u2014 nothing is faked.","source_path":null,"stack":"python/pytest, playwright, shell/bash","ext_status":null,"proved":22,"total":22,"deferred":0,"status":"VERIFIED","vhash":"0ef07382","receipt":{"commit":"6cd4518e322d351ea3c91a7b877f1e59f90f2492","timestamp":"2026-04-07T13:46:48.452182+00:00","stale":false},"rules":[{"id":"RULE-1","description":"Prerequisites \u2014 purlin:init creates a valid Purlin project before the workflow starts","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Run purlin:init via claude -p in a temp dir; verify .purlin/config.json exists with required fields","test_file":"dev/test_e2e_figma_web.py","test_name":"test_init_config","tier":"e2e","status":"pass","audit":"STRONG"}]},{"id":"RULE-10","description":"All 5 documented steps produce their expected artifacts \u2014 no extra commands needed","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-10","description":"Verify all 5 steps produced artifacts: config, anchor, spec, HTML, tests","test_file":"dev/test_e2e_figma_web.py","test_name":"test_all_steps_produced_artifacts","tier":"e2e","status":"pass","audit":"STRONG"}]},{"id":"RULE-11","description":"FORBIDDEN \u2014 Modifying existing framework docs, proof files, skill definitions, or source code to make the workflow succeed","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-11","description":"Check git diff of framework repo; verify no new changes to docs/, skills/, scripts/, references/","test_file":"dev/test_e2e_figma_web.py","test_name":"test_no_framework_mods","tier":"e2e","status":"pass","audit":"STRONG"}]},{"id":"RULE-12","description":"FORBIDDEN \u2014 Claude silently fixing errors; on failure it must report the issue","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-12","description":"Send a broken Figma URL via claude -p; verify Claude reports an error rather than silently succeeding","test_file":"dev/test_e2e_figma_web.py","test_name":"test_failure_reports_error","tier":"e2e","status":"pass","audit":"STRONG"}]},{"id":"RULE-13","description":"The audit static_checks pipeline returns a classification for anchor proofs","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-13","description":"Run static_checks.py on the anchor test file; verify it returns proof classifications","test_file":"dev/test_e2e_figma_web.py","test_name":"test_audit_classification","tier":"e2e","status":"pass","audit":"WEAK"}]},{"id":"RULE-14","description":"Each test run saves screenshot(s) to dev/figma_web_result.png showing the built UI","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-14","description":"Capture Playwright screenshot to dev/figma_web_result.png; verify valid PNG","test_file":"dev/test_e2e_figma_web.py","test_name":"test_screenshot_saved","tier":"e2e","status":"pass","audit":"STRONG"}]},{"id":"RULE-15","description":"Message 4+5 \u2014 \"run the tests\" and \"verify and ship\" result in passing tests (pytest exit 0)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-15","description":"Send messages 4+5 via claude -p --resume; run pytest in the built project; verify exit code 0","test_file":"dev/test_e2e_figma_web.py","test_name":"test_verify_succeeds","tier":"e2e","status":"pass","audit":"STRONG"}]},{"id":"RULE-2","description":"Message 1 \u2014 sharing the Figma URL creates a design anchor in specs/_anchors/ with Source, Visual-Reference, Pinned, and Type: design metadata","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Send message 1 (Figma URL) via claude -p --resume; verify anchor .md in specs/_anchors/ has > Source:, > Visual-Reference: figma://, > Pinned:, > Type: design","test_file":"dev/test_e2e_figma_web.py","test_name":"test_anchor_metadata","tier":"e2e","status":"pass","audit":"STRONG"}]},{"id":"RULE-3","description":"The anchor contains at least one visual-match rule with an @e2e proof","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Read the anchor; verify it has a visual-match rule and an @e2e proof","test_file":"dev/test_e2e_figma_web.py","test_name":"test_anchor_visual_rule","tier":"e2e","status":"pass","audit":"STRONG"}]},{"id":"RULE-4","description":"Message 2 \u2014 describing the feature creates a spec with > Requires: referencing the anchor","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Send message 2 via claude -p --resume; verify a feature spec exists with > Requires: referencing the anchor","test_file":"dev/test_e2e_figma_web.py","test_name":"test_spec_requires_anchor","tier":"e2e","status":"pass","audit":"STRONG"}]},{"id":"RULE-5","description":"The anchor contains the real Figma file key, proving Figma MCP was used","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Verify the anchor text contains the Figma file key TEZI0T6lObCJrC9mkmZT8v","test_file":"dev/test_e2e_figma_web.py","test_name":"test_figma_mcp_used","tier":"e2e","status":"pass","audit":"STRONG"}]},{"id":"RULE-6","description":"Message 3 \u2014 \"build it\" produces a functioning HTML file that renders in a browser","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Send message 3 via claude -p --resume; open the built HTML in Playwright; verify page renders visible content","test_file":"dev/test_e2e_figma_web.py","test_name":"test_ui_renders","tier":"e2e","status":"pass","audit":"WEAK"}]},{"id":"RULE-7","description":"The build writes tests with proof markers referencing the feature and anchor","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Read test files; verify proof markers reference both feature and anchor names","test_file":"dev/test_e2e_figma_web.py","test_name":"test_proof_markers","tier":"e2e","status":"pass","audit":"WEAK"}]},{"id":"RULE-8","description":"A Playwright screenshot of the built UI can be captured and compared against a reference","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-8","description":"Capture Playwright screenshot of built UI; verify it is a valid PNG alongside the MCP fixture reference","test_file":"dev/test_e2e_figma_web.py","test_name":"test_screenshot_pipeline","tier":"e2e","status":"pass","audit":"STRONG"}]},{"id":"RULE-9","description":"The built UI visually matches the Figma design within the fidelity threshold","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-9","description":"Compute pixel diff between built UI screenshot and MCP fixture reference; verify diff is within threshold","test_file":"dev/test_e2e_figma_web.py","test_name":"test_visual_fidelity","tier":"e2e","status":"pass","audit":"STRONG"}]},{"id":"skill_anchor/RULE-1","description":"Skill file has YAML frontmatter with `name` and `description` fields","label":"required","source":"skill_anchor","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Grep `skills/anchor/SKILL.md` for YAML frontmatter delimiters (`---`); verify `name:` and `description:` fields exist","test_file":"dev/test_skill_specs.py","test_name":"test_has_frontmatter","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"skill_anchor/RULE-2","description":"Skill file contains a `## Usage` section documenting command syntax","label":"required","source":"skill_anchor","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Grep `skills/anchor/SKILL.md` for `## Usage`; verify the section exists","test_file":"dev/test_skill_specs.py","test_name":"test_has_usage_section","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"skill_anchor/RULE-3","description":"The `name` field in frontmatter is `anchor`, matching the directory name","label":"required","source":"skill_anchor","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Extract `name:` from frontmatter; verify it equals `anchor`","test_file":"dev/test_skill_specs.py","test_name":"test_name_matches_directory","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"skill_anchor/RULE-4","description":"Skill includes commit instructions or git operations for file modifications","label":"required","source":"skill_anchor","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Grep `skills/anchor/SKILL.md` for commit instructions (`git commit`, `commit the`, `create.*commit`); verify present","test_file":"dev/test_skill_specs.py","test_name":"test_has_commit_instructions","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"skill_anchor/RULE-5","description":"An anchor written with only Part 1 (authoring) fields \u2014 no `> Source:`, `> Path:`, or `> Pinned:` \u2014 is parsed as a local anchor: `source_url` is None, no staleness checks run, and sync_status output contains no Source/Pinned lines for that anchor","label":"required","source":"skill_anchor","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"e2e: Create anchor with only Part 1 fields (Description, Type, rules, proofs \u2014 no Source/Path/Pinned); call _scan_specs; verify source_url is None and pinned is None. Run sync_status; verify output does NOT contain \"Source:\" or \"Pinned:\" for that anchor","test_file":"dev/test_e2e_external_refs.sh","test_name":"Part 1-only anchor parsed as local","tier":"e2e","status":"pass","audit":"STRONG"}]},{"id":"skill_anchor/RULE-6","description":"An anchor with Part 2 tracking fields (`> Source:`, `> Path:`, `> Pinned:`) added is parsed as externally-referenced: `source_url`, `pinned`, and `source_path` are all populated, and sync_status output shows Source/Path/Pinned lines for that anchor","label":"required","source":"skill_anchor","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"e2e: Create anchor with Part 1 fields plus Part 2 tracking fields (Source, Path, Pinned pointing to a local bare git repo); call _scan_specs; verify source_url, pinned, and source_path are all populated with correct values. Run sync_status; verify output contains \"Source:\", \"Path:\", and \"Pinned:\" lines","test_file":"dev/test_e2e_external_refs.sh","test_name":"Part 2 tracking fields fully parsed","tier":"e2e","status":"pass","audit":"WEAK"}]},{"id":"skill_anchor/RULE-7","description":"Adding Part 2 tracking fields to a Part 1-only anchor transitions it from local to externally-referenced \u2014 the same anchor file, before and after adding `> Source:` and `> Pinned:`, produces different `_scan_specs` results for `source_url` and `pinned`","label":"required","source":"skill_anchor","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"e2e: Create a Part 1-only anchor; call _scan_specs and capture source_url (None). Then add Part 2 fields to the same file; call _scan_specs again; verify source_url is now populated and pinned matches the written value","test_file":"dev/test_e2e_external_refs.sh","test_name":"Part 1\u21922 transition changes scan results","tier":"e2e","status":"pass","audit":"WEAK"}]}],"audit":{"integrity":80,"strong":12,"weak":3,"hollow":0,"manual":0,"findings":[{"proof_id":"PROOF-6","rule_id":"RULE-6","level":"WEAK","priority":"MEDIUM","criterion":"assertion is looser than proof description","fix":"Add Playwright assertion for modal-specific element: assert pw_page.query_selector('button') is not None."},{"proof_id":"PROOF-7","rule_id":"RULE-7","level":"WEAK","priority":"MEDIUM","criterion":"assertion is looser than proof description \u2014 does not verify both feature AND anchor name","fix":"Assert both feature_name and anchor_name appear in test sources."},{"proof_id":"PROOF-13","rule_id":"RULE-13","level":"WEAK","priority":"MEDIUM","criterion":"assertion is looser than proof description \u2014 only checks non-empty classifications list","fix":"Assert at least one proof entry has a recognized status: assert any(p.get('status') in ('pass','fail') for p in proofs)."}]}},{"name":"mcp_transport","category":"mcp","type":"feature","is_global":false,"source_url":null,"pinned":null,"description":"JSON-RPC 2.0 transport layer for the Purlin MCP server. Reads requests from stdin, dispatches to tool handlers, writes responses to stdout. Implements MCP protocol initialization and error handling.","source_path":null,"stack":"python/stdlib, json","ext_status":null,"proved":12,"total":12,"deferred":0,"status":"VERIFIED","vhash":"3769b2fe","receipt":{"commit":"6cd4518e322d351ea3c91a7b877f1e59f90f2492","timestamp":"2026-04-07T13:46:48.452182+00:00","stale":false},"rules":[{"id":"RULE-1","description":"The server implements MCP protocol version `2024-11-05` and responds to `initialize` with protocolVersion, capabilities, and serverInfo","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Send initialize request; verify protocolVersion and serverInfo","test_file":"dev/test_mcp_server.py","test_name":"test_initialize","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-2","description":"`tools/list` returns exactly 3 tools: `sync_status`, `purlin_config`, `drift` \u2014 matching the manifest definitions","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Send tools/list; verify exactly 3 tools named sync_status, purlin_config, drift","test_file":"dev/test_mcp_server.py","test_name":"test_tools_list","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-3","description":"`notifications/initialized` produces no response (notification, not request)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Send notifications/initialized; verify no response on stdout","test_file":"dev/test_mcp_server.py","test_name":"test_notification_no_response","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-4","description":"Invalid JSON input returns error code `-32700` (Parse error)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Send invalid JSON; verify error code -32700","test_file":"dev/test_mcp_server.py","test_name":"test_parse_error","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-5","description":"Unknown methods return error code `-32601` with the method name in the message","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Send unknown method; verify error code -32601 with method name","test_file":"dev/test_mcp_server.py","test_name":"test_unknown_method","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-6","description":"Unknown tool names in `tools/call` return error code `-32601` with the tool name in the message","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Send tools/call with unknown tool; verify error code -32601 with tool name","test_file":"dev/test_mcp_server.py","test_name":"test_unknown_tool","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-7","description":"Server logs startup to stderr \u2014 stdout is reserved for JSON-RPC responses","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Start server; verify startup message on stderr, not stdout","test_file":"dev/test_mcp_server.py","test_name":"test_startup_logs_to_stderr","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-1","description":"FORBIDDEN \u2014 No `eval()` or `exec()` calls in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Grep `scripts/` for `eval(` and `exec(` in `.py` files; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_eval_or_exec","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-2","description":"FORBIDDEN \u2014 No `subprocess` calls with `shell=True` in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Grep `scripts/**/*.py` for `shell\\s*=\\s*True`; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_shell_true","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-3","description":"FORBIDDEN \u2014 No `os.system()` calls in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Grep `scripts/**/*.py` for `os\\.system\\s*\\(`; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_os_system","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-4","description":"FORBIDDEN \u2014 No hardcoded credential assignments (password/secret/api_key/token literals) in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Grep `scripts/` for assignment patterns `(password|secret|api_key|token)\\s*=\\s*[\"'][^\"']+[\"']` in `.py`, `.sh`, `.js` files, excluding test files and comments; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_hardcoded_credentials","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-5","description":"All subprocess calls use list-mode arguments (not string commands)","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Grep `scripts/**/*.py` for `subprocess.run(` and `subprocess.call(`; verify each call's first argument is a list literal `[`, not a string","test_file":"dev/test_security.py","test_name":"test_subprocess_uses_list_args","tier":"unit","status":"pass","audit":""}]}],"audit":null},{"name":"pre_push_hook","category":"hooks","type":"feature","is_global":false,"source_url":null,"pinned":null,"description":"Pre-push git hook that blocks pushes when tests are failing. Runs unit-tier tests and checks sync_status for proof coverage. Supports two modes: warn (default) blocks only on FAILING, while strict blocks anything not VERIFIED.","source_path":null,"stack":"shell/bash, python3 (sync_status invocation, config parsing)","ext_status":null,"proved":14,"total":14,"deferred":0,"status":"VERIFIED","vhash":"0e655efb","receipt":{"commit":"6cd4518e322d351ea3c91a7b877f1e59f90f2492","timestamp":"2026-04-07T13:46:48.452182+00:00","stale":false},"rules":[{"id":"RULE-1","description":"Blocks push with exit 1 when any feature has status FAILING in sync_status output","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-17","description":"e2e: Create external anchor; create feature requiring it; set anchor proof to FAIL; run pre-push; verify exit 1 blocked","test_file":"dev/test_e2e_external_refs.sh","test_name":"pre-push blocks on anchor fail","tier":"unit","status":"pass","audit":"STRONG"},{"id":"PROOF-17","description":"e2e: Create external anchor; create feature requiring it; set anchor proof to FAIL; run pre-push; verify exit 1 blocked","test_file":"dev/test_e2e_external_refs.sh","test_name":"pre-push blocks on anchor fail","tier":"e2e","status":"pass","audit":"STRONG"},{"id":"PROOF-1","description":"Set up temp project with .purlin/ and specs/; create a spec with 3 rules; create proof file with one FAIL entry; run pre-push.sh; verify exit code is 1 and stdout contains \"PUSH BLOCKED\"","test_file":"","test_name":"","tier":"integration","status":"planned","audit":""},{"id":"PROOF-8","description":"Full lifecycle test: create temp git repo with .purlin/ and specs/; create spec with 3 rules; create proof file with 1 PASS, 1 FAIL, 1 unproved; run hook and verify exit 1 (blocked by FAIL); fix FAIL to PASS; run hook and verify exit 0 with warning (PARTIAL \u2014 unproved rules remain); add missing proof as PASS; run hook and verify exit 0 silently (all PASSING)","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"RULE-2","description":"Allows push with exit 0 and prints a warning when any feature is PARTIAL (some behavioral rules proved, none failing)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Set up temp project with .purlin/ and specs/; create proof file covering some but not all behavioral rules with no FAIL entries; run pre-push.sh; verify exit code is 0 and stdout contains \"partial coverage\"","test_file":"dev/test_pre_push_hook.py","test_name":"test_partial_coverage_exits_0_with_warning","tier":"integration","status":"pass","audit":"STRONG"}]},{"id":"RULE-3","description":"Allows push with exit 0 silently when no specs directory exists or specs directory contains no .md files","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Set up temp project with .purlin/ but no specs/ directory; run pre-push.sh; verify exit code is 0 and stdout is empty","test_file":"dev/test_pre_push_hook.py","test_name":"test_no_specs_dir_exits_0_silently","tier":"integration","status":"pass","audit":"STRONG"}]},{"id":"RULE-4","description":"Allows push with exit 0 when all features are PASSING or VERIFIED (all behavioral rules proved, no failures)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Set up temp project with .purlin/ and specs/; create proof file with all PASS entries (PASSING status); run pre-push.sh; verify exit code is 0","test_file":"dev/test_pre_push_hook.py","test_name":"test_all_pass_exits_0","tier":"integration","status":"pass","audit":"STRONG"},{"id":"PROOF-12","description":"Create spec with rule description containing \"FAIL\" (e.g. \"RULE-1: FAIL status badge is solid red pill\"); create all-pass proofs; run hook; verify exit 0 and no \"PUSH BLOCKED\" \u2014 description text must not false-positive trigger blocking","test_file":"dev/test_pre_push_hook.py","test_name":"test_fail_keyword_in_rule_description_does_not_block","tier":"integration","status":"pass","audit":"STRONG"}]},{"id":"RULE-5","description":"Detects test framework from `.purlin/config.json` `test_framework` field, falling back to auto-detection (pytest if conftest.py or pyproject.toml [tool.pytest] exists, jest if package.json contains jest, shell otherwise)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Set up temp project with `.purlin/config.json` containing `{\"test_framework\": \"pytest\"}`; verify pre-push.sh selects pytest; repeat with `{\"test_framework\": \"jest\"}` and verify jest is selected","test_file":"dev/test_pre_push_hook.py","test_name":"test_detects_framework_from_config_and_auto","tier":"integration","status":"pass","audit":"STRONG"}]},{"id":"RULE-6","description":"Runs only unit-tier tests (pytest excludes `not integration`, jest uses `--testPathPattern=unit`, shell runs `*.test.sh`)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Set up temp project with conftest.py and two test files (one unit, one @integration); run pre-push.sh; verify the unit test ran (sentinel file created) and the integration test was skipped (no sentinel)","test_file":"dev/test_pre_push_hook.py","test_name":"test_unit_test_runs_integration_skipped","tier":"integration","status":"pass","audit":"STRONG"}]},{"id":"RULE-7","description":"Produces output showing which features passed, which have partial coverage, and which are blocked with FAIL proofs","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Set up temp project with specs containing PASSING, FAILING, and PARTIAL features; run pre-push.sh; verify stdout contains \"PASSING features\", \"partial coverage\", and \"PUSH BLOCKED\" sections","test_file":"dev/test_pre_push_hook.py","test_name":"test_output_shows_blocked_and_recovery_on_fail","tier":"integration","status":"pass","audit":"STRONG"},{"id":"PROOF-13","description":"Create spec with one failing proof; run hook; verify recovery message contains `/purlin:unit-test test_feature`, `/purlin:status`, and `/purlin:build`","test_file":"dev/test_pre_push_hook.py","test_name":"test_recovery_message_lists_feature_specific_commands","tier":"integration","status":"pass","audit":"STRONG"}]},{"id":"RULE-8","description":"In strict mode (`\"pre_push\": \"strict\"` in config), blocks push with exit 1 when any feature is not VERIFIED \u2014 this includes PASSING features (full coverage but no receipt) and PARTIAL features (incomplete behavioral rule coverage); allows push only when all features are VERIFIED","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-9","description":"Set strict mode in config; create proofs for 2 of 3 behavioral rules (PARTIAL \u2014 no FAIL, but not fully covered); run hook; verify exit 1 and output contains \"strict mode\"","test_file":"dev/test_pre_push_hook.py","test_name":"test_strict_mode_blocks_partial_coverage","tier":"integration","status":"pass","audit":"STRONG"},{"id":"PROOF-10","description":"Set strict mode in config; create proofs for all behavioral rules (PASSING \u2014 full coverage, no receipt); run hook; verify exit 1 and output contains \"strict mode\" (PASSING blocked in strict, only VERIFIED allowed)","test_file":"dev/test_pre_push_hook.py","test_name":"test_strict_mode_allows_passing_blocks_partial","tier":"integration","status":"pass","audit":"STRONG"},{"id":"PROOF-14","description":"Set strict mode; create partial proofs (no FAIL); run hook; verify exit 1 and output contains `RECOVERY STEPS`, `/purlin:unit-test`, and `/purlin:verify`","test_file":"dev/test_pre_push_hook.py","test_name":"test_strict_mode_recovery_includes_verify_command","tier":"integration","status":"pass","audit":"STRONG"},{"id":"PROOF-15","description":"e2e: Strict mode with own rules proved but required rules unproved; verify exit 1 with strict mode block","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""},{"id":"PROOF-16","description":"e2e: Strict mode with all (own + required) rules proved; verify exit 0","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"RULE-9","description":"After `purlin:init`, `.git/hooks/pre-push` exists, is executable, and runs `scripts/hooks/pre-push.sh`","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-11","description":"Run purlin:init on a fresh git repo; verify .git/hooks/pre-push exists, is executable, and a git push with failing proofs is intercepted and blocked","test_file":"dev/test_pre_push_hook.py","test_name":"test_installed_hook_exists_is_executable_and_blocks","tier":"integration","status":"pass","audit":"STRONG"}]},{"id":"security_no_dangerous_patterns/RULE-1","description":"FORBIDDEN \u2014 No `eval()` or `exec()` calls in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Grep `scripts/` for `eval(` and `exec(` in `.py` files; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_eval_or_exec","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-2","description":"FORBIDDEN \u2014 No `subprocess` calls with `shell=True` in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Grep `scripts/**/*.py` for `shell\\s*=\\s*True`; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_shell_true","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"security_no_dangerous_patterns/RULE-3","description":"FORBIDDEN \u2014 No `os.system()` calls in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Grep `scripts/**/*.py` for `os\\.system\\s*\\(`; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_os_system","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"security_no_dangerous_patterns/RULE-4","description":"FORBIDDEN \u2014 No hardcoded credential assignments (password/secret/api_key/token literals) in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Grep `scripts/` for assignment patterns `(password|secret|api_key|token)\\s*=\\s*[\"'][^\"']+[\"']` in `.py`, `.sh`, `.js` files, excluding test files and comments; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_hardcoded_credentials","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"security_no_dangerous_patterns/RULE-5","description":"All subprocess calls use list-mode arguments (not string commands)","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Grep `scripts/**/*.py` for `subprocess.run(` and `subprocess.call(`; verify each call's first argument is a list literal `[`, not a string","test_file":"dev/test_security.py","test_name":"test_subprocess_uses_list_args","tier":"unit","status":"pass","audit":"STRONG"}]}],"audit":{"integrity":100,"strong":13,"weak":0,"hollow":0,"manual":0,"findings":[]}},{"name":"proof_common","category":"_anchors","type":"anchor","is_global":false,"source_url":null,"pinned":null,"description":"The behavior every Purlin proof plugin implements regardless of language \u2014 spec-directory resolution, proof-file naming, fallback, feature-scoped overwrite, the 7 required fields, pass/fail status, the no-marker no-op, glob-based discovery, the fallback stderr warning, and purge-on-rerun. Each per-language proof plugin spec (proof_plugins_pytest, proof_plugins_jest, proof_plugins_shell, proof_plugins_c, proof_plugins_php, proof_plugins_sql, proof_plugins_vitest, proof_plugins_xunit) requires this anchor and adds only its framework-specific rules.","source_path":null,"stack":null,"ext_status":null,"proved":10,"total":10,"deferred":0,"status":"VERIFIED","vhash":"b7f9d949","receipt":{"commit":"ac522f60debf1f866e0a6acfb25bcb10fafd2057","timestamp":"2026-06-02T15:45:42.990726+00:00","stale":false},"rules":[{"id":"RULE-1","description":"Each plugin resolves the spec directory by scanning `specs/**/*.md` and matching the feature name to the spec filename stem","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Create `specs/hooks/gate_hook.md` and run a proof plugin with feature `gate_hook`; verify the proof file is written to `specs/hooks/`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"spec dir resolution","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"RULE-10","description":"When a test is removed from a re-run, the old proof entry is purged and not carried over from the previous proof file","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-13","description":"Write a proof file with 2 proofs, then re-run with only 1; verify the removed entry is purged and not carried over","test_file":"dev/test_multilang_proof_plugins.py","test_name":"test_removed_test_purged_on_rerun","tier":"integration","status":"pass","audit":"STRONG"},{"id":"PROOF-12","description":"e2e: Write proof file with only 1 of 2 proofs (test deletion); verify coverage shows 1/2 not 2/2","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"RULE-2","description":"Proof files are written to the spec's directory as `.proofs-.json`","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Run a proof plugin for feature `gate_hook` tier `unit`; verify output file is named `gate_hook.proofs-unit.json`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"proof file naming","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"RULE-3","description":"When the spec directory for a feature is not found, the plugin falls back to writing to `specs/`","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Run a proof plugin for feature `nonexistent_feature` (no matching spec); verify proof file is written to `specs/`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"fallback to specs/","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"RULE-4","description":"Feature-scoped overwrite: existing entries for other features are preserved; only the current feature's entries are replaced","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Create a proof file with entries for features A and B; run the plugin for feature A only; verify feature B entries are preserved and feature A entries are replaced","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"feature-scoped overwrite","tier":"unit","status":"pass","audit":"STRONG"},{"id":"PROOF-10","description":"e2e: Create 2 specs; write proofs for each separately; verify both PASSING and no cross-contamination","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""},{"id":"PROOF-11","description":"e2e: Overwrite one feature's proofs via shell harness; verify target feature updated, other feature untouched","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"RULE-5","description":"Each proof entry contains all 7 required fields: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Run a proof plugin and read the output JSON; verify each entry in `proofs` array contains all 7 fields: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","test_file":"dev/test_multilang_proof_plugins.py","test_name":"test_python_proof_plugin_real_execution","tier":"integration","status":"pass","audit":"STRONG"},{"id":"PROOF-5","description":"Run a proof plugin and read the output JSON; verify each entry in `proofs` array contains all 7 fields: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"all 7 required fields","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"RULE-6","description":"`status` is `\"pass\"` when the test passes and `\"fail\"` when it fails \u2014 no other values","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Run a passing test and a failing test with proof markers; verify the passing test has `status: \"pass\"` and the failing test has `status: \"fail\"`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"pass/fail status","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"RULE-7","description":"If no proof markers are collected during a test run, no proof files are written (no-op)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Run a test suite with no proof markers; verify no `*.proofs-*.json` files are created","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"no markers no files","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"RULE-8","description":"Custom/community proof plugins installed to `.purlin/plugins/` require no registration \u2014 `sync_status` discovers proof files by globbing `specs/**/*.proofs-*.json`, so any plugin that writes files in that pattern works automatically","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-8","description":"Place a `.proofs-unit.json` file in a spec directory written by a non-built-in source; run `sync_status`; verify it reads the proofs","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"custom plugin proofs discovered by sync_status","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"RULE-9","description":"When spec directory lookup falls back to specs/ root, the plugin emits a warning to stderr naming the missing spec and suggesting purlin:spec ","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-9","description":"Run a proof plugin for a feature with no matching spec; verify stderr contains a warning naming the feature and suggesting purlin:spec","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"fallback warning to stderr","tier":"unit","status":"pass","audit":"STRONG"}]}],"audit":{"integrity":100,"strong":10,"weak":0,"hollow":0,"manual":0,"findings":[]}},{"name":"proof_plugins_c","category":"proof","type":"feature","is_global":false,"source_url":null,"pinned":null,"description":"The C proof plugin. A header-only collector prints accumulated proofs as JSON to stdout; a python emitter reads stdin and writes proof files. Inherits all shared proof-plugin behavior from proof_common.","source_path":null,"stack":"c/gcc (header-only collector), python3 emitter reading stdin","ext_status":null,"proved":24,"total":24,"deferred":0,"status":"VERIFIED","vhash":"36f70645","receipt":{"commit":"ac522f60debf1f866e0a6acfb25bcb10fafd2057","timestamp":"2026-06-02T15:45:42.990726+00:00","stale":false},"rules":[{"id":"RULE-1","description":"The C marker is `purlin_proof(\"feature\", \"PROOF-N\", \"RULE-N\", passed_bool, \"test_name\", __FILE__, \"tier\")` called from C source code","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Compile a C test with `purlin_proof()` calls using gcc; run the binary; verify stdout JSON contains all 7 required fields","test_file":"dev/test_multilang_proof_plugins.py","test_name":"test_c_proof_plugin_real_compilation","tier":"integration","status":"pass","audit":"STRONG"},{"id":"PROOF-3","description":"Compile a C test where the `passed_bool` is false; run and pipe to emitter; verify `status: \"fail\"` in the proof file","test_file":"dev/test_multilang_proof_plugins.py","test_name":"test_c_proof_plugin_failing_test","tier":"integration","status":"pass","audit":"STRONG"}]},{"id":"RULE-2","description":"`purlin_proof_finish()` prints accumulated proofs as JSON to stdout; `c_purlin_emit.py` reads stdin and performs feature-scoped overwrite to proof files","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Compile and run a C test; pipe stdout to `c_purlin_emit.py`; verify proof file is written to spec directory with correct entries","test_file":"dev/test_multilang_proof_plugins.py","test_name":"test_c_emit_pipeline_writes_to_spec_dir","tier":"integration","status":"pass","audit":"STRONG"}]},{"id":"proof_common/RULE-1","description":"Each plugin resolves the spec directory by scanning `specs/**/*.md` and matching the feature name to the spec filename stem","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Create `specs/hooks/gate_hook.md` and run a proof plugin with feature `gate_hook`; verify the proof file is written to `specs/hooks/`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"spec dir resolution","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"proof_common/RULE-10","description":"When a test is removed from a re-run, the old proof entry is purged and not carried over from the previous proof file","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-13","description":"Write a proof file with 2 proofs, then re-run with only 1; verify the removed entry is purged and not carried over","test_file":"dev/test_multilang_proof_plugins.py","test_name":"test_removed_test_purged_on_rerun","tier":"integration","status":"pass","audit":"STRONG"},{"id":"PROOF-12","description":"e2e: Write proof file with only 1 of 2 proofs (test deletion); verify coverage shows 1/2 not 2/2","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"proof_common/RULE-2","description":"Proof files are written to the spec's directory as `.proofs-.json`","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Run a proof plugin for feature `gate_hook` tier `unit`; verify output file is named `gate_hook.proofs-unit.json`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"proof file naming","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"proof_common/RULE-3","description":"When the spec directory for a feature is not found, the plugin falls back to writing to `specs/`","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Run a proof plugin for feature `nonexistent_feature` (no matching spec); verify proof file is written to `specs/`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"fallback to specs/","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"proof_common/RULE-4","description":"Feature-scoped overwrite: existing entries for other features are preserved; only the current feature's entries are replaced","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Create a proof file with entries for features A and B; run the plugin for feature A only; verify feature B entries are preserved and feature A entries are replaced","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"feature-scoped overwrite","tier":"unit","status":"pass","audit":"STRONG"},{"id":"PROOF-10","description":"e2e: Create 2 specs; write proofs for each separately; verify both PASSING and no cross-contamination","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""},{"id":"PROOF-11","description":"e2e: Overwrite one feature's proofs via shell harness; verify target feature updated, other feature untouched","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"proof_common/RULE-5","description":"Each proof entry contains all 7 required fields: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Run a proof plugin and read the output JSON; verify each entry in `proofs` array contains all 7 fields: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","test_file":"dev/test_multilang_proof_plugins.py","test_name":"test_python_proof_plugin_real_execution","tier":"integration","status":"pass","audit":"STRONG"},{"id":"PROOF-5","description":"Run a proof plugin and read the output JSON; verify each entry in `proofs` array contains all 7 fields: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"all 7 required fields","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"proof_common/RULE-6","description":"`status` is `\"pass\"` when the test passes and `\"fail\"` when it fails \u2014 no other values","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Run a passing test and a failing test with proof markers; verify the passing test has `status: \"pass\"` and the failing test has `status: \"fail\"`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"pass/fail status","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"proof_common/RULE-7","description":"If no proof markers are collected during a test run, no proof files are written (no-op)","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Run a test suite with no proof markers; verify no `*.proofs-*.json` files are created","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"no markers no files","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"proof_common/RULE-8","description":"Custom/community proof plugins installed to `.purlin/plugins/` require no registration \u2014 `sync_status` discovers proof files by globbing `specs/**/*.proofs-*.json`, so any plugin that writes files in that pattern works automatically","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-8","description":"Place a `.proofs-unit.json` file in a spec directory written by a non-built-in source; run `sync_status`; verify it reads the proofs","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"custom plugin proofs discovered by sync_status","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"proof_common/RULE-9","description":"When spec directory lookup falls back to specs/ root, the plugin emits a warning to stderr naming the missing spec and suggesting purlin:spec ","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-9","description":"Run a proof plugin for a feature with no matching spec; verify stderr contains a warning naming the feature and suggesting purlin:spec","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"fallback warning to stderr","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"schema_proof_format/RULE-1","description":"Proof files are named `.proofs-.json` and live in the same directory as their spec","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Create a proof file at `specs/test/foo.proofs-unit.json`; run sync_status; verify it reads the file and associates it with the `foo` spec","test_file":"dev/test_schema_proof_format.py","test_name":"test_proof_file_read_by_sync_status","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"schema_proof_format/RULE-2","description":"The JSON schema has a top-level `tier` string and a `proofs` array. Each proof entry contains: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Validate a proof JSON file against the required fields; verify all 7 fields (`feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`) are present in each proof entry","test_file":"dev/test_schema_proof_format.py","test_name":"test_proof_entry_has_all_seven_fields","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"schema_proof_format/RULE-3","description":"`status` is either `\"pass\"` or `\"fail\"` \u2014 no other values","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Create a proof entry with `status: \"error\"`; run sync_status; verify it is not counted as a passing proof","test_file":"dev/test_schema_proof_format.py","test_name":"test_invalid_status_not_counted","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"schema_proof_format/RULE-4","description":"`tier` values are: `\"unit\"`, `\"integration\"`, `\"e2e\"` \u2014 no other automated tiers","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Scan all real `*.proofs-*.json` files; verify every tier value (top-level and per-entry) is in the valid set {unit, integration, e2e}. Verify `spec_format.md` documents @integration and","test_file":"dev/test_schema_proof_format.py","test_name":"test_standard_tiers_documented","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-5","description":"Proof plugins use feature-scoped overwrite: load existing file, purge entries matching current feature, append new entries, write merged result","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Pre-seed a proof file with feature B entries; run the real pytest proof plugin for feature A via subprocess; verify feature B entries are preserved and feature A entries are added","test_file":"dev/test_schema_proof_format.py","test_name":"test_feature_scoped_overwrite","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-6","description":"Proof files are committed to git \u2014 they are project records, not ephemeral build artifacts","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Grep `.gitignore` for `*.proofs-*.json`; verify no gitignore rule excludes proof files","test_file":"dev/test_schema_proof_format.py","test_name":"test_proof_files_not_gitignored","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-7","description":"Manual proofs are stamped inline in the spec's `## Proof` section as `@manual(, , )`, not in proof JSON files","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Grep `references/formats/proofs_format.md` for `@manual`; verify manual stamp format is `@manual(, , )`","test_file":"dev/test_schema_proof_format.py","test_name":"test_manual_stamp_format_documented","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-1","description":"FORBIDDEN \u2014 No `eval()` or `exec()` calls in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Grep `scripts/` for `eval(` and `exec(` in `.py` files; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_eval_or_exec","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"security_no_dangerous_patterns/RULE-2","description":"FORBIDDEN \u2014 No `subprocess` calls with `shell=True` in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Grep `scripts/**/*.py` for `shell\\s*=\\s*True`; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_shell_true","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"security_no_dangerous_patterns/RULE-3","description":"FORBIDDEN \u2014 No `os.system()` calls in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Grep `scripts/**/*.py` for `os\\.system\\s*\\(`; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_os_system","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"security_no_dangerous_patterns/RULE-4","description":"FORBIDDEN \u2014 No hardcoded credential assignments (password/secret/api_key/token literals) in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Grep `scripts/` for assignment patterns `(password|secret|api_key|token)\\s*=\\s*[\"'][^\"']+[\"']` in `.py`, `.sh`, `.js` files, excluding test files and comments; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_hardcoded_credentials","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-5","description":"All subprocess calls use list-mode arguments (not string commands)","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Grep `scripts/**/*.py` for `subprocess.run(` and `subprocess.call(`; verify each call's first argument is a list literal `[`, not a string","test_file":"dev/test_security.py","test_name":"test_subprocess_uses_list_args","tier":"unit","status":"pass","audit":""}]}],"audit":{"integrity":100,"strong":3,"weak":0,"hollow":0,"manual":0,"findings":[]}},{"name":"proof_plugins_jest","category":"proof","type":"feature","is_global":false,"source_url":null,"pinned":null,"description":"The Jest proof reporter. Parses `[proof:...]` markers from test titles and emits standardized proof JSON. Inherits all shared proof-plugin behavior from proof_common.","source_path":null,"stack":"node/jest, custom reporter (onTestResult + onRunComplete hooks)","ext_status":null,"proved":26,"total":26,"deferred":0,"status":"VERIFIED","vhash":"2e0ab5ae","receipt":{"commit":"ac522f60debf1f866e0a6acfb25bcb10fafd2057","timestamp":"2026-06-02T15:45:42.990726+00:00","stale":false},"rules":[{"id":"RULE-1","description":"The marker is parsed from the test title using the pattern `[proof:feature:PROOF-N:RULE-N:tier]` where tier defaults to `\"unit\"`","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Create a test `it(\"works [proof:feat:PROOF-1:RULE-1:unit]\", ...)`; run Jest; verify proof entry has `feature: \"feat\"`, `id: \"PROOF-1\"`, `rule: \"RULE-1\"`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"jest marker parsing","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"RULE-2","description":"Test names without a `[proof:...]` marker are ignored","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Create a test without `[proof:...]` in the title; run Jest; verify no proof entry is emitted for that test","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"jest no marker ignored","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"RULE-3","description":"`test_file` is recorded as the path relative to Jest's `rootDir`","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Run Jest; verify `test_file` is relative to `rootDir`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"jest test_file relative","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"RULE-4","description":"Jest status `\"passed\"` maps to `\"pass\"` and all other statuses map to `\"fail\"`","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Create a failing Jest test with a proof marker; verify `status` is `\"fail\"`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"jest status mapping","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"proof_common/RULE-1","description":"Each plugin resolves the spec directory by scanning `specs/**/*.md` and matching the feature name to the spec filename stem","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Create `specs/hooks/gate_hook.md` and run a proof plugin with feature `gate_hook`; verify the proof file is written to `specs/hooks/`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"spec dir resolution","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"proof_common/RULE-10","description":"When a test is removed from a re-run, the old proof entry is purged and not carried over from the previous proof file","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-13","description":"Write a proof file with 2 proofs, then re-run with only 1; verify the removed entry is purged and not carried over","test_file":"dev/test_multilang_proof_plugins.py","test_name":"test_removed_test_purged_on_rerun","tier":"integration","status":"pass","audit":"STRONG"},{"id":"PROOF-12","description":"e2e: Write proof file with only 1 of 2 proofs (test deletion); verify coverage shows 1/2 not 2/2","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"proof_common/RULE-2","description":"Proof files are written to the spec's directory as `.proofs-.json`","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Run a proof plugin for feature `gate_hook` tier `unit`; verify output file is named `gate_hook.proofs-unit.json`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"proof file naming","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"proof_common/RULE-3","description":"When the spec directory for a feature is not found, the plugin falls back to writing to `specs/`","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Run a proof plugin for feature `nonexistent_feature` (no matching spec); verify proof file is written to `specs/`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"fallback to specs/","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"proof_common/RULE-4","description":"Feature-scoped overwrite: existing entries for other features are preserved; only the current feature's entries are replaced","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Create a proof file with entries for features A and B; run the plugin for feature A only; verify feature B entries are preserved and feature A entries are replaced","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"feature-scoped overwrite","tier":"unit","status":"pass","audit":"STRONG"},{"id":"PROOF-10","description":"e2e: Create 2 specs; write proofs for each separately; verify both PASSING and no cross-contamination","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""},{"id":"PROOF-11","description":"e2e: Overwrite one feature's proofs via shell harness; verify target feature updated, other feature untouched","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"proof_common/RULE-5","description":"Each proof entry contains all 7 required fields: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Run a proof plugin and read the output JSON; verify each entry in `proofs` array contains all 7 fields: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","test_file":"dev/test_multilang_proof_plugins.py","test_name":"test_python_proof_plugin_real_execution","tier":"integration","status":"pass","audit":"STRONG"},{"id":"PROOF-5","description":"Run a proof plugin and read the output JSON; verify each entry in `proofs` array contains all 7 fields: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"all 7 required fields","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"proof_common/RULE-6","description":"`status` is `\"pass\"` when the test passes and `\"fail\"` when it fails \u2014 no other values","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Run a passing test and a failing test with proof markers; verify the passing test has `status: \"pass\"` and the failing test has `status: \"fail\"`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"pass/fail status","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"proof_common/RULE-7","description":"If no proof markers are collected during a test run, no proof files are written (no-op)","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Run a test suite with no proof markers; verify no `*.proofs-*.json` files are created","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"no markers no files","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"proof_common/RULE-8","description":"Custom/community proof plugins installed to `.purlin/plugins/` require no registration \u2014 `sync_status` discovers proof files by globbing `specs/**/*.proofs-*.json`, so any plugin that writes files in that pattern works automatically","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-8","description":"Place a `.proofs-unit.json` file in a spec directory written by a non-built-in source; run `sync_status`; verify it reads the proofs","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"custom plugin proofs discovered by sync_status","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"proof_common/RULE-9","description":"When spec directory lookup falls back to specs/ root, the plugin emits a warning to stderr naming the missing spec and suggesting purlin:spec ","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-9","description":"Run a proof plugin for a feature with no matching spec; verify stderr contains a warning naming the feature and suggesting purlin:spec","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"fallback warning to stderr","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"schema_proof_format/RULE-1","description":"Proof files are named `.proofs-.json` and live in the same directory as their spec","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Create a proof file at `specs/test/foo.proofs-unit.json`; run sync_status; verify it reads the file and associates it with the `foo` spec","test_file":"dev/test_schema_proof_format.py","test_name":"test_proof_file_read_by_sync_status","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"schema_proof_format/RULE-2","description":"The JSON schema has a top-level `tier` string and a `proofs` array. Each proof entry contains: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Validate a proof JSON file against the required fields; verify all 7 fields (`feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`) are present in each proof entry","test_file":"dev/test_schema_proof_format.py","test_name":"test_proof_entry_has_all_seven_fields","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"schema_proof_format/RULE-3","description":"`status` is either `\"pass\"` or `\"fail\"` \u2014 no other values","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Create a proof entry with `status: \"error\"`; run sync_status; verify it is not counted as a passing proof","test_file":"dev/test_schema_proof_format.py","test_name":"test_invalid_status_not_counted","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"schema_proof_format/RULE-4","description":"`tier` values are: `\"unit\"`, `\"integration\"`, `\"e2e\"` \u2014 no other automated tiers","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Scan all real `*.proofs-*.json` files; verify every tier value (top-level and per-entry) is in the valid set {unit, integration, e2e}. Verify `spec_format.md` documents @integration and","test_file":"dev/test_schema_proof_format.py","test_name":"test_standard_tiers_documented","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"schema_proof_format/RULE-5","description":"Proof plugins use feature-scoped overwrite: load existing file, purge entries matching current feature, append new entries, write merged result","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Pre-seed a proof file with feature B entries; run the real pytest proof plugin for feature A via subprocess; verify feature B entries are preserved and feature A entries are added","test_file":"dev/test_schema_proof_format.py","test_name":"test_feature_scoped_overwrite","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-6","description":"Proof files are committed to git \u2014 they are project records, not ephemeral build artifacts","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Grep `.gitignore` for `*.proofs-*.json`; verify no gitignore rule excludes proof files","test_file":"dev/test_schema_proof_format.py","test_name":"test_proof_files_not_gitignored","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-7","description":"Manual proofs are stamped inline in the spec's `## Proof` section as `@manual(, , )`, not in proof JSON files","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Grep `references/formats/proofs_format.md` for `@manual`; verify manual stamp format is `@manual(, , )`","test_file":"dev/test_schema_proof_format.py","test_name":"test_manual_stamp_format_documented","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-1","description":"FORBIDDEN \u2014 No `eval()` or `exec()` calls in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Grep `scripts/` for `eval(` and `exec(` in `.py` files; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_eval_or_exec","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"security_no_dangerous_patterns/RULE-2","description":"FORBIDDEN \u2014 No `subprocess` calls with `shell=True` in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Grep `scripts/**/*.py` for `shell\\s*=\\s*True`; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_shell_true","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"security_no_dangerous_patterns/RULE-3","description":"FORBIDDEN \u2014 No `os.system()` calls in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Grep `scripts/**/*.py` for `os\\.system\\s*\\(`; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_os_system","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"security_no_dangerous_patterns/RULE-4","description":"FORBIDDEN \u2014 No hardcoded credential assignments (password/secret/api_key/token literals) in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Grep `scripts/` for assignment patterns `(password|secret|api_key|token)\\s*=\\s*[\"'][^\"']+[\"']` in `.py`, `.sh`, `.js` files, excluding test files and comments; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_hardcoded_credentials","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"security_no_dangerous_patterns/RULE-5","description":"All subprocess calls use list-mode arguments (not string commands)","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Grep `scripts/**/*.py` for `subprocess.run(` and `subprocess.call(`; verify each call's first argument is a list literal `[`, not a string","test_file":"dev/test_security.py","test_name":"test_subprocess_uses_list_args","tier":"unit","status":"pass","audit":""}]}],"audit":{"integrity":100,"strong":4,"weak":0,"hollow":0,"manual":0,"findings":[]}},{"name":"proof_plugins_php","category":"proof","type":"feature","is_global":false,"source_url":null,"pinned":null,"description":"The PHP proof runner. Runs each `@purlin`-annotated function individually and maps thrown exceptions to fail. Inherits all shared proof-plugin behavior from proof_common.","source_path":null,"stack":"php (standalone runner, docblock annotations)","ext_status":null,"proved":24,"total":24,"deferred":0,"status":"VERIFIED","vhash":"f45dc3b5","receipt":{"commit":"ac522f60debf1f866e0a6acfb25bcb10fafd2057","timestamp":"2026-06-02T15:45:42.990726+00:00","stale":false},"rules":[{"id":"RULE-1","description":"The PHP marker is a `/** @purlin feature PROOF-N RULE-N [tier] */` docblock annotation before the test function","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Create a PHP test with `@purlin` docblock; run `phpunit_purlin.php`; verify proof entry has correct feature, id, rule from the docblock","test_file":"dev/test_multilang_proof_plugins.py","test_name":"test_php_proof_plugin_real_execution","tier":"integration","status":"pass","audit":"STRONG"}]},{"id":"RULE-2","description":"The PHP plugin runs each annotated function individually; an unhandled exception maps to `status: \"fail\"`, no exception maps to `status: \"pass\"`","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Create a PHP test that throws an exception; run plugin; verify `status: \"fail\"`. Create one that doesn't throw; verify `status: \"pass\"`","test_file":"dev/test_multilang_proof_plugins.py","test_name":"test_php_proof_plugin_failing_test","tier":"integration","status":"pass","audit":"STRONG"}]},{"id":"proof_common/RULE-1","description":"Each plugin resolves the spec directory by scanning `specs/**/*.md` and matching the feature name to the spec filename stem","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Create `specs/hooks/gate_hook.md` and run a proof plugin with feature `gate_hook`; verify the proof file is written to `specs/hooks/`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"spec dir resolution","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"proof_common/RULE-10","description":"When a test is removed from a re-run, the old proof entry is purged and not carried over from the previous proof file","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-13","description":"Write a proof file with 2 proofs, then re-run with only 1; verify the removed entry is purged and not carried over","test_file":"dev/test_multilang_proof_plugins.py","test_name":"test_removed_test_purged_on_rerun","tier":"integration","status":"pass","audit":"STRONG"},{"id":"PROOF-12","description":"e2e: Write proof file with only 1 of 2 proofs (test deletion); verify coverage shows 1/2 not 2/2","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"proof_common/RULE-2","description":"Proof files are written to the spec's directory as `.proofs-.json`","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Run a proof plugin for feature `gate_hook` tier `unit`; verify output file is named `gate_hook.proofs-unit.json`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"proof file naming","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"proof_common/RULE-3","description":"When the spec directory for a feature is not found, the plugin falls back to writing to `specs/`","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Run a proof plugin for feature `nonexistent_feature` (no matching spec); verify proof file is written to `specs/`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"fallback to specs/","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"proof_common/RULE-4","description":"Feature-scoped overwrite: existing entries for other features are preserved; only the current feature's entries are replaced","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Create a proof file with entries for features A and B; run the plugin for feature A only; verify feature B entries are preserved and feature A entries are replaced","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"feature-scoped overwrite","tier":"unit","status":"pass","audit":"STRONG"},{"id":"PROOF-10","description":"e2e: Create 2 specs; write proofs for each separately; verify both PASSING and no cross-contamination","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""},{"id":"PROOF-11","description":"e2e: Overwrite one feature's proofs via shell harness; verify target feature updated, other feature untouched","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"proof_common/RULE-5","description":"Each proof entry contains all 7 required fields: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Run a proof plugin and read the output JSON; verify each entry in `proofs` array contains all 7 fields: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","test_file":"dev/test_multilang_proof_plugins.py","test_name":"test_python_proof_plugin_real_execution","tier":"integration","status":"pass","audit":"STRONG"},{"id":"PROOF-5","description":"Run a proof plugin and read the output JSON; verify each entry in `proofs` array contains all 7 fields: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"all 7 required fields","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"proof_common/RULE-6","description":"`status` is `\"pass\"` when the test passes and `\"fail\"` when it fails \u2014 no other values","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Run a passing test and a failing test with proof markers; verify the passing test has `status: \"pass\"` and the failing test has `status: \"fail\"`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"pass/fail status","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"proof_common/RULE-7","description":"If no proof markers are collected during a test run, no proof files are written (no-op)","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Run a test suite with no proof markers; verify no `*.proofs-*.json` files are created","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"no markers no files","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"proof_common/RULE-8","description":"Custom/community proof plugins installed to `.purlin/plugins/` require no registration \u2014 `sync_status` discovers proof files by globbing `specs/**/*.proofs-*.json`, so any plugin that writes files in that pattern works automatically","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-8","description":"Place a `.proofs-unit.json` file in a spec directory written by a non-built-in source; run `sync_status`; verify it reads the proofs","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"custom plugin proofs discovered by sync_status","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"proof_common/RULE-9","description":"When spec directory lookup falls back to specs/ root, the plugin emits a warning to stderr naming the missing spec and suggesting purlin:spec ","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-9","description":"Run a proof plugin for a feature with no matching spec; verify stderr contains a warning naming the feature and suggesting purlin:spec","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"fallback warning to stderr","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"schema_proof_format/RULE-1","description":"Proof files are named `.proofs-.json` and live in the same directory as their spec","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Create a proof file at `specs/test/foo.proofs-unit.json`; run sync_status; verify it reads the file and associates it with the `foo` spec","test_file":"dev/test_schema_proof_format.py","test_name":"test_proof_file_read_by_sync_status","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"schema_proof_format/RULE-2","description":"The JSON schema has a top-level `tier` string and a `proofs` array. Each proof entry contains: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Validate a proof JSON file against the required fields; verify all 7 fields (`feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`) are present in each proof entry","test_file":"dev/test_schema_proof_format.py","test_name":"test_proof_entry_has_all_seven_fields","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"schema_proof_format/RULE-3","description":"`status` is either `\"pass\"` or `\"fail\"` \u2014 no other values","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Create a proof entry with `status: \"error\"`; run sync_status; verify it is not counted as a passing proof","test_file":"dev/test_schema_proof_format.py","test_name":"test_invalid_status_not_counted","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-4","description":"`tier` values are: `\"unit\"`, `\"integration\"`, `\"e2e\"` \u2014 no other automated tiers","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Scan all real `*.proofs-*.json` files; verify every tier value (top-level and per-entry) is in the valid set {unit, integration, e2e}. Verify `spec_format.md` documents @integration and","test_file":"dev/test_schema_proof_format.py","test_name":"test_standard_tiers_documented","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-5","description":"Proof plugins use feature-scoped overwrite: load existing file, purge entries matching current feature, append new entries, write merged result","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Pre-seed a proof file with feature B entries; run the real pytest proof plugin for feature A via subprocess; verify feature B entries are preserved and feature A entries are added","test_file":"dev/test_schema_proof_format.py","test_name":"test_feature_scoped_overwrite","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-6","description":"Proof files are committed to git \u2014 they are project records, not ephemeral build artifacts","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Grep `.gitignore` for `*.proofs-*.json`; verify no gitignore rule excludes proof files","test_file":"dev/test_schema_proof_format.py","test_name":"test_proof_files_not_gitignored","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-7","description":"Manual proofs are stamped inline in the spec's `## Proof` section as `@manual(, , )`, not in proof JSON files","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Grep `references/formats/proofs_format.md` for `@manual`; verify manual stamp format is `@manual(, , )`","test_file":"dev/test_schema_proof_format.py","test_name":"test_manual_stamp_format_documented","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-1","description":"FORBIDDEN \u2014 No `eval()` or `exec()` calls in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Grep `scripts/` for `eval(` and `exec(` in `.py` files; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_eval_or_exec","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"security_no_dangerous_patterns/RULE-2","description":"FORBIDDEN \u2014 No `subprocess` calls with `shell=True` in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Grep `scripts/**/*.py` for `shell\\s*=\\s*True`; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_shell_true","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"security_no_dangerous_patterns/RULE-3","description":"FORBIDDEN \u2014 No `os.system()` calls in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Grep `scripts/**/*.py` for `os\\.system\\s*\\(`; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_os_system","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-4","description":"FORBIDDEN \u2014 No hardcoded credential assignments (password/secret/api_key/token literals) in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Grep `scripts/` for assignment patterns `(password|secret|api_key|token)\\s*=\\s*[\"'][^\"']+[\"']` in `.py`, `.sh`, `.js` files, excluding test files and comments; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_hardcoded_credentials","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-5","description":"All subprocess calls use list-mode arguments (not string commands)","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Grep `scripts/**/*.py` for `subprocess.run(` and `subprocess.call(`; verify each call's first argument is a list literal `[`, not a string","test_file":"dev/test_security.py","test_name":"test_subprocess_uses_list_args","tier":"unit","status":"pass","audit":""}]}],"audit":{"integrity":100,"strong":2,"weak":0,"hollow":0,"manual":0,"findings":[]}},{"name":"proof_plugins_pytest","category":"proof","type":"feature","is_global":false,"source_url":null,"pinned":null,"description":"The pytest proof plugin. Collects `@pytest.mark.proof(...)` markers during the test call phase and emits standardized proof JSON. Inherits all shared proof-plugin behavior (spec-dir resolution, naming, fallback, feature-scoped overwrite, the 7 fields, status, no-op, discovery, stderr warning, purge) from proof_common.","source_path":null,"stack":"python/stdlib, pytest plugin (pytest_configure + pytest_runtest_makereport hooks)","ext_status":null,"proved":26,"total":26,"deferred":0,"status":"VERIFIED","vhash":"2e0ab5ae","receipt":{"commit":"ac522f60debf1f866e0a6acfb25bcb10fafd2057","timestamp":"2026-06-02T15:45:42.990726+00:00","stale":false},"rules":[{"id":"RULE-1","description":"The marker signature is `@pytest.mark.proof(\"feature\", \"PROOF-N\", \"RULE-N\", tier=\"unit\")` where tier defaults to `\"unit\"`","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Create a test with `@pytest.mark.proof(\"feat\", \"PROOF-1\", \"RULE-1\")`; run pytest; verify the proof entry has `feature: \"feat\"`, `tier: \"unit\"`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"pytest marker signature","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"RULE-2","description":"Markers with fewer than 3 positional args are silently skipped","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Create a test with `@pytest.mark.proof(\"feat\", \"PROOF-1\")` (only 2 args); run pytest; verify no proof entry is emitted for that test","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"pytest fewer args skipped","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"RULE-3","description":"`test_file` is recorded as the path relative to the pytest rootdir","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Run pytest from a project root; verify `test_file` in the proof entry is relative to the root (not absolute)","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"pytest test_file relative","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"RULE-4","description":"The plugin registers itself via `pytest_configure` and collects results in `pytest_runtest_makereport` during the `call` phase only","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Verify `pytest_configure` registers the `proof` marker and the `purlin_proof` plugin","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"pytest_configure registers marker + plugin","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"proof_common/RULE-1","description":"Each plugin resolves the spec directory by scanning `specs/**/*.md` and matching the feature name to the spec filename stem","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Create `specs/hooks/gate_hook.md` and run a proof plugin with feature `gate_hook`; verify the proof file is written to `specs/hooks/`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"spec dir resolution","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"proof_common/RULE-10","description":"When a test is removed from a re-run, the old proof entry is purged and not carried over from the previous proof file","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-13","description":"Write a proof file with 2 proofs, then re-run with only 1; verify the removed entry is purged and not carried over","test_file":"dev/test_multilang_proof_plugins.py","test_name":"test_removed_test_purged_on_rerun","tier":"integration","status":"pass","audit":"STRONG"},{"id":"PROOF-12","description":"e2e: Write proof file with only 1 of 2 proofs (test deletion); verify coverage shows 1/2 not 2/2","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"proof_common/RULE-2","description":"Proof files are written to the spec's directory as `.proofs-.json`","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Run a proof plugin for feature `gate_hook` tier `unit`; verify output file is named `gate_hook.proofs-unit.json`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"proof file naming","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"proof_common/RULE-3","description":"When the spec directory for a feature is not found, the plugin falls back to writing to `specs/`","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Run a proof plugin for feature `nonexistent_feature` (no matching spec); verify proof file is written to `specs/`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"fallback to specs/","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"proof_common/RULE-4","description":"Feature-scoped overwrite: existing entries for other features are preserved; only the current feature's entries are replaced","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Create a proof file with entries for features A and B; run the plugin for feature A only; verify feature B entries are preserved and feature A entries are replaced","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"feature-scoped overwrite","tier":"unit","status":"pass","audit":"STRONG"},{"id":"PROOF-10","description":"e2e: Create 2 specs; write proofs for each separately; verify both PASSING and no cross-contamination","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""},{"id":"PROOF-11","description":"e2e: Overwrite one feature's proofs via shell harness; verify target feature updated, other feature untouched","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"proof_common/RULE-5","description":"Each proof entry contains all 7 required fields: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Run a proof plugin and read the output JSON; verify each entry in `proofs` array contains all 7 fields: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","test_file":"dev/test_multilang_proof_plugins.py","test_name":"test_python_proof_plugin_real_execution","tier":"integration","status":"pass","audit":"STRONG"},{"id":"PROOF-5","description":"Run a proof plugin and read the output JSON; verify each entry in `proofs` array contains all 7 fields: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"all 7 required fields","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"proof_common/RULE-6","description":"`status` is `\"pass\"` when the test passes and `\"fail\"` when it fails \u2014 no other values","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Run a passing test and a failing test with proof markers; verify the passing test has `status: \"pass\"` and the failing test has `status: \"fail\"`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"pass/fail status","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"proof_common/RULE-7","description":"If no proof markers are collected during a test run, no proof files are written (no-op)","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Run a test suite with no proof markers; verify no `*.proofs-*.json` files are created","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"no markers no files","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"proof_common/RULE-8","description":"Custom/community proof plugins installed to `.purlin/plugins/` require no registration \u2014 `sync_status` discovers proof files by globbing `specs/**/*.proofs-*.json`, so any plugin that writes files in that pattern works automatically","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-8","description":"Place a `.proofs-unit.json` file in a spec directory written by a non-built-in source; run `sync_status`; verify it reads the proofs","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"custom plugin proofs discovered by sync_status","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"proof_common/RULE-9","description":"When spec directory lookup falls back to specs/ root, the plugin emits a warning to stderr naming the missing spec and suggesting purlin:spec ","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-9","description":"Run a proof plugin for a feature with no matching spec; verify stderr contains a warning naming the feature and suggesting purlin:spec","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"fallback warning to stderr","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"schema_proof_format/RULE-1","description":"Proof files are named `.proofs-.json` and live in the same directory as their spec","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Create a proof file at `specs/test/foo.proofs-unit.json`; run sync_status; verify it reads the file and associates it with the `foo` spec","test_file":"dev/test_schema_proof_format.py","test_name":"test_proof_file_read_by_sync_status","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"schema_proof_format/RULE-2","description":"The JSON schema has a top-level `tier` string and a `proofs` array. Each proof entry contains: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Validate a proof JSON file against the required fields; verify all 7 fields (`feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`) are present in each proof entry","test_file":"dev/test_schema_proof_format.py","test_name":"test_proof_entry_has_all_seven_fields","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"schema_proof_format/RULE-3","description":"`status` is either `\"pass\"` or `\"fail\"` \u2014 no other values","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Create a proof entry with `status: \"error\"`; run sync_status; verify it is not counted as a passing proof","test_file":"dev/test_schema_proof_format.py","test_name":"test_invalid_status_not_counted","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"schema_proof_format/RULE-4","description":"`tier` values are: `\"unit\"`, `\"integration\"`, `\"e2e\"` \u2014 no other automated tiers","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Scan all real `*.proofs-*.json` files; verify every tier value (top-level and per-entry) is in the valid set {unit, integration, e2e}. Verify `spec_format.md` documents @integration and","test_file":"dev/test_schema_proof_format.py","test_name":"test_standard_tiers_documented","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"schema_proof_format/RULE-5","description":"Proof plugins use feature-scoped overwrite: load existing file, purge entries matching current feature, append new entries, write merged result","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Pre-seed a proof file with feature B entries; run the real pytest proof plugin for feature A via subprocess; verify feature B entries are preserved and feature A entries are added","test_file":"dev/test_schema_proof_format.py","test_name":"test_feature_scoped_overwrite","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-6","description":"Proof files are committed to git \u2014 they are project records, not ephemeral build artifacts","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Grep `.gitignore` for `*.proofs-*.json`; verify no gitignore rule excludes proof files","test_file":"dev/test_schema_proof_format.py","test_name":"test_proof_files_not_gitignored","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-7","description":"Manual proofs are stamped inline in the spec's `## Proof` section as `@manual(, , )`, not in proof JSON files","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Grep `references/formats/proofs_format.md` for `@manual`; verify manual stamp format is `@manual(, , )`","test_file":"dev/test_schema_proof_format.py","test_name":"test_manual_stamp_format_documented","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-1","description":"FORBIDDEN \u2014 No `eval()` or `exec()` calls in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Grep `scripts/` for `eval(` and `exec(` in `.py` files; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_eval_or_exec","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"security_no_dangerous_patterns/RULE-2","description":"FORBIDDEN \u2014 No `subprocess` calls with `shell=True` in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Grep `scripts/**/*.py` for `shell\\s*=\\s*True`; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_shell_true","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"security_no_dangerous_patterns/RULE-3","description":"FORBIDDEN \u2014 No `os.system()` calls in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Grep `scripts/**/*.py` for `os\\.system\\s*\\(`; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_os_system","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"security_no_dangerous_patterns/RULE-4","description":"FORBIDDEN \u2014 No hardcoded credential assignments (password/secret/api_key/token literals) in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Grep `scripts/` for assignment patterns `(password|secret|api_key|token)\\s*=\\s*[\"'][^\"']+[\"']` in `.py`, `.sh`, `.js` files, excluding test files and comments; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_hardcoded_credentials","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"security_no_dangerous_patterns/RULE-5","description":"All subprocess calls use list-mode arguments (not string commands)","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Grep `scripts/**/*.py` for `subprocess.run(` and `subprocess.call(`; verify each call's first argument is a list literal `[`, not a string","test_file":"dev/test_security.py","test_name":"test_subprocess_uses_list_args","tier":"unit","status":"pass","audit":""}]}],"audit":{"integrity":100,"strong":4,"weak":0,"hollow":0,"manual":0,"findings":[]}},{"name":"proof_plugins_shell","category":"proof","type":"feature","is_global":false,"source_url":null,"pinned":null,"description":"The shell proof harness. Sourced by bash test scripts; accumulates entries in memory and writes them on finish. Inherits all shared proof-plugin behavior from proof_common.","source_path":null,"stack":"shell/bash, inline python3 for JSON (no jq dependency)","ext_status":null,"proved":26,"total":26,"deferred":0,"status":"VERIFIED","vhash":"2e0ab5ae","receipt":{"commit":"ac522f60debf1f866e0a6acfb25bcb10fafd2057","timestamp":"2026-06-02T15:45:42.990726+00:00","stale":false},"rules":[{"id":"RULE-1","description":"`purlin_proof` accepts 5 args: `feature`, `proof_id`, `rule_id`, `status`, `test_name`; tier comes from `PURLIN_PROOF_TIER` env var (default: `\"unit\"`)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Call `purlin_proof \"feat\" \"PROOF-1\" \"RULE-1\" pass \"desc\"` with `PURLIN_PROOF_TIER=integration`; call `purlin_proof_finish`; verify the proof entry has `tier: \"integration\"`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"shell tier from PURLIN_PROOF_TIER","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"RULE-2","description":"`test_file` is recorded from `BASH_SOURCE[1]` (the caller's file)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Source `shell_purlin.sh` from a test script; call `purlin_proof`; verify `test_file` matches the caller's filename","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"shell test_file from BASH_SOURCE","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"RULE-3","description":"`purlin_proof_finish` must be called to write proof files \u2014 entries are accumulated in memory until then","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Call `purlin_proof` twice without calling `purlin_proof_finish`; verify no proof files exist yet. Then call `purlin_proof_finish`; verify files are written","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"shell finish required to write","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"RULE-4","description":"After `purlin_proof_finish`, the accumulated entries are cleared (reset for next batch)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Call `purlin_proof_finish`; verify `_PURLIN_PROOFS` is empty afterwards; call again; verify it's a no-op","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"shell entries cleared after finish","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"proof_common/RULE-1","description":"Each plugin resolves the spec directory by scanning `specs/**/*.md` and matching the feature name to the spec filename stem","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Create `specs/hooks/gate_hook.md` and run a proof plugin with feature `gate_hook`; verify the proof file is written to `specs/hooks/`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"spec dir resolution","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"proof_common/RULE-10","description":"When a test is removed from a re-run, the old proof entry is purged and not carried over from the previous proof file","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-13","description":"Write a proof file with 2 proofs, then re-run with only 1; verify the removed entry is purged and not carried over","test_file":"dev/test_multilang_proof_plugins.py","test_name":"test_removed_test_purged_on_rerun","tier":"integration","status":"pass","audit":"STRONG"},{"id":"PROOF-12","description":"e2e: Write proof file with only 1 of 2 proofs (test deletion); verify coverage shows 1/2 not 2/2","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"proof_common/RULE-2","description":"Proof files are written to the spec's directory as `.proofs-.json`","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Run a proof plugin for feature `gate_hook` tier `unit`; verify output file is named `gate_hook.proofs-unit.json`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"proof file naming","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"proof_common/RULE-3","description":"When the spec directory for a feature is not found, the plugin falls back to writing to `specs/`","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Run a proof plugin for feature `nonexistent_feature` (no matching spec); verify proof file is written to `specs/`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"fallback to specs/","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"proof_common/RULE-4","description":"Feature-scoped overwrite: existing entries for other features are preserved; only the current feature's entries are replaced","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Create a proof file with entries for features A and B; run the plugin for feature A only; verify feature B entries are preserved and feature A entries are replaced","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"feature-scoped overwrite","tier":"unit","status":"pass","audit":"STRONG"},{"id":"PROOF-10","description":"e2e: Create 2 specs; write proofs for each separately; verify both PASSING and no cross-contamination","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""},{"id":"PROOF-11","description":"e2e: Overwrite one feature's proofs via shell harness; verify target feature updated, other feature untouched","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"proof_common/RULE-5","description":"Each proof entry contains all 7 required fields: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Run a proof plugin and read the output JSON; verify each entry in `proofs` array contains all 7 fields: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","test_file":"dev/test_multilang_proof_plugins.py","test_name":"test_python_proof_plugin_real_execution","tier":"integration","status":"pass","audit":"STRONG"},{"id":"PROOF-5","description":"Run a proof plugin and read the output JSON; verify each entry in `proofs` array contains all 7 fields: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"all 7 required fields","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"proof_common/RULE-6","description":"`status` is `\"pass\"` when the test passes and `\"fail\"` when it fails \u2014 no other values","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Run a passing test and a failing test with proof markers; verify the passing test has `status: \"pass\"` and the failing test has `status: \"fail\"`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"pass/fail status","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"proof_common/RULE-7","description":"If no proof markers are collected during a test run, no proof files are written (no-op)","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Run a test suite with no proof markers; verify no `*.proofs-*.json` files are created","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"no markers no files","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"proof_common/RULE-8","description":"Custom/community proof plugins installed to `.purlin/plugins/` require no registration \u2014 `sync_status` discovers proof files by globbing `specs/**/*.proofs-*.json`, so any plugin that writes files in that pattern works automatically","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-8","description":"Place a `.proofs-unit.json` file in a spec directory written by a non-built-in source; run `sync_status`; verify it reads the proofs","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"custom plugin proofs discovered by sync_status","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"proof_common/RULE-9","description":"When spec directory lookup falls back to specs/ root, the plugin emits a warning to stderr naming the missing spec and suggesting purlin:spec ","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-9","description":"Run a proof plugin for a feature with no matching spec; verify stderr contains a warning naming the feature and suggesting purlin:spec","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"fallback warning to stderr","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"schema_proof_format/RULE-1","description":"Proof files are named `.proofs-.json` and live in the same directory as their spec","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Create a proof file at `specs/test/foo.proofs-unit.json`; run sync_status; verify it reads the file and associates it with the `foo` spec","test_file":"dev/test_schema_proof_format.py","test_name":"test_proof_file_read_by_sync_status","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"schema_proof_format/RULE-2","description":"The JSON schema has a top-level `tier` string and a `proofs` array. Each proof entry contains: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Validate a proof JSON file against the required fields; verify all 7 fields (`feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`) are present in each proof entry","test_file":"dev/test_schema_proof_format.py","test_name":"test_proof_entry_has_all_seven_fields","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"schema_proof_format/RULE-3","description":"`status` is either `\"pass\"` or `\"fail\"` \u2014 no other values","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Create a proof entry with `status: \"error\"`; run sync_status; verify it is not counted as a passing proof","test_file":"dev/test_schema_proof_format.py","test_name":"test_invalid_status_not_counted","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"schema_proof_format/RULE-4","description":"`tier` values are: `\"unit\"`, `\"integration\"`, `\"e2e\"` \u2014 no other automated tiers","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Scan all real `*.proofs-*.json` files; verify every tier value (top-level and per-entry) is in the valid set {unit, integration, e2e}. Verify `spec_format.md` documents @integration and","test_file":"dev/test_schema_proof_format.py","test_name":"test_standard_tiers_documented","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"schema_proof_format/RULE-5","description":"Proof plugins use feature-scoped overwrite: load existing file, purge entries matching current feature, append new entries, write merged result","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Pre-seed a proof file with feature B entries; run the real pytest proof plugin for feature A via subprocess; verify feature B entries are preserved and feature A entries are added","test_file":"dev/test_schema_proof_format.py","test_name":"test_feature_scoped_overwrite","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-6","description":"Proof files are committed to git \u2014 they are project records, not ephemeral build artifacts","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Grep `.gitignore` for `*.proofs-*.json`; verify no gitignore rule excludes proof files","test_file":"dev/test_schema_proof_format.py","test_name":"test_proof_files_not_gitignored","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-7","description":"Manual proofs are stamped inline in the spec's `## Proof` section as `@manual(, , )`, not in proof JSON files","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Grep `references/formats/proofs_format.md` for `@manual`; verify manual stamp format is `@manual(, , )`","test_file":"dev/test_schema_proof_format.py","test_name":"test_manual_stamp_format_documented","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-1","description":"FORBIDDEN \u2014 No `eval()` or `exec()` calls in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Grep `scripts/` for `eval(` and `exec(` in `.py` files; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_eval_or_exec","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"security_no_dangerous_patterns/RULE-2","description":"FORBIDDEN \u2014 No `subprocess` calls with `shell=True` in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Grep `scripts/**/*.py` for `shell\\s*=\\s*True`; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_shell_true","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"security_no_dangerous_patterns/RULE-3","description":"FORBIDDEN \u2014 No `os.system()` calls in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Grep `scripts/**/*.py` for `os\\.system\\s*\\(`; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_os_system","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"security_no_dangerous_patterns/RULE-4","description":"FORBIDDEN \u2014 No hardcoded credential assignments (password/secret/api_key/token literals) in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Grep `scripts/` for assignment patterns `(password|secret|api_key|token)\\s*=\\s*[\"'][^\"']+[\"']` in `.py`, `.sh`, `.js` files, excluding test files and comments; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_hardcoded_credentials","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"security_no_dangerous_patterns/RULE-5","description":"All subprocess calls use list-mode arguments (not string commands)","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Grep `scripts/**/*.py` for `subprocess.run(` and `subprocess.call(`; verify each call's first argument is a list literal `[`, not a string","test_file":"dev/test_security.py","test_name":"test_subprocess_uses_list_args","tier":"unit","status":"pass","audit":""}]}],"audit":{"integrity":100,"strong":4,"weak":0,"hollow":0,"manual":0,"findings":[]}},{"name":"proof_plugins_sql","category":"proof","type":"feature","is_global":false,"source_url":null,"pinned":null,"description":"The SQL proof plugin. Runs `-- @purlin`-marked test blocks against sqlite3 and maps PASS/FAIL output to status. Inherits all shared proof-plugin behavior from proof_common.","source_path":null,"stack":"sql/sqlite3 (shell driver + inline python3)","ext_status":null,"proved":24,"total":24,"deferred":0,"status":"VERIFIED","vhash":"f45dc3b5","receipt":{"commit":"ac522f60debf1f866e0a6acfb25bcb10fafd2057","timestamp":"2026-06-02T15:45:42.990726+00:00","stale":false},"rules":[{"id":"RULE-1","description":"The SQL marker is `-- @purlin feature PROOF-N RULE-N [tier]` as a comment line preceding the test block","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Create a SQL file with `-- @purlin` markers; run `sql_purlin.sh`; verify proof entries match the marker fields","test_file":"dev/test_multilang_proof_plugins.py","test_name":"test_sql_proof_plugin_real_execution","tier":"integration","status":"pass","audit":"STRONG"}]},{"id":"RULE-2","description":"Each SQL test block must produce output starting with `PASS` or `FAIL` when executed against sqlite3; any other output or error maps to `status: \"fail\"`","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Create a SQL test block that outputs `PASS`; verify `status: \"pass\"`. Create one that outputs `FAIL`; verify `status: \"fail\"`","test_file":"dev/test_multilang_proof_plugins.py","test_name":"test_sql_proof_plugin_failing_test","tier":"integration","status":"pass","audit":"STRONG"}]},{"id":"proof_common/RULE-1","description":"Each plugin resolves the spec directory by scanning `specs/**/*.md` and matching the feature name to the spec filename stem","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Create `specs/hooks/gate_hook.md` and run a proof plugin with feature `gate_hook`; verify the proof file is written to `specs/hooks/`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"spec dir resolution","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"proof_common/RULE-10","description":"When a test is removed from a re-run, the old proof entry is purged and not carried over from the previous proof file","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-13","description":"Write a proof file with 2 proofs, then re-run with only 1; verify the removed entry is purged and not carried over","test_file":"dev/test_multilang_proof_plugins.py","test_name":"test_removed_test_purged_on_rerun","tier":"integration","status":"pass","audit":"STRONG"},{"id":"PROOF-12","description":"e2e: Write proof file with only 1 of 2 proofs (test deletion); verify coverage shows 1/2 not 2/2","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"proof_common/RULE-2","description":"Proof files are written to the spec's directory as `.proofs-.json`","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Run a proof plugin for feature `gate_hook` tier `unit`; verify output file is named `gate_hook.proofs-unit.json`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"proof file naming","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"proof_common/RULE-3","description":"When the spec directory for a feature is not found, the plugin falls back to writing to `specs/`","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Run a proof plugin for feature `nonexistent_feature` (no matching spec); verify proof file is written to `specs/`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"fallback to specs/","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"proof_common/RULE-4","description":"Feature-scoped overwrite: existing entries for other features are preserved; only the current feature's entries are replaced","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Create a proof file with entries for features A and B; run the plugin for feature A only; verify feature B entries are preserved and feature A entries are replaced","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"feature-scoped overwrite","tier":"unit","status":"pass","audit":"STRONG"},{"id":"PROOF-10","description":"e2e: Create 2 specs; write proofs for each separately; verify both PASSING and no cross-contamination","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""},{"id":"PROOF-11","description":"e2e: Overwrite one feature's proofs via shell harness; verify target feature updated, other feature untouched","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"proof_common/RULE-5","description":"Each proof entry contains all 7 required fields: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Run a proof plugin and read the output JSON; verify each entry in `proofs` array contains all 7 fields: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","test_file":"dev/test_multilang_proof_plugins.py","test_name":"test_python_proof_plugin_real_execution","tier":"integration","status":"pass","audit":"STRONG"},{"id":"PROOF-5","description":"Run a proof plugin and read the output JSON; verify each entry in `proofs` array contains all 7 fields: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"all 7 required fields","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"proof_common/RULE-6","description":"`status` is `\"pass\"` when the test passes and `\"fail\"` when it fails \u2014 no other values","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Run a passing test and a failing test with proof markers; verify the passing test has `status: \"pass\"` and the failing test has `status: \"fail\"`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"pass/fail status","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"proof_common/RULE-7","description":"If no proof markers are collected during a test run, no proof files are written (no-op)","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Run a test suite with no proof markers; verify no `*.proofs-*.json` files are created","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"no markers no files","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"proof_common/RULE-8","description":"Custom/community proof plugins installed to `.purlin/plugins/` require no registration \u2014 `sync_status` discovers proof files by globbing `specs/**/*.proofs-*.json`, so any plugin that writes files in that pattern works automatically","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-8","description":"Place a `.proofs-unit.json` file in a spec directory written by a non-built-in source; run `sync_status`; verify it reads the proofs","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"custom plugin proofs discovered by sync_status","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"proof_common/RULE-9","description":"When spec directory lookup falls back to specs/ root, the plugin emits a warning to stderr naming the missing spec and suggesting purlin:spec ","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-9","description":"Run a proof plugin for a feature with no matching spec; verify stderr contains a warning naming the feature and suggesting purlin:spec","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"fallback warning to stderr","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"schema_proof_format/RULE-1","description":"Proof files are named `.proofs-.json` and live in the same directory as their spec","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Create a proof file at `specs/test/foo.proofs-unit.json`; run sync_status; verify it reads the file and associates it with the `foo` spec","test_file":"dev/test_schema_proof_format.py","test_name":"test_proof_file_read_by_sync_status","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"schema_proof_format/RULE-2","description":"The JSON schema has a top-level `tier` string and a `proofs` array. Each proof entry contains: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Validate a proof JSON file against the required fields; verify all 7 fields (`feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`) are present in each proof entry","test_file":"dev/test_schema_proof_format.py","test_name":"test_proof_entry_has_all_seven_fields","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"schema_proof_format/RULE-3","description":"`status` is either `\"pass\"` or `\"fail\"` \u2014 no other values","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Create a proof entry with `status: \"error\"`; run sync_status; verify it is not counted as a passing proof","test_file":"dev/test_schema_proof_format.py","test_name":"test_invalid_status_not_counted","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-4","description":"`tier` values are: `\"unit\"`, `\"integration\"`, `\"e2e\"` \u2014 no other automated tiers","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Scan all real `*.proofs-*.json` files; verify every tier value (top-level and per-entry) is in the valid set {unit, integration, e2e}. Verify `spec_format.md` documents @integration and","test_file":"dev/test_schema_proof_format.py","test_name":"test_standard_tiers_documented","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-5","description":"Proof plugins use feature-scoped overwrite: load existing file, purge entries matching current feature, append new entries, write merged result","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Pre-seed a proof file with feature B entries; run the real pytest proof plugin for feature A via subprocess; verify feature B entries are preserved and feature A entries are added","test_file":"dev/test_schema_proof_format.py","test_name":"test_feature_scoped_overwrite","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-6","description":"Proof files are committed to git \u2014 they are project records, not ephemeral build artifacts","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Grep `.gitignore` for `*.proofs-*.json`; verify no gitignore rule excludes proof files","test_file":"dev/test_schema_proof_format.py","test_name":"test_proof_files_not_gitignored","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-7","description":"Manual proofs are stamped inline in the spec's `## Proof` section as `@manual(, , )`, not in proof JSON files","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Grep `references/formats/proofs_format.md` for `@manual`; verify manual stamp format is `@manual(, , )`","test_file":"dev/test_schema_proof_format.py","test_name":"test_manual_stamp_format_documented","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-1","description":"FORBIDDEN \u2014 No `eval()` or `exec()` calls in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Grep `scripts/` for `eval(` and `exec(` in `.py` files; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_eval_or_exec","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"security_no_dangerous_patterns/RULE-2","description":"FORBIDDEN \u2014 No `subprocess` calls with `shell=True` in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Grep `scripts/**/*.py` for `shell\\s*=\\s*True`; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_shell_true","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"security_no_dangerous_patterns/RULE-3","description":"FORBIDDEN \u2014 No `os.system()` calls in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Grep `scripts/**/*.py` for `os\\.system\\s*\\(`; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_os_system","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-4","description":"FORBIDDEN \u2014 No hardcoded credential assignments (password/secret/api_key/token literals) in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Grep `scripts/` for assignment patterns `(password|secret|api_key|token)\\s*=\\s*[\"'][^\"']+[\"']` in `.py`, `.sh`, `.js` files, excluding test files and comments; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_hardcoded_credentials","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-5","description":"All subprocess calls use list-mode arguments (not string commands)","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Grep `scripts/**/*.py` for `subprocess.run(` and `subprocess.call(`; verify each call's first argument is a list literal `[`, not a string","test_file":"dev/test_security.py","test_name":"test_subprocess_uses_list_args","tier":"unit","status":"pass","audit":""}]}],"audit":{"integrity":100,"strong":2,"weak":0,"hollow":0,"manual":0,"findings":[]}},{"name":"proof_plugins_vitest","category":"proof","type":"feature","is_global":false,"source_url":null,"pinned":null,"description":"The Vitest TypeScript reporter. Reuses the Jest marker syntax in test titles and collects proofs by walking the onFinished(files) task tree. Inherits all shared proof-plugin behavior from proof_common.","source_path":null,"stack":"typescript/vitest, custom reporter (onFinished(files) tree walk, Vitest 2.x\u20134.x)","ext_status":null,"proved":24,"total":24,"deferred":0,"status":"VERIFIED","vhash":"f45dc3b5","receipt":{"commit":"ac522f60debf1f866e0a6acfb25bcb10fafd2057","timestamp":"2026-06-02T15:45:42.990726+00:00","stale":false},"rules":[{"id":"RULE-1","description":"The TypeScript Vitest reporter uses the same `[proof:feature:PROOF-N:RULE-N:tier]` marker syntax in test titles as the Jest reporter","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Drive `vitest_purlin.ts` `onFinished` with a test task whose name contains `[proof:feat:PROOF-1:RULE-1:integration]`; verify the marker parses into `feature: \"feat\"`, `id: \"PROOF-1\"`, `rule: \"RULE-1\"`, `tier: \"integration\"` \u2014 identical fields to the Jest reporter's parsing of the same marker","test_file":"dev/test_multilang_proof_plugins.py","test_name":"test_vitest_reporter_marker_parsing","tier":"integration","status":"pass","audit":"STRONG"}]},{"id":"RULE-2","description":"The Vitest reporter collects proofs by recursively walking the file/suite/test tree passed to `onFinished(files)` \u2014 the stable Vitest 2.x\u20134.x reporter API. For each task of type `test`/`custom` it reads `task.name`, maps `task.result.state === \"pass\"` to `\"pass\"` (all other terminal states to `\"fail\"`), skips tasks with no terminal result (`skip`/`todo`/unrun), and resolves `test_file` from the file task's `filepath`. It does not use `onTaskUpdate` (whose pack shape changed in Vitest 2 and silently yielded zero proofs)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Load `vitest_purlin.ts` (compiled with tsc, or run directly via Node TypeScript type-stripping); drive its `onFinished(files)` with a synthetic Vitest 2.x+ task tree (a file suite task carrying `filepath` and nested `test` tasks whose names contain `[proof:...]` markers and whose `result.state` is `pass`/`fail`/`skip`); verify the emitted proof JSON has all 7 fields, that `state: pass` \u2192 `status: \"pass\"` while `state: fail` \u2192 `status: \"fail\"`, that a `skip` task is not recorded, and that `test_file` is resolved from the file task's `filepath`","test_file":"dev/test_multilang_proof_plugins.py","test_name":"test_vitest_reporter_onfinished_walk","tier":"integration","status":"pass","audit":"STRONG"}]},{"id":"proof_common/RULE-1","description":"Each plugin resolves the spec directory by scanning `specs/**/*.md` and matching the feature name to the spec filename stem","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Create `specs/hooks/gate_hook.md` and run a proof plugin with feature `gate_hook`; verify the proof file is written to `specs/hooks/`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"spec dir resolution","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"proof_common/RULE-10","description":"When a test is removed from a re-run, the old proof entry is purged and not carried over from the previous proof file","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-13","description":"Write a proof file with 2 proofs, then re-run with only 1; verify the removed entry is purged and not carried over","test_file":"dev/test_multilang_proof_plugins.py","test_name":"test_removed_test_purged_on_rerun","tier":"integration","status":"pass","audit":"STRONG"},{"id":"PROOF-12","description":"e2e: Write proof file with only 1 of 2 proofs (test deletion); verify coverage shows 1/2 not 2/2","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"proof_common/RULE-2","description":"Proof files are written to the spec's directory as `.proofs-.json`","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Run a proof plugin for feature `gate_hook` tier `unit`; verify output file is named `gate_hook.proofs-unit.json`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"proof file naming","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"proof_common/RULE-3","description":"When the spec directory for a feature is not found, the plugin falls back to writing to `specs/`","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Run a proof plugin for feature `nonexistent_feature` (no matching spec); verify proof file is written to `specs/`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"fallback to specs/","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"proof_common/RULE-4","description":"Feature-scoped overwrite: existing entries for other features are preserved; only the current feature's entries are replaced","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Create a proof file with entries for features A and B; run the plugin for feature A only; verify feature B entries are preserved and feature A entries are replaced","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"feature-scoped overwrite","tier":"unit","status":"pass","audit":"STRONG"},{"id":"PROOF-10","description":"e2e: Create 2 specs; write proofs for each separately; verify both PASSING and no cross-contamination","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""},{"id":"PROOF-11","description":"e2e: Overwrite one feature's proofs via shell harness; verify target feature updated, other feature untouched","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"proof_common/RULE-5","description":"Each proof entry contains all 7 required fields: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Run a proof plugin and read the output JSON; verify each entry in `proofs` array contains all 7 fields: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","test_file":"dev/test_multilang_proof_plugins.py","test_name":"test_python_proof_plugin_real_execution","tier":"integration","status":"pass","audit":"STRONG"},{"id":"PROOF-5","description":"Run a proof plugin and read the output JSON; verify each entry in `proofs` array contains all 7 fields: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"all 7 required fields","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"proof_common/RULE-6","description":"`status` is `\"pass\"` when the test passes and `\"fail\"` when it fails \u2014 no other values","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Run a passing test and a failing test with proof markers; verify the passing test has `status: \"pass\"` and the failing test has `status: \"fail\"`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"pass/fail status","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"proof_common/RULE-7","description":"If no proof markers are collected during a test run, no proof files are written (no-op)","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Run a test suite with no proof markers; verify no `*.proofs-*.json` files are created","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"no markers no files","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"proof_common/RULE-8","description":"Custom/community proof plugins installed to `.purlin/plugins/` require no registration \u2014 `sync_status` discovers proof files by globbing `specs/**/*.proofs-*.json`, so any plugin that writes files in that pattern works automatically","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-8","description":"Place a `.proofs-unit.json` file in a spec directory written by a non-built-in source; run `sync_status`; verify it reads the proofs","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"custom plugin proofs discovered by sync_status","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"proof_common/RULE-9","description":"When spec directory lookup falls back to specs/ root, the plugin emits a warning to stderr naming the missing spec and suggesting purlin:spec ","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-9","description":"Run a proof plugin for a feature with no matching spec; verify stderr contains a warning naming the feature and suggesting purlin:spec","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"fallback warning to stderr","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"schema_proof_format/RULE-1","description":"Proof files are named `.proofs-.json` and live in the same directory as their spec","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Create a proof file at `specs/test/foo.proofs-unit.json`; run sync_status; verify it reads the file and associates it with the `foo` spec","test_file":"dev/test_schema_proof_format.py","test_name":"test_proof_file_read_by_sync_status","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"schema_proof_format/RULE-2","description":"The JSON schema has a top-level `tier` string and a `proofs` array. Each proof entry contains: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Validate a proof JSON file against the required fields; verify all 7 fields (`feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`) are present in each proof entry","test_file":"dev/test_schema_proof_format.py","test_name":"test_proof_entry_has_all_seven_fields","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"schema_proof_format/RULE-3","description":"`status` is either `\"pass\"` or `\"fail\"` \u2014 no other values","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Create a proof entry with `status: \"error\"`; run sync_status; verify it is not counted as a passing proof","test_file":"dev/test_schema_proof_format.py","test_name":"test_invalid_status_not_counted","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-4","description":"`tier` values are: `\"unit\"`, `\"integration\"`, `\"e2e\"` \u2014 no other automated tiers","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Scan all real `*.proofs-*.json` files; verify every tier value (top-level and per-entry) is in the valid set {unit, integration, e2e}. Verify `spec_format.md` documents @integration and","test_file":"dev/test_schema_proof_format.py","test_name":"test_standard_tiers_documented","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-5","description":"Proof plugins use feature-scoped overwrite: load existing file, purge entries matching current feature, append new entries, write merged result","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Pre-seed a proof file with feature B entries; run the real pytest proof plugin for feature A via subprocess; verify feature B entries are preserved and feature A entries are added","test_file":"dev/test_schema_proof_format.py","test_name":"test_feature_scoped_overwrite","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-6","description":"Proof files are committed to git \u2014 they are project records, not ephemeral build artifacts","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Grep `.gitignore` for `*.proofs-*.json`; verify no gitignore rule excludes proof files","test_file":"dev/test_schema_proof_format.py","test_name":"test_proof_files_not_gitignored","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-7","description":"Manual proofs are stamped inline in the spec's `## Proof` section as `@manual(, , )`, not in proof JSON files","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Grep `references/formats/proofs_format.md` for `@manual`; verify manual stamp format is `@manual(, , )`","test_file":"dev/test_schema_proof_format.py","test_name":"test_manual_stamp_format_documented","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-1","description":"FORBIDDEN \u2014 No `eval()` or `exec()` calls in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Grep `scripts/` for `eval(` and `exec(` in `.py` files; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_eval_or_exec","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"security_no_dangerous_patterns/RULE-2","description":"FORBIDDEN \u2014 No `subprocess` calls with `shell=True` in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Grep `scripts/**/*.py` for `shell\\s*=\\s*True`; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_shell_true","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"security_no_dangerous_patterns/RULE-3","description":"FORBIDDEN \u2014 No `os.system()` calls in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Grep `scripts/**/*.py` for `os\\.system\\s*\\(`; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_os_system","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-4","description":"FORBIDDEN \u2014 No hardcoded credential assignments (password/secret/api_key/token literals) in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Grep `scripts/` for assignment patterns `(password|secret|api_key|token)\\s*=\\s*[\"'][^\"']+[\"']` in `.py`, `.sh`, `.js` files, excluding test files and comments; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_hardcoded_credentials","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-5","description":"All subprocess calls use list-mode arguments (not string commands)","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Grep `scripts/**/*.py` for `subprocess.run(` and `subprocess.call(`; verify each call's first argument is a list literal `[`, not a string","test_file":"dev/test_security.py","test_name":"test_subprocess_uses_list_args","tier":"unit","status":"pass","audit":""}]}],"audit":{"integrity":100,"strong":2,"weak":0,"hollow":0,"manual":0,"findings":[]}},{"name":"proof_plugins_xunit","category":"proof","type":"feature","is_global":false,"source_url":null,"pinned":null,"description":"The xUnit/.NET proof plugin (`scripts/proof/xunit_purlin.cs`). A custom `dotnet test` logger collects proof markers expressed as the `PurlinProof` test trait, maps the test outcome to pass/fail, and emits standardized proof JSON. Inherits all shared proof-plugin behavior (spec-dir resolution, naming, fallback, feature-scoped overwrite, the 7 fields, status, no-op, discovery, stderr warning, purge) from proof_common.","source_path":null,"stack":"dotnet/xunit, custom ITestLoggerWithParameters registered via `dotnet test --logger purlin`; trait-based marker, also compatible with NUnit/MSTest via TestCase.Traits","ext_status":null,"proved":28,"total":28,"deferred":0,"status":"VERIFIED","vhash":"352882e9","receipt":{"commit":"cf3a827f0291004d88a0b6941ad3608ccf631e80","timestamp":"2026-06-02T18:48:41.710705+00:00","stale":false},"rules":[{"id":"RULE-1","description":"The .NET marker is the test trait `[Trait(\"PurlinProof\", \"feature:PROOF-N:RULE-N:tier\")]` (xUnit), equivalently `[Category]`/`[TestProperty]` in NUnit/MSTest; the trait value is a colon-delimited `feature:PROOF-N:RULE-N:tier` string where tier defaults to `\"unit\"`","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Build an xUnit test annotated `[Trait(\"PurlinProof\", \"feat:PROOF-1:RULE-1:unit\")]`; run `dotnet test --logger purlin`; verify the proof entry has `feature: \"feat\"`, `id: \"PROOF-1\"`, `rule: \"RULE-1\"`, `tier: \"unit\"`","test_file":"dev/test_multilang_proof_plugins.py","test_name":"test_trait_marker_parses","tier":"integration","status":"pass","audit":"STRONG"}]},{"id":"RULE-2","description":"The plugin is a custom `dotnet test` logger (`ITestLoggerWithParameters`) registered via `dotnet test --logger purlin`; it collects results during the run, not by post-parsing a `.trx` file","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Run `dotnet test --logger purlin` on a project with one marked test; verify the logger is invoked and produces proof output during the run (no separate `.trx` parse step required)","test_file":"dev/test_multilang_proof_plugins.py","test_name":"test_logger_runs_in_process","tier":"integration","status":"pass","audit":"STRONG"}]},{"id":"RULE-3","description":"Tests without a `PurlinProof` trait are ignored \u2014 no proof entry is emitted for them","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Run a test with no `PurlinProof` trait; verify no proof entry is emitted for that test","test_file":"dev/test_multilang_proof_plugins.py","test_name":"test_untagged_ignored","tier":"integration","status":"pass","audit":"STRONG"}]},{"id":"RULE-4","description":"A test `Outcome` of `Passed` maps to `status: \"pass\"`; `Failed` and all other non-skipped outcomes (e.g. `NotExecuted`) map to `status: \"fail\"`; a `Skipped` test is not recorded at all","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Run a passing marked test and a failing marked test; verify `status: \"pass\"` and `status: \"fail\"` respectively; add a `[Fact(Skip=\"...\")]` marked test and verify it is not recorded","test_file":"dev/test_multilang_proof_plugins.py","test_name":"test_status_mapping_and_skip_excluded","tier":"integration","status":"pass","audit":"STRONG"}]},{"id":"RULE-5","description":"`test_file` is recorded as the source file path relative to the project root (resolved from `TestCase` source information / `CodeFilePath`); `test_name` is the fully-qualified test method name","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Run `dotnet test` from a project root; verify `test_file` is relative (not absolute) and `test_name` is the fully-qualified method name","test_file":"dev/test_multilang_proof_plugins.py","test_name":"test_relative_file_and_fq_name","tier":"integration","status":"pass","audit":"STRONG"}]},{"id":"RULE-6","description":"On run completion the logger emits proof JSON to the resolved spec directory following the shared feature-scoped overwrite contract","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Pre-seed a proof file with feature B entries; run the logger for feature A; verify feature B entries are preserved and feature A entries are replaced (inherits the proof_common feature-scoped overwrite contract)","test_file":"dev/test_multilang_proof_plugins.py","test_name":"test_feature_scoped_overwrite","tier":"integration","status":"pass","audit":"STRONG"}]},{"id":"proof_common/RULE-1","description":"Each plugin resolves the spec directory by scanning `specs/**/*.md` and matching the feature name to the spec filename stem","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Create `specs/hooks/gate_hook.md` and run a proof plugin with feature `gate_hook`; verify the proof file is written to `specs/hooks/`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"spec dir resolution","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"proof_common/RULE-10","description":"When a test is removed from a re-run, the old proof entry is purged and not carried over from the previous proof file","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-13","description":"Write a proof file with 2 proofs, then re-run with only 1; verify the removed entry is purged and not carried over","test_file":"dev/test_multilang_proof_plugins.py","test_name":"test_removed_test_purged_on_rerun","tier":"integration","status":"pass","audit":"STRONG"},{"id":"PROOF-12","description":"e2e: Write proof file with only 1 of 2 proofs (test deletion); verify coverage shows 1/2 not 2/2","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"proof_common/RULE-2","description":"Proof files are written to the spec's directory as `.proofs-.json`","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Run a proof plugin for feature `gate_hook` tier `unit`; verify output file is named `gate_hook.proofs-unit.json`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"proof file naming","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"proof_common/RULE-3","description":"When the spec directory for a feature is not found, the plugin falls back to writing to `specs/`","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Run a proof plugin for feature `nonexistent_feature` (no matching spec); verify proof file is written to `specs/`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"fallback to specs/","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"proof_common/RULE-4","description":"Feature-scoped overwrite: existing entries for other features are preserved; only the current feature's entries are replaced","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Create a proof file with entries for features A and B; run the plugin for feature A only; verify feature B entries are preserved and feature A entries are replaced","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"feature-scoped overwrite","tier":"unit","status":"pass","audit":"STRONG"},{"id":"PROOF-10","description":"e2e: Create 2 specs; write proofs for each separately; verify both PASSING and no cross-contamination","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""},{"id":"PROOF-11","description":"e2e: Overwrite one feature's proofs via shell harness; verify target feature updated, other feature untouched","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"proof_common/RULE-5","description":"Each proof entry contains all 7 required fields: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Run a proof plugin and read the output JSON; verify each entry in `proofs` array contains all 7 fields: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","test_file":"dev/test_multilang_proof_plugins.py","test_name":"test_python_proof_plugin_real_execution","tier":"integration","status":"pass","audit":"STRONG"},{"id":"PROOF-5","description":"Run a proof plugin and read the output JSON; verify each entry in `proofs` array contains all 7 fields: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"all 7 required fields","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"proof_common/RULE-6","description":"`status` is `\"pass\"` when the test passes and `\"fail\"` when it fails \u2014 no other values","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Run a passing test and a failing test with proof markers; verify the passing test has `status: \"pass\"` and the failing test has `status: \"fail\"`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"pass/fail status","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"proof_common/RULE-7","description":"If no proof markers are collected during a test run, no proof files are written (no-op)","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Run a test suite with no proof markers; verify no `*.proofs-*.json` files are created","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"no markers no files","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"proof_common/RULE-8","description":"Custom/community proof plugins installed to `.purlin/plugins/` require no registration \u2014 `sync_status` discovers proof files by globbing `specs/**/*.proofs-*.json`, so any plugin that writes files in that pattern works automatically","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-8","description":"Place a `.proofs-unit.json` file in a spec directory written by a non-built-in source; run `sync_status`; verify it reads the proofs","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"custom plugin proofs discovered by sync_status","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"proof_common/RULE-9","description":"When spec directory lookup falls back to specs/ root, the plugin emits a warning to stderr naming the missing spec and suggesting purlin:spec ","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-9","description":"Run a proof plugin for a feature with no matching spec; verify stderr contains a warning naming the feature and suggesting purlin:spec","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"fallback warning to stderr","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"schema_proof_format/RULE-1","description":"Proof files are named `.proofs-.json` and live in the same directory as their spec","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Create a proof file at `specs/test/foo.proofs-unit.json`; run sync_status; verify it reads the file and associates it with the `foo` spec","test_file":"dev/test_schema_proof_format.py","test_name":"test_proof_file_read_by_sync_status","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"schema_proof_format/RULE-2","description":"The JSON schema has a top-level `tier` string and a `proofs` array. Each proof entry contains: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Validate a proof JSON file against the required fields; verify all 7 fields (`feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`) are present in each proof entry","test_file":"dev/test_schema_proof_format.py","test_name":"test_proof_entry_has_all_seven_fields","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"schema_proof_format/RULE-3","description":"`status` is either `\"pass\"` or `\"fail\"` \u2014 no other values","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Create a proof entry with `status: \"error\"`; run sync_status; verify it is not counted as a passing proof","test_file":"dev/test_schema_proof_format.py","test_name":"test_invalid_status_not_counted","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"schema_proof_format/RULE-4","description":"`tier` values are: `\"unit\"`, `\"integration\"`, `\"e2e\"` \u2014 no other automated tiers","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Scan all real `*.proofs-*.json` files; verify every tier value (top-level and per-entry) is in the valid set {unit, integration, e2e}. Verify `spec_format.md` documents @integration and","test_file":"dev/test_schema_proof_format.py","test_name":"test_standard_tiers_documented","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"schema_proof_format/RULE-5","description":"Proof plugins use feature-scoped overwrite: load existing file, purge entries matching current feature, append new entries, write merged result","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Pre-seed a proof file with feature B entries; run the real pytest proof plugin for feature A via subprocess; verify feature B entries are preserved and feature A entries are added","test_file":"dev/test_schema_proof_format.py","test_name":"test_feature_scoped_overwrite","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"schema_proof_format/RULE-6","description":"Proof files are committed to git \u2014 they are project records, not ephemeral build artifacts","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Grep `.gitignore` for `*.proofs-*.json`; verify no gitignore rule excludes proof files","test_file":"dev/test_schema_proof_format.py","test_name":"test_proof_files_not_gitignored","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"schema_proof_format/RULE-7","description":"Manual proofs are stamped inline in the spec's `## Proof` section as `@manual(, , )`, not in proof JSON files","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Grep `references/formats/proofs_format.md` for `@manual`; verify manual stamp format is `@manual(, , )`","test_file":"dev/test_schema_proof_format.py","test_name":"test_manual_stamp_format_documented","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-1","description":"FORBIDDEN \u2014 No `eval()` or `exec()` calls in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Grep `scripts/` for `eval(` and `exec(` in `.py` files; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_eval_or_exec","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"security_no_dangerous_patterns/RULE-2","description":"FORBIDDEN \u2014 No `subprocess` calls with `shell=True` in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Grep `scripts/**/*.py` for `shell\\s*=\\s*True`; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_shell_true","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"security_no_dangerous_patterns/RULE-3","description":"FORBIDDEN \u2014 No `os.system()` calls in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Grep `scripts/**/*.py` for `os\\.system\\s*\\(`; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_os_system","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"security_no_dangerous_patterns/RULE-4","description":"FORBIDDEN \u2014 No hardcoded credential assignments (password/secret/api_key/token literals) in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Grep `scripts/` for assignment patterns `(password|secret|api_key|token)\\s*=\\s*[\"'][^\"']+[\"']` in `.py`, `.sh`, `.js` files, excluding test files and comments; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_hardcoded_credentials","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"security_no_dangerous_patterns/RULE-5","description":"All subprocess calls use list-mode arguments (not string commands)","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Grep `scripts/**/*.py` for `subprocess.run(` and `subprocess.call(`; verify each call's first argument is a list literal `[`, not a string","test_file":"dev/test_security.py","test_name":"test_subprocess_uses_list_args","tier":"unit","status":"pass","audit":"STRONG"}]}],"audit":{"integrity":100,"strong":6,"weak":0,"hollow":0,"manual":0,"findings":[]}},{"name":"purlin_agent","category":"instructions","type":"feature","is_global":false,"source_url":null,"pinned":null,"description":"Defines the Purlin Agent persona and behavior for Claude Code \u2014 the primary instruction document that shapes how Claude operates in spec-driven mode. Covers the core development loop, proof marker syntax, coverage gates, and routing between skills.","source_path":null,"stack":"markdown (agent definition)","ext_status":null,"proved":16,"total":16,"deferred":0,"status":"VERIFIED","vhash":"26603850","receipt":{"commit":"6cd4518e322d351ea3c91a7b877f1e59f90f2492","timestamp":"2026-04-07T13:46:48.452182+00:00","stale":false},"rules":[{"id":"RULE-1","description":"The agent definition contains a YAML frontmatter with `name`, `description`, and `model` fields","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Grep `agents/purlin.md` for YAML frontmatter delimiters (`---`); verify `name:`, `description:`, and `model:` fields exist between them","test_file":"dev/test_purlin_agent.py","test_name":"test_yaml_frontmatter","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-2","description":"The `## Core Loop` section defines exactly 4 numbered steps: do the work, call sync_status, follow directives, ship with verify","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Grep `agents/purlin.md` for `## Core Loop`; verify it contains exactly 4 numbered items and the keywords \"Do the work\", \"sync_status\", \"Follow\", and \"Ship\"","test_file":"dev/test_purlin_agent.py","test_name":"test_core_loop_four_steps","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-3","description":"The `## Specs` section includes the 2-section format template with `> Description:`, `## Rules`, `## Proof`","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Grep `agents/purlin.md` for `## Specs`; verify it contains `> Description:`, `## Rules`, `## Proof` within the template","test_file":"dev/test_purlin_agent.py","test_name":"test_specs_section_template","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-4","description":"The `## Proof Markers` section documents marker syntax for all 3 frameworks: pytest, Jest, and shell","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Grep `agents/purlin.md` for `## Proof Markers`; verify it contains `**pytest:`, `**Jest:`, and `**Shell:` framework subsections","test_file":"dev/test_purlin_agent.py","test_name":"test_proof_markers_three_frameworks","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-5","description":"The `## Hard Gates` section defines exactly 1 gate: proof coverage","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Grep `agents/purlin.md` for `## Hard Gates`; verify it lists \"Proof coverage\" and contains exactly 1 gate","test_file":"dev/test_purlin_agent.py","test_name":"test_hard_gates_exactly_one","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-6","description":"The `## Implicit Routing` section maps user intents to actions including role-based priority handling (test/build/fix \u2192 read spec + build, status \u2192 sync_status, drift \u2192 purlin:drift, spec \u2192 purlin:spec, verify \u2192 purlin:verify with independent audit, engineer items \u2192 drift + build/unit-test, QA items \u2192 drift + verify)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Grep `agents/purlin.md` for `## Implicit Routing`; verify it contains mappings for \"test\", \"status\", \"drift\", \"spec\", \"verify\", \"engineer\", and \"QA\"","test_file":"dev/test_purlin_agent.py","test_name":"test_implicit_routing","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-7","description":"The `## Skills` table lists all 12 skills with their purpose","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Grep `agents/purlin.md` for `## Skills`; count table rows; verify there are 12 skill entries","test_file":"dev/test_purlin_agent.py","test_name":"test_skills_table_twelve_entries","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-8","description":"The `## References` table lists all 12 reference entries with their topic","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-8","description":"Grep `agents/purlin.md` for `## References`; count table rows; verify there are 12 reference entries","test_file":"dev/test_purlin_agent.py","test_name":"test_references_table_twelve_entries","tier":"unit","status":"pass","audit":""}]},{"id":"schema_spec_format/RULE-1","description":"Every spec MUST contain exactly two required sections: `## Rules`, `## Proof`","label":"required","source":"schema_spec_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Parse `references/formats/spec_format.md`; find the `## Required Sections` area; verify both sections (`## Rules`, `## Proof`) appear within it","test_file":"dev/test_schema_spec_format.py","test_name":"test_format_documents_two_sections","tier":"unit","status":"pass","audit":""}]},{"id":"schema_spec_format/RULE-2","description":"Rules are numbered sequentially as `RULE-N` (RULE-1, RULE-2, ...). Unnumbered lines under `## Rules` trigger a sync_status WARNING","label":"required","source":"schema_spec_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Create a spec with an unnumbered line under `## Rules` (e.g., `- some constraint without RULE-N prefix`); run sync_status; verify it reports a WARNING about unnumbered rules","test_file":"dev/test_schema_spec_format.py","test_name":"test_unnumbered_rule_triggers_warning","tier":"unit","status":"pass","audit":""}]},{"id":"schema_spec_format/RULE-3","description":"Proofs are numbered as `PROOF-N (RULE-N)` \u2014 each proof references the rule it covers","label":"required","source":"schema_spec_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Grep `specs/**/*.md` for lines under `## Proof`; verify every proof line matches the pattern `PROOF-\\d+ \\(RULE-\\d+\\)`","test_file":"dev/test_schema_spec_format.py","test_name":"test_all_proof_lines_match_pattern","tier":"unit","status":"pass","audit":""}]},{"id":"schema_spec_format/RULE-4","description":"Every RULE must have at least one corresponding PROOF","label":"required","source":"schema_spec_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Create a spec with RULE-1 but no PROOF referencing RULE-1; run sync_status; verify it reports the rule as uncovered","test_file":"dev/test_schema_spec_format.py","test_name":"test_rule_without_proof_shows_uncovered","tier":"unit","status":"pass","audit":""}]},{"id":"schema_spec_format/RULE-5","description":"`> Requires:` metadata, when present, is a comma-separated list of anchor names whose rules also apply to this feature","label":"required","source":"schema_spec_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Create a spec with `> Requires: base_anchor`; run sync_status; verify the report includes `base_anchor/RULE-1` in the coverage output","test_file":"dev/test_schema_spec_format.py","test_name":"test_requires_includes_referenced_rules","tier":"unit","status":"pass","audit":""}]},{"id":"schema_spec_format/RULE-6","description":"`> Scope:` metadata, when present, is a comma-separated list of source file paths the feature touches","label":"required","source":"schema_spec_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Create a spec with `> Scope: scripts/mcp/purlin_server.py`; verify sync_status parses and displays the scope files","test_file":"dev/test_schema_spec_format.py","test_name":"test_scope_metadata_parsed","tier":"unit","status":"pass","audit":""}]},{"id":"schema_spec_format/RULE-7","description":"Feature specs use heading `# Feature: `, anchor specs use `# Anchor: `","label":"required","source":"schema_spec_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Grep `specs/**/*.md` for first-level headings; verify each starts with `# Feature:` or `# Anchor:`","test_file":"dev/test_schema_spec_format.py","test_name":"test_spec_headings_use_correct_prefix","tier":"unit","status":"pass","audit":""}]},{"id":"schema_spec_format/RULE-8","description":"`> Description:` metadata, when present, is a plain-language summary of the spec; supports multi-line via `>` continuation lines that do not match a metadata field pattern (`> FieldName:`)","label":"required","source":"schema_spec_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-8","description":"Create a spec with `> Description: First line\\n> second line\\n> Scope: src/`; parse description; verify it equals \"First line second line\" (continuation joined, Scope not included)","test_file":"dev/test_schema_spec_format.py","test_name":"test_multiline_description_continuation","tier":"unit","status":"pass","audit":""}]}],"audit":null},{"name":"purlin_config","category":"mcp","type":"feature","is_global":false,"source_url":null,"pinned":null,"description":"MCP tool for reading and writing Purlin configuration. Reads the full config or a single key, and writes single keys atomically.","source_path":null,"stack":"python/stdlib, json","ext_status":null,"proved":6,"total":6,"deferred":0,"status":"VERIFIED","vhash":"ddd0d4e8","receipt":{"commit":"6cd4518e322d351ea3c91a7b877f1e59f90f2492","timestamp":"2026-04-07T13:46:48.452182+00:00","stale":false},"rules":[{"id":"RULE-1","description":"Reads the full config or a single key, and writes single keys via `update_config`","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Call with action \"write\" then action \"read\"; verify the written value is returned","test_file":"dev/test_mcp_server.py","test_name":"test_read_write","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-1","description":"FORBIDDEN \u2014 No `eval()` or `exec()` calls in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Grep `scripts/` for `eval(` and `exec(` in `.py` files; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_eval_or_exec","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-2","description":"FORBIDDEN \u2014 No `subprocess` calls with `shell=True` in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Grep `scripts/**/*.py` for `shell\\s*=\\s*True`; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_shell_true","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-3","description":"FORBIDDEN \u2014 No `os.system()` calls in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Grep `scripts/**/*.py` for `os\\.system\\s*\\(`; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_os_system","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-4","description":"FORBIDDEN \u2014 No hardcoded credential assignments (password/secret/api_key/token literals) in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Grep `scripts/` for assignment patterns `(password|secret|api_key|token)\\s*=\\s*[\"'][^\"']+[\"']` in `.py`, `.sh`, `.js` files, excluding test files and comments; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_hardcoded_credentials","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-5","description":"All subprocess calls use list-mode arguments (not string commands)","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Grep `scripts/**/*.py` for `subprocess.run(` and `subprocess.call(`; verify each call's first argument is a list literal `[`, not a string","test_file":"dev/test_security.py","test_name":"test_subprocess_uses_list_args","tier":"unit","status":"pass","audit":""}]}],"audit":null},{"name":"purlin_references","category":"instructions","type":"feature","is_global":false,"source_url":null,"pinned":null,"description":"Ten reference documents that define Purlin's formats, conventions, and quality standards. These are the authoritative source that skills and agents reference, ensuring structural consistency across the framework.","source_path":null,"stack":"markdown (reference documentation)","ext_status":null,"proved":30,"total":30,"deferred":0,"status":"VERIFIED","vhash":"eebca835","receipt":{"commit":"8f4c47d21dd51e50f2994e2df67b48af5c2371f2","timestamp":"2026-06-05T13:43:25.311450+00:00","stale":false},"rules":[{"id":"RULE-1","description":"`spec_format.md` documents the 2 required sections (`## Rules`, `## Proof`), the `> Description:` metadata field, and the RULE-N/PROOF-N numbering convention","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Grep `references/formats/spec_format.md` for `## Rules`, `## Proof`; verify both appear as required sections. Grep for `> Description:` in the metadata fields table. Grep for `RULE-N` pattern documentation","test_file":"dev/test_purlin_references.py","test_name":"test_spec_format_required_sections","tier":"unit","status":"pass","audit":"EXCLUDED"}]},{"id":"RULE-10","description":"`spec_quality_guide.md` includes test failure diagnosis guidance with the three categories (code bug, test bug, spec drift) and assertion integrity rules","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-10","description":"Grep `references/spec_quality_guide.md` for `Code bug`, `Test bug`, `Spec drift`, and `Assertion Integrity`; verify all appear","test_file":"dev/test_purlin_references.py","test_name":"test_quality_guide_test_failure_diagnosis","tier":"unit","status":"pass","audit":"EXCLUDED"}]},{"id":"RULE-11","description":"`spec_quality_guide.md` includes audience-appropriate language guidance mapping artifacts to their intended readers","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-11","description":"Grep `references/spec_quality_guide.md` for `Audience-Appropriate Language`; verify the section exists","test_file":"dev/test_purlin_references.py","test_name":"test_quality_guide_audience_language","tier":"unit","status":"pass","audit":"EXCLUDED"}]},{"id":"RULE-12","description":"`drift_criteria.md` documents file classification order, NO_IMPACT patterns, behavioral directory exclusions, significance mapping, structural-only drift indicators, and precomputed drift flags","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-12","description":"Grep `references/drift_criteria.md` for `File Classification`, `NO_IMPACT Patterns`, `Behavioral Directory Exclusions`, `Significance Classification`, `Structural-Only Drift`, `drift_flags`; verify all sections present","test_file":"dev/test_purlin_references.py","test_name":"test_drift_criteria_sections","tier":"unit","status":"pass","audit":"EXCLUDED"}]},{"id":"RULE-13","description":"`spec_quality_guide.md` documents E2E proof descriptions as observable flows (arrange \u2192 act \u2192 observe through the real running app), forbids naming source files or internal functions in proof descriptions, and requires descriptions to stay tool-agnostic (executable by any e2e runner)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-13","description":"Grep `references/spec_quality_guide.md` for the \"E2E proof descriptions\" section; verify it contains arrange \u2192 act \u2192 observe flow language, the ban on naming source files/internal functions, and tool-agnostic phrasing","test_file":"dev/test_purlin_references.py","test_name":"test_quality_guide_e2e_proof_section","tier":"unit","status":"pass","audit":"EXCLUDED"}]},{"id":"RULE-14","description":"`audit_criteria.md` defines E2E Proof Tier Integrity criteria \u2014 tier mismatch (an `@e2e` test that never drives the real UI) and source-constant assertion (asserting a config constant where the rule describes runtime behavior) \u2014 applying to ALL `@e2e` proofs, not just design anchors","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-14","description":"Grep `references/audit_criteria.md` for \"E2E Proof Tier Integrity\", \"tier mismatch\", and \"source-constant\"; verify the section applies to all `@e2e` proofs, not only design anchors","test_file":"dev/test_purlin_references.py","test_name":"test_audit_criteria_e2e_tier_integrity","tier":"unit","status":"pass","audit":"EXCLUDED"}]},{"id":"RULE-15","description":"`supported_frameworks.md` documents end-to-end (browser) proofs: no dedicated e2e proof reporter ships, `@e2e` proofs are tool-agnostic, and proof emission wires through the existing plugins (Vitest/Jest markers or shell `purlin_proof` wrappers)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-15","description":"Grep `references/supported_frameworks.md` for the end-to-end proofs section; verify it states no dedicated e2e reporter ships, describes tool-agnostic `@e2e` proofs, and documents wiring through existing plugins","test_file":"dev/test_purlin_references.py","test_name":"test_supported_frameworks_e2e_section","tier":"unit","status":"pass","audit":"EXCLUDED"}]},{"id":"RULE-2","description":"`proofs_format.md` documents the proof JSON schema with all 7 required fields and the feature-scoped overwrite merge behavior","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Grep `references/formats/proofs_format.md` for the 7 field names: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`; verify all appear. Grep for \"feature-scoped overwrite\"","test_file":"dev/test_purlin_references.py","test_name":"test_proofs_format_fields_and_merge","tier":"unit","status":"pass","audit":"EXCLUDED"}]},{"id":"RULE-3","description":"`proofs_format.md` documents proof markers for all 3 frameworks: pytest, Jest, shell","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Grep `references/formats/proofs_format.md` for `### pytest`, `### Jest`, `### Shell`; verify all 3 framework subsections exist","test_file":"dev/test_purlin_references.py","test_name":"test_proofs_format_three_frameworks","tier":"unit","status":"pass","audit":"EXCLUDED"}]},{"id":"RULE-4","description":"`anchor_format.md` documents anchor file location (`specs/_anchors/`), metadata fields (`> Source:`, `> Pinned:`, `> Global:`), sync protocol, and global anchor behavior","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Grep `references/formats/anchor_format.md` for `_anchors/`, `> Source:`, `> Pinned:`, `> Global:`; verify all appear","test_file":"dev/test_purlin_references.py","test_name":"test_anchor_format_metadata","tier":"unit","status":"pass","audit":"EXCLUDED"}]},{"id":"RULE-5","description":"`anchor_format.md` documents all 8 type values: `design`, `api`, `security`, `brand`, `platform`, `schema`, `legal`, `prodbrief`","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Grep `references/formats/anchor_format.md` for all 8 type values: `design`, `api`, `security`, `brand`, `platform`, `schema`, `legal`, `prodbrief`; verify all appear in the type metadata documentation","test_file":"dev/test_purlin_references.py","test_name":"test_anchor_format_eight_type_values","tier":"unit","status":"pass","audit":"EXCLUDED"}]},{"id":"RULE-6","description":"`hard_gates.md` documents exactly 1 gate: proof coverage","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Grep `references/hard_gates.md` for \"Proof coverage\"; verify it appears and no second gate is defined","test_file":"dev/test_purlin_references.py","test_name":"test_hard_gates_exactly_one","tier":"unit","status":"pass","audit":"EXCLUDED"}]},{"id":"RULE-7","description":"`commit_conventions.md` documents all 8 commit prefixes: spec, feat, fix, test, verify, anchor, chore, docs","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Grep `references/commit_conventions.md` for the 8 prefixes: `spec`, `feat`, `fix`, `test`, `verify`, `anchor`, `chore`, `docs`; verify all 8 appear","test_file":"dev/test_purlin_references.py","test_name":"test_commit_conventions_eight_prefixes","tier":"unit","status":"pass","audit":"EXCLUDED"}]},{"id":"RULE-8","description":"`purlin_commands.md` lists all 12 skills grouped by category (Authoring, Building, Quality, Reporting, Project)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-8","description":"Grep `references/purlin_commands.md` for `Authoring`, `Building`, `Quality`, `Reporting`, `Project`; verify all 5 category headers exist. Count skill entries; verify 12","test_file":"dev/test_purlin_references.py","test_name":"test_purlin_commands_categories_and_skills","tier":"unit","status":"pass","audit":"EXCLUDED"}]},{"id":"RULE-9","description":"`spec_quality_guide.md` includes guidance on writing rules (rebuild test, contract boundaries, coverage dimensions), proof descriptions, tier assignment, and FORBIDDEN patterns","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-9","description":"Grep `references/spec_quality_guide.md` for \"rebuild test\", \"contract boundary/boundaries\", \"Coverage dimensions\", \"FORBIDDEN\", \"Tier\"; verify the guide covers the rebuild test, contract boundaries, coverage dimensions, forbidden patterns, and tier assignment","test_file":"dev/test_purlin_references.py","test_name":"test_spec_quality_guide_coverage","tier":"unit","status":"pass","audit":"EXCLUDED"}]},{"id":"schema_spec_format/RULE-1","description":"Every spec MUST contain exactly two required sections: `## Rules`, `## Proof`","label":"required","source":"schema_spec_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Parse `references/formats/spec_format.md`; find the `## Required Sections` area; verify both sections (`## Rules`, `## Proof`) appear within it","test_file":"dev/test_schema_spec_format.py","test_name":"test_format_documents_two_sections","tier":"unit","status":"pass","audit":"EXCLUDED"}]},{"id":"schema_spec_format/RULE-2","description":"Rules are numbered sequentially as `RULE-N` (RULE-1, RULE-2, ...). Unnumbered lines under `## Rules` trigger a sync_status WARNING","label":"required","source":"schema_spec_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Create a spec with an unnumbered line under `## Rules` (e.g., `- some constraint without RULE-N prefix`); run sync_status; verify it reports a WARNING about unnumbered rules","test_file":"dev/test_schema_spec_format.py","test_name":"test_unnumbered_rule_triggers_warning","tier":"unit","status":"pass","audit":"EXCLUDED"}]},{"id":"schema_spec_format/RULE-3","description":"Proofs are numbered as `PROOF-N (RULE-N)` \u2014 each proof references the rule it covers","label":"required","source":"schema_spec_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Grep `specs/**/*.md` for lines under `## Proof`; verify every proof line matches the pattern `PROOF-\\d+ \\(RULE-\\d+\\)`","test_file":"dev/test_schema_spec_format.py","test_name":"test_all_proof_lines_match_pattern","tier":"unit","status":"pass","audit":"EXCLUDED"}]},{"id":"schema_spec_format/RULE-4","description":"Every RULE must have at least one corresponding PROOF","label":"required","source":"schema_spec_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Create a spec with RULE-1 but no PROOF referencing RULE-1; run sync_status; verify it reports the rule as uncovered","test_file":"dev/test_schema_spec_format.py","test_name":"test_rule_without_proof_shows_uncovered","tier":"unit","status":"pass","audit":"EXCLUDED"}]},{"id":"schema_spec_format/RULE-5","description":"`> Requires:` metadata, when present, is a comma-separated list of anchor names whose rules also apply to this feature","label":"required","source":"schema_spec_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Create a spec with `> Requires: base_anchor`; run sync_status; verify the report includes `base_anchor/RULE-1` in the coverage output","test_file":"dev/test_schema_spec_format.py","test_name":"test_requires_includes_referenced_rules","tier":"unit","status":"pass","audit":"EXCLUDED"}]},{"id":"schema_spec_format/RULE-6","description":"`> Scope:` metadata, when present, is a comma-separated list of source file paths the feature touches","label":"required","source":"schema_spec_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Create a spec with `> Scope: scripts/mcp/purlin_server.py`; verify sync_status parses and displays the scope files","test_file":"dev/test_schema_spec_format.py","test_name":"test_scope_metadata_parsed","tier":"unit","status":"pass","audit":"EXCLUDED"}]},{"id":"schema_spec_format/RULE-7","description":"Feature specs use heading `# Feature: `, anchor specs use `# Anchor: `","label":"required","source":"schema_spec_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Grep `specs/**/*.md` for first-level headings; verify each starts with `# Feature:` or `# Anchor:`","test_file":"dev/test_schema_spec_format.py","test_name":"test_spec_headings_use_correct_prefix","tier":"unit","status":"pass","audit":"EXCLUDED"}]},{"id":"schema_spec_format/RULE-8","description":"`> Description:` metadata, when present, is a plain-language summary of the spec; supports multi-line via `>` continuation lines that do not match a metadata field pattern (`> FieldName:`)","label":"required","source":"schema_spec_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-8","description":"Create a spec with `> Description: First line\\n> second line\\n> Scope: src/`; parse description; verify it equals \"First line second line\" (continuation joined, Scope not included)","test_file":"dev/test_schema_spec_format.py","test_name":"test_multiline_description_continuation","tier":"unit","status":"pass","audit":"EXCLUDED"}]},{"id":"schema_proof_format/RULE-1","description":"Proof files are named `.proofs-.json` and live in the same directory as their spec","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Create a proof file at `specs/test/foo.proofs-unit.json`; run sync_status; verify it reads the file and associates it with the `foo` spec","test_file":"dev/test_schema_proof_format.py","test_name":"test_proof_file_read_by_sync_status","tier":"unit","status":"pass","audit":"EXCLUDED"}]},{"id":"schema_proof_format/RULE-2","description":"The JSON schema has a top-level `tier` string and a `proofs` array. Each proof entry contains: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Validate a proof JSON file against the required fields; verify all 7 fields (`feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`) are present in each proof entry","test_file":"dev/test_schema_proof_format.py","test_name":"test_proof_entry_has_all_seven_fields","tier":"unit","status":"pass","audit":"EXCLUDED"}]},{"id":"schema_proof_format/RULE-3","description":"`status` is either `\"pass\"` or `\"fail\"` \u2014 no other values","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Create a proof entry with `status: \"error\"`; run sync_status; verify it is not counted as a passing proof","test_file":"dev/test_schema_proof_format.py","test_name":"test_invalid_status_not_counted","tier":"unit","status":"pass","audit":"EXCLUDED"}]},{"id":"schema_proof_format/RULE-4","description":"`tier` values are: `\"unit\"`, `\"integration\"`, `\"e2e\"` \u2014 no other automated tiers","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Scan all real `*.proofs-*.json` files; verify every tier value (top-level and per-entry) is in the valid set {unit, integration, e2e}. Verify `spec_format.md` documents @integration and","test_file":"dev/test_schema_proof_format.py","test_name":"test_standard_tiers_documented","tier":"unit","status":"pass","audit":"EXCLUDED"}]},{"id":"schema_proof_format/RULE-5","description":"Proof plugins use feature-scoped overwrite: load existing file, purge entries matching current feature, append new entries, write merged result","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Pre-seed a proof file with feature B entries; run the real pytest proof plugin for feature A via subprocess; verify feature B entries are preserved and feature A entries are added","test_file":"dev/test_schema_proof_format.py","test_name":"test_feature_scoped_overwrite","tier":"unit","status":"pass","audit":"EXCLUDED"}]},{"id":"schema_proof_format/RULE-6","description":"Proof files are committed to git \u2014 they are project records, not ephemeral build artifacts","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Grep `.gitignore` for `*.proofs-*.json`; verify no gitignore rule excludes proof files","test_file":"dev/test_schema_proof_format.py","test_name":"test_proof_files_not_gitignored","tier":"unit","status":"pass","audit":"EXCLUDED"}]},{"id":"schema_proof_format/RULE-7","description":"Manual proofs are stamped inline in the spec's `## Proof` section as `@manual(, , )`, not in proof JSON files","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Grep `references/formats/proofs_format.md` for `@manual`; verify manual stamp format is `@manual(, , )`","test_file":"dev/test_schema_proof_format.py","test_name":"test_manual_stamp_format_documented","tier":"unit","status":"pass","audit":"EXCLUDED"}]}],"audit":null},{"name":"purlin_report","category":"dashboard","type":"feature","is_global":false,"source_url":null,"pinned":null,"description":"Static HTML dashboard that renders Purlin coverage data from `.purlin/report-data.js`. Opens via `file://` in any browser with no server or build step.","source_path":null,"stack":"html/css/javascript","ext_status":null,"proved":44,"total":44,"deferred":0,"status":"VERIFIED","vhash":"378f19d9","receipt":{"commit":"e8dbadf5c33ff73ac0c89efdf5d736078047d57d","timestamp":"2026-06-05T12:24:43.424415+00:00","stale":false},"rules":[{"id":"RULE-1","description":"HTML loads .purlin/report-data.js via a script tag before rendering","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Use Playwright to read page source; verify script tag with src=\".purlin/report-data.js\" is present","test_file":"dev/test_purlin_report.py","test_name":"test_script_tag_loads_data_js","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"RULE-10","description":"Anchor features display a type pill (anchor or global) next to their name","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-10","description":"Write report-data.js with anchor features (one global, one local); load in Playwright; verify elements with class \"tp-global\" and \"tp-anchor\" exist; take screenshot","test_file":"dev/test_purlin_report.py","test_name":"test_anchor_type_pills","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-11","description":"Anchors with source_url display an external link icon with the URL as tooltip","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-11","description":"Write report-data.js with anchor having source_url; load in Playwright; verify element with class \"ext-icon\" exists and has title attribute containing the URL","test_file":"dev/test_purlin_report.py","test_name":"test_anchor_external_link_icon","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-12","description":"Feature table columns are sortable by clicking column headers","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-12","description":"Load page; click \"Coverage\" column header; verify table row order changes (first row name differs from default sort); take screenshot","test_file":"dev/test_purlin_report.py","test_name":"test_table_sorting","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-13","description":"Footer docs link uses docs_url from PURLIN_DATA, not a hardcoded URL","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-13","description":"Write report-data.js with docs_url set to \"https://example.com/docs\"; load in Playwright; verify footer link href equals \"https://example.com/docs\"","test_file":"dev/test_purlin_report.py","test_name":"test_footer_docs_url","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-14","description":"Summary strip shows integrity percentage from audit_summary with color coding, or dash with \"run purlin:audit\" when null","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-14","description":"Write report-data.js with audit_summary.integrity=85; load in Playwright; verify a summary card contains \"85%\"; write another with audit_summary=null; reload; verify dash shown instead; take screenshots of both","test_file":"dev/test_purlin_report.py","test_name":"test_integrity_card_display","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-15","description":"Header shows last audit time from audit_summary, with amber warning when stale","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-15","description":"Write report-data.js with audit_summary.stale=true; load in Playwright; verify element with class \"audit-time stale\" exists; take screenshot","test_file":"dev/test_purlin_report.py","test_name":"test_audit_time_stale_class","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-16","description":"Status column is centered in both the feature table and the expanded rules sub-table at all viewport widths","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-16","description":"Load page in Playwright at 1920px and 1280px widths; expand a feature; verify the status badge td in the feature table has text-align:center; verify the status td in the rules sub-table has text-align:center; take screenshots at both widths","test_file":"dev/test_purlin_report.py","test_name":"test_status_column_centered_at_multiple_widths","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-17","description":"Dashboard uses full available width up to 2400px and is responsive down to 1100px","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-17","description":"Load page in Playwright at viewport width 2400px; verify dashboard container max-width is 2400px; resize to 1100px; verify summary strip reflows to 3 columns; take screenshots at both widths","test_file":"dev/test_purlin_report.py","test_name":"test_responsive_layout","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-18","description":"Coverage bar fill width matches the proved/total fraction \u2014 a feature with 2/6 coverage has a bar fill at ~33%, not 100%","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-18","description":"Create features with known coverage fractions (2/6 and 5/5); load in Playwright; measure the cov-fill element width as a percentage of the cov-bar; verify 2/6 bar is ~33% and 5/5 bar is 100%","test_file":"dev/test_purlin_report.py","test_name":"test_coverage_bar_width_matches_fraction","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-19","description":"Features are grouped by category with collapsible section headers showing rolled-up summaries (total count, coverage fraction, and status breakdown); category coverage bar is amber when any feature is untested or coverage is incomplete, red when any feature is failing, green only when all features are passing or verified; this grouping applies independently within both the Specs and Anchors sections","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-19","description":"Write report-data.js with features in 3 categories: \"skills\" (2 PASSING), \"mcp\" (1 PASSING + 1 UNTESTED), \"_anchors\" (1 VERIFIED anchor); load in Playwright; verify the Specs section has 2 category headers (\"skills\", \"mcp\"); verify the Anchors section has 1 category header (\"_anchors\"); verify \"mcp\" category has amber coverage bar; take screenshot","test_file":"dev/test_purlin_report.py","test_name":"test_category_headers_with_rolled_up_summaries","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-2","description":"When PURLIN_DATA is undefined, dashboard shows a no-data message with instructions","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Use Playwright to load purlin-report.html without report-data.js; take screenshot; verify page contains text \"No dashboard data\" and \"purlin:status\"","test_file":"dev/test_purlin_report.py","test_name":"test_no_data_message","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"RULE-20","description":"Category sections are expanded by default; a category renders collapsed only when localStorage explicitly records it as collapsed","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-20","description":"Load page with categorized features and empty localStorage; verify feature rows (.fr) are visible without any interaction; click a category header; verify its features become hidden; take screenshot","test_file":"dev/test_purlin_report.py","test_name":"test_categories_expanded_by_default","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"RULE-21","description":"Category and feature open/closed state is persisted to localStorage and restored on reload","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-21","description":"Load page (categories expanded by default); collapse a category; reload page; verify the same category is still collapsed and other categories remain expanded; expand it; reload; verify it is expanded","test_file":"dev/test_purlin_report.py","test_name":"test_category_state_persists_across_reloads","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"RULE-22","description":"Proof audit tags (STRONG/WEAK/HOLLOW) only appear when audit_summary has data; when audit_summary is null (integrity card shows dash), no audit tags render on any proof","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-22","description":"Load page with audit_summary having integrity=85 and features with proof-level audit tags; expand a feature; verify .atag elements appear on proofs; take screenshot. Then load with audit_summary=null but same per-feature audit data; expand the same feature; verify zero .atag elements render; take screenshot","test_file":"dev/test_purlin_report.py","test_name":"test_audit_tags_visible_with_audit_data_hidden_without","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-23","description":"Expanded detail view shows a status-colored action banner above the rules table: PARTIAL shows how many rules need proofs, FAILING shows how many tests are failing, PASSING shows \"run purlin:verify\", UNTESTED shows \"write tests to begin coverage\", VERIFIED shows no banner","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-23","description":"Write report-data.js with features in each status (PARTIAL with 2 NONE rules, FAILING with 1 FAIL rule, PASSING, UNTESTED, VERIFIED); expand each; verify PARTIAL has .ab-partial banner containing \"2 rules need proofs\"; FAILING has .ab-failing banner containing \"1 test failing\"; PASSING has .ab-passing banner containing \"purlin:verify\"; UNTESTED has .ab-untested banner containing \"write tests\"; VERIFIED has no .ab element; take screenshots","test_file":"dev/test_purlin_report.py","test_name":"test_action_banners_per_status","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-24","description":"Rules with NONE status display with an amber left border, visually distinguishing uncovered rules from proved ones","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-24","description":"Expand a PARTIAL feature with NONE rules; verify rule rows with NONE status have class \"rule-np\"; verify the first td of those rows has a computed border-left-color matching amber; take screenshot","test_file":"dev/test_purlin_report.py","test_name":"test_no_proof_rules_have_amber_border","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-25","description":"Rules with FAIL status display with a red left border, visually distinguishing failing rules from passing ones","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-25","description":"Expand a FAILING feature with FAIL rules; verify rule rows with FAIL status have class \"rule-fail\"; verify the first td of those rows has a computed border-left-color matching red; take screenshot","test_file":"dev/test_purlin_report.py","test_name":"test_fail_rules_have_red_border","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-26","description":"Expanded detail view for anchors with source_url shows an \"External Reference\" info block above the rules table, displaying Source URL (as clickable link), Path (if present, in code font), and Pinned version (SHA truncated to 7 chars for git, full timestamp for Figma); unpinned anchors show amber \"Unpinned\" text","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-26","description":"Write report-data.js with an anchor feature having source_url, pinned (40-char SHA), and source_path; expand the anchor; verify .ext-ref-block exists containing a Source link, Path in code element, and Pinned truncated to 7 chars; write another anchor with no pinned; expand it; verify amber \"Unpinned\" text; take screenshots","test_file":"dev/test_purlin_report.py","test_name":"test_external_reference_block","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-27","description":"External link icon tooltip includes Source URL, Path (if present), and Pinned value (truncated)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-27","description":"Write report-data.js with anchor having source_url, pinned, and source_path; verify .ext-icon title attribute contains all three values","test_file":"dev/test_purlin_report.py","test_name":"test_ext_icon_tooltip_includes_pinned","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-28","description":"Anchors with `ext_status` of \"stale\" or \"unpinned\" display an amber outline \"STALE\" badge next to the external link icon, with tooltip \"Run purlin:anchor sync to update\"; anchors with \"current\" or null ext_status show no badge","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-28","description":"Write report-data.js with one anchor having ext_status=\"stale\" and another with ext_status=null; load dashboard; verify stale anchor has .ext-stale badge with text \"STALE\" and tooltip containing \"sync\"; verify non-stale anchor has no .ext-stale badge","test_file":"dev/test_purlin_report.py","test_name":"test_stale_badge_on_stale_anchor","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-29","description":"When `uncommitted` array in PURLIN_DATA is non-empty, an expandable \"uncommitted work\" section appears between the summary strip and the feature table, showing the file count when collapsed and the file list when expanded; when the array is empty or absent, no section appears","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-29","description":"Write report-data.js with uncommitted=[\"M file1.py\",\"?? file2.js\"]; load dashboard; verify .uw-section exists with count \"2\"; click the header; verify .uw-files becomes visible with file list; reload with uncommitted=[]; verify no .uw-section exists","test_file":"dev/test_purlin_report.py","test_name":"test_uncommitted_section","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-3","description":"Summary strip shows total features, verified, passing, incomplete (partial + untested combined), failing counts from PURLIN_DATA.summary; the incomplete card shows a subtitle breakdown when both partial and untested are nonzero","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Write report-data.js with summary {total_features:10, verified:4, passing:2, partial:2, failing:1, untested:1}; load page in Playwright; verify summary cards show: Features=10, Verified=4, Passing=2, Incomplete=3 (with subtitle \"2 partial, 1 untested\"), Failing=1; take screenshot","test_file":"dev/test_purlin_report.py","test_name":"test_summary_strip_counts","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"RULE-30","description":"Expanded detail view shows the spec's `> Description:` content as a description block (`.desc-block`) using the full width of the detail container, positioned between the external reference block (or action banner if no external reference) and the \"Rules & Proofs\" header; the block is only rendered when the feature has a non-null, non-empty description","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-30","description":"Write report-data.js with one feature having description \"Handles user login\" and another with description null; expand each; verify the first shows a .desc-block containing \"Handles user login\" positioned before the rules table; verify the second has no .desc-block element","test_file":"dev/test_purlin_report.py","test_name":"test_description_block_rendering","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-31","description":"Proofs whose description matches structural patterns (grep, file exists, section present, verify contains) display a green \"Structural\" tag (solid green background, white text) next to the proof ID; structural tags render independently of audit data and are not counted in the integrity score","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-31","description":"Write report-data.js with audit_summary having integrity data and a feature with two proofs: one with a structural description (\"Grep scripts/ for eval(); verify zero matches\") and one behavioral (\"Returns 401 when token is expired\"); expand the feature; verify the structural proof has an .atag-st element with text \"Structural\" and computed background-color matching green (#22c55e); verify the behavioral proof does not have an .atag-st element; verify the structural proof's tag has white text color","test_file":"dev/test_purlin_report.py","test_name":"test_structural_proof_tag_rendering","tier":"unit","status":"pass","audit":"HOLLOW"}]},{"id":"RULE-32","description":"Anchor features render in a separate \"Anchors\" section below the Specs section, with its own section label and table; the Specs section contains only non-anchor features","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-32","description":"Write report-data.js with 2 regular features (category \"auth\") and 2 anchor features (category \"_anchors\"); load in Playwright; verify two distinct .section-label elements exist with text \"Specs\" and \"Anchors\"; verify anchor feature names appear only inside the Anchors section table; verify no anchor features appear in the Specs section table; take screenshot","test_file":"dev/test_purlin_report.py","test_name":"test_anchors_separate_section","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-33","description":"Planned proofs (proof entries with status \"planned\") render in the rule's proof cell greyed with a \"not run\" indicator, visually distinct from executed proofs and from the bare em dash shown when a rule has no proofs at all; planned proofs never show an audit tag","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-33","description":"Write report-data.js with audit_summary having integrity data and one rule whose proofs array contains one entry with status \"planned\" (empty test_file/test_name) and one executed entry, plus a second rule with an empty proofs array; expand the feature; verify the planned proof renders inside an element with class .rprf-planned containing \"not run\" and has no .atag or .atag-st element; verify the executed proof renders normally; verify the empty-proofs rule still shows an em dash; take screenshot","test_file":"dev/test_purlin_report.py","test_name":"test_planned_proof_renders_greyed_not_run","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"RULE-4","description":"Feature table renders one row per feature with name, coverage fraction, status badge, integrity, and verified columns","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Write report-data.js with 8 features in mixed states; load in Playwright; count table rows with class \"fr\"; verify count is 8; take screenshot","test_file":"dev/test_purlin_report.py","test_name":"test_feature_table_row_count","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"RULE-5","description":"Clicking a feature row expands it to show per-rule detail","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Load page with features; click first feature row; verify a detail row with class \"dr\" becomes visible; verify it contains a rules table; take screenshot of expanded state","test_file":"dev/test_purlin_report.py","test_name":"test_row_expand_shows_detail","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"RULE-6","description":"Expanded detail shows each rule with id, description, source (blank for own rules, \"required\" or \"global\" for others), status, and proof column; when a rule has multiple proofs, all proofs are stacked vertically in a single cell separated by a thin divider, not split across rowspan rows","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Expand a feature with own + global rules, one rule having 2 proofs; verify own rules have empty Source column; verify global rules show \"global\" in Source column; verify the multi-proof rule has both proofs stacked in a single td with a .rprf-sep divider between them; take screenshot","test_file":"dev/test_purlin_report.py","test_name":"test_expanded_rule_sources_and_multi_proof_stacking","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"RULE-7","description":"Toggling dark/light mode persists the preference to localStorage","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Load page; click theme toggle; verify data-theme attribute changes; reload page; verify theme persisted from localStorage","test_file":"dev/test_purlin_report.py","test_name":"test_theme_toggle_persists","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"RULE-8","description":"Staleness indicator shows amber warning when data is older than 1 hour","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-8","description":"Write report-data.js with timestamp set to 2 hours ago; load in Playwright; verify staleness text contains \"ago\" and has CSS class \"warning\"; take screenshot","test_file":"dev/test_purlin_report.py","test_name":"test_staleness_warning_amber","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"RULE-9","description":"Staleness indicator shows red warning when data is older than 24 hours","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-9","description":"Write report-data.js with timestamp set to 2 days ago; load in Playwright; verify staleness text has CSS class \"stale\"; take screenshot","test_file":"dev/test_purlin_report.py","test_name":"test_staleness_stale_red","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"dashboard_visual/RULE-1","description":"Dark theme background is #0f172a, card background is #1e293b","label":"required","source":"dashboard_visual","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Load dashboard in Playwright with dark theme; verify computed background-color of body is #0f172a and card element is #1e293b","test_file":"dev/test_purlin_report.py","test_name":"test_dark_theme_backgrounds","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"dashboard_visual/RULE-10","description":"Integrity color coding: green at 80%+, amber at 50-79%, red below 50%, gray when null","label":"required","source":"dashboard_visual","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-10","description":"Load dashboard with features at integrity 90%, 60%, and 30%; verify green, amber, and red color classes respectively","test_file":"dev/test_purlin_report.py","test_name":"test_integrity_color_coding","tier":"unit","status":"pass","audit":""}]},{"id":"dashboard_visual/RULE-11","description":"All colors and visual constants are defined as CSS custom properties in :root or theme selectors, not hardcoded in component styles","label":"required","source":"dashboard_visual","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-11","description":"Grep purlin-report.html for hardcoded hex colors outside of CSS custom property definitions; verify none exist in component style rules","test_file":"dev/test_purlin_report.py","test_name":"test_no_hardcoded_hex_outside_custom_properties","tier":"unit","status":"pass","audit":""}]},{"id":"dashboard_visual/RULE-2","description":"Light theme background is #f1f5f9, card background is #ffffff","label":"required","source":"dashboard_visual","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Load dashboard in Playwright with light theme; verify computed background-color of body is #f1f5f9 and card element is #ffffff","test_file":"dev/test_purlin_report.py","test_name":"test_light_theme_backgrounds","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"dashboard_visual/RULE-3","description":"Status green is #22c55e, amber is #f59e0b, red is #ef4444, teal accent is #2dd4bf","label":"required","source":"dashboard_visual","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Grep purlin-report.html CSS for --green: #22c55e, --amber: #f59e0b, --red: #ef4444, --teal: #2dd4bf; verify all present","test_file":"dev/test_purlin_report.py","test_name":"test_status_colors_in_css","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"dashboard_visual/RULE-4","description":"Font stack is -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif","label":"required","source":"dashboard_visual","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Grep purlin-report.html for the sans-serif font stack; verify it includes -apple-system and Roboto","test_file":"dev/test_purlin_report.py","test_name":"test_sans_serif_font_stack","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"dashboard_visual/RULE-5","description":"Monospace font stack is \"SF Mono\", \"Fira Code\", \"Cascadia Code\", \"JetBrains Mono\", Consolas, monospace","label":"required","source":"dashboard_visual","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Grep purlin-report.html for the monospace font stack; verify it includes \"SF Mono\" and Consolas","test_file":"dev/test_purlin_report.py","test_name":"test_mono_font_stack","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"dashboard_visual/RULE-6","description":"VERIFIED status badge is solid green pill with white text","label":"required","source":"dashboard_visual","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Load dashboard with a VERIFIED feature in Playwright; verify .sb-ready element has background-color matching --green and white text","test_file":"dev/test_purlin_report.py","test_name":"test_ready_badge_style","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"dashboard_visual/RULE-7","description":"PARTIAL status badge is amber outline pill (transparent background, amber border)","label":"required","source":"dashboard_visual","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Load dashboard with a partial feature in Playwright; verify .sb-partial element has transparent background and amber border","test_file":"dev/test_purlin_report.py","test_name":"test_partial_badge_style","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"dashboard_visual/RULE-8","description":"FAIL status badge is solid red pill with white text","label":"required","source":"dashboard_visual","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-8","description":"Load dashboard with a FAIL feature in Playwright; verify .sb-fail element has background-color matching --red","test_file":"dev/test_purlin_report.py","test_name":"test_fail_badge_style","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"dashboard_visual/RULE-9","description":"UNTESTED status badge is gray pill with amber text; no-proofs badge (generic) is gray pill at reduced opacity","label":"required","source":"dashboard_visual","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-9","description":"Load dashboard with an UNTESTED feature in Playwright; verify .sb-untested element has gray background and amber text color; verify a separate no-proofs element (.sb-none) has reduced opacity","test_file":"dev/test_purlin_report.py","test_name":"test_untested_badge_and_no_proofs_opacity","tier":"unit","status":"pass","audit":"STRONG"}]}],"audit":{"integrity":92,"strong":12,"weak":0,"hollow":1,"manual":0,"findings":[{"proof_id":"PROOF-31","rule_id":"RULE-31","level":"HOLLOW","priority":"LOW","criterion":"assert_true (heuristic)","fix":"Review the Playwright assertions to ensure they check computed CSS color values (not just element existence) and that the color check uses a specific color value rather than a truthy check"}]}},{"name":"purlin_skills","category":"instructions","type":"feature","is_global":false,"source_url":null,"pinned":null,"description":"Twelve skill definition files that define user-facing Purlin commands. Each skill is a SKILL.md with YAML frontmatter and structured instructions that Claude Code executes when invoked. These are the primary user interface to Purlin \u2014 they define what each command does, its usage syntax, and step-by-step workflow.","source_path":null,"stack":"markdown (skill definitions)","ext_status":null,"proved":15,"total":15,"deferred":0,"status":"VERIFIED","vhash":"5bd9ab85","receipt":{"commit":"18b76cee2076ec24a0af7173d8cb33cda81e1e2b","timestamp":"2026-04-03T23:42:43.968125+00:00","stale":false},"rules":[{"id":"RULE-1","description":"Each skill file has YAML frontmatter with `name` and `description` fields","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"For each `skills/*/SKILL.md`, grep for YAML frontmatter delimiters (`---`); verify `name:` and `description:` fields exist","test_file":"dev/test_purlin_skills.py","test_name":"test_each_skill_has_frontmatter","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-10","description":"Drift skill requires reading git diffs for behavioral changes, not just interpreting MCP categories","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-10","description":"Grep `skills/drift/SKILL.md` for `git diff`; verify the diff-reading requirement is present","test_file":"dev/test_purlin_skills.py","test_name":"test_drift_requires_reading_diffs","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-11","description":"Spec skill update workflow (Step 7) presents a delta report showing KEEPING/ADDING/UPDATING/REMOVING before applying changes","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-11","description":"Grep `skills/spec/SKILL.md` for `KEEPING`, `ADDING`, `UPDATING`, and `REMOVING`; verify the delta report structure is present","test_file":"dev/test_purlin_skills.py","test_name":"test_spec_has_delta_report_structure","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-12","description":"Skills that write proof descriptions (build, spec, spec-from-code) include mandatory tier tag review","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-12","description":"Grep `skills/build/SKILL.md`, `skills/spec/SKILL.md`, and `skills/spec-from-code/SKILL.md` for `tier`; verify all three contain tier review requirements","test_file":"dev/test_purlin_skills.py","test_name":"test_proof_writing_skills_have_tier_review","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-13","description":"Init skill `--add-plugin` validates plugin files against language-specific patterns (Python: `proofs`+`json`, JS: `proofs`+`JSON`, Shell: `purlin_proof`, Java: `proofs`+`Proof`) and warns if validation fails","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-13","description":"Grep `skills/init/SKILL.md` for the validation table language entries (`Python`, `JavaScript`, `Shell`, `Java`) and the warning text `doesn't look like a standard proof plugin`; verify all present","test_file":"dev/test_purlin_skills.py","test_name":"test_init_add_plugin_validates_by_language","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-14","description":"Init skill `--add-plugin` supports both local file paths and git URL sources with distinct handling for each","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-14","description":"Grep `skills/init/SKILL.md` for `local file path` and `git URL`; verify both source types are documented with distinct handling steps","test_file":"dev/test_purlin_skills.py","test_name":"test_init_add_plugin_supports_file_and_git","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-15","description":"Init skill `--list-plugins` identifies built-in plugins (`pytest_purlin`, `jest_purlin`, `purlin-proof`) by framework name and labels all others as `custom`","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-15","description":"Grep `skills/init/SKILL.md` for `pytest_purlin.py` with `Python/pytest`, `jest_purlin.js` with `JavaScript/Jest`, and the label `custom`; verify the labeling table exists","test_file":"dev/test_purlin_skills.py","test_name":"test_init_list_plugins_labels_builtin_and_custom","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-2","description":"Exactly 12 skill files exist, one per directory under `skills/`","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Glob `skills/*/SKILL.md`; verify exactly 12 files are returned","test_file":"dev/test_purlin_skills.py","test_name":"test_exactly_twelve_skill_files","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-3","description":"Each skill file contains a `## Usage` section documenting the command syntax","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"For each `skills/*/SKILL.md`, grep for `## Usage`; verify the section exists","test_file":"dev/test_purlin_skills.py","test_name":"test_each_skill_has_usage_section","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-4","description":"Skill names in frontmatter match their directory names (e.g., `skills/build/SKILL.md` has `name: build`)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"For each `skills/*/SKILL.md`, extract the `name:` from frontmatter and the directory name; verify they match","test_file":"dev/test_purlin_skills.py","test_name":"test_skill_name_matches_directory","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-5","description":"Skills that modify files (build, spec, unit-test, verify, init, anchor) include commit instructions or git operations","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Grep `skills/build/SKILL.md`, `skills/spec/SKILL.md`, `skills/unit-test/SKILL.md`, `skills/verify/SKILL.md`, `skills/init/SKILL.md`, `skills/anchor/SKILL.md` for `git commit` or `commit the` or `create.*commit`; verify each contains positive commit instructions","test_file":"dev/test_purlin_skills.py","test_name":"test_modify_skills_have_commit_instructions","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-6","description":"Skills that call MCP tools (status, drift, find) reference the tool by name (`sync_status`, `drift`)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Grep `skills/status/SKILL.md` for `sync_status`; grep `skills/drift/SKILL.md` for `drift`; grep `skills/find/SKILL.md` for `sync_status`; verify each references its MCP tool","test_file":"dev/test_purlin_skills.py","test_name":"test_mcp_skills_reference_tools","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-7","description":"Build and unit-test skills require calling `sync_status` after tests and state it is not optional","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Grep `skills/build/SKILL.md` and `skills/unit-test/SKILL.md` for `sync_status`; verify both contain the reference. Grep build for \"not optional\"; verify present.","test_file":"dev/test_purlin_skills.py","test_name":"test_build_and_unittest_require_sync_status","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-8","description":"Verify skill prohibits modifying code or test files during verification","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-8","description":"Grep `skills/verify/SKILL.md` for `NEVER modify`; verify the read-only constraint is present","test_file":"dev/test_purlin_skills.py","test_name":"test_verify_prohibits_modifying_files","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-9","description":"Build skill includes test failure diagnosis guidance requiring root cause analysis before fixing","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-9","description":"Grep `skills/build/SKILL.md` for `diagnose` and `Never weaken`; verify both are present","test_file":"dev/test_purlin_skills.py","test_name":"test_build_has_failure_diagnosis_guidance","tier":"unit","status":"pass","audit":""}]}],"audit":null},{"name":"purlin_teammate_definitions","category":"instructions","type":"feature","is_global":false,"source_url":null,"pinned":null,"description":"Defines three Claude Code agent roles for the Purlin verify-audit-build loop: an auditor that evaluates proof quality, a builder that fixes code/tests based on audit feedback, and a reviewer that validates specs and detects spec drift. These definitions live in `.claude/agents/` where Claude Code discovers them as available agent roles.","source_path":null,"stack":"markdown (agent definitions)","ext_status":null,"proved":4,"total":4,"deferred":0,"status":"VERIFIED","vhash":"b3ef613b","receipt":{"commit":"18b76cee2076ec24a0af7173d8cb33cda81e1e2b","timestamp":"2026-04-03T23:42:43.968125+00:00","stale":false},"rules":[{"id":"RULE-1","description":"purlin-auditor.md exists with YAML frontmatter containing `name: purlin-auditor`, `description`, and `model` fields","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Read `.claude/agents/purlin-auditor.md`; verify YAML frontmatter contains `name: purlin-auditor`, a `description:` field, and a `model:` field","test_file":"dev/test_purlin_teammate_definitions.py","test_name":"test_auditor_frontmatter","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-2","description":"purlin-builder.md exists with YAML frontmatter containing `name: purlin-builder`, `description`, and `model` fields","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Read `.claude/agents/purlin-builder.md`; verify YAML frontmatter contains `name: purlin-builder`, a `description:` field, and a `model:` field","test_file":"dev/test_purlin_teammate_definitions.py","test_name":"test_builder_frontmatter","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-3","description":"purlin-reviewer.md exists with YAML frontmatter containing `name: purlin-reviewer`, `description`, and `model` fields","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Read `.claude/agents/purlin-reviewer.md`; verify YAML frontmatter contains `name: purlin-reviewer`, a `description:` field, and a `model:` field","test_file":"dev/test_purlin_teammate_definitions.py","test_name":"test_reviewer_frontmatter","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-4","description":"All three agent definitions are located in the `.claude/agents/` directory","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Glob `.claude/agents/purlin-*.md`; verify exactly 3 files are found and all are in `.claude/agents/`","test_file":"dev/test_purlin_teammate_definitions.py","test_name":"test_all_three_in_agents_directory","tier":"unit","status":"pass","audit":""}]}],"audit":null},{"name":"purlin_version","category":"instructions","type":"feature","is_global":false,"source_url":null,"pinned":null,"description":"Ensures the Purlin version string is defined in exactly one place (the VERSION file) and all references to it read from that file or match its value. Prevents version drift when releasing new versions.","source_path":null,"stack":null,"ext_status":null,"proved":5,"total":5,"deferred":0,"status":"VERIFIED","vhash":"175608df","receipt":{"commit":"5336ff4a5d11a06dc1a5d9f9b75b8c2b10f473e2","timestamp":"2026-05-26T07:45:32.457582+00:00","stale":false},"rules":[{"id":"RULE-1","description":"A VERSION file exists at the project root containing a single semver string","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Read VERSION file; verify it exists and contains a valid semver string (X.Y.Z)","test_file":"dev/test_purlin_version.py","test_name":"test_version_file_exists_and_is_valid_semver","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-2","description":"purlin_server.py reads the version from the VERSION file via _read_version(), not a hardcoded string","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Grep purlin_server.py for _read_version; verify it's called to set PURLIN_VERSION; verify SERVER_INFO uses PURLIN_VERSION","test_file":"dev/test_purlin_version.py","test_name":"test_purlin_server_uses_read_version_function","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-3","description":"templates/config.json version field matches the VERSION file","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Read VERSION file and templates/config.json; verify the version field in the template matches the VERSION file content","test_file":"dev/test_purlin_version.py","test_name":"test_template_config_version_matches_version_file","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-4","description":"No hardcoded version strings in purlin_server.py (no literal \"0.9.0\" or similar version patterns)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Grep purlin_server.py for hardcoded version patterns like \"0.9.0\" or \"0.10.0\"; verify zero matches outside of comments","test_file":"dev/test_purlin_version.py","test_name":"test_no_hardcoded_version_strings_in_purlin_server","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-5","description":"The .claude-plugin/plugin.json version field matches the VERSION file","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Read VERSION file and .claude-plugin/plugin.json; verify the version field in the manifest matches the VERSION file content","test_file":"dev/test_purlin_version.py","test_name":"test_plugin_manifest_version_matches_version_file","tier":"unit","status":"pass","audit":""}]}],"audit":null},{"name":"report_data","category":"mcp","type":"feature","is_global":false,"source_url":null,"pinned":null,"description":"When `\"report\": true` is set in `.purlin/config.json`, `sync_status` writes a JS data file (`.purlin/report-data.js`) containing structured coverage, rule, and verification data for the HTML dashboard.","source_path":null,"stack":"python/stdlib, json","ext_status":null,"proved":22,"total":22,"deferred":0,"status":"VERIFIED","vhash":"629723ab","receipt":{"commit":"e8dbadf5c33ff73ac0c89efdf5d736078047d57d","timestamp":"2026-06-05T12:24:43.424415+00:00","stale":false},"rules":[{"id":"RULE-1","description":"When config has `\"report\": true`, sync_status writes `.purlin/report-data.js`","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Set report=true in config, call sync_status, verify .purlin/report-data.js exists","test_file":"dev/test_report_data.py","test_name":"test_report_written_when_enabled","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"RULE-10","description":"Rule label is one of own, required, or global","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-10","description":"Parse report data, verify all rule labels are valid enum values","test_file":"dev/test_report_data.py","test_name":"test_all_rule_labels_are_valid","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"RULE-11","description":"docs_url is dynamically derived from the Purlin plugin git remote","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-11","description":"Call _get_plugin_docs_url, verify it returns a URL derived from git remote","test_file":"dev/test_report_data.py","test_name":"test_docs_url_derived_from_git_remote","tier":"unit","status":"pass","audit":"WEAK"}]},{"id":"RULE-12","description":"Anchor features have type \"anchor\" and include source_url when `> Source:` is present in spec","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-12","description":"Create an anchor spec with > Source:, regenerate report, verify source_url in output","test_file":"dev/test_report_data.py","test_name":"test_anchor_with_source_includes_source_url","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"RULE-13","description":"anchors_summary.total matches the count of features with type \"anchor\"","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-13","description":"Parse report data, count anchor features, verify matches anchors_summary.total","test_file":"dev/test_report_data.py","test_name":"test_anchors_summary_total_matches_anchor_count","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"RULE-14","description":"sync_status output includes dashboard file URL when purlin-report.html exists at project root","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-14","description":"Place purlin-report.html at root, call sync_status with report=true, verify output contains file:// URL","test_file":"dev/test_report_data.py","test_name":"test_dashboard_url_in_output_when_html_exists","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"RULE-15","description":"report-data.js includes an audit_summary object with integrity percentage, assessment counts, last audit timestamp, relative time, and stale boolean; null when no audit cache exists","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-15","description":"Create an audit cache with STRONG/WEAK entries and timestamps, regenerate report, verify audit_summary fields; delete cache, regenerate, verify audit_summary is null","test_file":"dev/test_report_data.py","test_name":"test_audit_summary_fields_present_and_null_when_no_cache","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"RULE-16","description":"Per-feature audit data is populated from the audit cache when entries exist for that feature","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-16","description":"Create an audit cache with entries for a specific feature, regenerate report, verify that feature's audit field is populated with correct integrity and findings","test_file":"dev/test_report_data.py","test_name":"test_per_feature_audit_populated_from_cache","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"RULE-17","description":"All proved rules count toward the coverage fraction (proved/total) regardless of proof type \u2014 grep-based and behavioral proofs are treated equally. Proof quality is assessed by the auditor (STRONG/WEAK/HOLLOW), not the coverage system","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-17","description":"Create a feature with 3 rules (2 behavioral, 1 grep-based); write passing proofs for all 3; build report data; verify proved==3, total==3, and status==PASSING \u2014 all proofs count equally","test_file":"dev/test_report_data.py","test_name":"test_all_proofs_count_equally","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"RULE-18","description":"Features with partial behavioral coverage have status \"PARTIAL\" in report data, not \"PASSING\", even if all existing proofs pass \u2014 PASSING requires every behavioral rule to have a passing proof","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-18","description":"Create a feature with 3 behavioral rules; write passing proofs for only 2 of them; build report data; verify feature status is \"PARTIAL\" not \"PASSING\"","test_file":"dev/test_report_data.py","test_name":"test_partial_coverage_gets_partial_status_not_passing","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"RULE-19","description":"Every feature entry includes a `category` field derived from the spec's parent directory under `specs/` (e.g., `specs/skills/skill_build.md` has category `skills`)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-19","description":"","test_file":"dev/test_report_data.py","test_name":"test_feature_category_derived_from_spec_directory","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"RULE-2","description":"When config lacks `\"report\"` or it is false, no report file is written","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Call sync_status without report config, verify no report-data.js written","test_file":"dev/test_report_data.py","test_name":"test_no_report_written_when_disabled","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"RULE-20","description":"Coverage invariant \u2014 for every feature in report data, status PASSING or VERIFIED implies proved == total (100% coverage fraction). No feature may show PASSING or VERIFIED with proved < total","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-20","description":"Create multiple features \u2014 one PASSING (behavioral-only), one PASSING (mixed behavioral+structural), one VERIFIED with receipt, one PARTIAL; build report data; assert every PASSING/VERIFIED feature has proved == total and every PARTIAL feature has proved < total","test_file":"dev/test_report_data.py","test_name":"test_passing_verified_always_100pct_coverage","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"RULE-21","description":"Every feature entry includes a `description` field containing the text of the spec's `> Description:` metadata field (with multi-line continuations joined), or null if the field is absent","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-21","description":"Create a spec with `> Description: Handles user login.`; build report data; verify feature description equals \"Handles user login.\"; create a spec with no `> Description:` field; verify description is null","test_file":"dev/test_report_data.py","test_name":"test_description_from_metadata_field","tier":"unit","status":"pass","audit":"STRONG"},{"id":"PROOF-21","description":"Create a spec with `> Description: Handles user login.`; build report data; verify feature description equals \"Handles user login.\"; create a spec with no `> Description:` field; verify description is null","test_file":"dev/test_report_data.py","test_name":"test_description_multiline","tier":"unit","status":"pass","audit":"STRONG"},{"id":"PROOF-21","description":"Create a spec with `> Description: Handles user login.`; build report data; verify feature description equals \"Handles user login.\"; create a spec with no `> Description:` field; verify description is null","test_file":"dev/test_report_data.py","test_name":"test_description_null_when_absent","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"RULE-22","description":"Planned proofs do not affect coverage \u2014 proved/total counts, vhash, and feature status are computed from executed proofs only; a rule whose only proofs are planned has status NONE","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-23","description":"Create a feature with one rule whose only proof is planned (no executed result); build report data; verify proved==0, feature status is UNTESTED, vhash is null, and the rule status is NONE","test_file":"dev/test_report_data.py","test_name":"test_planned_proofs_do_not_affect_coverage","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"RULE-3","description":"Report file contains `const PURLIN_DATA = {...};` that parses as valid JavaScript","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Read report-data.js, strip JS wrapper, verify JSON parses successfully","test_file":"dev/test_report_data.py","test_name":"test_report_file_is_valid_js_with_json","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"RULE-4","description":"PURLIN_DATA.summary counts match the feature list (verified + passing + partial + failing + untested == total_features)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Parse report data, verify summary counts sum correctly","test_file":"dev/test_report_data.py","test_name":"test_summary_counts_sum_to_total","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"RULE-5","description":"Every feature entry has fields: name, type, is_global, proved, total, deferred, status, vhash, receipt, rules, audit","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Parse report data, verify every feature has all required fields","test_file":"dev/test_report_data.py","test_name":"test_every_feature_has_required_fields","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"RULE-6","description":"Features with all proofs passing (status \"PASSING\" or \"VERIFIED\") have a non-null vhash; others have null vhash","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Parse report data, verify PASSING/VERIFIED features have vhash and others don't","test_file":"dev/test_report_data.py","test_name":"test_passing_features_have_vhash_others_do_not","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"RULE-7","description":"Features with receipt files include commit, timestamp, and stale fields in receipt","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Create a receipt file, regenerate report, verify receipt fields present","test_file":"dev/test_report_data.py","test_name":"test_receipt_fields_present_when_receipt_file_exists","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"RULE-8","description":"Each rule entry has fields: id, description, label, source, is_deferred, is_assumed, status, proofs (array of proof objects where each proof has id, description, test_file, test_name, tier, status, audit). The proofs array contains executed proofs plus planned proofs \u2014 spec `PROOF-N` entries from the rule's source `## Proof` section with no executed result \u2014 distinguished by status \"planned\" with empty test_file, test_name, and audit, and tier parsed from the proof description's @tag (default unit); a proof id with an executed result for a rule is never also emitted as planned for that rule","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-8","description":"Parse report data, verify every rule entry has all required fields","test_file":"dev/test_report_data.py","test_name":"test_every_rule_entry_has_required_fields","tier":"unit","status":"pass","audit":"STRONG"},{"id":"PROOF-22","description":"Create a spec whose `## Proof` section declares PROOF-1 (RULE-1) and PROOF-2 (RULE-1) `@integration`; write an executed proof result for PROOF-1 only; build report data; verify RULE-1's proofs array contains PROOF-1 with status pass and PROOF-2 with status \"planned\", empty test_file/test_name/audit, and tier \"integration\"; verify PROOF-1 does not also appear as planned","test_file":"dev/test_report_data.py","test_name":"test_planned_proofs_appear_in_proofs_array","tier":"unit","status":"pass","audit":"STRONG"},{"id":"PROOF-22","description":"Create a spec whose `## Proof` section declares PROOF-1 (RULE-1) and PROOF-2 (RULE-1) `@integration`; write an executed proof result for PROOF-1 only; build report data; verify RULE-1's proofs array contains PROOF-1 with status pass and PROOF-2 with status \"planned\", empty test_file/test_name/audit, and tier \"integration\"; verify PROOF-1 does not also appear as planned","test_file":"dev/test_report_data.py","test_name":"test_planned_proofs_for_required_anchor_rules","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"RULE-9","description":"Rule status is one of PASS, FAIL, NONE, or DEFERRED","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-9","description":"Parse report data, verify all rule statuses are valid enum values","test_file":"dev/test_report_data.py","test_name":"test_all_rule_statuses_are_valid","tier":"unit","status":"pass","audit":"STRONG"}]}],"audit":{"integrity":96,"strong":22,"weak":1,"hollow":0,"manual":0,"findings":[{"proof_id":"PROOF-11","rule_id":"RULE-11","level":"WEAK","priority":"HIGH","criterion":"conditional assertion \u2014 url can be None without test failure","fix":"Inject a known git remote URL (via mock or test fixture with git remote) so the test always exercises the URL derivation path, then assert the URL starts with https:// and contains the expected path segment"}]}},{"name":"schema_proof_format","category":"_anchors","type":"anchor","is_global":false,"source_url":null,"pinned":null,"description":"Defines the JSON schema for proof files emitted by test runners and how plugins merge results when re-running tests (feature-scoped overwrite). The `sync_status` tool reads these files to compute rule coverage.","source_path":null,"stack":null,"ext_status":null,"proved":7,"total":7,"deferred":0,"status":"VERIFIED","vhash":"e492d09a","receipt":{"commit":"18b76cee2076ec24a0af7173d8cb33cda81e1e2b","timestamp":"2026-04-03T23:42:43.968125+00:00","stale":false},"rules":[{"id":"RULE-1","description":"Proof files are named `.proofs-.json` and live in the same directory as their spec","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Create a proof file at `specs/test/foo.proofs-unit.json`; run sync_status; verify it reads the file and associates it with the `foo` spec","test_file":"dev/test_schema_proof_format.py","test_name":"test_proof_file_read_by_sync_status","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-2","description":"The JSON schema has a top-level `tier` string and a `proofs` array. Each proof entry contains: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Validate a proof JSON file against the required fields; verify all 7 fields (`feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`) are present in each proof entry","test_file":"dev/test_schema_proof_format.py","test_name":"test_proof_entry_has_all_seven_fields","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-3","description":"`status` is either `\"pass\"` or `\"fail\"` \u2014 no other values","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Create a proof entry with `status: \"error\"`; run sync_status; verify it is not counted as a passing proof","test_file":"dev/test_schema_proof_format.py","test_name":"test_invalid_status_not_counted","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-4","description":"`tier` values are: `\"unit\"`, `\"integration\"`, `\"e2e\"` \u2014 no other automated tiers","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Scan all real `*.proofs-*.json` files; verify every tier value (top-level and per-entry) is in the valid set {unit, integration, e2e}. Verify `spec_format.md` documents @integration and","test_file":"dev/test_schema_proof_format.py","test_name":"test_standard_tiers_documented","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-5","description":"Proof plugins use feature-scoped overwrite: load existing file, purge entries matching current feature, append new entries, write merged result","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Pre-seed a proof file with feature B entries; run the real pytest proof plugin for feature A via subprocess; verify feature B entries are preserved and feature A entries are added","test_file":"dev/test_schema_proof_format.py","test_name":"test_feature_scoped_overwrite","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-6","description":"Proof files are committed to git \u2014 they are project records, not ephemeral build artifacts","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Grep `.gitignore` for `*.proofs-*.json`; verify no gitignore rule excludes proof files","test_file":"dev/test_schema_proof_format.py","test_name":"test_proof_files_not_gitignored","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-7","description":"Manual proofs are stamped inline in the spec's `## Proof` section as `@manual(, , )`, not in proof JSON files","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Grep `references/formats/proofs_format.md` for `@manual`; verify manual stamp format is `@manual(, , )`","test_file":"dev/test_schema_proof_format.py","test_name":"test_manual_stamp_format_documented","tier":"unit","status":"pass","audit":""}]}],"audit":null},{"name":"schema_spec_format","category":"_anchors","type":"anchor","is_global":false,"source_url":null,"pinned":null,"description":"Defines the canonical 2-section format for all Purlin specs: Rules and Proof. The `sync_status` tool parses specs against this format and reports violations as warnings or missing coverage.","source_path":null,"stack":null,"ext_status":null,"proved":8,"total":8,"deferred":0,"status":"VERIFIED","vhash":"45becb45","receipt":{"commit":"d460be568d744c38a89b9f8624a939a1d324c967","timestamp":"2026-04-07T13:47:29.481586+00:00","stale":false},"rules":[{"id":"RULE-1","description":"Every spec MUST contain exactly two required sections: `## Rules`, `## Proof`","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Parse `references/formats/spec_format.md`; find the `## Required Sections` area; verify both sections (`## Rules`, `## Proof`) appear within it","test_file":"dev/test_schema_spec_format.py","test_name":"test_format_documents_two_sections","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-2","description":"Rules are numbered sequentially as `RULE-N` (RULE-1, RULE-2, ...). Unnumbered lines under `## Rules` trigger a sync_status WARNING","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Create a spec with an unnumbered line under `## Rules` (e.g., `- some constraint without RULE-N prefix`); run sync_status; verify it reports a WARNING about unnumbered rules","test_file":"dev/test_schema_spec_format.py","test_name":"test_unnumbered_rule_triggers_warning","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-3","description":"Proofs are numbered as `PROOF-N (RULE-N)` \u2014 each proof references the rule it covers","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Grep `specs/**/*.md` for lines under `## Proof`; verify every proof line matches the pattern `PROOF-\\d+ \\(RULE-\\d+\\)`","test_file":"dev/test_schema_spec_format.py","test_name":"test_all_proof_lines_match_pattern","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-4","description":"Every RULE must have at least one corresponding PROOF","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Create a spec with RULE-1 but no PROOF referencing RULE-1; run sync_status; verify it reports the rule as uncovered","test_file":"dev/test_schema_spec_format.py","test_name":"test_rule_without_proof_shows_uncovered","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-5","description":"`> Requires:` metadata, when present, is a comma-separated list of anchor names whose rules also apply to this feature","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Create a spec with `> Requires: base_anchor`; run sync_status; verify the report includes `base_anchor/RULE-1` in the coverage output","test_file":"dev/test_schema_spec_format.py","test_name":"test_requires_includes_referenced_rules","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-6","description":"`> Scope:` metadata, when present, is a comma-separated list of source file paths the feature touches","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Create a spec with `> Scope: scripts/mcp/purlin_server.py`; verify sync_status parses and displays the scope files","test_file":"dev/test_schema_spec_format.py","test_name":"test_scope_metadata_parsed","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-7","description":"Feature specs use heading `# Feature: `, anchor specs use `# Anchor: `","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Grep `specs/**/*.md` for first-level headings; verify each starts with `# Feature:` or `# Anchor:`","test_file":"dev/test_schema_spec_format.py","test_name":"test_spec_headings_use_correct_prefix","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-8","description":"`> Description:` metadata, when present, is a plain-language summary of the spec; supports multi-line via `>` continuation lines that do not match a metadata field pattern (`> FieldName:`)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-8","description":"Create a spec with `> Description: First line\\n> second line\\n> Scope: src/`; parse description; verify it equals \"First line second line\" (continuation joined, Scope not included)","test_file":"dev/test_schema_spec_format.py","test_name":"test_multiline_description_continuation","tier":"unit","status":"pass","audit":""}]}],"audit":null},{"name":"security_no_dangerous_patterns","category":"_anchors","type":"anchor","is_global":false,"source_url":"/Users/richlabarca/LocalCode/purlin/dev/external-refs/security-policy.git","pinned":"e18cecd9932191ea24361f2c472bb2818307e4d6","description":"Enforces the absence of dangerous code patterns across all executable Purlin framework code. The codebase is currently clean \u2014 this anchor exists to prove and maintain that state. Any feature requiring this anchor inherits these constraints.","source_path":"security_policy.md","stack":null,"ext_status":"error","proved":5,"total":5,"deferred":0,"status":"VERIFIED","vhash":"175608df","receipt":{"commit":"18b76cee2076ec24a0af7173d8cb33cda81e1e2b","timestamp":"2026-04-03T23:42:43.968125+00:00","stale":false},"rules":[{"id":"RULE-1","description":"FORBIDDEN \u2014 No `eval()` or `exec()` calls in scripts/","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Grep `scripts/` for `eval(` and `exec(` in `.py` files; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_eval_or_exec","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-2","description":"FORBIDDEN \u2014 No `subprocess` calls with `shell=True` in scripts/","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Grep `scripts/**/*.py` for `shell\\s*=\\s*True`; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_shell_true","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-3","description":"FORBIDDEN \u2014 No `os.system()` calls in scripts/","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Grep `scripts/**/*.py` for `os\\.system\\s*\\(`; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_os_system","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-4","description":"FORBIDDEN \u2014 No hardcoded credential assignments (password/secret/api_key/token literals) in scripts/","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Grep `scripts/` for assignment patterns `(password|secret|api_key|token)\\s*=\\s*[\"'][^\"']+[\"']` in `.py`, `.sh`, `.js` files, excluding test files and comments; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_hardcoded_credentials","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-5","description":"All subprocess calls use list-mode arguments (not string commands)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Grep `scripts/**/*.py` for `subprocess.run(` and `subprocess.call(`; verify each call's first argument is a list literal `[`, not a string","test_file":"dev/test_security.py","test_name":"test_subprocess_uses_list_args","tier":"unit","status":"pass","audit":""}]}],"audit":null},{"name":"skill_anchor","category":"skills","type":"feature","is_global":false,"source_url":null,"pinned":null,"description":"The `purlin:anchor` skill creates, syncs, and manages anchor specs \u2014 cross-cutting constraints with optional external references. Anchors define shared rules that other features reference via `> Requires:`.","source_path":null,"stack":"markdown (skill definition)","ext_status":null,"proved":7,"total":7,"deferred":0,"status":"VERIFIED","vhash":"e492d09a","receipt":{"commit":"6cd4518e322d351ea3c91a7b877f1e59f90f2492","timestamp":"2026-04-07T13:46:48.452182+00:00","stale":false},"rules":[{"id":"RULE-1","description":"Skill file has YAML frontmatter with `name` and `description` fields","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Grep `skills/anchor/SKILL.md` for YAML frontmatter delimiters (`---`); verify `name:` and `description:` fields exist","test_file":"dev/test_skill_specs.py","test_name":"test_has_frontmatter","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-2","description":"Skill file contains a `## Usage` section documenting command syntax","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Grep `skills/anchor/SKILL.md` for `## Usage`; verify the section exists","test_file":"dev/test_skill_specs.py","test_name":"test_has_usage_section","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-3","description":"The `name` field in frontmatter is `anchor`, matching the directory name","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Extract `name:` from frontmatter; verify it equals `anchor`","test_file":"dev/test_skill_specs.py","test_name":"test_name_matches_directory","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-4","description":"Skill includes commit instructions or git operations for file modifications","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Grep `skills/anchor/SKILL.md` for commit instructions (`git commit`, `commit the`, `create.*commit`); verify present","test_file":"dev/test_skill_specs.py","test_name":"test_has_commit_instructions","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-5","description":"An anchor written with only Part 1 (authoring) fields \u2014 no `> Source:`, `> Path:`, or `> Pinned:` \u2014 is parsed as a local anchor: `source_url` is None, no staleness checks run, and sync_status output contains no Source/Pinned lines for that anchor","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"e2e: Create anchor with only Part 1 fields (Description, Type, rules, proofs \u2014 no Source/Path/Pinned); call _scan_specs; verify source_url is None and pinned is None. Run sync_status; verify output does NOT contain \"Source:\" or \"Pinned:\" for that anchor","test_file":"dev/test_e2e_external_refs.sh","test_name":"Part 1-only anchor parsed as local","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-6","description":"An anchor with Part 2 tracking fields (`> Source:`, `> Path:`, `> Pinned:`) added is parsed as externally-referenced: `source_url`, `pinned`, and `source_path` are all populated, and sync_status output shows Source/Path/Pinned lines for that anchor","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"e2e: Create anchor with Part 1 fields plus Part 2 tracking fields (Source, Path, Pinned pointing to a local bare git repo); call _scan_specs; verify source_url, pinned, and source_path are all populated with correct values. Run sync_status; verify output contains \"Source:\", \"Path:\", and \"Pinned:\" lines","test_file":"dev/test_e2e_external_refs.sh","test_name":"Part 2 tracking fields fully parsed","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-7","description":"Adding Part 2 tracking fields to a Part 1-only anchor transitions it from local to externally-referenced \u2014 the same anchor file, before and after adding `> Source:` and `> Pinned:`, produces different `_scan_specs` results for `source_url` and `pinned`","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"e2e: Create a Part 1-only anchor; call _scan_specs and capture source_url (None). Then add Part 2 fields to the same file; call _scan_specs again; verify source_url is now populated and pinned matches the written value","test_file":"dev/test_e2e_external_refs.sh","test_name":"Part 1\u21922 transition changes scan results","tier":"e2e","status":"pass","audit":""}]}],"audit":null},{"name":"skill_audit","category":"skills","type":"feature","is_global":false,"source_url":null,"pinned":null,"description":"The `purlin:audit` skill evaluates proof quality with STRONG/WEAK/HOLLOW assessments. It is read-only \u2014 it never modifies code or test files.","source_path":null,"stack":"markdown (skill definition)","ext_status":null,"proved":14,"total":14,"deferred":0,"status":"VERIFIED","vhash":"2eb3ab10","receipt":{"commit":"6cd4518e322d351ea3c91a7b877f1e59f90f2492","timestamp":"2026-04-07T13:46:48.452182+00:00","stale":false},"rules":[{"id":"RULE-1","description":"Skill file has YAML frontmatter with `name` and `description` fields","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Grep `skills/audit/SKILL.md` for YAML frontmatter delimiters (`---`); verify `name:` and `description:` fields exist","test_file":"dev/test_skill_specs.py","test_name":"test_has_frontmatter","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-10","description":"External LLM rates well-structured tests with real assertions as STRONG or WEAK (never HOLLOW)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-10","description":"e2e: Audit strong test code with external LLM; verify STRONG or WEAK (not HOLLOW)","test_file":"dev/test_skill_specs.py","test_name":"test_static_checks_passes_well_structured_test","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-11","description":"Response parsing extracts all required fields (PROOF-ID, ASSESSMENT, CRITERION, WHY, FIX) from external LLM output","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-11","description":"e2e: Parse external LLM response; verify ASSESSMENT, CRITERION, WHY, FIX fields extracted","test_file":"dev/test_skill_specs.py","test_name":"test_skill_documents_external_llm_response_fields","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-12","description":"Two-pass flow works end-to-end: static_checks catches HOLLOW in Pass 1, only surviving proofs go to external LLM in Pass 2","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-12","description":"e2e: Mixed-quality test file; static_checks catches assert True; valid test goes to external LLM","test_file":"dev/test_skill_specs.py","test_name":"test_two_pass_flow_hollow_caught_in_pass1_valid_passes_through","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-13","description":"Custom audit LLM command in config responds to ping, config stores audit_llm and audit_llm_name, and the two-pass audit completes","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-13","description":"e2e: Write config with fake LLM command; verify ping, config fields, and two-pass audit","test_file":"dev/test_skill_specs.py","test_name":"test_config_stores_audit_llm_fields_and_skill_documents_external_llm_mode","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-14","description":"Criteria are loaded via the single `load_criteria()` function (`--load-criteria` CLI); built-in criteria always apply, additional team criteria are appended \u2014 never replaced","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-14","description":"e2e: Create fake git repo with additional criteria; configure project; verify load_criteria returns built-in + additional with separator; verify Pass 1 still catches assert True; verify additional criteria reach fake LLM prompt","test_file":"dev/test_skill_specs.py","test_name":"test_load_criteria_assembles_builtin_plus_additional_and_pass1_still_works","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-2","description":"Skill file contains a `## Usage` section documenting command syntax","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Grep `skills/audit/SKILL.md` for `## Usage`; verify the section exists","test_file":"dev/test_skill_specs.py","test_name":"test_has_usage_section","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-3","description":"The `name` field in frontmatter is `audit`, matching the directory name","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Extract `name:` from frontmatter; verify it equals `audit`","test_file":"dev/test_skill_specs.py","test_name":"test_name_matches_directory","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-4","description":"Independent auditor mode documents instructions to read audit criteria and assess proofs as STRONG/WEAK/HOLLOW","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"e2e: Grep skills/audit/SKILL.md for independent auditor section; verify audit_criteria.md and STRONG/WEAK/HOLLOW","test_file":"dev/test_skill_specs.py","test_name":"test_independent_auditor_reads_criteria_and_assesses","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-5","description":"Independent auditor mode documents spawning a builder with HOLLOW/WEAK findings","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"e2e: Grep independent auditor section for purlin-builder and Spawn; verify builder protocol","test_file":"dev/test_skill_specs.py","test_name":"test_independent_auditor_spawns_builder","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-6","description":"Independent auditor mode documents re-auditing fixed proofs after builder responds","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"e2e: Grep independent auditor section for re-audit; verify re-check loop","test_file":"dev/test_skill_specs.py","test_name":"test_independent_auditor_re_audits_after_builder","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-7","description":"Independent auditor mode terminates after all findings addressed or after 3 rounds on any single proof","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"e2e: Grep independent auditor section for 3 rounds; verify termination condition","test_file":"dev/test_skill_specs.py","test_name":"test_independent_auditor_terminates_after_3_rounds","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-8","description":"Anchor rule handling documents reporting to the lead for ambiguous anchor rules","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-8","description":"e2e: Grep anchor rule handling for report to lead; verify ambiguous anchor protocol","test_file":"dev/test_skill_specs.py","test_name":"test_anchor_rule_handling_reports_to_lead","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-9","description":"External LLM detects deliberately hollow tests (assert True, assert None) as non-STRONG","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-9","description":"e2e: Audit hollow test code with external LLM; verify returns HOLLOW or WEAK","test_file":"dev/test_skill_specs.py","test_name":"test_static_checks_detects_hollow_test_as_non_strong","tier":"e2e","status":"pass","audit":""}]}],"audit":null},{"name":"skill_build","category":"skills","type":"feature","is_global":false,"source_url":null,"pinned":null,"description":"The `purlin:build` skill reads a spec, loads all its rules (including `> Requires:` dependencies), and implements the feature. It handles test execution, failure diagnosis, and proof generation.","source_path":null,"stack":"markdown (skill definition)","ext_status":null,"proved":12,"total":12,"deferred":0,"status":"VERIFIED","vhash":"40c0eaa6","receipt":{"commit":"1eaaf8bec1ee53d2f944fb79774699f7d68f8ab1","timestamp":"2026-06-02T15:47:23.471069+00:00","stale":false},"rules":[{"id":"RULE-1","description":"Skill file has YAML frontmatter with `name` and `description` fields","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Grep `skills/build/SKILL.md` for YAML frontmatter delimiters (`---`); verify `name:` and `description:` fields exist","test_file":"dev/test_skill_specs.py","test_name":"test_has_frontmatter","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-10","description":"The changeset summary is included as the commit message body in the build commit","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-10","description":"Grep `skills/build/SKILL.md` for commit message body instructions that reference the changeset summary and commit_conventions.md; verify present","test_file":"dev/test_skill_specs.py","test_name":"test_changeset_summary_in_commit_body","tier":"unit","status":"pass","audit":""},{"id":"PROOF-14","description":"In the temp project, commit with changeset summary as body; verify commit subject uses feat(): prefix and body contains all 3 changeset sections","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_e2e_build_changeset.sh","test_name":"git commit body contains changeset summary with feat prefix and all 3 sections","tier":"e2e","status":"pass","audit":""},{"id":"PROOF-18","description":"After the agent build session, verify git log shows a feat(): commit whose body contains RULE references and the changeset summary sections","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"RULE-11","description":"When running as proof fixer, the changeset summary maps fixed proofs instead of rules and omits the Decisions section","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-11","description":"Grep `skills/build/SKILL.md` for proof fixer changeset instructions in the \"When Running as Proof Fixer\" section; verify it documents mapping fixed proofs and skipping Decisions","test_file":"dev/test_skill_specs.py","test_name":"test_proof_fixer_changeset_maps_proofs","tier":"unit","status":"pass","audit":""},{"id":"PROOF-15","description":"Build a proof-fixer-style changeset; verify it maps PROOF-N instead of RULE-N and has no Decisions section","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_e2e_build_changeset.sh","test_name":"proof fixer changeset uses PROOF-N mappings, no Decisions section","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-12","description":"Build skill has exit criteria requiring tests pass, changeset summary printed, all changes committed, and no uncommitted proof files before the skill can complete","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-12","description":"Grep `skills/build/SKILL.md` for \"Exit Criteria\" section; verify it requires tests pass, changeset summary, committed changes, and no uncommitted proof files","test_file":"dev/test_skill_specs.py","test_name":"test_has_exit_criteria","tier":"unit","status":"pass","audit":""},{"id":"PROOF-16","description":"After the build session in the temp project, verify exit criteria: all tests pass, changeset summary valid, all proofs committed, git status clean","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_e2e_build_changeset.sh","test_name":"exit criteria verified: tests pass, changeset printed, all committed, no uncommitted proofs","tier":"e2e","status":"pass","audit":""},{"id":"PROOF-19","description":"After the agent build session, verify git status has no uncommitted proof files or source files, and proof files are tracked in git","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"RULE-2","description":"Skill file contains a `## Usage` section documenting command syntax","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Grep `skills/build/SKILL.md` for `## Usage`; verify the section exists","test_file":"dev/test_skill_specs.py","test_name":"test_has_usage_section","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-3","description":"The `name` field in frontmatter is `build`, matching the directory name","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Extract `name:` from frontmatter; verify it equals `build`","test_file":"dev/test_skill_specs.py","test_name":"test_name_matches_directory","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-4","description":"Skill includes commit instructions or git operations for file modifications","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Grep `skills/build/SKILL.md` for commit instructions (`git commit`, `commit the`, `create.*commit`); verify present","test_file":"dev/test_skill_specs.py","test_name":"test_has_commit_instructions","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-5","description":"Skill requires calling `sync_status` after tests and states it is not optional","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Grep `skills/build/SKILL.md` for `sync_status` and `not optional`; verify both present","test_file":"dev/test_skill_specs.py","test_name":"test_requires_sync_status_not_optional","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-6","description":"Skill includes test failure diagnosis guidance requiring root cause analysis before fixing","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Grep `skills/build/SKILL.md` for `diagnose` and `Never weaken`; verify both present","test_file":"dev/test_skill_specs.py","test_name":"test_has_failure_diagnosis_guidance","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-7","description":"Skill includes mandatory tier tag review for proof descriptions","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Grep `skills/build/SKILL.md` for tier review instructions and tier tag references (`@integration`/`@e2e`/unit tier); verify present","test_file":"dev/test_skill_specs.py","test_name":"test_has_tier_review","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-8","description":"Build skill documents proof fixer mode with instructions to fix proofs based on audit feedback and report back","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-8","description":"e2e: Grep skills/build/SKILL.md for proof fixer mode; verify fix proofs and report instructions","test_file":"dev/test_skill_specs.py","test_name":"test_documents_proof_fixer_mode","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-9","description":"Build skill produces a changeset summary after tests pass, with three sections: Changeset (rule\u2192file:line mapping), Decisions (judgment calls), and Review (focus areas)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-9","description":"Grep `skills/build/SKILL.md` for \"Changeset Summary\", \"Changeset\", \"Decisions\", \"Review\" section headers; verify all three summary sections are documented with format examples","test_file":"dev/test_skill_specs.py","test_name":"test_documents_changeset_summary","tier":"unit","status":"pass","audit":""},{"id":"PROOF-13","description":"Create a temp project with spec+code+tests, run pytest to emit proofs, build a changeset summary, validate it has all 3 sections with RULE\u2192file:line mappings","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_e2e_build_changeset.sh","test_name":"changeset summary has Changeset, Decisions, Review sections with RULE\u2192file:line mappings","tier":"e2e","status":"pass","audit":""},{"id":"PROOF-17","description":"Run purlin:build via claude -p with a spec containing ambiguous rules; verify agent output contains Changeset with RULE mappings, non-empty Decisions with real judgment calls, and non-empty Review with risk areas","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]}],"audit":null},{"name":"skill_drift","category":"skills","type":"feature","is_global":false,"source_url":null,"pinned":null,"description":"The `purlin:drift` skill detects spec drift by calling the `drift` MCP tool and summarizing changes since last verification, cross-referenced with specs. It produces PM/QA/eng-readable reports.","source_path":null,"stack":"markdown (skill definition)","ext_status":null,"proved":5,"total":5,"deferred":0,"status":"VERIFIED","vhash":"175608df","receipt":{"commit":"5f42a16ba84ca8b20a9185eabe1d7a8f73a84b72","timestamp":"2026-04-04T22:48:45.094616+00:00","stale":false},"rules":[{"id":"RULE-1","description":"Skill file has YAML frontmatter with `name` and `description` fields","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Grep `skills/drift/SKILL.md` for YAML frontmatter delimiters (`---`); verify `name:` and `description:` fields exist","test_file":"dev/test_skill_specs.py","test_name":"test_has_frontmatter","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-2","description":"Skill file contains a `## Usage` section documenting command syntax","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Grep `skills/drift/SKILL.md` for `## Usage`; verify the section exists","test_file":"dev/test_skill_specs.py","test_name":"test_has_usage_section","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-3","description":"The `name` field in frontmatter is `drift`, matching the directory name","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Extract `name:` from frontmatter; verify it equals `drift`","test_file":"dev/test_skill_specs.py","test_name":"test_name_matches_directory","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-4","description":"Skill references the `drift` MCP tool by name","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Grep `skills/drift/SKILL.md` for `drift`; verify the MCP tool is referenced","test_file":"dev/test_skill_specs.py","test_name":"test_references_drift_mcp_tool","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-5","description":"Skill requires reading git diffs for behavioral changes, not just interpreting MCP categories","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Grep `skills/drift/SKILL.md` for `git diff`; verify the diff-reading requirement is present","test_file":"dev/test_skill_specs.py","test_name":"test_requires_reading_diffs","tier":"unit","status":"pass","audit":""}]}],"audit":null},{"name":"skill_find","category":"skills","type":"feature","is_global":false,"source_url":null,"pinned":null,"description":"The `purlin:find` skill searches specs by name and displays rule coverage from `sync_status`.","source_path":null,"stack":"markdown (skill definition)","ext_status":null,"proved":4,"total":4,"deferred":0,"status":"VERIFIED","vhash":"b3ef613b","receipt":{"commit":"5f42a16ba84ca8b20a9185eabe1d7a8f73a84b72","timestamp":"2026-04-04T22:48:45.094616+00:00","stale":false},"rules":[{"id":"RULE-1","description":"Skill file has YAML frontmatter with `name` and `description` fields","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Grep `skills/find/SKILL.md` for YAML frontmatter delimiters (`---`); verify `name:` and `description:` fields exist","test_file":"dev/test_skill_specs.py","test_name":"test_has_frontmatter","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-2","description":"Skill file contains a `## Usage` section documenting command syntax","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Grep `skills/find/SKILL.md` for `## Usage`; verify the section exists","test_file":"dev/test_skill_specs.py","test_name":"test_has_usage_section","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-3","description":"The `name` field in frontmatter is `find`, matching the directory name","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Extract `name:` from frontmatter; verify it equals `find`","test_file":"dev/test_skill_specs.py","test_name":"test_name_matches_directory","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-4","description":"Skill references the `sync_status` MCP tool by name","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Grep `skills/find/SKILL.md` for `sync_status`; verify the MCP tool is referenced","test_file":"dev/test_skill_specs.py","test_name":"test_references_sync_status_mcp_tool","tier":"unit","status":"pass","audit":""}]}],"audit":null},{"name":"skill_init","category":"skills","type":"feature","is_global":false,"source_url":null,"pinned":null,"description":"The `purlin:init` skill initializes a project for spec-driven development. It creates `.purlin/`, `specs/`, detects the test framework, scaffolds the proof plugin, and manages plugin configuration.","source_path":null,"stack":"markdown (skill definition)","ext_status":null,"proved":48,"total":48,"deferred":0,"status":"VERIFIED","vhash":"4bfdc3c0","receipt":{"commit":"7a6ecc730ea1d7eb9ed95f00f5875dbb307e134c","timestamp":"2026-06-05T13:07:52.525362+00:00","stale":false},"rules":[{"id":"RULE-1","description":"Skill file has YAML frontmatter with `name` and `description` fields","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Grep `skills/init/SKILL.md` for YAML frontmatter delimiters (`---`); verify `name:` and `description:` fields exist","test_file":"dev/test_skill_specs.py","test_name":"test_has_frontmatter","tier":"unit","status":"pass","audit":"EXCLUDED"}]},{"id":"RULE-10","description":"The version field in config.json matches the contents of the VERSION file","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-10","description":"e2e: Read VERSION file and config.json version; verify identical","test_file":"dev/test_skill_specs.py","test_name":"test_config_version_matches_version_file","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"RULE-11","description":"Default config values are test_framework: auto, spec_dir: specs, pre_push: warn, report: true, digest: auto","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-11","description":"e2e: Create project with no overrides; verify default values","test_file":"dev/test_skill_specs.py","test_name":"test_config_template_default_values","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"RULE-12","description":"When conftest.py exists at project root, auto-detection selects pytest","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-12","description":"e2e: Create project with conftest.py; run pre-push; verify pytest detected","test_file":"dev/test_skill_specs.py","test_name":"test_documents_conftest_py_detects_pytest","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-13","description":"When pyproject.toml contains [tool.pytest], auto-detection selects pytest","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-13","description":"e2e: Create project with pyproject.toml [tool.pytest]; verify pytest detected","test_file":"dev/test_skill_specs.py","test_name":"test_documents_pyproject_toml_detects_pytest","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-14","description":"When package.json contains jest, auto-detection selects jest","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-14","description":"e2e: Create project with package.json jest; verify jest detected","test_file":"dev/test_skill_specs.py","test_name":"test_documents_package_json_jest_detects_jest","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-15","description":"When package.json contains vitest, init scaffolds vitest_purlin.ts (Vitest's reporter API differs from Jest's \u2014 Vitest never calls onTestResult/onRunComplete \u2014 so the native TypeScript reporter is used for both JS and TS Vitest projects; Vitest loads .ts reporters natively via Vite)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-15","description":"e2e: Create project with vitest; verify vitest_purlin.ts scaffolded","test_file":"dev/test_skill_specs.py","test_name":"test_documents_vitest_maps_to_vitest_plugin","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-16","description":"When both conftest.py and package.json with jest exist, both plugins are scaffolded and test_framework is pytest,jest","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-16","description":"e2e: Create project with conftest.py + package.json jest; verify both plugins and pytest,jest config","test_file":"dev/test_skill_specs.py","test_name":"test_documents_multi_framework_scaffolding","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-17","description":"When no framework indicators are present, shell plugin is scaffolded as fallback","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-17","description":"e2e: Create project with no framework indicators; verify purlin-proof.sh scaffolded","test_file":"dev/test_skill_specs.py","test_name":"test_documents_no_framework_asks_user_not_silent_shell","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-18","description":"Scaffolded pytest_purlin.py in .purlin/plugins/ is byte-identical to scripts/proof/pytest_purlin.py","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-18","description":"e2e: Diff scaffolded pytest_purlin.py against source; verify byte-identical","test_file":"dev/test_skill_specs.py","test_name":"test_pytest_plugin_source_exists_and_is_copy_source","tier":"unit","status":"pass","audit":"WEAK"}]},{"id":"RULE-19","description":"Scaffolded jest_purlin.js in .purlin/plugins/ is byte-identical to scripts/proof/jest_purlin.js","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-19","description":"e2e: Diff scaffolded jest_purlin.js against source; verify byte-identical","test_file":"dev/test_skill_specs.py","test_name":"test_jest_plugin_source_exists_and_is_copy_source","tier":"unit","status":"pass","audit":"WEAK"}]},{"id":"RULE-2","description":"Skill file contains a `## Usage` section documenting command syntax","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Grep `skills/init/SKILL.md` for `## Usage`; verify the section exists","test_file":"dev/test_skill_specs.py","test_name":"test_has_usage_section","tier":"unit","status":"pass","audit":"EXCLUDED"}]},{"id":"RULE-20","description":"Scaffolded purlin-proof.sh in .purlin/plugins/ is byte-identical to scripts/proof/shell_purlin.sh","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-20","description":"e2e: Diff scaffolded purlin-proof.sh against source; verify byte-identical","test_file":"dev/test_skill_specs.py","test_name":"test_shell_plugin_source_exists_and_is_copy_source","tier":"unit","status":"pass","audit":"WEAK"}]},{"id":"RULE-21","description":"Scaffolded pytest_purlin.py produces valid .proofs-unit.json when run with @pytest.mark.proof markers","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-21","description":"e2e: Scaffold pytest plugin; create spec; run pytest with markers; verify valid proofs emitted","test_file":"dev/test_skill_specs.py","test_name":"test_pytest_plugin_emits_valid_proofs_json","tier":"e2e","status":"pass","audit":"STRONG"}]},{"id":"RULE-22","description":"Scaffolded jest_purlin.js produces valid .proofs-unit.json when invoked as reporter with [proof:...] markers","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-22","description":"e2e: Scaffold jest reporter; create spec; exercise via node; verify valid proofs emitted","test_file":"dev/test_skill_specs.py","test_name":"test_jest_plugin_has_proof_emission_logic","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"RULE-23","description":"Scaffolded purlin-proof.sh produces valid .proofs-unit.json when purlin_proof + purlin_proof_finish are called","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-23","description":"e2e: Scaffold shell harness; create spec; call purlin_proof + purlin_proof_finish; verify valid proofs","test_file":"dev/test_skill_specs.py","test_name":"test_shell_plugin_emits_valid_proofs_json","tier":"e2e","status":"pass","audit":"STRONG"}]},{"id":"RULE-24","description":"After init with empty specs dir, sync_status returns No specs found without errors","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-24","description":"e2e: Create project with empty specs/; run sync_status; verify No specs found","test_file":"dev/test_skill_specs.py","test_name":"test_sync_status_returns_no_specs_found_for_empty_project","tier":"e2e","status":"pass","audit":"STRONG"}]},{"id":"RULE-25","description":"Status progression: no proofs reports UNTESTED, all passing reports PASSING, one failing reports FAILING","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-25","description":"e2e: Status progression: no proofs UNTESTED, passing proofs PASSING, failing proof FAILING","test_file":"dev/test_skill_specs.py","test_name":"test_status_progression_untested_passing_failing","tier":"e2e","status":"pass","audit":"STRONG"}]},{"id":"RULE-26","description":"When report: true in config, sync_status generates .purlin/report-data.js containing valid PURLIN_DATA","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-26","description":"e2e: Create project with report:true and passing proofs; run sync_status; verify report-data.js generated","test_file":"dev/test_skill_specs.py","test_name":"test_sync_status_generates_report_data_js_when_report_true","tier":"e2e","status":"pass","audit":"STRONG"}]},{"id":"RULE-27","description":".gitignore contains all required purlin entries and does NOT contain `.purlin/report-data.js` (the digest is committed)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-27","description":"e2e: Verify .gitignore contains all required purlin entries","test_file":"dev/test_skill_specs.py","test_name":"test_documents_required_gitignore_entries","tier":"unit","status":"pass","audit":"EXCLUDED"}]},{"id":"RULE-28","description":"Running init twice (re-init) does not create duplicate entries in .gitignore","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-28","description":"e2e: Run init twice; verify no duplicate gitignore entries","test_file":"dev/test_skill_specs.py","test_name":"test_documents_reinit_does_not_duplicate_gitignore","tier":"unit","status":"pass","audit":"EXCLUDED"}]},{"id":"RULE-29","description":"After init, .git/hooks/pre-push exists, is executable, and contains purlin","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-29","description":"e2e: Verify .git/hooks/pre-push exists, is executable, contains purlin","test_file":"dev/test_skill_specs.py","test_name":"test_documents_pre_push_hook_installation","tier":"unit","status":"pass","audit":"EXCLUDED"}]},{"id":"RULE-3","description":"The `name` field in frontmatter is `init`, matching the directory name","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Extract `name:` from frontmatter; verify it equals `init`","test_file":"dev/test_skill_specs.py","test_name":"test_name_matches_directory","tier":"unit","status":"pass","audit":"EXCLUDED"}]},{"id":"RULE-30","description":"When a non-purlin pre-push hook exists before init, the existing hook is preserved","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-30","description":"e2e: Create existing non-purlin hook; run init; verify hook preserved","test_file":"dev/test_skill_specs.py","test_name":"test_documents_existing_hook_preservation","tier":"unit","status":"pass","audit":"EXCLUDED"}]},{"id":"RULE-31","description":"When report: true, purlin-report.html exists at project root; when report: false, it does not","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-31","description":"e2e: Verify report:true creates purlin-report.html, report:false does not","test_file":"dev/test_skill_specs.py","test_name":"test_documents_report_html_toggle","tier":"unit","status":"pass","audit":"EXCLUDED"}]},{"id":"RULE-32","description":"Full lifecycle works end-to-end: init creates structure, spec created, proof plugin runs, proofs emitted, sync_status reports PASSING, pre-push hook allows push","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-32","description":"e2e: Python lifecycle: init, spec, pytest, proofs, PASSING, hook ok","test_file":"dev/test_skill_specs.py","test_name":"test_full_lifecycle_init_spec_proof_passing","tier":"e2e","status":"pass","audit":"STRONG"},{"id":"PROOF-33","description":"e2e: Shell lifecycle: init, spec, shell proof, proofs, PASSING","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""},{"id":"PROOF-34","description":"e2e: Jest lifecycle: init, spec, reporter, proofs, PASSING","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"RULE-33","description":"Init prints DETECTING CODEBASE before scanning for test frameworks","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-35","description":"e2e: Verify SKILL.md contains \"DETECTING CODEBASE\" print instruction before framework scan","test_file":"dev/test_skill_specs.py","test_name":"test_skill_prints_detecting_codebase_before_scan","tier":"e2e","status":"pass","audit":"EXCLUDED"}]},{"id":"RULE-34","description":"Init always presents the full framework selection list to the user, even when auto-detection succeeds, with detected frameworks pre-selected","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-36","description":"e2e: Verify SKILL.md documents always presenting the framework selection list with [x] and [ ] markers, including when auto-detection succeeds","test_file":"dev/test_skill_specs.py","test_name":"test_skill_always_presents_framework_selection_list","tier":"e2e","status":"pass","audit":"EXCLUDED"}]},{"id":"RULE-35","description":"When a single framework is detected, the selection list shows it pre-selected with [x] and all others unselected with [ ]","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-37","description":"e2e: Verify SKILL.md shows a single-detection example with one [x] pre-selected and remaining [ ] unselected","test_file":"dev/test_skill_specs.py","test_name":"test_skill_shows_single_detection_preselected","tier":"e2e","status":"pass","audit":"EXCLUDED"}]},{"id":"RULE-36","description":"When multiple frameworks are detected, all detected frameworks are pre-selected in the list","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-38","description":"e2e: Verify SKILL.md shows a multi-detection example with multiple [x] pre-selected","test_file":"dev/test_skill_specs.py","test_name":"test_skill_shows_multi_detection_preselected","tier":"e2e","status":"pass","audit":"EXCLUDED"}]},{"id":"RULE-37","description":"When no frameworks are detected, the selection list shows all options unselected","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-39","description":"e2e: Verify SKILL.md shows a no-detection example with all [ ] unselected","test_file":"dev/test_skill_specs.py","test_name":"test_skill_shows_no_detection_all_unselected","tier":"e2e","status":"pass","audit":"EXCLUDED"}]},{"id":"RULE-38","description":"The Purlin MCP server is bundled with the plugin \u2014 `.claude-plugin/plugin.json` declares a `purlin` entry under `mcpServers`; init does NOT write a `purlin` entry into the project's `.mcp.json` (a project-scope entry would shadow the plugin server with a version-pinned path that breaks on every plugin update)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-40","description":"Parse `.claude-plugin/plugin.json`; verify `mcpServers.purlin` exists. Grep `skills/init/SKILL.md`; verify it does NOT instruct creating a `purlin` entry in the project's `.mcp.json` (no init-time path resolution remains)","test_file":"dev/test_skill_specs.py","test_name":"test_mcp_server_bundled_in_plugin_manifest","tier":"unit","status":"pass","audit":"EXCLUDED"}]},{"id":"RULE-39","description":"The bundled MCP server entry uses the `python3` command with `args` pointing to `${CLAUDE_PLUGIN_ROOT}/scripts/mcp/purlin_server.py`, so the path always resolves to the currently installed plugin version","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-41","description":"Parse `.claude-plugin/plugin.json`; verify `mcpServers.purlin.command` is `python3` and `args` contains `${CLAUDE_PLUGIN_ROOT}/scripts/mcp/purlin_server.py`","test_file":"dev/test_skill_specs.py","test_name":"test_bundled_mcp_server_uses_plugin_root_variable","tier":"unit","status":"pass","audit":"EXCLUDED"}]},{"id":"RULE-4","description":"Skill includes commit instructions or git operations for file modifications","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Grep `skills/init/SKILL.md` for commit instructions (`git commit`, `commit the`, `create.*commit`); verify present","test_file":"dev/test_skill_specs.py","test_name":"test_has_commit_instructions","tier":"unit","status":"pass","audit":"EXCLUDED"}]},{"id":"RULE-40","description":"Init migrates legacy MCP config: when the project's `.mcp.json` has a `purlin` entry under `mcpServers` (pre-0.9.4 version-pinned path), init removes that entry, preserves all other server entries, deletes the file only when nothing else remains in it, and tells the user to reload plugins or restart the session","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-42","description":"Grep `skills/init/SKILL.md` for the legacy migration step; verify it documents removing the `purlin` entry from the project's `.mcp.json`, preserving other server entries, and reloading plugins","test_file":"dev/test_skill_specs.py","test_name":"test_documents_legacy_mcp_json_migration","tier":"unit","status":"pass","audit":"EXCLUDED"}]},{"id":"RULE-41","description":"Config has `\"digest\"` field with value `\"auto\"`, `\"warn\"`, or `\"off\"`","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-43","description":"e2e: Read config.json after init; verify `digest` field exists with value `auto`","test_file":"dev/test_skill_specs.py","test_name":"test_config_template_has_digest_field","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"RULE-42","description":"After init, `.git/hooks/pre-commit` exists, is executable, and contains `purlin`","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-44","description":"e2e: Verify `.git/hooks/pre-commit` exists, is executable, and contains `purlin`","test_file":"dev/test_skill_specs.py","test_name":"test_documents_pre_commit_hook_installation","tier":"unit","status":"pass","audit":"EXCLUDED"}]},{"id":"RULE-43","description":"When a non-purlin pre-commit hook exists before init, the existing hook is preserved","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-45","description":"e2e: Create existing non-purlin pre-commit hook; run init; verify hook preserved","test_file":"dev/test_skill_specs.py","test_name":"test_documents_existing_pre_commit_hook_preservation","tier":"unit","status":"pass","audit":"EXCLUDED"}]},{"id":"RULE-44","description":"In digest `auto` mode, running `git commit` triggers the pre-commit hook which regenerates `.purlin/report-data.js` and stages it into the commit","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-46","description":"e2e: Create repo with spec+proofs, run `git commit`; verify `.purlin/report-data.js` is tracked in the commit via `git show HEAD:.purlin/report-data.js`","test_file":"dev/test_skill_specs.py","test_name":"test_pre_commit_hook_generates_and_stages_digest","tier":"e2e","status":"pass","audit":"STRONG"}]},{"id":"RULE-45","description":"After digest generation, `report-data.js` contains a `timestamp` field with a recent ISO timestamp and a `git_sha` field","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-47","description":"e2e: After commit, parse `report-data.js`; verify `timestamp` is within last 60s and `git_sha` field is present","test_file":"dev/test_skill_specs.py","test_name":"test_digest_has_timestamp_and_git_sha","tier":"e2e","status":"pass","audit":"STRONG"}]},{"id":"RULE-46","description":"Digest generation does NOT trigger a new audit \u2014 `audit_summary` reflects only cached data (null when no cache exists)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-48","description":"e2e: After commit in fresh project (no audit cache), verify `audit_summary` in digest is null","test_file":"dev/test_skill_specs.py","test_name":"test_digest_does_not_trigger_audit","tier":"e2e","status":"pass","audit":"STRONG"}]},{"id":"RULE-47","description":"Scaffolded vitest_purlin.ts in .purlin/plugins/ is byte-identical to scripts/proof/vitest_purlin.ts","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-49","description":"e2e: Diff scaffolded vitest_purlin.ts against scripts/proof/vitest_purlin.ts; verify byte-identical","test_file":"dev/test_skill_specs.py","test_name":"test_vitest_reporter_scaffold_byte_identical","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-48","description":"The framework selection list is built dynamically from the framework registry (`references/supported_frameworks.md`) and presents an option for every proof plugin shipped in `scripts/proof/`; the skill does not hardcode framework names, so a newly shipped plugin (e.g. xUnit) appears in the selection once it is registered","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-51","description":"Cross-reference distribution and registry: every proof plugin file in `scripts/proof/` is referenced in `references/supported_frameworks.md`, and every `scripts/proof/` path the registry names exists on disk \u2014 so the registry-built selection list covers exactly the shipped frameworks","test_file":"dev/test_skill_specs.py","test_name":"test_registry_covers_every_shipped_plugin","tier":"integration","status":"pass","audit":"STRONG"},{"id":"PROOF-50","description":"Grep `skills/init/SKILL.md`; verify it instructs building the selection list dynamically from `references/supported_frameworks.md` and explicitly says not to hardcode framework names","test_file":"dev/test_skill_specs.py","test_name":"test_selection_list_built_from_registry_not_hardcoded","tier":"unit","status":"pass","audit":"EXCLUDED"}]},{"id":"RULE-5","description":"`--add-plugin` validates plugin files against language-specific patterns (Python: `proofs`+`json`, JS: `proofs`+`JSON`, Shell: `purlin_proof`, Java: `proofs`+`Proof`) and warns if validation fails","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Grep `skills/init/SKILL.md` for language validation entries (`Python`, `JavaScript`, `Shell`, `Java`) and warning text `doesn't look like a standard proof plugin`; verify all present","test_file":"dev/test_skill_specs.py","test_name":"test_add_plugin_validates_by_language","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-6","description":"`--add-plugin` supports both local file paths and git URL sources with distinct handling for each","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Grep `skills/init/SKILL.md` for `local file path` and `git URL`; verify both source types are documented with distinct handling steps","test_file":"dev/test_skill_specs.py","test_name":"test_add_plugin_supports_file_and_git","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-7","description":"`--list-plugins` identifies built-in plugins (`pytest_purlin`, `jest_purlin`, `purlin-proof`) by framework name and labels all others as `custom`","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Grep `skills/init/SKILL.md` for `pytest_purlin.py` with `Python/pytest`, `jest_purlin.js` with `JavaScript/Jest`, and the label `custom`; verify the labeling table exists","test_file":"dev/test_skill_specs.py","test_name":"test_list_plugins_labels_builtin_and_custom","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-8","description":"After init, `.purlin/`, `.purlin/plugins/`, `specs/`, and `specs/_anchors/` directories all exist","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-8","description":"e2e: Create init-equivalent structure; verify .purlin/, .purlin/plugins/, specs/, specs/_anchors/ all exist","test_file":"dev/test_skill_specs.py","test_name":"test_documents_required_directory_structure","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-9","description":"`config.json` is valid JSON containing all 6 required fields: version, test_framework, spec_dir, pre_push, report, digest","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-9","description":"e2e: Read config.json; parse as JSON; verify all 5 required fields present","test_file":"dev/test_skill_specs.py","test_name":"test_config_template_has_six_required_fields","tier":"unit","status":"pass","audit":"STRONG"}]}],"audit":{"integrity":83,"strong":15,"weak":3,"hollow":0,"manual":0,"findings":[{"proof_id":"PROOF-18","rule_id":"RULE-18","level":"WEAK","priority":"HIGH","criterion":"missing negative test / incomplete behavioral coverage","fix":"Scaffold the file into a tmp_path, then open both source and dest in binary mode and assert the bytes match."},{"proof_id":"PROOF-19","rule_id":"RULE-19","level":"WEAK","priority":"HIGH","criterion":"missing negative test / incomplete behavioral coverage","fix":"Scaffold jest_purlin.js into a temp .purlin/plugins/ directory and assert byte-identity with the source file."},{"proof_id":"PROOF-20","rule_id":"RULE-20","level":"WEAK","priority":"HIGH","criterion":"missing negative test / incomplete behavioral coverage","fix":"Scaffold shell_purlin.sh into a temp directory and assert byte-identity with scripts/proof/shell_purlin.sh."}]}},{"name":"skill_rename","category":"skills","type":"feature","is_global":false,"source_url":null,"pinned":null,"description":"The `purlin:rename` skill renames a feature across all Purlin artifacts \u2014 specs, proofs, markers, and references \u2014 in one atomic operation.","source_path":null,"stack":"markdown (skill definition)","ext_status":null,"proved":3,"total":3,"deferred":0,"status":"VERIFIED","vhash":"7748e8cb","receipt":{"commit":"5f42a16ba84ca8b20a9185eabe1d7a8f73a84b72","timestamp":"2026-04-04T22:48:45.094616+00:00","stale":false},"rules":[{"id":"RULE-1","description":"Skill file has YAML frontmatter with `name` and `description` fields","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Grep `skills/rename/SKILL.md` for YAML frontmatter delimiters (`---`); verify `name:` and `description:` fields exist","test_file":"dev/test_skill_specs.py","test_name":"test_has_frontmatter","tier":"unit","status":"pass","audit":"EXCLUDED"}]},{"id":"RULE-2","description":"Skill file contains a `## Usage` section documenting command syntax","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Grep `skills/rename/SKILL.md` for `## Usage`; verify the section exists","test_file":"dev/test_skill_specs.py","test_name":"test_has_usage_section","tier":"unit","status":"pass","audit":"EXCLUDED"}]},{"id":"RULE-3","description":"The `name` field in frontmatter is `rename`, matching the directory name","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Extract `name:` from frontmatter; verify it equals `rename`","test_file":"dev/test_skill_specs.py","test_name":"test_name_matches_directory","tier":"unit","status":"pass","audit":"EXCLUDED"}]}],"audit":null},{"name":"skill_spec","category":"skills","type":"feature","is_global":false,"source_url":null,"pinned":null,"description":"The `purlin:spec` skill scaffolds or edits feature specs in 3-section format. It accepts any input \u2014 plain English, PRDs, customer feedback, code files, images \u2014 and extracts structured rules.","source_path":null,"stack":"markdown (skill definition)","ext_status":null,"proved":8,"total":8,"deferred":0,"status":"VERIFIED","vhash":"69092c78","receipt":{"commit":"8f4c47d21dd51e50f2994e2df67b48af5c2371f2","timestamp":"2026-06-05T13:43:25.311450+00:00","stale":false},"rules":[{"id":"RULE-1","description":"Skill file has YAML frontmatter with `name` and `description` fields","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Grep `skills/spec/SKILL.md` for YAML frontmatter delimiters (`---`); verify `name:` and `description:` fields exist","test_file":"dev/test_skill_specs.py","test_name":"test_has_frontmatter","tier":"unit","status":"pass","audit":"EXCLUDED"}]},{"id":"RULE-2","description":"Skill file contains a `## Usage` section documenting command syntax","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Grep `skills/spec/SKILL.md` for `## Usage`; verify the section exists","test_file":"dev/test_skill_specs.py","test_name":"test_has_usage_section","tier":"unit","status":"pass","audit":"EXCLUDED"}]},{"id":"RULE-3","description":"The `name` field in frontmatter is `spec`, matching the directory name","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Extract `name:` from frontmatter; verify it equals `spec`","test_file":"dev/test_skill_specs.py","test_name":"test_name_matches_directory","tier":"unit","status":"pass","audit":"EXCLUDED"}]},{"id":"RULE-4","description":"Skill includes commit instructions or git operations for file modifications","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Grep `skills/spec/SKILL.md` for commit instructions (`git commit`, `commit the`, `create.*commit`); verify present","test_file":"dev/test_skill_specs.py","test_name":"test_has_commit_instructions","tier":"unit","status":"pass","audit":"EXCLUDED"}]},{"id":"RULE-5","description":"Update workflow (Step 7) presents a delta report showing KEEPING/ADDING/UPDATING/REMOVING before applying changes","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Grep `skills/spec/SKILL.md` for `KEEPING`, `ADDING`, `UPDATING`, and `REMOVING`; verify the delta report structure is present","test_file":"dev/test_skill_specs.py","test_name":"test_has_delta_report_structure","tier":"unit","status":"pass","audit":"EXCLUDED"}]},{"id":"RULE-6","description":"Skill includes mandatory tier tag review for proof descriptions","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Grep `skills/spec/SKILL.md` for tier review instructions and tier tag references (`@integration`/`@e2e`/unit tier); verify present","test_file":"dev/test_skill_specs.py","test_name":"test_has_tier_review","tier":"unit","status":"pass","audit":"EXCLUDED"}]},{"id":"RULE-7","description":"Spec skill has exit criteria requiring the spec file is committed and no uncommitted spec files remain before the skill can complete","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Grep `skills/spec/SKILL.md` for \"Exit Criteria\" section; verify it requires spec committed and no uncommitted spec files","test_file":"dev/test_skill_specs.py","test_name":"test_has_exit_criteria","tier":"unit","status":"pass","audit":"EXCLUDED"},{"id":"PROOF-8","description":"Run purlin:spec via claude -p in a temp project; verify spec .md is committed, git status clean for specs/, and git log shows spec(): commit","test_file":"dev/test_e2e_build_agent.py","test_name":"test_spec_committed","tier":"e2e","status":"pass","audit":"STRONG"}]},{"id":"RULE-8","description":"Validate-Before-Commit includes an e2e proof-description check: `@e2e` proofs must describe an observable flow and must not name source files or internal functions, per `references/spec_quality_guide.md` (\"E2E proof descriptions\")","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-9","description":"Grep `skills/spec/SKILL.md` Validate-Before-Commit for the `@e2e` observable-flow check and its pointer to `spec_quality_guide.md` \"E2E proof descriptions\"; verify present","test_file":"dev/test_skill_specs.py","test_name":"test_validate_e2e_observable_flow","tier":"unit","status":"pass","audit":"EXCLUDED"}]}],"audit":{"integrity":100,"strong":1,"weak":0,"hollow":0,"manual":0,"findings":[]}},{"name":"skill_spec_from_code","category":"skills","type":"feature","is_global":false,"source_url":null,"pinned":null,"description":"The `purlin:spec-from-code` skill scans codebases and migrates existing specs in any format to the current compliant 3-section format.","source_path":null,"stack":"markdown (skill definition)","ext_status":null,"proved":32,"total":32,"deferred":0,"status":"VERIFIED","vhash":"9e996e68","receipt":{"commit":"8f4c47d21dd51e50f2994e2df67b48af5c2371f2","timestamp":"2026-06-05T13:43:25.311450+00:00","stale":false},"rules":[{"id":"RULE-1","description":"Skill file has YAML frontmatter with `name` and `description` fields","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Grep `skills/spec-from-code/SKILL.md` for YAML frontmatter delimiters (`---`); verify `name:` and `description:` fields exist","test_file":"dev/test_skill_specs.py","test_name":"test_has_frontmatter","tier":"unit","status":"pass","audit":"EXCLUDED"}]},{"id":"RULE-10","description":"A spec generated from a plain description contains sequentially numbered RULE-N lines starting at RULE-1 with no gaps","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-19","description":"e2e: Create spec with 4 sequentially numbered rules; verify numbers are 1,2,3,4 with no gaps","test_file":"dev/test_e2e_spec_from_input.py","test_name":"test_sequential_rule_numbering","tier":"e2e","status":"pass","audit":"EXCLUDED"},{"id":"PROOF-19","description":"e2e: Create spec with 4 sequentially numbered rules; verify numbers are 1,2,3,4 with no gaps","test_file":"dev/test_e2e_spec_migration.py","test_name":"test_sequential_rule_numbering","tier":"e2e","status":"pass","audit":"EXCLUDED"}]},{"id":"RULE-11","description":"A spec generated from a plain description contains PROOF-N (RULE-N) lines where every rule has at least one proof","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-20","description":"e2e: Verify every RULE-N has at least one PROOF referencing it","test_file":"dev/test_e2e_spec_from_input.py","test_name":"test_every_rule_has_proof","tier":"e2e","status":"pass","audit":"EXCLUDED"},{"id":"PROOF-20","description":"e2e: Verify every RULE-N has at least one PROOF referencing it","test_file":"dev/test_e2e_spec_migration.py","test_name":"test_every_rule_has_proof","tier":"e2e","status":"pass","audit":"EXCLUDED"}]},{"id":"RULE-12","description":"A spec generated from a plain description contains no (assumed) tags when all values were explicitly stated","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-21","description":"e2e: Verify plain-description spec with explicit values has no (assumed) tags","test_file":"dev/test_e2e_spec_from_input.py","test_name":"test_no_assumed_tags_on_explicit_values","tier":"e2e","status":"pass","audit":"EXCLUDED"},{"id":"PROOF-21","description":"e2e: Verify plain-description spec with explicit values has no (assumed) tags","test_file":"dev/test_e2e_spec_migration.py","test_name":"test_no_assumed_tags_with_explicit_values","tier":"e2e","status":"pass","audit":"EXCLUDED"}]},{"id":"RULE-13","description":"A spec generated from a PRD extracts every testable constraint as a RULE-N line \u2014 at least 5 rules for a multi-requirement PRD","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-22","description":"e2e: Create PRD spec with 6 constraints; verify at least 5 RULE-N lines","test_file":"dev/test_e2e_spec_from_input.py","test_name":"test_prd_extracts_at_least_5_rules","tier":"e2e","status":"pass","audit":"EXCLUDED"},{"id":"PROOF-22","description":"e2e: Create PRD spec with 6 constraints; verify at least 5 RULE-N lines","test_file":"dev/test_e2e_spec_migration.py","test_name":"test_prd_extracts_at_least_5_rules","tier":"e2e","status":"pass","audit":"EXCLUDED"}]},{"id":"RULE-14","description":"A spec generated from a PRD contains valid metadata: `> Description:`, `> Scope:`, and `> Stack:` fields are present","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-23","description":"e2e: Verify PRD spec has Description, Scope, and Stack metadata","test_file":"dev/test_e2e_spec_from_input.py","test_name":"test_prd_metadata_present","tier":"e2e","status":"pass","audit":"EXCLUDED"},{"id":"PROOF-23","description":"e2e: Verify PRD spec has Description, Scope, and Stack metadata","test_file":"dev/test_e2e_spec_migration.py","test_name":"test_prd_has_valid_metadata","tier":"e2e","status":"pass","audit":"EXCLUDED"}]},{"id":"RULE-15","description":"A spec generated from a PRD includes `> Requires:` referencing anchors whose scope overlaps with the feature's scope","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-24","description":"e2e: Create project with overlapping anchor; verify Requires references it and sync_status shows required rules","test_file":"dev/test_e2e_spec_from_input.py","test_name":"test_requires_anchor_in_coverage","tier":"e2e","status":"pass","audit":"STRONG"},{"id":"PROOF-24","description":"e2e: Create project with overlapping anchor; verify Requires references it and sync_status shows required rules","test_file":"dev/test_e2e_spec_migration.py","test_name":"test_prd_requires_overlapping_anchor","tier":"e2e","status":"pass","audit":"STRONG"}]},{"id":"RULE-16","description":"A spec generated from a vague description adds `(assumed \u2014 )` tags to rules where the agent inferred specific values","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-25","description":"e2e: Create vague-input spec; verify (assumed) tags present","test_file":"dev/test_e2e_spec_from_input.py","test_name":"test_assumed_tags_present","tier":"e2e","status":"pass","audit":"EXCLUDED"},{"id":"PROOF-25","description":"e2e: Create vague-input spec; verify (assumed) tags present","test_file":"dev/test_e2e_spec_migration.py","test_name":"test_vague_input_has_assumed_tags","tier":"e2e","status":"pass","audit":"EXCLUDED"}]},{"id":"RULE-17","description":"Rules with (assumed) tags still follow RULE-N format and are parseable by sync_status","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-26","description":"e2e: Run sync_status on vague-input spec; verify parses without errors with correct rule count","test_file":"dev/test_e2e_spec_from_input.py","test_name":"test_assumed_rules_parseable_by_sync_status","tier":"e2e","status":"pass","audit":"STRONG"},{"id":"PROOF-26","description":"e2e: Run sync_status on vague-input spec; verify parses without errors with correct rule count","test_file":"dev/test_e2e_spec_migration.py","test_name":"test_assumed_tags_parseable_by_sync_status","tier":"e2e","status":"pass","audit":"STRONG"}]},{"id":"RULE-18","description":"A spec generated from customer feedback translates complaints into testable RULE-N constraints with specific thresholds or behaviors","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-27","description":"e2e: Create customer feedback spec; verify rules have specific thresholds","test_file":"dev/test_e2e_spec_from_input.py","test_name":"test_feedback_translated_to_specific_rules","tier":"e2e","status":"pass","audit":"EXCLUDED"},{"id":"PROOF-27","description":"e2e: Create customer feedback spec; verify rules have specific thresholds","test_file":"dev/test_e2e_spec_migration.py","test_name":"test_customer_feedback_has_specific_thresholds","tier":"e2e","status":"pass","audit":"EXCLUDED"}]},{"id":"RULE-19","description":"Every proof line across all four scenarios ends with an appropriate tier tag (@integration, @e2e, @manual, or no tag for unit)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-28","description":"e2e: Parse proof lines across all four scenarios; verify tier tags present","test_file":"dev/test_e2e_spec_from_input.py","test_name":"test_all_proofs_have_tier_tags","tier":"e2e","status":"pass","audit":"EXCLUDED"},{"id":"PROOF-28","description":"e2e: Parse proof lines across all four scenarios; verify tier tags present","test_file":"dev/test_e2e_spec_migration.py","test_name":"test_all_scenarios_have_tier_tags","tier":"e2e","status":"pass","audit":"EXCLUDED"}]},{"id":"RULE-2","description":"Skill file contains a `## Usage` section documenting command syntax","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Grep `skills/spec-from-code/SKILL.md` for `## Usage`; verify the section exists","test_file":"dev/test_skill_specs.py","test_name":"test_has_usage_section","tier":"unit","status":"pass","audit":"EXCLUDED"}]},{"id":"RULE-20","description":"sync_status successfully parses specs from all four scenarios without errors, reporting correct rule counts and UNTESTED status","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-29","description":"e2e: Run sync_status on all four scenarios; verify UNTESTED status and correct rule counts","test_file":"dev/test_e2e_spec_from_input.py","test_name":"test_sync_status_parses_all_scenarios","tier":"e2e","status":"pass","audit":"STRONG"},{"id":"PROOF-29","description":"e2e: Run sync_status on all four scenarios; verify UNTESTED status and correct rule counts","test_file":"dev/test_e2e_spec_migration.py","test_name":"test_sync_status_parses_all_scenarios","tier":"e2e","status":"pass","audit":"STRONG"}]},{"id":"RULE-21","description":"The `## Rules` and `## Proof` sections are both present in specs from all four scenarios","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-30","description":"e2e: Verify ## Rules and ## Proof sections exist in all four scenario specs","test_file":"dev/test_e2e_spec_from_input.py","test_name":"test_all_specs_have_rules_and_proof_sections","tier":"e2e","status":"pass","audit":"EXCLUDED"},{"id":"PROOF-30","description":"e2e: Verify ## Rules and ## Proof sections exist in all four scenario specs","test_file":"dev/test_e2e_spec_migration.py","test_name":"test_all_scenarios_have_rules_and_proof_sections","tier":"e2e","status":"pass","audit":"EXCLUDED"}]},{"id":"RULE-22","description":"When a vague-input spec's (assumed) rule is updated with an explicit value, the (assumed) tag is removed and the rule remains valid","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-31","description":"e2e: Update (assumed) rule with explicit value; verify tag removed and RULE-N format valid","test_file":"dev/test_e2e_spec_from_input.py","test_name":"test_corrected_assumed_rule_is_valid","tier":"e2e","status":"pass","audit":"STRONG"},{"id":"PROOF-31","description":"e2e: Update (assumed) rule with explicit value; verify tag removed and RULE-N format valid","test_file":"dev/test_e2e_spec_migration.py","test_name":"test_assumed_tag_removal_on_explicit_update","tier":"e2e","status":"pass","audit":"STRONG"}]},{"id":"RULE-23","description":"Skill contains data contract extraction mandatory for ALL features (not just UI) with five categories: inbound contracts, outbound contracts, transformation rules, state transitions, and access contracts","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-33","description":"e2e: Create a simulated component with API field consumption, filter/sort transformations, conditional gates by user segment, and missing-data fallbacks; verify the extraction identifies inbound fields, transformations, access gates, and failure modes","test_file":"dev/test_e2e_ui_extraction.py","test_name":"test_contract_extraction_identifies_all_categories","tier":"e2e","status":"pass","audit":"EXCLUDED"},{"id":"PROOF-32","description":"Grep SKILL.md for \"Inbound contracts\", \"Outbound contracts\", \"Transformation rules\", \"State transitions\", \"Access contracts\" as subsections of step 4; verify all five exist and step is mandatory for ALL features","test_file":"","test_name":"","tier":"unit","status":"planned","audit":""}]},{"id":"RULE-24","description":"Skill contains a mandatory draft-and-evaluate step that applies rebuild/behavior/overlap tests to every candidate rule, and a rebuild-risk filter that verifies contract coverage across all five categories before presenting specs","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-34","description":"Grep SKILL.md for \"Draft and evaluate\" AND \"rebuild test\" in Phase 3; verify the step applies rebuild, behavior, and overlap tests. Grep for \"Verify contract coverage\" in the rebuild-risk filter; verify it checks all five contract categories","test_file":"dev/test_skill_specs.py","test_name":"test_draft_and_evaluate_with_rebuild_test","tier":"unit","status":"pass","audit":"EXCLUDED"}]},{"id":"RULE-25","description":"Migration from `features/` reads `.impl.md` companion files and extracts active deviations as rules reflecting actual behavior","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-36","description":"e2e: Create a features/ directory with a spec and .impl.md containing a PM-ACCEPTED deviation; verify the deviation's actual behavior becomes a RULE-N in the migrated spec","test_file":"dev/test_e2e_ui_extraction.py","test_name":"test_impl_deviation_extraction","tier":"e2e","status":"pass","audit":"EXCLUDED"},{"id":"PROOF-35","description":"Grep SKILL.md for \".impl.md\" AND \"Active Deviations\" AND \"PM-ACCEPTED\"; verify the skill reads deviations and converts PM-accepted deviations to rules reflecting actual behavior","test_file":"","test_name":"","tier":"unit","status":"planned","audit":""}]},{"id":"RULE-26","description":"Migration from `features/` reads `.discoveries.md` companion files and converts resolved bugs to regression rules and open bugs to `(deferred)` rules","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-38","description":"e2e: Create features/ with .discoveries.md containing one RESOLVED bug and one OPEN bug; verify the resolved bug becomes a RULE-N and the open bug becomes a RULE-N with (deferred) tag","test_file":"dev/test_e2e_ui_extraction.py","test_name":"test_discoveries_bug_extraction","tier":"e2e","status":"pass","audit":"EXCLUDED"},{"id":"PROOF-37","description":"Grep SKILL.md for \".discoveries.md\" AND \"Resolved bugs\" AND \"(deferred)\"; verify resolved bugs become rules and open bugs become deferred rules","test_file":"","test_name":"","tier":"unit","status":"planned","audit":""}]},{"id":"RULE-27","description":"`.discoveries.md` Figma/design references are preserved as `> Visual-Reference:` metadata or `@manual` proof references during migration","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-39","description":"Grep SKILL.md for \"Visual-Reference\" AND \"Figma\" in the .discoveries.md migration section; verify design references are preserved as metadata or manual proof references","test_file":"dev/test_skill_specs.py","test_name":"test_discoveries_figma_preserved_as_visual_reference","tier":"unit","status":"pass","audit":"EXCLUDED"}]},{"id":"RULE-28","description":"Quality guide references coverage dimensions instead of a fixed rule count target","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-40","description":"Grep spec_quality_guide.md for \"Coverage dimensions\"; verify section exists. Grep for \"5\u201310 rules per feature\"; verify the fixed target no longer exists","test_file":"dev/test_skill_specs.py","test_name":"test_quality_guide_coverage_dimensions_not_fixed_count","tier":"unit","status":"pass","audit":"EXCLUDED"}]},{"id":"RULE-29","description":"The taxonomy phase never produces a category folder containing a single spec \u2014 single-feature categories are merged into a related category during taxonomy review, or the spec is placed directly at `specs/.md` when no category fits","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-41","description":"Grep `skills/spec-from-code/SKILL.md` for the Phase 2 single-feature category check; verify it instructs merging single-feature categories into a related category or placing the spec at `specs/.md` without a folder","test_file":"dev/test_skill_specs.py","test_name":"test_no_single_spec_category_folders","tier":"unit","status":"pass","audit":"EXCLUDED"}]},{"id":"RULE-3","description":"The `name` field in frontmatter is `spec-from-code`, matching the directory name","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Extract `name:` from frontmatter; verify it equals `spec-from-code`","test_file":"dev/test_skill_specs.py","test_name":"test_name_matches_directory","tier":"unit","status":"pass","audit":"EXCLUDED"}]},{"id":"RULE-30","description":"The tier review pass (Phase 3 step 7) includes an inverse check: every `@e2e` proof description must read as an observable flow (arrange \u2192 act \u2192 observe through the real running app) and must not name a source file or internal function; mis-tagged proofs are rewritten as boundary observations or retagged, per `references/spec_quality_guide.md` (\"E2E proof descriptions\")","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-42","description":"Grep `skills/spec-from-code/SKILL.md` step 7 for the inverse check: \"observable flow\", arrange \u2192 act \u2192 observe, and \"must not name a source file or internal function\"; verify the pointer to `spec_quality_guide.md` \"E2E proof descriptions\" is present","test_file":"dev/test_skill_specs.py","test_name":"test_tier_review_inverse_check","tier":"unit","status":"pass","audit":"EXCLUDED"},{"id":"PROOF-45","description":"Grep `references/spec_quality_guide.md` for the \"E2E proof descriptions\" section; verify the canonical guidance the skill points to exists","test_file":"dev/test_skill_specs.py","test_name":"test_quality_guide_has_e2e_flow_section","tier":"unit","status":"pass","audit":"EXCLUDED"}]},{"id":"RULE-31","description":"Spec validation (Phase 3 step 11) includes a proof implementation-coupling check that rejects proof descriptions naming source files or internal symbols as the asserted target","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-43","description":"Grep `skills/spec-from-code/SKILL.md` step 11 for the proof implementation-coupling check; verify it rejects proof descriptions naming source files or internal symbols as the asserted target","test_file":"dev/test_skill_specs.py","test_name":"test_step11_proof_coupling_check","tier":"unit","status":"pass","audit":"EXCLUDED"}]},{"id":"RULE-32","description":"When a category's generated proofs include `@e2e` and no e2e-capable test runner was detected in Phase 1, the skill surfaces a warning in the category review block and in the Phase 4 summary (tool-agnostic \u2014 Playwright, Cypress, MCP-driven browser, etc.)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-44","description":"Grep `skills/spec-from-code/SKILL.md` for the e2e-runner warning in BOTH the step 12 review block and the Phase 4 summary; verify both locations mention `@e2e` proofs with no e2e runner detected","test_file":"dev/test_skill_specs.py","test_name":"test_e2e_runner_reality_check","tier":"unit","status":"pass","audit":"EXCLUDED"}]},{"id":"RULE-4","description":"Skill includes mandatory tier tag review for proof descriptions","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Grep `skills/spec-from-code/SKILL.md` for tier review instructions and tier tag references (`@integration`/`@e2e`/unit tier); verify present","test_file":"dev/test_skill_specs.py","test_name":"test_has_tier_review","tier":"unit","status":"pass","audit":"EXCLUDED"}]},{"id":"RULE-5","description":"Phase 1 detects existing specs in both `features/` (legacy) and `specs/` (non-compliant) as migration candidates","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-10","description":"e2e: Create features/auth/login.md with Given/When/Then; verify 3 scenarios detected for conversion","test_file":"dev/test_e2e_spec_migration.py","test_name":"test_legacy_given_when_then_converted_to_rules","tier":"e2e","status":"pass","audit":"EXCLUDED"},{"id":"PROOF-17","description":"e2e: Create features/auth/login.md; verify category=auth name=login maps to specs/auth/login.md","test_file":"dev/test_e2e_spec_migration.py","test_name":"test_legacy_feature_name_and_category_preserved","tier":"e2e","status":"pass","audit":"EXCLUDED"},{"id":"PROOF-5","description":"Grep SKILL.md for `features/` detection AND `specs/` non-compliant detection in Phase 1; verify both paths exist","test_file":"","test_name":"","tier":"unit","status":"planned","audit":""}]},{"id":"RULE-6","description":"Non-compliant specs in `specs/` are detected by checking for: missing `## Rules` section, unnumbered rules, missing `## Proof` section, or missing `> Description:` metadata","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-11","description":"e2e: Create spec with unnumbered rules; verify sync_status warns about non-numbered rules","test_file":"dev/test_e2e_spec_migration.py","test_name":"test_unnumbered_rules_get_renumbered","tier":"e2e","status":"pass","audit":"WEAK"},{"id":"PROOF-12","description":"e2e: Create spec missing > Description:; verify field absent and ## What it does present for derivation","test_file":"dev/test_e2e_spec_migration.py","test_name":"test_missing_description_detected","tier":"e2e","status":"pass","audit":"EXCLUDED"},{"id":"PROOF-13","description":"e2e: Create spec with Rules but no Proof section; verify Proof section absent","test_file":"dev/test_e2e_spec_migration.py","test_name":"test_missing_proof_section_detected","tier":"e2e","status":"pass","audit":"EXCLUDED"},{"id":"PROOF-6","description":"Grep SKILL.md for compliance checks: \"Missing `## Rules`\", \"unnumbered\", \"Missing `## Proof`\", \"Missing `> Description:`\"; verify all four criteria are documented","test_file":"","test_name":"","tier":"unit","status":"planned","audit":""}]},{"id":"RULE-7","description":"Compliant specs (numbered rules, proofs, proper sections) are left untouched during migration","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-14","description":"e2e: Create fully compliant spec; verify sync_status reports zero warnings","test_file":"dev/test_e2e_spec_migration.py","test_name":"test_compliant_spec_passes_sync_status","tier":"e2e","status":"pass","audit":"STRONG"},{"id":"PROOF-15","description":"e2e: Verify compliant spec has Description, numbered rules, and proofs (excluded from migration)","test_file":"dev/test_e2e_spec_migration.py","test_name":"test_compliant_spec_left_untouched","tier":"e2e","status":"pass","audit":"EXCLUDED"},{"id":"PROOF-7","description":"Grep SKILL.md for \"Compliant specs\" or \"left untouched\"; verify compliant specs are explicitly excluded from migration","test_file":"","test_name":"","tier":"unit","status":"planned","audit":""}]},{"id":"RULE-8","description":"Migration preserves the original spec's rules, descriptions, and metadata with minimal loss of fidelity","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-16","description":"e2e: Create spec with Scope, Stack metadata but missing Description; verify Scope and Stack preserved verbatim","test_file":"dev/test_e2e_spec_migration.py","test_name":"test_existing_metadata_preserved","tier":"e2e","status":"pass","audit":"EXCLUDED"},{"id":"PROOF-18","description":"e2e: Migrate unnumbered spec; verify all original rule content preserved in numbered format with proofs","test_file":"dev/test_e2e_spec_migration.py","test_name":"test_llm_evaluates_migration_fidelity","tier":"e2e","status":"pass","audit":"EXCLUDED"},{"id":"PROOF-8","description":"Grep SKILL.md for \"primary input\" and \"preserve\"; verify migration uses old spec as primary input","test_file":"","test_name":"","tier":"integration","status":"planned","audit":""}]},{"id":"RULE-9","description":"Phase 4 offers to remove `features/` after migration but does NOT remove non-compliant specs from `specs/` (they are overwritten in place)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Grep SKILL.md for `features/` detection AND `specs/` non-compliant detection in Phase 1; verify both paths exist","test_file":"dev/test_skill_specs.py","test_name":"test_phase4_cleanup_offer_and_overwrite_in_place","tier":"unit","status":"pass","audit":"EXCLUDED"},{"id":"PROOF-9","description":"Grep SKILL.md for `features/` cleanup offer AND \"overwritten in place\" for specs/; verify both paths exist","test_file":"","test_name":"","tier":"unit","status":"planned","audit":""}]}],"audit":{"integrity":83,"strong":5,"weak":1,"hollow":0,"manual":0,"findings":[{"proof_id":"PROOF-11","rule_id":"RULE-6","level":"WEAK","priority":"HIGH","criterion":"partial behavioral coverage \u2014 verifies detection but not actual renumbering","fix":"Run the migration on the unnumbered spec and assert the output spec has RULE-1, RULE-2, RULE-3, RULE-4 numbered rules."}]}},{"name":"skill_status","category":"skills","type":"feature","is_global":false,"source_url":null,"pinned":null,"description":"The `purlin:status` skill calls the `sync_status` MCP tool and displays per-feature rule coverage with actionable directives.","source_path":null,"stack":"markdown (skill definition)","ext_status":null,"proved":4,"total":4,"deferred":0,"status":"VERIFIED","vhash":"b3ef613b","receipt":{"commit":"5f42a16ba84ca8b20a9185eabe1d7a8f73a84b72","timestamp":"2026-04-04T22:48:45.094616+00:00","stale":false},"rules":[{"id":"RULE-1","description":"Skill file has YAML frontmatter with `name` and `description` fields","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Grep `skills/status/SKILL.md` for YAML frontmatter delimiters (`---`); verify `name:` and `description:` fields exist","test_file":"dev/test_skill_specs.py","test_name":"test_has_frontmatter","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-2","description":"Skill file contains a `## Usage` section documenting command syntax","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Grep `skills/status/SKILL.md` for `## Usage`; verify the section exists","test_file":"dev/test_skill_specs.py","test_name":"test_has_usage_section","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-3","description":"The `name` field in frontmatter is `status`, matching the directory name","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Extract `name:` from frontmatter; verify it equals `status`","test_file":"dev/test_skill_specs.py","test_name":"test_name_matches_directory","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-4","description":"Skill references the `sync_status` MCP tool by name","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Grep `skills/status/SKILL.md` for `sync_status`; verify the MCP tool is referenced","test_file":"dev/test_skill_specs.py","test_name":"test_references_sync_status_mcp_tool","tier":"unit","status":"pass","audit":""}]}],"audit":null},{"name":"skill_unit_test","category":"skills","type":"feature","is_global":false,"source_url":null,"pinned":null,"description":"The `purlin:unit-test` skill runs tests (unit tier unless `--all`), emits proof files via feature-scoped overwrite, and reports coverage per feature.","source_path":null,"stack":"markdown (skill definition)","ext_status":null,"proved":5,"total":5,"deferred":0,"status":"VERIFIED","vhash":"175608df","receipt":{"commit":"5f42a16ba84ca8b20a9185eabe1d7a8f73a84b72","timestamp":"2026-04-04T22:48:45.094616+00:00","stale":false},"rules":[{"id":"RULE-1","description":"Skill file has YAML frontmatter with `name` and `description` fields","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Grep `skills/unit-test/SKILL.md` for YAML frontmatter delimiters (`---`); verify `name:` and `description:` fields exist","test_file":"dev/test_skill_specs.py","test_name":"test_has_frontmatter","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-2","description":"Skill file contains a `## Usage` section documenting command syntax","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Grep `skills/unit-test/SKILL.md` for `## Usage`; verify the section exists","test_file":"dev/test_skill_specs.py","test_name":"test_has_usage_section","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-3","description":"The `name` field in frontmatter is `unit-test`, matching the directory name","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Extract `name:` from frontmatter; verify it equals `unit-test`","test_file":"dev/test_skill_specs.py","test_name":"test_name_matches_directory","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-4","description":"Skill includes commit instructions or git operations for file modifications","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Grep `skills/unit-test/SKILL.md` for commit instructions (`git commit`, `commit the`, `create.*commit`); verify present","test_file":"dev/test_skill_specs.py","test_name":"test_has_commit_instructions","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-5","description":"Skill requires calling `sync_status` after tests and states it is not optional","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Grep `skills/unit-test/SKILL.md` for `sync_status` and `not optional`; verify both present","test_file":"dev/test_skill_specs.py","test_name":"test_requires_sync_status_not_optional","tier":"unit","status":"pass","audit":""}]}],"audit":null},{"name":"skill_verify","category":"skills","type":"feature","is_global":false,"source_url":null,"pinned":null,"description":"The `purlin:verify` skill runs the full test suite across all tiers, then issues verification receipts for every feature with complete rule coverage.","source_path":null,"stack":"markdown (skill definition)","ext_status":null,"proved":6,"total":6,"deferred":0,"status":"VERIFIED","vhash":"e13ac1f4","receipt":{"commit":"1eaaf8bec1ee53d2f944fb79774699f7d68f8ab1","timestamp":"2026-06-02T15:47:23.471069+00:00","stale":false},"rules":[{"id":"RULE-1","description":"Skill file has YAML frontmatter with `name` and `description` fields","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Grep `skills/verify/SKILL.md` for YAML frontmatter delimiters (`---`); verify `name:` and `description:` fields exist","test_file":"dev/test_skill_specs.py","test_name":"test_has_frontmatter","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-2","description":"Skill file contains a `## Usage` section documenting command syntax","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Grep `skills/verify/SKILL.md` for `## Usage`; verify the section exists","test_file":"dev/test_skill_specs.py","test_name":"test_has_usage_section","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-3","description":"The `name` field in frontmatter is `verify`, matching the directory name","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Extract `name:` from frontmatter; verify it equals `verify`","test_file":"dev/test_skill_specs.py","test_name":"test_name_matches_directory","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-4","description":"Skill includes commit instructions or git operations for file modifications","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Grep `skills/verify/SKILL.md` for commit instructions (`git commit`, `commit the`, `create.*commit`); verify present","test_file":"dev/test_skill_specs.py","test_name":"test_has_commit_instructions","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-5","description":"Skill prohibits modifying code or test files during verification","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Grep `skills/verify/SKILL.md` for `NEVER modify`; verify the read-only constraint is present","test_file":"dev/test_skill_specs.py","test_name":"test_verify_prohibits_modifying_files","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-6","description":"Verify skill Step 4e documents independent audit that reports the final integrity score","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"e2e: Grep skills/verify/SKILL.md for independent audit; verify integrity score and purlin-auditor reference","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_e2e_teammate_audit_loop.sh","test_name":"verify Step 4e documents independent audit with integrity score and purlin-auditor","tier":"e2e","status":"pass","audit":""},{"id":"PROOF-6","description":"e2e: Grep skills/verify/SKILL.md for independent audit; verify integrity score and purlin-auditor reference","test_file":"dev/test_skill_specs.py","test_name":"test_step_4e_documents_independent_audit","tier":"unit","status":"pass","audit":""}]}],"audit":null},{"name":"static_checks","category":"audit","type":"feature","is_global":false,"source_url":null,"pinned":null,"description":"Deterministic pre-filter that catches structural test problems without any LLM. Uses Python's `ast` module for Python tests, a brace-balancing tokenizer for JS/TS tests, regex for Shell tests, and language-agnostic proof-file checks (proof ID collisions, orphan rules) that operate on JSON regardless of source language. Runs before the LLM audit pass so that structural issues like `assert True` are always caught regardless of which LLM performs the semantic evaluation.","source_path":null,"stack":null,"ext_status":null,"proved":26,"total":26,"deferred":0,"status":"VERIFIED","vhash":"fe40cdeb","receipt":{"commit":"85b09702318edd3da649d7d95473bc147a6c381c","timestamp":"2026-05-23T21:00:50.391930+00:00","stale":false},"rules":[{"id":"RULE-1","description":"Detects assert True / tautological assertions in Python test functions","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Run static_checks on a file with assert True; verify status=fail check=assert_true","test_file":"dev/test_static_checks.py","test_name":"test_detects_assert_true","tier":"unit","status":"pass","audit":""},{"id":"PROOF-1","description":"Run static_checks on a file with assert True; verify status=fail check=assert_true","test_file":"dev/test_static_checks.py","test_name":"test_detects_assert_is_not_none","tier":"unit","status":"pass","audit":""},{"id":"PROOF-1","description":"Run static_checks on a file with assert True; verify status=fail check=assert_true","test_file":"dev/test_static_checks.py","test_name":"test_detects_assert_len_gte_zero","tier":"unit","status":"pass","audit":""},{"id":"PROOF-17","description":"e2e: Create test with assert True and a valid test; verify assert_true detected on first, pass on second","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"RULE-10","description":"compute_proof_hash returns a deterministic 16-char hex hash from (rule text, proof description, test code)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-10","description":"Call compute_proof_hash with same inputs twice and verify identical 16-char hex output; call with different inputs and verify different hash","test_file":"dev/test_static_checks.py","test_name":"test_compute_proof_hash_deterministic","tier":"unit","status":"pass","audit":""},{"id":"PROOF-10","description":"Call compute_proof_hash with same inputs twice and verify identical 16-char hex output; call with different inputs and verify different hash","test_file":"dev/test_static_checks.py","test_name":"test_compute_proof_hash_different_inputs","tier":"unit","status":"pass","audit":""},{"id":"PROOF-10","description":"Call compute_proof_hash with same inputs twice and verify identical 16-char hex output; call with different inputs and verify different hash","test_file":"dev/test_static_checks.py","test_name":"test_compute_proof_hash_no_separator_collision","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-11","description":"read_audit_cache returns an empty dict when no cache file exists and parses valid JSON when it does","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-11","description":"Call read_audit_cache on a nonexistent path and verify empty dict; write valid JSON to the cache path and verify it parses correctly","test_file":"dev/test_static_checks.py","test_name":"test_read_cache_missing_file","tier":"unit","status":"pass","audit":""},{"id":"PROOF-11","description":"Call read_audit_cache on a nonexistent path and verify empty dict; write valid JSON to the cache path and verify it parses correctly","test_file":"dev/test_static_checks.py","test_name":"test_read_cache_valid","tier":"unit","status":"pass","audit":""},{"id":"PROOF-11","description":"Call read_audit_cache on a nonexistent path and verify empty dict; write valid JSON to the cache path and verify it parses correctly","test_file":"dev/test_static_checks.py","test_name":"test_read_cache_non_dict_returns_empty","tier":"unit","status":"pass","audit":""},{"id":"PROOF-11","description":"Call read_audit_cache on a nonexistent path and verify empty dict; write valid JSON to the cache path and verify it parses correctly","test_file":"dev/test_static_checks.py","test_name":"test_read_cache_corrupt_json_returns_empty","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-12","description":"write_audit_cache writes atomically via tmp + os.replace","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-28","description":"e2e: Call write_audit_cache with 3 entries; verify audit_cache.json created with 3 keys","test_file":"dev/test_e2e_audit_cache_pipeline.py","test_name":"test_write_cache_creates_file","tier":"e2e","status":"pass","audit":""},{"id":"PROOF-12","description":"Call write_audit_cache, then read the file back and verify contents match the written dict","test_file":"dev/test_static_checks.py","test_name":"test_write_cache_atomic","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-13","description":"Shell if/else proof pairs (same proof_id and rule_id with one pass and one fail branch) are recognized as a single conditional proof where the if-condition is the assertion, not flagged as hardcoded pass","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-13","description":"Create shell test with if/else purlin_proof pair; run static_checks; verify status=pass (not flagged). Also verify a bare hardcoded pass without if/else is still caught","test_file":"dev/test_static_checks.py","test_name":"test_if_else_pair_not_flagged","tier":"unit","status":"pass","audit":""},{"id":"PROOF-13","description":"Create shell test with if/else purlin_proof pair; run static_checks; verify status=pass (not flagged). Also verify a bare hardcoded pass without if/else is still caught","test_file":"dev/test_static_checks.py","test_name":"test_hardcoded_pass_still_caught","tier":"unit","status":"pass","audit":""},{"id":"PROOF-13","description":"Create shell test with if/else purlin_proof pair; run static_checks; verify status=pass (not flagged). Also verify a bare hardcoded pass without if/else is still caught","test_file":"dev/test_static_checks.py","test_name":"test_if_else_pair_no_condition_flagged","tier":"unit","status":"pass","audit":""},{"id":"PROOF-27","description":"e2e: Create shell test with if/else purlin_proof pair; verify pass; verify bare hardcoded pass still caught","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"RULE-14","description":"Python assert_true results include a literal field (true for assert True/assertTrue(True), false for heuristic patterns like assert x is not None)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-14","description":"Run static_checks on file with assert True; verify literal=true. Run on file with assert x is not None; verify literal=false","test_file":"dev/test_static_checks.py","test_name":"test_literal_assert_true_has_literal_true","tier":"unit","status":"pass","audit":""},{"id":"PROOF-14","description":"Run static_checks on file with assert True; verify literal=true. Run on file with assert x is not None; verify literal=false","test_file":"dev/test_static_checks.py","test_name":"test_heuristic_assert_has_literal_false","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-15","description":"Proof ID collisions within a feature are detected \u2014 same PROOF-N targeting different RULE-N values in a proof JSON file","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-15","description":"Create proof JSON with two entries sharing PROOF-1 but targeting RULE-1 and RULE-2; call check_proof_file; verify result contains check='proof_id_collision' with both rules listed. Test with proof JSON from multiple language contexts (Python pytest, JavaScript Jest, Shell, C, PHP, SQL, TypeScript) to verify language-agnostic detection","test_file":"dev/test_static_checks.py","test_name":"test_detects_collision_per_language[python-_python_proof]","tier":"unit","status":"pass","audit":""},{"id":"PROOF-15","description":"Create proof JSON with two entries sharing PROOF-1 but targeting RULE-1 and RULE-2; call check_proof_file; verify result contains check='proof_id_collision' with both rules listed. Test with proof JSON from multiple language contexts (Python pytest, JavaScript Jest, Shell, C, PHP, SQL, TypeScript) to verify language-agnostic detection","test_file":"dev/test_static_checks.py","test_name":"test_detects_collision_per_language[javascript-_jest_proof]","tier":"unit","status":"pass","audit":""},{"id":"PROOF-15","description":"Create proof JSON with two entries sharing PROOF-1 but targeting RULE-1 and RULE-2; call check_proof_file; verify result contains check='proof_id_collision' with both rules listed. Test with proof JSON from multiple language contexts (Python pytest, JavaScript Jest, Shell, C, PHP, SQL, TypeScript) to verify language-agnostic detection","test_file":"dev/test_static_checks.py","test_name":"test_detects_collision_per_language[shell-_shell_proof]","tier":"unit","status":"pass","audit":""},{"id":"PROOF-15","description":"Create proof JSON with two entries sharing PROOF-1 but targeting RULE-1 and RULE-2; call check_proof_file; verify result contains check='proof_id_collision' with both rules listed. Test with proof JSON from multiple language contexts (Python pytest, JavaScript Jest, Shell, C, PHP, SQL, TypeScript) to verify language-agnostic detection","test_file":"dev/test_static_checks.py","test_name":"test_detects_collision_per_language[c-_c_proof]","tier":"unit","status":"pass","audit":""},{"id":"PROOF-15","description":"Create proof JSON with two entries sharing PROOF-1 but targeting RULE-1 and RULE-2; call check_proof_file; verify result contains check='proof_id_collision' with both rules listed. Test with proof JSON from multiple language contexts (Python pytest, JavaScript Jest, Shell, C, PHP, SQL, TypeScript) to verify language-agnostic detection","test_file":"dev/test_static_checks.py","test_name":"test_detects_collision_per_language[php-_php_proof]","tier":"unit","status":"pass","audit":""},{"id":"PROOF-15","description":"Create proof JSON with two entries sharing PROOF-1 but targeting RULE-1 and RULE-2; call check_proof_file; verify result contains check='proof_id_collision' with both rules listed. Test with proof JSON from multiple language contexts (Python pytest, JavaScript Jest, Shell, C, PHP, SQL, TypeScript) to verify language-agnostic detection","test_file":"dev/test_static_checks.py","test_name":"test_detects_collision_per_language[sql-_sql_proof]","tier":"unit","status":"pass","audit":""},{"id":"PROOF-15","description":"Create proof JSON with two entries sharing PROOF-1 but targeting RULE-1 and RULE-2; call check_proof_file; verify result contains check='proof_id_collision' with both rules listed. Test with proof JSON from multiple language contexts (Python pytest, JavaScript Jest, Shell, C, PHP, SQL, TypeScript) to verify language-agnostic detection","test_file":"dev/test_static_checks.py","test_name":"test_detects_collision_per_language[typescript-_typescript_proof]","tier":"unit","status":"pass","audit":""},{"id":"PROOF-15","description":"Create proof JSON with two entries sharing PROOF-1 but targeting RULE-1 and RULE-2; call check_proof_file; verify result contains check='proof_id_collision' with both rules listed. Test with proof JSON from multiple language contexts (Python pytest, JavaScript Jest, Shell, C, PHP, SQL, TypeScript) to verify language-agnostic detection","test_file":"dev/test_static_checks.py","test_name":"test_no_collision_when_ids_unique","tier":"unit","status":"pass","audit":""},{"id":"PROOF-15","description":"Create proof JSON with two entries sharing PROOF-1 but targeting RULE-1 and RULE-2; call check_proof_file; verify result contains check='proof_id_collision' with both rules listed. Test with proof JSON from multiple language contexts (Python pytest, JavaScript Jest, Shell, C, PHP, SQL, TypeScript) to verify language-agnostic detection","test_file":"dev/test_static_checks.py","test_name":"test_collision_with_mixed_languages","tier":"unit","status":"pass","audit":""},{"id":"PROOF-15","description":"Create proof JSON with two entries sharing PROOF-1 but targeting RULE-1 and RULE-2; call check_proof_file; verify result contains check='proof_id_collision' with both rules listed. Test with proof JSON from multiple language contexts (Python pytest, JavaScript Jest, Shell, C, PHP, SQL, TypeScript) to verify language-agnostic detection","test_file":"dev/test_static_checks.py","test_name":"test_multiple_collisions_detected","tier":"unit","status":"pass","audit":""},{"id":"PROOF-15","description":"Create proof JSON with two entries sharing PROOF-1 but targeting RULE-1 and RULE-2; call check_proof_file; verify result contains check='proof_id_collision' with both rules listed. Test with proof JSON from multiple language contexts (Python pytest, JavaScript Jest, Shell, C, PHP, SQL, TypeScript) to verify language-agnostic detection","test_file":"dev/test_multilang_proof_plugins.py","test_name":"test_collision_detected_in_c_output","tier":"integration","status":"pass","audit":""}]},{"id":"RULE-16","description":"Proof entries referencing non-existent rules in the spec are flagged as orphans","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-16","description":"Create proof JSON with entry targeting RULE-99 on a spec with only RULE-1 through RULE-3; call check_proof_file with spec_path; verify result contains check='proof_rule_orphan'. Test with proof JSON from multiple language contexts to verify language-agnostic detection","test_file":"dev/test_static_checks.py","test_name":"test_detects_orphan_per_language[python-_python_proof]","tier":"unit","status":"pass","audit":""},{"id":"PROOF-16","description":"Create proof JSON with entry targeting RULE-99 on a spec with only RULE-1 through RULE-3; call check_proof_file with spec_path; verify result contains check='proof_rule_orphan'. Test with proof JSON from multiple language contexts to verify language-agnostic detection","test_file":"dev/test_static_checks.py","test_name":"test_detects_orphan_per_language[javascript-_jest_proof]","tier":"unit","status":"pass","audit":""},{"id":"PROOF-16","description":"Create proof JSON with entry targeting RULE-99 on a spec with only RULE-1 through RULE-3; call check_proof_file with spec_path; verify result contains check='proof_rule_orphan'. Test with proof JSON from multiple language contexts to verify language-agnostic detection","test_file":"dev/test_static_checks.py","test_name":"test_detects_orphan_per_language[shell-_shell_proof]","tier":"unit","status":"pass","audit":""},{"id":"PROOF-16","description":"Create proof JSON with entry targeting RULE-99 on a spec with only RULE-1 through RULE-3; call check_proof_file with spec_path; verify result contains check='proof_rule_orphan'. Test with proof JSON from multiple language contexts to verify language-agnostic detection","test_file":"dev/test_static_checks.py","test_name":"test_detects_orphan_per_language[c-_c_proof]","tier":"unit","status":"pass","audit":""},{"id":"PROOF-16","description":"Create proof JSON with entry targeting RULE-99 on a spec with only RULE-1 through RULE-3; call check_proof_file with spec_path; verify result contains check='proof_rule_orphan'. Test with proof JSON from multiple language contexts to verify language-agnostic detection","test_file":"dev/test_static_checks.py","test_name":"test_detects_orphan_per_language[php-_php_proof]","tier":"unit","status":"pass","audit":""},{"id":"PROOF-16","description":"Create proof JSON with entry targeting RULE-99 on a spec with only RULE-1 through RULE-3; call check_proof_file with spec_path; verify result contains check='proof_rule_orphan'. Test with proof JSON from multiple language contexts to verify language-agnostic detection","test_file":"dev/test_static_checks.py","test_name":"test_detects_orphan_per_language[sql-_sql_proof]","tier":"unit","status":"pass","audit":""},{"id":"PROOF-16","description":"Create proof JSON with entry targeting RULE-99 on a spec with only RULE-1 through RULE-3; call check_proof_file with spec_path; verify result contains check='proof_rule_orphan'. Test with proof JSON from multiple language contexts to verify language-agnostic detection","test_file":"dev/test_static_checks.py","test_name":"test_detects_orphan_per_language[typescript-_typescript_proof]","tier":"unit","status":"pass","audit":""},{"id":"PROOF-16","description":"Create proof JSON with entry targeting RULE-99 on a spec with only RULE-1 through RULE-3; call check_proof_file with spec_path; verify result contains check='proof_rule_orphan'. Test with proof JSON from multiple language contexts to verify language-agnostic detection","test_file":"dev/test_static_checks.py","test_name":"test_no_orphan_when_all_rules_exist","tier":"unit","status":"pass","audit":""},{"id":"PROOF-16","description":"Create proof JSON with entry targeting RULE-99 on a spec with only RULE-1 through RULE-3; call check_proof_file with spec_path; verify result contains check='proof_rule_orphan'. Test with proof JSON from multiple language contexts to verify language-agnostic detection","test_file":"dev/test_static_checks.py","test_name":"test_required_anchor_rules_not_flagged","tier":"unit","status":"pass","audit":""},{"id":"PROOF-16","description":"Create proof JSON with entry targeting RULE-99 on a spec with only RULE-1 through RULE-3; call check_proof_file with spec_path; verify result contains check='proof_rule_orphan'. Test with proof JSON from multiple language contexts to verify language-agnostic detection","test_file":"dev/test_static_checks.py","test_name":"test_no_orphan_check_without_spec","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-17","description":"Each audit cache entry contains all required fields: assessment, criterion, why, fix, feature, proof_id, rule_id, priority, cached_at","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-29","description":"e2e: Write cache; verify every entry has all required fields and cached_at is valid ISO 8601","test_file":"dev/test_e2e_audit_cache_pipeline.py","test_name":"test_cache_entry_required_fields","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-18","description":"clear_audit_cache atomically replaces the cache file with an empty dict {}","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-32","description":"e2e: Write cache with entries; call clear_audit_cache; read back; verify empty dict","test_file":"dev/test_e2e_audit_cache_pipeline.py","test_name":"test_clear_cache_produces_empty_dict","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-19","description":"write_audit_cache stamps every entry with the real current UTC time, overwriting any caller-provided cached_at","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-33","description":"e2e: Write cache with stale cached_at (midnight UTC); read back; verify cached_at is within 5 seconds of real current time","test_file":"dev/test_e2e_audit_cache_pipeline.py","test_name":"test_write_cache_stamps_real_utc_time","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-2","description":"Detects test functions with no assertion statements","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Run static_checks on a file with no assertions; verify status=fail check=no_assertions","test_file":"dev/test_static_checks.py","test_name":"test_detects_no_assertions","tier":"unit","status":"pass","audit":""},{"id":"PROOF-18","description":"e2e: Create test with no assertions; verify no_assertions detected","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"RULE-21","description":"load_criteria returns built-in criteria always, appends cached additional criteria from `.purlin/cache/additional_criteria.md` if present, appends extra path if provided; no other function in static_checks.py assembles criteria text","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-35","description":"Call load_criteria with no config; verify only built-in content. Save additional file to cache; call again; verify built-in + separator + additional. Pass extra_path; verify all three present","test_file":"dev/test_static_checks.py","test_name":"test_builtin_only","tier":"unit","status":"pass","audit":""},{"id":"PROOF-35","description":"Call load_criteria with no config; verify only built-in content. Save additional file to cache; call again; verify built-in + separator + additional. Pass extra_path; verify all three present","test_file":"dev/test_static_checks.py","test_name":"test_with_additional_criteria","tier":"unit","status":"pass","audit":""},{"id":"PROOF-35","description":"Call load_criteria with no config; verify only built-in content. Save additional file to cache; call again; verify built-in + separator + additional. Pass extra_path; verify all three present","test_file":"dev/test_static_checks.py","test_name":"test_with_extra_path","tier":"unit","status":"pass","audit":""},{"id":"PROOF-35","description":"Call load_criteria with no config; verify only built-in content. Save additional file to cache; call again; verify built-in + separator + additional. Pass extra_path; verify all three present","test_file":"dev/test_static_checks.py","test_name":"test_all_three_sources","tier":"unit","status":"pass","audit":""},{"id":"PROOF-35","description":"Call load_criteria with no config; verify only built-in content. Save additional file to cache; call again; verify built-in + separator + additional. Pass extra_path; verify all three present","test_file":"dev/test_static_checks.py","test_name":"test_no_additional_without_cache_file","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-22","description":"prune_audit_cache removes all cache entries whose hash key is not in the provided live_keys set, preserving entries whose key IS in live_keys with all fields intact","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-38","description":"e2e: Write 5 cache entries via write_audit_cache; write 3 live keys to a temp file; call --prune-cache --live-keys-file; verify JSON output shows pruned=2, kept=3; read cache back and confirm exactly 3 entries remain","test_file":"dev/test_e2e_audit_cache_pipeline.py","test_name":"test_prune_cache_via_cli","tier":"e2e","status":"pass","audit":""},{"id":"PROOF-36","description":"Write cache with 3 entries (keys \"aaa\", \"bbb\", \"ccc\"); call prune_audit_cache with live_keys={\"aaa\",\"ccc\"}; read back; verify \"bbb\" removed, \"aaa\" and \"ccc\" preserved with all original fields intact","test_file":"dev/test_static_checks.py","test_name":"test_prune_removes_dead_preserves_live","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-23","description":"prune_audit_cache with an empty live_keys set on a non-empty cache produces an empty cache (full sweep), and with all keys live produces an identical cache (no false pruning)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-37","description":"Write cache with 3 entries; call prune_audit_cache with live_keys=set(); read back; verify empty dict. Write cache with 3 entries; call prune_audit_cache with all 3 keys as live; read back; verify all 3 entries preserved with identical content","test_file":"dev/test_static_checks.py","test_name":"test_prune_empty_live_keys_clears_all","tier":"unit","status":"pass","audit":""},{"id":"PROOF-37","description":"Write cache with 3 entries; call prune_audit_cache with live_keys=set(); read back; verify empty dict. Write cache with 3 entries; call prune_audit_cache with all 3 keys as live; read back; verify all 3 entries preserved with identical content","test_file":"dev/test_static_checks.py","test_name":"test_prune_all_keys_live_preserves_all","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-24","description":"write_audit_cache merges new entries into the existing cache on disk \u2014 entries from prior writes for different features are preserved, not overwritten. Entries for the same (feature, proof_id) are deduplicated by keeping the latest cached_at","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-30","description":"e2e: Write cache with 3 entries for same (feature, proof_id) at different timestamps; call _read_audit_cache_by_feature; verify dedup to 1 entry keeping the latest cached_at","test_file":"dev/test_e2e_audit_cache_pipeline.py","test_name":"test_read_side_dedup_keeps_latest_entry","tier":"e2e","status":"pass","audit":""},{"id":"PROOF-31","description":"e2e: Write cache with 2 entries for same (feature, proof_id) \u2014 HOLLOW older, STRONG newer \u2014 plus a distinct entry; verify only the latest (STRONG) per (feature, proof_id) is kept and the unique entry survives","test_file":"dev/test_e2e_audit_cache_pipeline.py","test_name":"test_write_side_pruning_removes_stale_duplicates","tier":"e2e","status":"pass","audit":""},{"id":"PROOF-39","description":"Write 3 entries for feature_a via write_audit_cache; then write 2 entries for feature_b via a second call; read cache back; verify all 5 entries are present. Then write 1 updated entry for feature_a (same proof_id, newer assessment); read back; verify feature_b entries are untouched and feature_a has the updated entry","test_file":"dev/test_static_checks.py","test_name":"test_second_write_preserves_first_write_entries","tier":"unit","status":"pass","audit":""},{"id":"PROOF-39","description":"Write 3 entries for feature_a via write_audit_cache; then write 2 entries for feature_b via a second call; read cache back; verify all 5 entries are present. Then write 1 updated entry for feature_a (same proof_id, newer assessment); read back; verify feature_b entries are untouched and feature_a has the updated entry","test_file":"dev/test_static_checks.py","test_name":"test_update_entry_preserves_other_features","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-25","description":"write_audit_cache protects the entire read\u2192merge\u2192write cycle with an exclusive file lock (`fcntl.flock` on `audit_cache.json.lock`) so that concurrent subagent writers serialize correctly and no entries are lost","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-40","description":"Run two threads calling write_audit_cache concurrently with entries for different features; verify all entries from both threads survive in the final cache. Verify the lock file is created adjacent to the cache file during the write","test_file":"dev/test_static_checks.py","test_name":"test_lock_file_created_alongside_cache","tier":"unit","status":"pass","audit":""},{"id":"PROOF-40","description":"Run two threads calling write_audit_cache concurrently with entries for different features; verify all entries from both threads survive in the final cache. Verify the lock file is created adjacent to the cache file during the write","test_file":"dev/test_static_checks.py","test_name":"test_concurrent_writes_preserve_all_entries","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-26","description":"`--write-cache` CLI flag reads a JSON dict of cache entries from stdin and merges them into the existing audit cache via write_audit_cache, printing a JSON status response with `status: \"merged\"` and entry count","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-41","description":"Call `--write-cache` via CLI with JSON on stdin; verify entries are merged and status response is correct. Seed cache first, then call `--write-cache` with entries for a different feature; verify both old and new entries survive","test_file":"dev/test_static_checks.py","test_name":"test_write_cache_cli_merges_entries","tier":"unit","status":"pass","audit":""},{"id":"PROOF-41","description":"Call `--write-cache` via CLI with JSON on stdin; verify entries are merged and status response is correct. Seed cache first, then call `--write-cache` with entries for a different feature; verify both old and new entries survive","test_file":"dev/test_static_checks.py","test_name":"test_write_cache_cli_merges_with_existing","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-27","description":"check_js detects tautological assertions (`expect(true).toBe(true)`) and JS/TS test bodies with no `expect()` calls, returning the same JSON shape (proof_id, rule_id, test_name, status, reason) as check_python","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-42","description":"e2e: Run the real static_checks.py CLI on a `.ts` file containing a `[proof:...]` test with `expect(true).toBe(true)`; verify status=fail check=assert_true. Run on one whose body has no `expect()`; verify status=fail check=no_assertions. Run on a clean test; verify status=pass","test_file":"dev/test_static_checks.py","test_name":"test_check_js_assertion_detection","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-28","description":"check_js parses JS/TS test files with a brace-balancing tokenizer that (a) matches test titles containing apostrophes regardless of quote style, and (b) captures full test bodies containing nested braces \u2014 options objects, destructured parameters, type assertions \u2014 without truncating at the first inner `}`","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-43","description":"e2e: Run the real static_checks.py CLI on the issue #2 repro \u2014 `it(\"execSync options trigger early-truncation [proof:demo:PROOF-1:RULE-1]\", () => { execSync(\"ls\", { cwd: \".\", encoding: \"utf8\" }); expect(out).toMatch(/./); })` and `it(\"cd's into a sibling [proof:demo:PROOF-2:RULE-2]\", () => { expect(1).toBe(1); })`; verify BOTH PROOF-1 and PROOF-2 appear in the output (apostrophe title matched) and PROOF-1 is NOT flagged no_assertions (options-object body fully captured, expect() seen)","test_file":"dev/test_static_checks.py","test_name":"test_check_js_tokenizer_handles_braces_and_apostrophes","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-3","description":"Detects bare except:pass around code under test","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Run static_checks on a file with except Exception: pass; verify status=fail check=bare_except","test_file":"dev/test_static_checks.py","test_name":"test_detects_bare_except_pass","tier":"unit","status":"pass","audit":""},{"id":"PROOF-3","description":"Run static_checks on a file with except Exception: pass; verify status=fail check=bare_except","test_file":"dev/test_static_checks.py","test_name":"test_detects_bare_except_no_type","tier":"unit","status":"pass","audit":""},{"id":"PROOF-25","description":"e2e: Create test with bare except:pass; verify bare_except detected","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"RULE-4","description":"Detects logic mirroring (expected value from same function as SUT)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Run static_checks on a file with logic mirroring; verify status=fail check=logic_mirroring","test_file":"dev/test_static_checks.py","test_name":"test_detects_logic_mirroring","tier":"unit","status":"pass","audit":""},{"id":"PROOF-4","description":"Run static_checks on a file with logic mirroring; verify status=fail check=logic_mirroring","test_file":"dev/test_static_checks.py","test_name":"test_no_mirroring_with_literal","tier":"unit","status":"pass","audit":""},{"id":"PROOF-19","description":"e2e: Create test with logic mirroring (expected from same function as SUT); verify logic_mirroring detected","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"RULE-5","description":"Detects mock target matching the function being tested (requires --spec-path)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Run static_checks with --spec-path on a file mocking the rule's function; verify status=fail check=mock_target_match","test_file":"dev/test_static_checks.py","test_name":"test_detects_mock_matching_rule","tier":"unit","status":"pass","audit":""},{"id":"PROOF-5","description":"Run static_checks with --spec-path on a file mocking the rule's function; verify status=fail check=mock_target_match","test_file":"dev/test_static_checks.py","test_name":"test_no_match_when_mock_unrelated","tier":"unit","status":"pass","audit":""},{"id":"PROOF-24","description":"e2e: Create test mocking bcrypt on rule about bcrypt; verify mock_target_match detected","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"RULE-6","description":"Returns JSON with proof_id, rule_id, test_name, status, reason for each proof","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Run static_checks on any file; verify JSON output has proofs array with required fields","test_file":"dev/test_static_checks.py","test_name":"test_json_has_required_fields","tier":"unit","status":"pass","audit":""},{"id":"PROOF-22","description":"e2e: Parse JSON output; verify proofs array has proof_id, rule_id, test_name, status, reason fields","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"RULE-7","description":"Always exits 0 for completed analysis; defects are reported via JSON output status=fail, not exit codes. Non-zero exits (2) are reserved for real errors (bad args, missing files)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Run static_checks on a clean file and verify exit 0; run on a flawed file and verify exit 0 with status=fail in JSON output","test_file":"dev/test_static_checks.py","test_name":"test_exit_0_when_all_pass","tier":"unit","status":"pass","audit":""},{"id":"PROOF-7","description":"Run static_checks on a clean file and verify exit 0; run on a flawed file and verify exit 0 with status=fail in JSON output","test_file":"dev/test_static_checks.py","test_name":"test_exit_0_with_fail_status_when_defects_found","tier":"unit","status":"pass","audit":""},{"id":"PROOF-20","description":"e2e: Create structurally valid but semantically weak test; verify passes structural checks","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""},{"id":"PROOF-21","description":"e2e: Create 3 strong tests; verify all pass structural checks with exit 0","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""},{"id":"PROOF-23","description":"e2e: Run on clean and flawed files; verify exit 0 for both; verify flawed has status=fail in JSON","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"RULE-8","description":"check_spec_coverage returns rule_count and proof_count for the spec","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-8","description":"Create spec with rules and proofs; call check_spec_coverage; verify rule_count and proof_count are correct","test_file":"dev/test_static_checks.py","test_name":"test_spec_coverage_counts","tier":"unit","status":"pass","audit":""},{"id":"PROOF-8","description":"Create spec with rules and proofs; call check_spec_coverage; verify rule_count and proof_count are correct","test_file":"dev/test_static_checks.py","test_name":"test_spec_coverage_via_cli","tier":"unit","status":"pass","audit":""},{"id":"PROOF-8","description":"Create spec with rules and proofs; call check_spec_coverage; verify rule_count and proof_count are correct","test_file":"dev/test_static_checks.py","test_name":"test_spec_coverage_empty","tier":"unit","status":"pass","audit":""},{"id":"PROOF-26","description":"e2e: Create specs with rules and proofs; call check_spec_coverage; verify rule_count and proof_count","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]}],"audit":null},{"name":"sync_status","category":"mcp","type":"feature","is_global":false,"source_url":null,"pinned":null,"description":"Rule coverage reporting tool. Scans specs and proof files, computes per-feature coverage including own, required, and global anchor rules. Reports actionable directives and computes verification hashes.","source_path":null,"stack":"python/stdlib, json, glob, re, hashlib","ext_status":null,"proved":58,"total":58,"deferred":0,"status":"VERIFIED","vhash":"3a6de590","receipt":{"commit":"fb2089313b93d8f101a8b571039b7765a4583a2d","timestamp":"2026-06-05T14:37:11.418976+00:00","stale":false},"rules":[{"id":"RULE-1","description":"Scans `specs/**/*.md` for RULE-N lines, reads `*.proofs-*.json`, and reports per-feature coverage including own rules, required rules (from `> Requires:`), and global anchor rules, with actionable directives","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Create a spec with rules plus a required anchor; run sync_status; verify coverage includes own and required rules with directives","test_file":"dev/test_mcp_server.py","test_name":"test_rules_with_required_no_proofs","tier":"unit","status":"pass","audit":"STRONG"},{"id":"PROOF-34","description":"e2e: Create anchor with Source/Pinned/Path metadata; run _scan_specs; verify pinned and source_path extracted","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"RULE-10","description":"Displays rule labels: `(own)` for the feature's own rules, `(required)` for `> Requires:` rules, `(global)` for global anchor rules","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-10","description":"Create a feature requiring an anchor; verify (own) and (required) labels","test_file":"dev/test_mcp_server.py","test_name":"test_rule_labels","tier":"unit","status":"pass","audit":"STRONG"},{"id":"PROOF-23","description":"e2e: Run sync_status on feature with required and global rules; verify (own), (required), (global) labels on rule lines","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"RULE-11","description":"Shows a scope overlap advisory when an anchor's `> Scope:` overlaps a feature's scope but is not in `> Requires:`","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-11","description":"Create an anchor with overlapping scope but no Requires; verify scope overlap advisory","test_file":"dev/test_mcp_server.py","test_name":"test_scope_overlap_suggestion","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"RULE-12","description":"Warns when a `> Requires:` target does not match any known spec name","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-12","description":"Create a feature spec with `> Requires: does_not_exist`; run sync_status; verify warning about unresolved requires target","test_file":"dev/test_mcp_server.py","test_name":"test_unresolved_requires_warning","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"RULE-13","description":"Warns when a spec has manual proofs but no `> Scope:` metadata","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-13","description":"Create a spec with manual proof but no `> Scope:`; verify warning about staleness detection; create another with `> Scope:` and verify no warning","test_file":"dev/test_mcp_server.py","test_name":"test_manual_proof_without_scope_warning","tier":"unit","status":"pass","audit":"STRONG"},{"id":"PROOF-13","description":"Create a spec with manual proof but no `> Scope:`; verify warning about staleness detection; create another with `> Scope:` and verify no warning","test_file":"dev/test_mcp_server.py","test_name":"test_manual_proof_with_scope_no_warning","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"RULE-14","description":"Prefers proof files adjacent to their spec over proof files in the specs/ root directory","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-14","description":"Create a proof file at specs/ root and a matching proof in a subdirectory next to its spec; run sync_status; verify the subdirectory proof is used","test_file":"dev/test_mcp_server.py","test_name":"test_prefers_subdirectory_proofs","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"RULE-15","description":"Explains receipt staleness cause: distinguishes own rule changes from required/global anchor rule changes","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-15","description":"Create a feature requiring an anchor; verify and write receipt; add a rule to the anchor; run sync_status; verify output explains staleness is from the anchor change","test_file":"dev/test_mcp_server.py","test_name":"test_receipt_staleness_from_anchor_change","tier":"unit","status":"pass","audit":"STRONG"},{"id":"PROOF-31","description":"e2e: Add rule to spec; recompute vhash; verify mismatch with stale receipt","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"RULE-16","description":"Warns when `specs/` contains uncommitted changes to `.md` or `.proofs-*.json` files, listing the affected files and recommending a commit before drift or verify","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-16","description":"Create a temp git repo with a committed spec; modify the spec without committing; run sync_status; verify warning appears with filename; commit the change; run sync_status again; verify no warning","test_file":"dev/test_mcp_server.py","test_name":"test_warns_uncommitted_spec_changes","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"RULE-17","description":"All proofs are displayed uniformly \u2014 grep-based and behavioral proofs both show PASS/FAIL status with no visual distinction in coverage reporting","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-64","description":"","test_file":"dev/test_mcp_server.py","test_name":"test_proof_types_display_uniformly","tier":"unit","status":"pass","audit":"HOLLOW"},{"id":"PROOF-17","description":"Create a spec with structural-only proofs; verify output shows structural checks with \"not counted\" label and summary line","test_file":"","test_name":"","tier":"integration","status":"planned","audit":""}]},{"id":"RULE-18","description":"sync_status output begins with a summary table showing feature name, coverage fraction, and status (VERIFIED/PASSING/FAILING/PARTIAL/UNTESTED) for all features, sorted by status priority (FAILING, PARTIAL, PASSING, VERIFIED, UNTESTED)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-18","description":"Create 3 features (one VERIFIED, one PARTIAL, one with no proofs); run sync_status; verify output starts with summary table, correct ready count, and detail follows","test_file":"dev/test_mcp_server.py","test_name":"test_summary_table","tier":"unit","status":"pass","audit":"HOLLOW"}]},{"id":"RULE-19","description":"sync_status appends an integrity summary after the features VERIFIED line showing the integrity percentage and relative time since last purlin:audit, sourced from the audit cache; when no cache exists, shows a prompt to run purlin:audit","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-43","description":"e2e: Write audit cache with STRONG/WEAK entries; run sync_status; verify Integrity percentage and relative time","test_file":"dev/test_e2e_audit_cache_pipeline.py","test_name":"test_sync_status_shows_integrity_line","tier":"e2e","status":"pass","audit":"STRONG"},{"id":"PROOF-44","description":"e2e: No audit cache; run sync_status; verify \"No audit data\" message","test_file":"dev/test_e2e_audit_cache_pipeline.py","test_name":"test_sync_status_no_cache_shows_no_audit_data","tier":"e2e","status":"pass","audit":"STRONG"},{"id":"PROOF-50","description":"e2e: Write cache then delete; verify sync_status reverts to no-audit state","test_file":"dev/test_e2e_audit_cache_pipeline.py","test_name":"test_deleting_cache_reverts_to_no_audit_state","tier":"e2e","status":"pass","audit":"STRONG"},{"id":"PROOF-19","description":"Create a temp project with an audit cache containing STRONG and WEAK entries with timestamps; run sync_status; verify output contains \"Integrity: NN%\" and \"last purlin:audit:\" with relative time; delete the cache; run sync_status; verify output contains \"No audit data\"","test_file":"","test_name":"","tier":"integration","status":"planned","audit":""}]},{"id":"RULE-2","description":"Status is determined by proof coverage: VERIFIED (all rules proved + passing + non-stale receipt exists), PASSING (all rules proved + passing + no current receipt), PARTIAL (some but not all rules proved, none failing), FAILING (any proof has status FAIL), UNTESTED (zero proofs). Partial coverage never earns PASSING \u2014 every rule must have a passing proof","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Create a spec with all behavioral rules proved but no receipt; verify \"PASSING\" and vhash in output","test_file":"dev/test_mcp_server.py","test_name":"test_ready_with_vhash","tier":"unit","status":"pass","audit":"STRONG"},{"id":"PROOF-25","description":"e2e: Add proofs for all 5 rules (own + required + global); run sync_status; verify 5/5 and PASSING","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""},{"id":"PROOF-33","description":"e2e: Create specs with proofs; verify both PASSING and check_spec_coverage returns correct counts","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""},{"id":"PROOF-42","description":"e2e: Coverage progression UNTESTED to PARTIAL to PASSING with external anchor rules","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"RULE-20","description":"Reports UNTESTED when a feature has zero behavioral proofs \u2014 this replaces the em-dash display for features with no proofs","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-20","description":"Create a spec with 2 behavioral rules but zero proof entries; run sync_status; verify status is \"UNTESTED\" in the summary table","test_file":"dev/test_mcp_server.py","test_name":"test_untested_status_for_zero_proofs","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"RULE-21","description":"Reports PARTIAL (not PASSING) when a feature has some rules proved and passing but not all rules are covered \u2014 partial coverage never earns PASSING status","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-21","description":"Create a spec with 3 behavioral rules; write proof file with 2 passing proofs covering RULE-1 and RULE-2 only; run sync_status; verify status is \"PARTIAL\" not \"PASSING\" despite both proofs passing","test_file":"dev/test_mcp_server.py","test_name":"test_partial_status_when_not_all_rules_proved","tier":"unit","status":"pass","audit":"STRONG"},{"id":"PROOF-24","description":"e2e: Add proofs for feature's 2 own rules only; run sync_status; verify 2/5 and not VERIFIED","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"RULE-22","description":"Anchor detail shows Source URL, Path (if present), and Pinned value (SHA truncated to 7 chars) for anchors with `> Source:` metadata","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-65","description":"","test_file":"dev/test_mcp_server.py","test_name":"test_anchor_detail_shows_source_path_and_pinned","tier":"unit","status":"pass","audit":"STRONG"},{"id":"PROOF-35","description":"e2e: Create external anchor; run sync_status; verify Source, Path, and Pinned lines in output","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"RULE-23","description":"Shows unpinned warning for anchors with `> Source:` but no `> Pinned:`","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-66","description":"","test_file":"dev/test_mcp_server.py","test_name":"test_anchor_unpinned_warning","tier":"unit","status":"pass","audit":"STRONG"},{"id":"PROOF-36","description":"e2e: Create anchor with Source but no Pinned; run sync_status; verify unpinned warning","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"RULE-24","description":"report-data.js feature entries include `pinned` and `source_path` fields when the anchor spec has `> Pinned:` and `> Path:` metadata","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-67","description":"","test_file":"dev/test_mcp_server.py","test_name":"test_report_data_includes_pinned_and_source_path","tier":"unit","status":"pass","audit":"HOLLOW"},{"id":"PROOF-37","description":"e2e: Create external anchor with report=true; verify report-data.js has pinned and source_path fields","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"RULE-25","description":"Shows \"consider re-auditing\" when the audit cache is older than 24 hours","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-45","description":"e2e: Write cache with timestamps from 3 days ago; verify \"consider re-auditing\"","test_file":"dev/test_e2e_audit_cache_pipeline.py","test_name":"test_sync_status_stale_cache_warns","tier":"e2e","status":"pass","audit":"STRONG"}]},{"id":"RULE-26","description":"report-data.js includes audit_summary with integrity, assessment counts, last_audit, last_audit_relative, and stale fields when cache exists; audit_summary is null when no cache exists","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-46","description":"e2e: Write cache with report=true; parse report-data.js; verify audit_summary fields","test_file":"dev/test_e2e_audit_cache_pipeline.py","test_name":"test_report_data_audit_summary_fields","tier":"e2e","status":"pass","audit":"HOLLOW"},{"id":"PROOF-47","description":"e2e: No cache with report=true; verify report-data.js audit_summary is null","test_file":"dev/test_e2e_audit_cache_pipeline.py","test_name":"test_report_data_audit_summary_null_when_no_cache","tier":"e2e","status":"pass","audit":"STRONG"}]},{"id":"RULE-27","description":"report-data.js per-feature audit data is populated from cache entries matching the feature name","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-48","description":"e2e: Write cache for feature login; verify per-feature audit data with correct integrity and findings","test_file":"dev/test_e2e_audit_cache_pipeline.py","test_name":"test_report_data_per_feature_audit_data","tier":"e2e","status":"pass","audit":"HOLLOW"}]},{"id":"RULE-28","description":"Cache entries without a feature field are excluded from per-feature audit data but counted in project-wide summary","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-49","description":"e2e: Write cache with/without feature field; verify per-feature excludes no-feature entries; verify project-wide counts both","test_file":"dev/test_e2e_audit_cache_pipeline.py","test_name":"test_cache_entries_without_feature_excluded_from_per_feature_but_counted_globally","tier":"e2e","status":"pass","audit":"STRONG"}]},{"id":"RULE-29","description":"Per-feature integrity = (STRONG + MANUAL) / (STRONG + WEAK + HOLLOW + MANUAL) \u2014 measures proof quality only; NONE rules do not affect the integrity denominator","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-51","description":"e2e: Feature with 5 rules, 3 proved (2 STRONG, 1 WEAK); verify integrity = 2/3 = 67% (quality only, NONE excluded)","test_file":"dev/test_e2e_audit_cache_pipeline.py","test_name":"test_integrity_is_quality_only","tier":"e2e","status":"pass","audit":"HOLLOW"},{"id":"PROOF-54","description":"e2e: Feature with 4 rules, 2 STRONG proofs + 2 NONE; verify integrity = 100% not 50%","test_file":"dev/test_e2e_audit_cache_pipeline.py","test_name":"test_integrity_ignores_no_proof_rules_completely","tier":"e2e","status":"pass","audit":"HOLLOW"}]},{"id":"RULE-3","description":"Warns about unnumbered lines under `## Rules` and missing `## Rules` sections","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Create a spec with unnumbered rule; verify WARNING in output","test_file":"dev/test_mcp_server.py","test_name":"test_warns_unnumbered_rules","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"RULE-30","description":"Per-feature integrity counts only proofs cached under that feature name \u2014 required/global anchor proofs are counted under the anchor's own feature, not the consuming feature","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-52","description":"e2e: Feature with 2 own STRONG rules requiring anchor with 3 rules; verify integrity = 2/2 = 100% (anchor proofs counted under anchor)","test_file":"dev/test_e2e_audit_cache_pipeline.py","test_name":"test_integrity_counts_only_own_feature_proofs","tier":"e2e","status":"pass","audit":"HOLLOW"}]},{"id":"RULE-31","description":"Global integrity (project-wide) = (STRONG + MANUAL) / (STRONG + WEAK + HOLLOW + MANUAL) across all features \u2014 NONE rules do not affect the denominator","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-53","description":"e2e: Two features each with 1 NONE rule; write 2 STRONG each; verify global integrity = 4/4 = 100% (NONE excluded)","test_file":"dev/test_e2e_audit_cache_pipeline.py","test_name":"test_global_integrity_quality_only","tier":"e2e","status":"pass","audit":"STRONG"}]},{"id":"RULE-32","description":"sync_status CLI integrity percentage matches the audit_summary.integrity value in report-data.js","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-55","description":"e2e: Write cache; run sync_status with report=true; verify CLI integrity percentage matches report-data.js audit_summary.integrity","test_file":"dev/test_e2e_audit_cache_pipeline.py","test_name":"test_cli_integrity_matches_report_data","tier":"e2e","status":"pass","audit":"STRONG"},{"id":"PROOF-57","description":"e2e: Create isolated project with 5-rule feature, 3 proved (1 STRONG, 1 WEAK, 1 HOLLOW) + 2 NONE; run sync_status with report=true; verify CLI, dashboard, and computed integrity all equal 33% (NONE excluded from denominator)","test_file":"dev/test_e2e_audit_cache_pipeline.py","test_name":"test_integrity_with_no_proof_rules_all_sources_match","tier":"e2e","status":"pass","audit":"STRONG"}]},{"id":"RULE-33","description":"The integrity formula `(STRONG + MANUAL) / (STRONG + WEAK + HOLLOW + MANUAL)` is consistent across `references/audit_criteria.md`, `skills/audit/SKILL.md`, and this spec; none of these files include NONE in the integrity formula denominator","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-56","description":"Grep `references/audit_criteria.md`, `skills/audit/SKILL.md`, and `specs/mcp/sync_status.md` for the integrity formula; verify all three contain `(STRONG + MANUAL) / (STRONG + WEAK + HOLLOW + MANUAL)` and none include NONE in the integrity denominator","test_file":"dev/test_mcp_server.py","test_name":"test_integrity_formula_consistent_across_files","tier":"unit","status":"pass","audit":"EXCLUDED"},{"id":"PROOF-56","description":"Grep `references/audit_criteria.md`, `skills/audit/SKILL.md`, and `specs/mcp/sync_status.md` for the integrity formula; verify all three contain `(STRONG + MANUAL) / (STRONG + WEAK + HOLLOW + MANUAL)` and none include NONE in the integrity denominator","test_file":"dev/test_e2e_audit_cache_pipeline.py","test_name":"test_integrity_formula_consistent_across_sources","tier":"unit","status":"pass","audit":"EXCLUDED"}]},{"id":"RULE-34","description":"Integrity is computed by exactly one function (`_compute_integrity`) \u2014 both `_read_audit_summary` and `_build_feature_audit` delegate to it; no other function in purlin_server.py contains the integrity formula","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-58","description":"Grep purlin_server.py for the integrity computation pattern; verify `_compute_integrity` is the only function containing the formula and that both `_read_audit_summary` and `_build_feature_audit` call it","test_file":"dev/test_e2e_audit_cache_pipeline.py","test_name":"test_compute_integrity_is_single_function","tier":"unit","status":"pass","audit":"EXCLUDED"},{"id":"PROOF-60","description":"e2e: Create isolated project with 2 features, each with different audit mixes; run sync_status with report=true; verify per-feature and global integrity in CLI output match report-data.js per-feature audit.integrity and audit_summary.integrity","test_file":"dev/test_e2e_audit_cache_pipeline.py","test_name":"test_multi_feature_integrity_cli_matches_dashboard","tier":"e2e","status":"pass","audit":"STRONG"}]},{"id":"RULE-35","description":"Feature status is determined by exactly one function (`_determine_status`) \u2014 all call sites in purlin_server.py delegate to it; no other function contains the status determination logic","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-59","description":"Grep purlin_server.py for the status determination pattern; verify `_determine_status` is the only function containing the if/elif chain and that all three call sites delegate to it","test_file":"dev/test_e2e_audit_cache_pipeline.py","test_name":"test_determine_status_is_single_function","tier":"unit","status":"pass","audit":"EXCLUDED"},{"id":"PROOF-61","description":"e2e: Create isolated project with features in every status (VERIFIED, PASSING, PARTIAL, FAILING, UNTESTED); verify CLI summary table status matches report-data.js per-feature status for all five","test_file":"dev/test_e2e_audit_cache_pipeline.py","test_name":"test_all_statuses_cli_matches_dashboard","tier":"e2e","status":"pass","audit":"STRONG"}]},{"id":"RULE-36","description":"`_scan_specs` parses `> Stack:` metadata from spec files and includes it in feature info; report-data.js includes `stack` field when present","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-62","description":"Create spec with `> Stack: python/stdlib, json`; run `_scan_specs`; verify features dict has `stack == \"python/stdlib, json\"`. Create spec without Stack; verify `stack is None`","test_file":"dev/test_mcp_server.py","test_name":"test_scan_specs_parses_stack","tier":"unit","status":"pass","audit":"STRONG"},{"id":"PROOF-62","description":"Create spec with `> Stack: python/stdlib, json`; run `_scan_specs`; verify features dict has `stack == \"python/stdlib, json\"`. Create spec without Stack; verify `stack is None`","test_file":"dev/test_mcp_server.py","test_name":"test_scan_specs_stack_absent","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"RULE-37","description":"Does NOT warn about rule count \u2014 rule count scales with feature complexity per `references/spec_quality_guide.md` (\"Coverage dimensions\")","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-63","description":"Create feature with 3 rules; verify NO rule-count warning. Create feature with 12 rules; verify NO rule-count warning. Create anchor with 2 rules; verify NO warning. Create instruction spec with 3 rules; verify NO warning","test_file":"dev/test_mcp_server.py","test_name":"test_no_rule_count_warning_for_few_rules","tier":"unit","status":"pass","audit":"STRONG"},{"id":"PROOF-63","description":"Create feature with 3 rules; verify NO rule-count warning. Create feature with 12 rules; verify NO rule-count warning. Create anchor with 2 rules; verify NO warning. Create instruction spec with 3 rules; verify NO warning","test_file":"dev/test_mcp_server.py","test_name":"test_no_rule_count_warning_for_many_rules","tier":"unit","status":"pass","audit":"STRONG"},{"id":"PROOF-63","description":"Create feature with 3 rules; verify NO rule-count warning. Create feature with 12 rules; verify NO rule-count warning. Create anchor with 2 rules; verify NO warning. Create instruction spec with 3 rules; verify NO warning","test_file":"dev/test_mcp_server.py","test_name":"test_no_rule_count_warning_for_anchors","tier":"unit","status":"pass","audit":"STRONG"},{"id":"PROOF-63","description":"Create feature with 3 rules; verify NO rule-count warning. Create feature with 12 rules; verify NO rule-count warning. Create anchor with 2 rules; verify NO warning. Create instruction spec with 3 rules; verify NO warning","test_file":"dev/test_mcp_server.py","test_name":"test_no_rule_count_warning_for_instructions","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"RULE-38","description":"When the project's `.mcp.json` defines a `purlin` server whose command or args path points into the Claude plugin cache (`.claude/plugins/cache/`), sync_status prepends a preamble advisory warning that the entry shadows the plugin-bundled MCP server and is pinned to an old plugin version, with a `\u2192 Run: purlin:init --mcp` directive; no advisory when `.mcp.json` is absent, has no `purlin` entry, or the `purlin` entry points elsewhere (e.g., a dev checkout)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-68","description":"Create a temp project with `.mcp.json` defining `mcpServers.purlin` with an args path containing `.claude/plugins/cache/purlin/`; run sync_status; verify the preamble contains the legacy-entry advisory and `\u2192 Run: purlin:init --mcp`. Rewrite the entry with a non-cache path (dev checkout); verify no advisory. Delete `.mcp.json`; verify no advisory","test_file":"dev/test_mcp_server.py","test_name":"test_legacy_mcp_entry_advisory","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-4","description":"Required rules from `> Requires:` specs count toward a feature's coverage total (X/total) with `(required)` label; proofs are looked up under the source spec's feature name","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Create spec A with RULE-1 and spec B requiring A; verify B's total includes A's rule with (required) label","test_file":"dev/test_mcp_server.py","test_name":"test_requires_counts_for_coverage","tier":"unit","status":"pass","audit":"STRONG"},{"id":"PROOF-22","description":"e2e: Create anchor (2 rules), global anchor (1 rule), feature (2 own + Requires); run sync_status; verify 0/5 total","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""},{"id":"PROOF-38","description":"e2e: Create 1 external anchor (2 rules) + feature (1 own); verify coverage 0/3","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""},{"id":"PROOF-39","description":"e2e: Create 2 external anchors + feature; verify coverage 0/5","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""},{"id":"PROOF-40","description":"e2e: Create 1 external + 1 local anchor + feature; verify coverage 0/4","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"RULE-5","description":"Detects manual proof staleness by checking if scope files have commits newer than the stamp's commit SHA","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Create a spec with manual stamp at old SHA and modified scope file; verify MANUAL PROOF STALE","test_file":"dev/test_mcp_server.py","test_name":"test_manual_proof_staleness","tier":"unit","status":"pass","audit":"STRONG"},{"id":"PROOF-26","description":"e2e: Create spec with @manual stamp at current HEAD; verify PASS with verified date","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""},{"id":"PROOF-27","description":"e2e: Edit scope file and commit; verify MANUAL PROOF STALE with re-verify directive","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""},{"id":"PROOF-28","description":"e2e: Re-stamp with new HEAD SHA; verify PASS again","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"RULE-6","description":"`vhash` is computed as `sha256(sorted rule IDs including prefixed required/global keys + \"|\" + sorted proof ID:status pairs for all relevant proofs)[:8]`","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Compute vhash with prefixed required key; verify it includes the prefix and differs from hash without it","test_file":"dev/test_mcp_server.py","test_name":"test_vhash_with_prefixed_keys","tier":"unit","status":"pass","audit":"HOLLOW"},{"id":"PROOF-29","description":"e2e: Compute vhash and write receipt; verify receipt has correct vhash, commit, rules, proofs","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""},{"id":"PROOF-30","description":"e2e: Recompute vhash with no changes; verify matches receipt on disk","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""},{"id":"PROOF-32","description":"e2e: Add proof for new rule; write new receipt; verify different vhash and audit matches","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"RULE-7","description":"All proofs count equally toward coverage regardless of proof type (grep-based, behavioral, etc.). Proof quality (STRONG/WEAK/HOLLOW) is assessed by the auditor, not the coverage system","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Create a spec with all grep-based proofs; verify structural checks are reported separately and feature is not VERIFIED","test_file":"dev/test_mcp_server.py","test_name":"test_structural_only_detection","tier":"unit","status":"pass","audit":"STRONG"},{"id":"PROOF-7","description":"Create a spec with all grep-based proofs; verify structural checks are reported separately and feature is not VERIFIED","test_file":"dev/test_mcp_server.py","test_name":"test_all_proof_types_count_equally","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"RULE-8","description":"Detects `> Global: true` metadata on anchor specs and sets the `is_global` flag","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-8","description":"Create an anchor with `> Global: true`; verify is_global flag in scan result","test_file":"dev/test_mcp_server.py","test_name":"test_scan_specs_detects_global","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"RULE-9","description":"Global anchor rules auto-apply to all non-anchor feature specs without needing `> Requires:`; they appear in coverage with `(global)` label","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-9","description":"Create a global anchor and a feature with no Requires; verify global rule appears with (global) label","test_file":"dev/test_mcp_server.py","test_name":"test_global_anchor_auto_applies","tier":"unit","status":"pass","audit":"STRONG"},{"id":"PROOF-41","description":"e2e: Create global external anchor; verify auto-applies to all features","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"schema_spec_format/RULE-1","description":"Every spec MUST contain exactly two required sections: `## Rules`, `## Proof`","label":"required","source":"schema_spec_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Parse `references/formats/spec_format.md`; find the `## Required Sections` area; verify both sections (`## Rules`, `## Proof`) appear within it","test_file":"dev/test_schema_spec_format.py","test_name":"test_format_documents_two_sections","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"schema_spec_format/RULE-2","description":"Rules are numbered sequentially as `RULE-N` (RULE-1, RULE-2, ...). Unnumbered lines under `## Rules` trigger a sync_status WARNING","label":"required","source":"schema_spec_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Create a spec with an unnumbered line under `## Rules` (e.g., `- some constraint without RULE-N prefix`); run sync_status; verify it reports a WARNING about unnumbered rules","test_file":"dev/test_schema_spec_format.py","test_name":"test_unnumbered_rule_triggers_warning","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"schema_spec_format/RULE-3","description":"Proofs are numbered as `PROOF-N (RULE-N)` \u2014 each proof references the rule it covers","label":"required","source":"schema_spec_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Grep `specs/**/*.md` for lines under `## Proof`; verify every proof line matches the pattern `PROOF-\\d+ \\(RULE-\\d+\\)`","test_file":"dev/test_schema_spec_format.py","test_name":"test_all_proof_lines_match_pattern","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"schema_spec_format/RULE-4","description":"Every RULE must have at least one corresponding PROOF","label":"required","source":"schema_spec_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Create a spec with RULE-1 but no PROOF referencing RULE-1; run sync_status; verify it reports the rule as uncovered","test_file":"dev/test_schema_spec_format.py","test_name":"test_rule_without_proof_shows_uncovered","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"schema_spec_format/RULE-5","description":"`> Requires:` metadata, when present, is a comma-separated list of anchor names whose rules also apply to this feature","label":"required","source":"schema_spec_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Create a spec with `> Requires: base_anchor`; run sync_status; verify the report includes `base_anchor/RULE-1` in the coverage output","test_file":"dev/test_schema_spec_format.py","test_name":"test_requires_includes_referenced_rules","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"schema_spec_format/RULE-6","description":"`> Scope:` metadata, when present, is a comma-separated list of source file paths the feature touches","label":"required","source":"schema_spec_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Create a spec with `> Scope: scripts/mcp/purlin_server.py`; verify sync_status parses and displays the scope files","test_file":"dev/test_schema_spec_format.py","test_name":"test_scope_metadata_parsed","tier":"unit","status":"pass","audit":"HOLLOW"}]},{"id":"schema_spec_format/RULE-7","description":"Feature specs use heading `# Feature: `, anchor specs use `# Anchor: `","label":"required","source":"schema_spec_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Grep `specs/**/*.md` for first-level headings; verify each starts with `# Feature:` or `# Anchor:`","test_file":"dev/test_schema_spec_format.py","test_name":"test_spec_headings_use_correct_prefix","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"schema_spec_format/RULE-8","description":"`> Description:` metadata, when present, is a plain-language summary of the spec; supports multi-line via `>` continuation lines that do not match a metadata field pattern (`> FieldName:`)","label":"required","source":"schema_spec_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-8","description":"Create a spec with `> Description: First line\\n> second line\\n> Scope: src/`; parse description; verify it equals \"First line second line\" (continuation joined, Scope not included)","test_file":"dev/test_schema_spec_format.py","test_name":"test_multiline_description_continuation","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"schema_proof_format/RULE-1","description":"Proof files are named `.proofs-.json` and live in the same directory as their spec","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Create a proof file at `specs/test/foo.proofs-unit.json`; run sync_status; verify it reads the file and associates it with the `foo` spec","test_file":"dev/test_schema_proof_format.py","test_name":"test_proof_file_read_by_sync_status","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"schema_proof_format/RULE-2","description":"The JSON schema has a top-level `tier` string and a `proofs` array. Each proof entry contains: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Validate a proof JSON file against the required fields; verify all 7 fields (`feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`) are present in each proof entry","test_file":"dev/test_schema_proof_format.py","test_name":"test_proof_entry_has_all_seven_fields","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"schema_proof_format/RULE-3","description":"`status` is either `\"pass\"` or `\"fail\"` \u2014 no other values","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Create a proof entry with `status: \"error\"`; run sync_status; verify it is not counted as a passing proof","test_file":"dev/test_schema_proof_format.py","test_name":"test_invalid_status_not_counted","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"schema_proof_format/RULE-4","description":"`tier` values are: `\"unit\"`, `\"integration\"`, `\"e2e\"` \u2014 no other automated tiers","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Scan all real `*.proofs-*.json` files; verify every tier value (top-level and per-entry) is in the valid set {unit, integration, e2e}. Verify `spec_format.md` documents @integration and","test_file":"dev/test_schema_proof_format.py","test_name":"test_standard_tiers_documented","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"schema_proof_format/RULE-5","description":"Proof plugins use feature-scoped overwrite: load existing file, purge entries matching current feature, append new entries, write merged result","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Pre-seed a proof file with feature B entries; run the real pytest proof plugin for feature A via subprocess; verify feature B entries are preserved and feature A entries are added","test_file":"dev/test_schema_proof_format.py","test_name":"test_feature_scoped_overwrite","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"schema_proof_format/RULE-6","description":"Proof files are committed to git \u2014 they are project records, not ephemeral build artifacts","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Grep `.gitignore` for `*.proofs-*.json`; verify no gitignore rule excludes proof files","test_file":"dev/test_schema_proof_format.py","test_name":"test_proof_files_not_gitignored","tier":"unit","status":"pass","audit":"HOLLOW"}]},{"id":"schema_proof_format/RULE-7","description":"Manual proofs are stamped inline in the spec's `## Proof` section as `@manual(, , )`, not in proof JSON files","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Grep `references/formats/proofs_format.md` for `@manual`; verify manual stamp format is `@manual(, , )`","test_file":"dev/test_schema_proof_format.py","test_name":"test_manual_stamp_format_documented","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"security_no_dangerous_patterns/RULE-1","description":"FORBIDDEN \u2014 No `eval()` or `exec()` calls in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Grep `scripts/` for `eval(` and `exec(` in `.py` files; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_eval_or_exec","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"security_no_dangerous_patterns/RULE-2","description":"FORBIDDEN \u2014 No `subprocess` calls with `shell=True` in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Grep `scripts/**/*.py` for `shell\\s*=\\s*True`; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_shell_true","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"security_no_dangerous_patterns/RULE-3","description":"FORBIDDEN \u2014 No `os.system()` calls in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Grep `scripts/**/*.py` for `os\\.system\\s*\\(`; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_os_system","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"security_no_dangerous_patterns/RULE-4","description":"FORBIDDEN \u2014 No hardcoded credential assignments (password/secret/api_key/token literals) in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Grep `scripts/` for assignment patterns `(password|secret|api_key|token)\\s*=\\s*[\"'][^\"']+[\"']` in `.py`, `.sh`, `.js` files, excluding test files and comments; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_hardcoded_credentials","tier":"unit","status":"pass","audit":"STRONG"}]},{"id":"security_no_dangerous_patterns/RULE-5","description":"All subprocess calls use list-mode arguments (not string commands)","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Grep `scripts/**/*.py` for `subprocess.run(` and `subprocess.call(`; verify each call's first argument is a list literal `[`, not a string","test_file":"dev/test_security.py","test_name":"test_subprocess_uses_list_args","tier":"unit","status":"pass","audit":"STRONG"}]}],"audit":{"integrity":78,"strong":32,"weak":0,"hollow":9,"manual":0,"findings":[{"proof_id":"PROOF-18","rule_id":"RULE-18","level":"HOLLOW","priority":"MEDIUM","criterion":"logic_mirroring","fix":"Verify the actual table cells contain expected status values (PARTIAL/PASSING/UNTESTED) rather than relying on index positions of feature names that the test controls"},{"proof_id":"PROOF-6","rule_id":"RULE-6","level":"HOLLOW","priority":"MEDIUM","criterion":"logic_mirroring","fix":"Compute the expected vhash using hashlib.sha256 directly with the known input string, using a literal precomputed hex value, instead of calling the function under test"},{"proof_id":"PROOF-64","rule_id":"RULE-38","level":"HOLLOW","priority":"MEDIUM","criterion":"logic_mirroring (static check false positive: str.index called on both sides of ordering comparison)","fix":"Replace the ordering assertion with a non-index-based check: split result on the feature name boundary or use a regex, e.g. assert result.startswith(\"\u26a0\") or assert result.find(\"Legacy MCP config\") < result.find(\"login\"). Alternatively, add \"index\" to the static checker's benign set in static_checks.py."},{"proof_id":"PROOF-67","rule_id":"RULE-24","level":"HOLLOW","priority":"LOW","criterion":"assert_true (heuristic)","fix":"This is likely a static_checks false positive \u2014 the assertions check specific literal field values. Consider rewriting to combine the not-None check with the field checks using a descriptive message rather than a separate assertion"},{"proof_id":"PROOF-46","rule_id":"RULE-26","level":"HOLLOW","priority":"LOW","criterion":"assert_true (heuristic)","fix":"This appears to be a static_checks false positive on the guard assertion. The test substantively checks specific values. Consolidate the not-None guard into the first field check to avoid the flagged pattern"},{"proof_id":"PROOF-48","rule_id":"RULE-27","level":"HOLLOW","priority":"LOW","criterion":"assert_true (heuristic)","fix":"Consolidate guard assertions: replace assert audit with explicit field checks that surface None gracefully, e.g. assert audit is not None, f\"login feature has no audit data\""},{"proof_id":"PROOF-51","rule_id":"RULE-29","level":"HOLLOW","priority":"LOW","criterion":"assert_true (heuristic)","fix":"Consolidate guard assertion: assert payments is not None, f\"payments feature not found in report_data[features]\""},{"proof_id":"PROOF-52","rule_id":"RULE-30","level":"HOLLOW","priority":"LOW","criterion":"assert_true (heuristic)","fix":"Replace guard assert checkout is not None with assert checkout is not None, f\"checkout not found\""},{"proof_id":"PROOF-54","rule_id":"RULE-29","level":"HOLLOW","priority":"LOW","criterion":"assert_true (heuristic)","fix":"Replace guard assert feature is not None with assert feature is not None, f\"feature not found\""}]}}],"anchors_summary":{"total":5,"with_source":1,"global":0},"audit_summary":{"integrity":91,"strong":194,"weak":9,"hollow":11,"manual":0,"behavioral_total":214,"last_audit":"2026-06-05T14:33:47.564263+00:00","last_audit_relative":"3 minutes ago","stale":false},"drift":{"since":"last verification (49 minutes ago)","commits":["a84746bf verify(sync_status): re-issue receipt \u2014 58/58 rules","fb208931 feat(sync_status): advise legacy .mcp.json migration (RULE-38)","4beda43d spec(sync_status): advise legacy .mcp.json migration (RULE-38)","2312d593 release: v0.9.4 \u2014 plugin-bundled MCP server & e2e proof quality","141dac46 docs: add v0.9.4 release notes for e2e proof quality changes"],"files":[{"path":".claude-plugin/plugin.json","category":"CHANGED_BEHAVIOR","spec":"purlin_version","diff_stat":"+4 -2"},{"path":".purlin/report-data.js","category":"NEW_BEHAVIOR","spec":null,"diff_stat":"+1 -1"},{"path":"RELEASE_NOTES.md","category":"NO_IMPACT","spec":null,"diff_stat":"+23 -0"},{"path":"VERSION","category":"CHANGED_BEHAVIOR","spec":"purlin_version","diff_stat":"+1 -1"},{"path":"dev/test_mcp_server.py","category":"TESTS_ADDED","spec":null,"diff_stat":"+43 -0"},{"path":"scripts/mcp/purlin_server.py","category":"CHANGED_BEHAVIOR","spec":"purlin_version","diff_stat":"+37 -0"},{"path":"specs/mcp/sync_status.md","category":"CHANGED_SPECS","spec":"sync_status","diff_stat":"+2 -0"},{"path":"specs/mcp/sync_status.proofs-unit.json","category":"TESTS_ADDED","spec":"sync_status","diff_stat":"+9 -0"},{"path":"specs/mcp/sync_status.receipt.json","category":"NEW_BEHAVIOR","spec":null,"diff_stat":"+426 -300"},{"path":"templates/config.json","category":"CHANGED_BEHAVIOR","spec":"purlin_version","diff_stat":"+1 -1"}],"spec_changes":[{"spec":"sync_status","new_rules":["RULE-38"],"removed_rules":[]}],"proof_status":{"purlin_skills":{"proved":15,"total":15,"status":"VERIFIED","failing_rules":[]},"purlin_teammate_definitions":{"proved":4,"total":4,"status":"VERIFIED","failing_rules":[]},"purlin_version":{"proved":5,"total":5,"status":"VERIFIED","failing_rules":[]},"purlin_references":{"proved":30,"total":30,"status":"VERIFIED","failing_rules":[]},"purlin_agent":{"proved":16,"total":16,"status":"VERIFIED","failing_rules":[]},"proof_plugins_c":{"proved":24,"total":24,"status":"VERIFIED","failing_rules":[]},"proof_plugins_pytest":{"proved":26,"total":26,"status":"VERIFIED","failing_rules":[]},"proof_plugins_shell":{"proved":26,"total":26,"status":"VERIFIED","failing_rules":[]},"proof_plugins_jest":{"proved":26,"total":26,"status":"VERIFIED","failing_rules":[]},"proof_plugins_vitest":{"proved":24,"total":24,"status":"VERIFIED","failing_rules":[]},"proof_plugins_xunit":{"proved":28,"total":28,"status":"VERIFIED","failing_rules":[]},"proof_plugins_sql":{"proved":24,"total":24,"status":"VERIFIED","failing_rules":[]},"proof_plugins_php":{"proved":24,"total":24,"status":"VERIFIED","failing_rules":[]},"figma_web":{"proved":22,"total":22,"status":"VERIFIED","failing_rules":[]},"mcp_transport":{"proved":12,"total":12,"status":"VERIFIED","failing_rules":[]},"purlin_config":{"proved":6,"total":6,"status":"VERIFIED","failing_rules":[]},"config_engine":{"proved":15,"total":15,"status":"VERIFIED","failing_rules":[]},"sync_status":{"proved":58,"total":58,"status":"VERIFIED","failing_rules":[]},"drift":{"proved":29,"total":29,"status":"VERIFIED","failing_rules":[]},"report_data":{"proved":22,"total":22,"status":"VERIFIED","failing_rules":[]},"purlin_report":{"proved":44,"total":44,"status":"VERIFIED","failing_rules":[]},"static_checks":{"proved":26,"total":26,"status":"VERIFIED","failing_rules":[]},"pre_push_hook":{"proved":14,"total":14,"status":"VERIFIED","failing_rules":[]},"skill_audit":{"proved":14,"total":14,"status":"VERIFIED","failing_rules":[]},"skill_status":{"proved":4,"total":4,"status":"VERIFIED","failing_rules":[]},"skill_verify":{"proved":6,"total":6,"status":"VERIFIED","failing_rules":[]},"skill_build":{"proved":12,"total":12,"status":"VERIFIED","failing_rules":[]},"skill_spec":{"proved":8,"total":8,"status":"VERIFIED","failing_rules":[]},"skill_drift":{"proved":5,"total":5,"status":"VERIFIED","failing_rules":[]},"skill_init":{"proved":48,"total":48,"status":"VERIFIED","failing_rules":[]},"skill_spec_from_code":{"proved":32,"total":32,"status":"VERIFIED","failing_rules":[]},"skill_anchor":{"proved":7,"total":7,"status":"VERIFIED","failing_rules":[]},"skill_rename":{"proved":3,"total":3,"status":"VERIFIED","failing_rules":[]},"skill_unit_test":{"proved":5,"total":5,"status":"VERIFIED","failing_rules":[]},"skill_find":{"proved":4,"total":4,"status":"VERIFIED","failing_rules":[]}},"drift_flags":[],"broken_scopes":[{"spec":"schema_spec_format","missing_paths":["specs/**/*.md"],"existing_paths":[]},{"spec":"schema_proof_format","missing_paths":["specs/**/*.proofs-*.json"],"existing_paths":["scripts/proof/"]},{"spec":"security_no_dangerous_patterns","missing_paths":["scripts/**/*.py","scripts/**/*.sh","scripts/**/*.js"],"existing_paths":[]}],"external_anchor_drift":[{"anchor":"security_no_dangerous_patterns","source_url":"/Users/richlabarca/LocalCode/purlin/dev/external-refs/security-policy.git","pinned":"e18cecd9932191ea24361f2c472bb2818307e4d6","status":"error","error":"fatal: '/Users/richlabarca/LocalCode/purlin/dev/external-refs/security-policy.git' does not appear to be a git repository\nfatal: Could not read from remote repository.\n\nPlease make sure you have the c"}],"rule_details":{"purlin_version":{"rules":[{"rule_id":"RULE-1","description":"A VERSION file exists at the project root containing a single semver string","proof_status":"pass"},{"rule_id":"RULE-2","description":"purlin_server.py reads the version from the VERSION file via _read_version(), not a hardcoded string","proof_status":"pass"},{"rule_id":"RULE-3","description":"templates/config.json version field matches the VERSION file","proof_status":"pass"},{"rule_id":"RULE-4","description":"No hardcoded version strings in purlin_server.py (no literal \"0.9.0\" or similar version patterns)","proof_status":"pass"},{"rule_id":"RULE-5","description":"The .claude-plugin/plugin.json version field matches the VERSION file","proof_status":"pass"}],"changed_files":[".claude-plugin/plugin.json","VERSION","scripts/mcp/purlin_server.py","templates/config.json"],"total_rules":5,"proved_rules":5}}},"uncommitted":["M RELEASE_NOTES.md"],"git_sha":"a84746bf2bfabae4cafdd73d1239b538dc618d48"}; +const PURLIN_DATA = {"timestamp":"2026-06-25T16:48:18.612232+00:00","project":"Purlin","version":"0.9.2","docs_url":"https://github.com/rlabarca/purlin/blob/main/docs/index.md","summary":{"total_features":35,"verified":34,"passing":1,"partial":0,"failing":0,"untested":0},"features":[{"name":"config_engine","category":"mcp","type":"feature","is_global":false,"source_url":null,"pinned":null,"description":"Two-file configuration system that separates shared team defaults (`config.json`, committed) from per-user overrides (`config.local.json`, gitignored). Resolution merges both files so framework updates remain visible while respecting local preferences.","source_path":null,"stack":"python/stdlib, json","ext_status":null,"proved":15,"total":15,"deferred":0,"status":"VERIFIED","vhash":"3d48c34c","receipt":{"commit":"6cd4518e322d351ea3c91a7b877f1e59f90f2492","timestamp":"2026-04-07T13:46:48.452182+00:00","stale":false},"rules":[{"id":"RULE-1","description":"`find_project_root` returns `PURLIN_PROJECT_ROOT` env var when set and the directory exists","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Set PURLIN_PROJECT_ROOT to a temp dir with .purlin/ inside; call find_project_root; verify it returns that dir without climbing","test_file":"dev/test_config_engine.py","test_name":"test_env_var_takes_precedence","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-10","description":"`update_config` uses atomic replacement (write to .tmp, then os.replace) to prevent partial writes","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-10","description":"Call update_config; verify no .tmp file remains and os.replace is used in source","test_file":"dev/test_config_engine.py","test_name":"test_atomic_replacement","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-2","description":"`find_project_root` climbs from the start directory looking for a `.purlin/` marker directory","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Create /tmp/a/b/c with .purlin/ in /tmp/a; call find_project_root(start_dir=\"/tmp/a/b/c\"); verify returns /tmp/a","test_file":"dev/test_config_engine.py","test_name":"test_climbs_to_purlin_marker","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-3","description":"`find_project_root` falls back to cwd if no `.purlin/` marker is found in any ancestor","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Call find_project_root on a dir with no .purlin/ ancestor; verify returns cwd","test_file":"dev/test_config_engine.py","test_name":"test_falls_back_to_cwd","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-4","description":"`resolve_config` merges config.json (base) with config.local.json (overrides) \u2014 local keys win for duplicates, base keys are preserved when absent from local","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Create config.json with {\"team\": \"default\", \"shared\": \"base\"} and config.local.json with {\"shared\": \"override\", \"local_only\": true}; call resolve_config; verify result is {\"team\": \"default\", \"shared\": \"override\", \"local_only\": true} \u2014 base key preserved, shared key overridden, local-only key included","test_file":"dev/test_config_engine.py","test_name":"test_merge_base_with_overrides","tier":"unit","status":"pass","audit":""},{"id":"PROOF-11","description":"Create config.json with {\"report\": true, \"version\": \"0.9.0\"} and config.local.json with {\"pre_push\": \"strict\"}; call resolve_config; verify result has all three keys \u2014 framework key \"report\" visible despite not being in local. This is the key scenario: framework adds a new default, existing user keeps their overrides, new default is visible","test_file":"dev/test_config_engine.py","test_name":"test_framework_new_key_visible_through_existing_local","tier":"unit","status":"pass","audit":""},{"id":"PROOF-4","description":"Create config.json with {\"team\": \"default\", \"shared\": \"base\"} and config.local.json with {\"shared\": \"override\", \"local_only\": true}; call resolve_config; verify result is {\"team\": \"default\", \"shared\": \"override\", \"local_only\": true} \u2014 base key preserved, shared key overridden, local-only key included","test_file":"dev/test_config_engine.py","test_name":"test_local_only_keys_included","tier":"unit","status":"pass","audit":""},{"id":"PROOF-4","description":"Create config.json with {\"team\": \"default\", \"shared\": \"base\"} and config.local.json with {\"shared\": \"override\", \"local_only\": true}; call resolve_config; verify result is {\"team\": \"default\", \"shared\": \"override\", \"local_only\": true} \u2014 base key preserved, shared key overridden, local-only key included","test_file":"dev/test_config_engine.py","test_name":"test_empty_local_returns_shared","tier":"unit","status":"pass","audit":""},{"id":"PROOF-4","description":"Create config.json with {\"team\": \"default\", \"shared\": \"base\"} and config.local.json with {\"shared\": \"override\", \"local_only\": true}; call resolve_config; verify result is {\"team\": \"default\", \"shared\": \"override\", \"local_only\": true} \u2014 base key preserved, shared key overridden, local-only key included","test_file":"dev/test_config_engine.py","test_name":"test_cli_dump","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-5","description":"`resolve_config` returns config.json contents when config.local.json does not exist","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Create config.json with {\"key\": \"val\"} and no config.local.json; call resolve_config; verify returns {\"key\": \"val\"} and config.local.json was NOT created","test_file":"dev/test_config_engine.py","test_name":"test_shared_only_no_copy","tier":"unit","status":"pass","audit":""},{"id":"PROOF-5","description":"Create config.json with {\"key\": \"val\"} and no config.local.json; call resolve_config; verify returns {\"key\": \"val\"} and config.local.json was NOT created","test_file":"dev/test_config_engine.py","test_name":"test_local_without_shared","tier":"unit","status":"pass","audit":""},{"id":"PROOF-5","description":"Create config.json with {\"key\": \"val\"} and no config.local.json; call resolve_config; verify returns {\"key\": \"val\"} and config.local.json was NOT created","test_file":"dev/test_config_engine.py","test_name":"test_cli_key","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-6","description":"`resolve_config` ignores config.local.json and warns to stderr when it contains invalid JSON, returning config.json only","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Create config.local.json with invalid JSON and config.json with {\"key\": \"fallback\"}; call resolve_config; verify returns {\"key\": \"fallback\"} and stderr contains warning","test_file":"dev/test_config_engine.py","test_name":"test_malformed_local_falls_back","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-7","description":"`resolve_config` returns empty dict when neither config file exists","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Call resolve_config in a dir with no config files; verify returns {}","test_file":"dev/test_config_engine.py","test_name":"test_no_configs_returns_empty","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-8","description":"`update_config` writes only to config.local.json, never to config.json","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-12","description":"Call update_config to set \"report\" to false; verify config.json is untouched and config.local.json now has \"report\": false; call resolve_config; verify merged result has \"report\": false (local override wins)","test_file":"dev/test_config_engine.py","test_name":"test_local_override_wins_in_merge","tier":"unit","status":"pass","audit":""},{"id":"PROOF-8","description":"Create config.json with {\"team\": \"v1\"}; call update_config(root, \"user_pref\", \"dark\"); verify config.json still has {\"team\": \"v1\"} unchanged and config.local.json has {\"user_pref\": \"dark\"}","test_file":"dev/test_config_engine.py","test_name":"test_writes_only_to_local","tier":"unit","status":"pass","audit":""},{"id":"PROOF-8","description":"Create config.json with {\"team\": \"v1\"}; call update_config(root, \"user_pref\", \"dark\"); verify config.json still has {\"team\": \"v1\"} unchanged and config.local.json has {\"user_pref\": \"dark\"}","test_file":"dev/test_config_engine.py","test_name":"test_update_creates_local_if_missing","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-9","description":"`update_config` preserves existing keys in config.local.json when adding or updating a key","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-9","description":"Create config.local.json with {\"existing\": \"keep\"}; call update_config(root, \"added\", \"new\"); verify config.local.json has both {\"existing\": \"keep\", \"added\": \"new\"}","test_file":"dev/test_config_engine.py","test_name":"test_preserves_existing_keys","tier":"unit","status":"pass","audit":""},{"id":"PROOF-9","description":"Create config.local.json with {\"existing\": \"keep\"}; call update_config(root, \"added\", \"new\"); verify config.local.json has both {\"existing\": \"keep\", \"added\": \"new\"}","test_file":"dev/test_config_engine.py","test_name":"test_update_overwrites_existing_key","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-1","description":"FORBIDDEN \u2014 No `eval()` or `exec()` calls in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Grep `scripts/` for `eval(` and `exec(` in `.py` files; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_eval_or_exec","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-2","description":"FORBIDDEN \u2014 No `subprocess` calls with `shell=True` in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Grep `scripts/**/*.py` for `shell\\s*=\\s*True`; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_shell_true","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-3","description":"FORBIDDEN \u2014 No `os.system()` calls in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Grep `scripts/**/*.py` for `os\\.system\\s*\\(`; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_os_system","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-4","description":"FORBIDDEN \u2014 No hardcoded credential assignments (password/secret/api_key/token literals) in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Grep `scripts/` for assignment patterns `(password|secret|api_key|token)\\s*=\\s*[\"'][^\"']+[\"']` in `.py`, `.sh`, `.js` files, excluding test files and comments; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_hardcoded_credentials","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-5","description":"All subprocess calls use list-mode arguments (not string commands)","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Grep `scripts/**/*.py` for `subprocess.run(` and `subprocess.call(`; verify each call's first argument is a list literal `[`, not a string","test_file":"dev/test_security.py","test_name":"test_subprocess_uses_list_args","tier":"unit","status":"pass","audit":""}]}],"audit":null},{"name":"dashboard_visual","category":"_anchors","type":"anchor","is_global":false,"source_url":null,"pinned":null,"description":"Defines the visual constants \u2014 colors, typography, status indicators, and spacing \u2014 for the Purlin dashboard. When required by other specs, it ensures the design system is enforced in one place.","source_path":null,"stack":null,"ext_status":null,"proved":11,"total":11,"deferred":0,"status":"VERIFIED","vhash":"2b240c30","receipt":{"commit":"4aa2a2e5db4c6232f1bf92169401ac2f3d68409f","timestamp":"2026-04-06T17:45:20.297645+00:00","stale":false},"rules":[{"id":"RULE-1","description":"Dark theme background is #0f172a, card background is #1e293b","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Load dashboard in Playwright with dark theme; verify computed background-color of body is #0f172a and card element is #1e293b","test_file":"dev/test_purlin_report.py","test_name":"test_dark_theme_backgrounds","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-10","description":"Integrity color coding: green at 80%+, amber at 50-79%, red below 50%, gray when null","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-10","description":"Load dashboard with features at integrity 90%, 60%, and 30%; verify green, amber, and red color classes respectively","test_file":"dev/test_purlin_report.py","test_name":"test_integrity_color_coding","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-11","description":"All colors and visual constants are defined as CSS custom properties in :root or theme selectors, not hardcoded in component styles","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-11","description":"Grep purlin-report.html for hardcoded hex colors outside of CSS custom property definitions; verify none exist in component style rules","test_file":"dev/test_purlin_report.py","test_name":"test_no_hardcoded_hex_outside_custom_properties","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-2","description":"Light theme background is #f1f5f9, card background is #ffffff","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Load dashboard in Playwright with light theme; verify computed background-color of body is #f1f5f9 and card element is #ffffff","test_file":"dev/test_purlin_report.py","test_name":"test_light_theme_backgrounds","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-3","description":"Status green is #22c55e, amber is #f59e0b, red is #ef4444, teal accent is #2dd4bf","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Grep purlin-report.html CSS for --green: #22c55e, --amber: #f59e0b, --red: #ef4444, --teal: #2dd4bf; verify all present","test_file":"dev/test_purlin_report.py","test_name":"test_status_colors_in_css","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-4","description":"Font stack is -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Grep purlin-report.html for the sans-serif font stack; verify it includes -apple-system and Roboto","test_file":"dev/test_purlin_report.py","test_name":"test_sans_serif_font_stack","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-5","description":"Monospace font stack is \"SF Mono\", \"Fira Code\", \"Cascadia Code\", \"JetBrains Mono\", Consolas, monospace","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Grep purlin-report.html for the monospace font stack; verify it includes \"SF Mono\" and Consolas","test_file":"dev/test_purlin_report.py","test_name":"test_mono_font_stack","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-6","description":"VERIFIED status badge is solid green pill with white text","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Load dashboard with a VERIFIED feature in Playwright; verify .sb-ready element has background-color matching --green and white text","test_file":"dev/test_purlin_report.py","test_name":"test_ready_badge_style","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-7","description":"PARTIAL status badge is amber outline pill (transparent background, amber border)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Load dashboard with a partial feature in Playwright; verify .sb-partial element has transparent background and amber border","test_file":"dev/test_purlin_report.py","test_name":"test_partial_badge_style","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-8","description":"FAIL status badge is solid red pill with white text","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-8","description":"Load dashboard with a FAIL feature in Playwright; verify .sb-fail element has background-color matching --red","test_file":"dev/test_purlin_report.py","test_name":"test_fail_badge_style","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-9","description":"UNTESTED status badge is gray pill with amber text; no-proofs badge (generic) is gray pill at reduced opacity","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-9","description":"Load dashboard with an UNTESTED feature in Playwright; verify .sb-untested element has gray background and amber text color; verify a separate no-proofs element (.sb-none) has reduced opacity","test_file":"dev/test_purlin_report.py","test_name":"test_untested_badge_and_no_proofs_opacity","tier":"unit","status":"pass","audit":""}]}],"audit":null},{"name":"drift","category":"mcp","type":"feature","is_global":false,"source_url":null,"pinned":null,"description":"Structured change summary tool for the purlin:drift skill. Resolves a \"since\" anchor, classifies changed files, detects spec drift, and returns machine-readable JSON for the drift skill to interpret and format.","source_path":null,"stack":"python/stdlib, json, subprocess (list-only), hashlib","ext_status":null,"proved":29,"total":29,"deferred":0,"status":"VERIFIED","vhash":"eb81ccd9","receipt":{"commit":"011ccbaa30dc0bae1ab95724e96609a5e91ecaba","timestamp":"2026-04-08T15:36:25.098091+00:00","stale":false},"rules":[{"id":"RULE-1","description":"Resolves the \"since\" anchor from: explicit argument (integer or date), most recent `verify:` commit, most recent tag, or smart fallback based on commits since Purlin initialization","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Call with explicit since=\"5\"; verify HEAD~5. Call with verify commit in log; verify that SHA","test_file":"dev/test_mcp_server.py","test_name":"test_since_anchor_resolution","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-10","description":"Detects broken scope paths \u2014 files referenced in `> Scope:` that no longer exist on disk \u2014 and returns them in a `broken_scopes` array","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-10","description":"Create spec with non-existent scope path; verify broken_scopes entry","test_file":"dev/test_mcp_server.py","test_name":"test_broken_scope_detection","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-11","description":"Returns a spec-from-code recommendation when no verification anchor exists and >= 30 commits have been made since Purlin initialization","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-11","description":"Create a temp repo with 50+ commits, no verify or tags; call drift; verify spec-from-code recommendation. Create repo with <30 commits; verify normal drift","test_file":"dev/test_mcp_server.py","test_name":"test_large_repo_recommends_spec_from_code","tier":"unit","status":"pass","audit":""},{"id":"PROOF-11","description":"Create a temp repo with 50+ commits, no verify or tags; call drift; verify spec-from-code recommendation. Create repo with <30 commits; verify normal drift","test_file":"dev/test_mcp_server.py","test_name":"test_small_repo_returns_normal_drift","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-12","description":"When an externally-referenced anchor has both external and local rules, drift detects staleness when the external source advances \u2014 returning an external_anchor_drift entry with status stale and the remote SHA","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-12","description":"e2e: Create bare git repo; create mixed anchor pinned to initial SHA; advance repo; run drift; verify external_anchor_drift entry with status=stale","test_file":"dev/test_drift.py","test_name":"test_mixed_anchor_stale_when_external_source_advances","tier":"e2e","status":"pass","audit":""},{"id":"PROOF-17","description":"e2e: Create external anchor pinned to initial SHA; advance repo; run drift; verify stale entry with remote SHA","test_file":"dev/test_drift.py","test_name":"test_stale_entry_includes_remote_sha","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-13","description":"When the external source advances AND the local anchor file is modified, drift surfaces both: an external_anchor_drift entry with status stale AND a spec_changes entry showing the new rule","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-14","description":"e2e: Advance external AND modify local anchor; run drift; verify both external_anchor_drift stale AND spec_changes with new_rules","test_file":"dev/test_drift.py","test_name":"test_external_advance_plus_local_modification_surfaces_both","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-14","description":"drift returns the anchor name in external_anchor_drift matching the anchor's spec name, not the external repo name or path","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-16","description":"e2e: Create anchor named local_security; advance repo; verify drift returns anchor=local_security","test_file":"dev/test_drift.py","test_name":"test_anchor_name_in_drift_matches_spec_name_not_repo_path","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-15","description":"Detects unpinned state when an anchor has `> Source:` but no `> Pinned:`, returning an external_anchor_drift entry with status `unpinned`","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-17","description":"e2e: Create external anchor pinned to initial SHA; advance repo; run drift; verify stale entry with remote SHA","test_file":"dev/test_mcp_server.py","test_name":"test_unpinned_anchor_detected","tier":"unit","status":"pass","audit":""},{"id":"PROOF-18","description":"e2e: Create anchor with Source but no Pinned; run drift; verify unpinned status","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"RULE-16","description":"Returns a `rule_details` object for each spec with CHANGED_BEHAVIOR files, containing per-rule ID, description, and proof status (pass/fail/unproved), plus the list of changed scope files","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-19","description":"Create spec with 3 rules and `> Scope:` pointing to a source file; add proofs for 2 of 3 rules; modify the scope file and commit; call drift; verify rule_details contains the spec with 3 rule entries, 2 with proof_status=pass, 1 with proof_status=unproved, and the changed file in changed_files","test_file":"dev/test_mcp_server.py","test_name":"test_rule_details_for_changed_behavior","tier":"integration","status":"pass","audit":""}]},{"id":"RULE-2","description":"Classifies changed files into categories: CHANGED_SPECS, TESTS_ADDED, CHANGED_BEHAVIOR, NO_IMPACT, NEW_BEHAVIOR","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Create changed spec, test file, and README; verify CHANGED_SPECS, TESTS_ADDED, NO_IMPACT classifications","test_file":"dev/test_mcp_server.py","test_name":"test_file_classification","tier":"unit","status":"pass","audit":""},{"id":"PROOF-13","description":"e2e: Modify local anchor file (add rule); commit; run drift; verify anchor classified as CHANGED_SPECS","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"RULE-3","description":"Returns JSON with `since`, `commits`, `files`, `spec_changes`, and `proof_status` fields","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Call drift; verify all 5 top-level JSON keys present","test_file":"dev/test_mcp_server.py","test_name":"test_drift_json_structure","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-4","description":"proof_status entries include a `structural_checks` count of structural proofs, and `proved`/`total` fields only count behavioral proofs","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Create spec with grep-based proof passing; verify proved=1 and total=1 in proof_status","test_file":"dev/test_mcp_server.py","test_name":"test_drift_structural_only_flag","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-5","description":"proof_status totals include required and global anchor rules, not just own rules","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Create feature requiring anchor with 2 rules; verify proof_status total includes anchor rules","test_file":"dev/test_mcp_server.py","test_name":"test_drift_includes_required_in_total","tier":"unit","status":"pass","audit":""},{"id":"PROOF-15","description":"e2e: Create feature requiring stale anchor; add all proofs; verify proof_status total=4 proved=4 despite staleness","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"RULE-6","description":"Classifies files in `skills/`, `agents/`, and `.claude/agents/` as NEW_BEHAVIOR when not in scope \u2014 never NO_IMPACT","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Create changed file at skills/new/SKILL.md with no scope; verify classified as NEW_BEHAVIOR","test_file":"dev/test_mcp_server.py","test_name":"test_skill_md_not_no_impact","tier":"unit","status":"pass","audit":""},{"id":"PROOF-6","description":"Create changed file at skills/new/SKILL.md with no scope; verify classified as NEW_BEHAVIOR","test_file":"dev/test_mcp_server.py","test_name":"test_agent_md_not_no_impact","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-7","description":"Scope matching supports directory prefix: `> Scope: src/api/` matches `src/api/login.js`","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Create spec with `> Scope: src/api/` and changed file src/api/login.js; verify CHANGED_BEHAVIOR","test_file":"dev/test_mcp_server.py","test_name":"test_scope_prefix_matching","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-8","description":"File entries include `behavioral_gap: true` when the file's spec has zero proved rules (total > 0 but proved == 0) and category is CHANGED_BEHAVIOR","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-8","description":"Create spec with zero proofs and changed scope file; verify behavioral_gap: true","test_file":"dev/test_mcp_server.py","test_name":"test_coverage_gap_drift_flag","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-9","description":"Returns a `drift_flags` array summarizing features with zero proved rules (coverage gap) that have changed files","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-9","description":"Same setup; verify drift_flags array entry with reason behavioral_gap_with_code_change","test_file":"dev/test_mcp_server.py","test_name":"test_drift_flags_array","tier":"unit","status":"pass","audit":""}]},{"id":"schema_spec_format/RULE-1","description":"Every spec MUST contain exactly two required sections: `## Rules`, `## Proof`","label":"required","source":"schema_spec_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Parse `references/formats/spec_format.md`; find the `## Required Sections` area; verify both sections (`## Rules`, `## Proof`) appear within it","test_file":"dev/test_schema_spec_format.py","test_name":"test_format_documents_two_sections","tier":"unit","status":"pass","audit":""}]},{"id":"schema_spec_format/RULE-2","description":"Rules are numbered sequentially as `RULE-N` (RULE-1, RULE-2, ...). Unnumbered lines under `## Rules` trigger a sync_status WARNING","label":"required","source":"schema_spec_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Create a spec with an unnumbered line under `## Rules` (e.g., `- some constraint without RULE-N prefix`); run sync_status; verify it reports a WARNING about unnumbered rules","test_file":"dev/test_schema_spec_format.py","test_name":"test_unnumbered_rule_triggers_warning","tier":"unit","status":"pass","audit":""}]},{"id":"schema_spec_format/RULE-3","description":"Proofs are numbered as `PROOF-N (RULE-N)` \u2014 each proof references the rule it covers","label":"required","source":"schema_spec_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Grep `specs/**/*.md` for lines under `## Proof`; verify every proof line matches the pattern `PROOF-\\d+ \\(RULE-\\d+\\)`","test_file":"dev/test_schema_spec_format.py","test_name":"test_all_proof_lines_match_pattern","tier":"unit","status":"pass","audit":""}]},{"id":"schema_spec_format/RULE-4","description":"Every RULE must have at least one corresponding PROOF","label":"required","source":"schema_spec_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Create a spec with RULE-1 but no PROOF referencing RULE-1; run sync_status; verify it reports the rule as uncovered","test_file":"dev/test_schema_spec_format.py","test_name":"test_rule_without_proof_shows_uncovered","tier":"unit","status":"pass","audit":""}]},{"id":"schema_spec_format/RULE-5","description":"`> Requires:` metadata, when present, is a comma-separated list of anchor names whose rules also apply to this feature","label":"required","source":"schema_spec_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Create a spec with `> Requires: base_anchor`; run sync_status; verify the report includes `base_anchor/RULE-1` in the coverage output","test_file":"dev/test_schema_spec_format.py","test_name":"test_requires_includes_referenced_rules","tier":"unit","status":"pass","audit":""}]},{"id":"schema_spec_format/RULE-6","description":"`> Scope:` metadata, when present, is a comma-separated list of source file paths the feature touches","label":"required","source":"schema_spec_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Create a spec with `> Scope: scripts/mcp/purlin_server.py`; verify sync_status parses and displays the scope files","test_file":"dev/test_schema_spec_format.py","test_name":"test_scope_metadata_parsed","tier":"unit","status":"pass","audit":""}]},{"id":"schema_spec_format/RULE-7","description":"Feature specs use heading `# Feature: `, anchor specs use `# Anchor: `","label":"required","source":"schema_spec_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Grep `specs/**/*.md` for first-level headings; verify each starts with `# Feature:` or `# Anchor:`","test_file":"dev/test_schema_spec_format.py","test_name":"test_spec_headings_use_correct_prefix","tier":"unit","status":"pass","audit":""}]},{"id":"schema_spec_format/RULE-8","description":"`> Description:` metadata, when present, is a plain-language summary of the spec; supports multi-line via `>` continuation lines that do not match a metadata field pattern (`> FieldName:`)","label":"required","source":"schema_spec_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-8","description":"Create a spec with `> Description: First line\\n> second line\\n> Scope: src/`; parse description; verify it equals \"First line second line\" (continuation joined, Scope not included)","test_file":"dev/test_schema_spec_format.py","test_name":"test_multiline_description_continuation","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-1","description":"FORBIDDEN \u2014 No `eval()` or `exec()` calls in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Grep `scripts/` for `eval(` and `exec(` in `.py` files; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_eval_or_exec","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-2","description":"FORBIDDEN \u2014 No `subprocess` calls with `shell=True` in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Grep `scripts/**/*.py` for `shell\\s*=\\s*True`; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_shell_true","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-3","description":"FORBIDDEN \u2014 No `os.system()` calls in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Grep `scripts/**/*.py` for `os\\.system\\s*\\(`; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_os_system","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-4","description":"FORBIDDEN \u2014 No hardcoded credential assignments (password/secret/api_key/token literals) in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Grep `scripts/` for assignment patterns `(password|secret|api_key|token)\\s*=\\s*[\"'][^\"']+[\"']` in `.py`, `.sh`, `.js` files, excluding test files and comments; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_hardcoded_credentials","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-5","description":"All subprocess calls use list-mode arguments (not string commands)","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Grep `scripts/**/*.py` for `subprocess.run(` and `subprocess.call(`; verify each call's first argument is a list literal `[`, not a string","test_file":"dev/test_security.py","test_name":"test_subprocess_uses_list_args","tier":"unit","status":"pass","audit":""}]}],"audit":null},{"name":"figma_web","category":"workflows","type":"feature","is_global":false,"source_url":null,"pinned":null,"description":"End-to-end workflow proof that the exact 5-message sequence from docs/examples/figma-web-app.md produces a working web UI with high visual fidelity to the Figma design. Uses the modal-test Figma file as the design reference. Each step is a real `claude -p` invocation \u2014 nothing is faked.","source_path":null,"stack":"python/pytest, playwright, shell/bash","ext_status":null,"proved":22,"total":22,"deferred":0,"status":"VERIFIED","vhash":"0ef07382","receipt":{"commit":"6cd4518e322d351ea3c91a7b877f1e59f90f2492","timestamp":"2026-04-07T13:46:48.452182+00:00","stale":false},"rules":[{"id":"RULE-1","description":"Prerequisites \u2014 purlin:init creates a valid Purlin project before the workflow starts","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Run purlin:init via claude -p in a temp dir; verify .purlin/config.json exists with required fields","test_file":"dev/test_e2e_figma_web.py","test_name":"test_init_config","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-10","description":"All 5 documented steps produce their expected artifacts \u2014 no extra commands needed","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-10","description":"Verify all 5 steps produced artifacts: config, anchor, spec, HTML, tests","test_file":"dev/test_e2e_figma_web.py","test_name":"test_all_steps_produced_artifacts","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-11","description":"FORBIDDEN \u2014 Modifying existing framework docs, proof files, skill definitions, or source code to make the workflow succeed","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-11","description":"Check git diff of framework repo; verify no new changes to docs/, skills/, scripts/, references/","test_file":"dev/test_e2e_figma_web.py","test_name":"test_no_framework_mods","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-12","description":"FORBIDDEN \u2014 Claude silently fixing errors; on failure it must report the issue","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-12","description":"Send a broken Figma URL via claude -p; verify Claude reports an error rather than silently succeeding","test_file":"dev/test_e2e_figma_web.py","test_name":"test_failure_reports_error","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-13","description":"The audit static_checks pipeline returns a classification for anchor proofs","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-13","description":"Run static_checks.py on the anchor test file; verify it returns proof classifications","test_file":"dev/test_e2e_figma_web.py","test_name":"test_audit_classification","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-14","description":"Each test run saves screenshot(s) to dev/figma_web_result.png showing the built UI","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-14","description":"Capture Playwright screenshot to dev/figma_web_result.png; verify valid PNG","test_file":"dev/test_e2e_figma_web.py","test_name":"test_screenshot_saved","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-15","description":"Message 4+5 \u2014 \"run the tests\" and \"verify and ship\" result in passing tests (pytest exit 0)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-15","description":"Send messages 4+5 via claude -p --resume; run pytest in the built project; verify exit code 0","test_file":"dev/test_e2e_figma_web.py","test_name":"test_verify_succeeds","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-2","description":"Message 1 \u2014 sharing the Figma URL creates a design anchor in specs/_anchors/ with Source, Visual-Reference, Pinned, and Type: design metadata","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Send message 1 (Figma URL) via claude -p --resume; verify anchor .md in specs/_anchors/ has > Source:, > Visual-Reference: figma://, > Pinned:, > Type: design","test_file":"dev/test_e2e_figma_web.py","test_name":"test_anchor_metadata","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-3","description":"The anchor contains at least one visual-match rule with an @e2e proof","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Read the anchor; verify it has a visual-match rule and an @e2e proof","test_file":"dev/test_e2e_figma_web.py","test_name":"test_anchor_visual_rule","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-4","description":"Message 2 \u2014 describing the feature creates a spec with > Requires: referencing the anchor","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Send message 2 via claude -p --resume; verify a feature spec exists with > Requires: referencing the anchor","test_file":"dev/test_e2e_figma_web.py","test_name":"test_spec_requires_anchor","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-5","description":"The anchor contains the real Figma file key, proving Figma MCP was used","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Verify the anchor text contains the Figma file key TEZI0T6lObCJrC9mkmZT8v","test_file":"dev/test_e2e_figma_web.py","test_name":"test_figma_mcp_used","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-6","description":"Message 3 \u2014 \"build it\" produces a functioning HTML file that renders in a browser","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Send message 3 via claude -p --resume; open the built HTML in Playwright; verify page renders visible content","test_file":"dev/test_e2e_figma_web.py","test_name":"test_ui_renders","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-7","description":"The build writes tests with proof markers referencing the feature and anchor","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Read test files; verify proof markers reference both feature and anchor names","test_file":"dev/test_e2e_figma_web.py","test_name":"test_proof_markers","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-8","description":"A Playwright screenshot of the built UI can be captured and compared against a reference","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-8","description":"Capture Playwright screenshot of built UI; verify it is a valid PNG alongside the MCP fixture reference","test_file":"dev/test_e2e_figma_web.py","test_name":"test_screenshot_pipeline","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-9","description":"The built UI visually matches the Figma design within the fidelity threshold","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-9","description":"Compute pixel diff between built UI screenshot and MCP fixture reference; verify diff is within threshold","test_file":"dev/test_e2e_figma_web.py","test_name":"test_visual_fidelity","tier":"e2e","status":"pass","audit":""}]},{"id":"skill_anchor/RULE-1","description":"Skill file has YAML frontmatter with `name` and `description` fields","label":"required","source":"skill_anchor","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Grep `skills/anchor/SKILL.md` for YAML frontmatter delimiters (`---`); verify `name:` and `description:` fields exist","test_file":"dev/test_skill_specs.py","test_name":"test_has_frontmatter","tier":"unit","status":"pass","audit":""}]},{"id":"skill_anchor/RULE-2","description":"Skill file contains a `## Usage` section documenting command syntax","label":"required","source":"skill_anchor","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Grep `skills/anchor/SKILL.md` for `## Usage`; verify the section exists","test_file":"dev/test_skill_specs.py","test_name":"test_has_usage_section","tier":"unit","status":"pass","audit":""}]},{"id":"skill_anchor/RULE-3","description":"The `name` field in frontmatter is `anchor`, matching the directory name","label":"required","source":"skill_anchor","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Extract `name:` from frontmatter; verify it equals `anchor`","test_file":"dev/test_skill_specs.py","test_name":"test_name_matches_directory","tier":"unit","status":"pass","audit":""}]},{"id":"skill_anchor/RULE-4","description":"Skill includes commit instructions or git operations for file modifications","label":"required","source":"skill_anchor","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Grep `skills/anchor/SKILL.md` for commit instructions (`git commit`, `commit the`, `create.*commit`); verify present","test_file":"dev/test_skill_specs.py","test_name":"test_has_commit_instructions","tier":"unit","status":"pass","audit":""}]},{"id":"skill_anchor/RULE-5","description":"An anchor written with only Part 1 (authoring) fields \u2014 no `> Source:`, `> Path:`, or `> Pinned:` \u2014 is parsed as a local anchor: `source_url` is None, no staleness checks run, and sync_status output contains no Source/Pinned lines for that anchor","label":"required","source":"skill_anchor","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"e2e: Create anchor with only Part 1 fields (Description, Type, rules, proofs \u2014 no Source/Path/Pinned); call _scan_specs; verify source_url is None and pinned is None. Run sync_status; verify output does NOT contain \"Source:\" or \"Pinned:\" for that anchor","test_file":"dev/test_e2e_external_refs.sh","test_name":"Part 1-only anchor parsed as local","tier":"e2e","status":"pass","audit":""}]},{"id":"skill_anchor/RULE-6","description":"An anchor with Part 2 tracking fields (`> Source:`, `> Path:`, `> Pinned:`) added is parsed as externally-referenced: `source_url`, `pinned`, and `source_path` are all populated, and sync_status output shows Source/Path/Pinned lines for that anchor","label":"required","source":"skill_anchor","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"e2e: Create anchor with Part 1 fields plus Part 2 tracking fields (Source, Path, Pinned pointing to a local bare git repo); call _scan_specs; verify source_url, pinned, and source_path are all populated with correct values. Run sync_status; verify output contains \"Source:\", \"Path:\", and \"Pinned:\" lines","test_file":"dev/test_e2e_external_refs.sh","test_name":"Part 2 tracking fields fully parsed","tier":"e2e","status":"pass","audit":""}]},{"id":"skill_anchor/RULE-7","description":"Adding Part 2 tracking fields to a Part 1-only anchor transitions it from local to externally-referenced \u2014 the same anchor file, before and after adding `> Source:` and `> Pinned:`, produces different `_scan_specs` results for `source_url` and `pinned`","label":"required","source":"skill_anchor","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"e2e: Create a Part 1-only anchor; call _scan_specs and capture source_url (None). Then add Part 2 fields to the same file; call _scan_specs again; verify source_url is now populated and pinned matches the written value","test_file":"dev/test_e2e_external_refs.sh","test_name":"Part 1\u21922 transition changes scan results","tier":"e2e","status":"pass","audit":""}]}],"audit":null},{"name":"mcp_transport","category":"mcp","type":"feature","is_global":false,"source_url":null,"pinned":null,"description":"JSON-RPC 2.0 transport layer for the Purlin MCP server. Reads requests from stdin, dispatches to tool handlers, writes responses to stdout. Implements MCP protocol initialization and error handling.","source_path":null,"stack":"python/stdlib, json","ext_status":null,"proved":12,"total":12,"deferred":0,"status":"VERIFIED","vhash":"3769b2fe","receipt":{"commit":"6cd4518e322d351ea3c91a7b877f1e59f90f2492","timestamp":"2026-04-07T13:46:48.452182+00:00","stale":false},"rules":[{"id":"RULE-1","description":"The server implements MCP protocol version `2024-11-05` and responds to `initialize` with protocolVersion, capabilities, and serverInfo","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Send initialize request; verify protocolVersion and serverInfo","test_file":"dev/test_mcp_server.py","test_name":"test_initialize","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-2","description":"`tools/list` returns exactly 3 tools: `sync_status`, `purlin_config`, `drift` \u2014 matching the manifest definitions","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Send tools/list; verify exactly 3 tools named sync_status, purlin_config, drift","test_file":"dev/test_mcp_server.py","test_name":"test_tools_list","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-3","description":"`notifications/initialized` produces no response (notification, not request)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Send notifications/initialized; verify no response on stdout","test_file":"dev/test_mcp_server.py","test_name":"test_notification_no_response","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-4","description":"Invalid JSON input returns error code `-32700` (Parse error)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Send invalid JSON; verify error code -32700","test_file":"dev/test_mcp_server.py","test_name":"test_parse_error","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-5","description":"Unknown methods return error code `-32601` with the method name in the message","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Send unknown method; verify error code -32601 with method name","test_file":"dev/test_mcp_server.py","test_name":"test_unknown_method","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-6","description":"Unknown tool names in `tools/call` return error code `-32601` with the tool name in the message","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Send tools/call with unknown tool; verify error code -32601 with tool name","test_file":"dev/test_mcp_server.py","test_name":"test_unknown_tool","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-7","description":"Server logs startup to stderr \u2014 stdout is reserved for JSON-RPC responses","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Start server; verify startup message on stderr, not stdout","test_file":"dev/test_mcp_server.py","test_name":"test_startup_logs_to_stderr","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-1","description":"FORBIDDEN \u2014 No `eval()` or `exec()` calls in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Grep `scripts/` for `eval(` and `exec(` in `.py` files; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_eval_or_exec","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-2","description":"FORBIDDEN \u2014 No `subprocess` calls with `shell=True` in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Grep `scripts/**/*.py` for `shell\\s*=\\s*True`; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_shell_true","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-3","description":"FORBIDDEN \u2014 No `os.system()` calls in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Grep `scripts/**/*.py` for `os\\.system\\s*\\(`; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_os_system","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-4","description":"FORBIDDEN \u2014 No hardcoded credential assignments (password/secret/api_key/token literals) in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Grep `scripts/` for assignment patterns `(password|secret|api_key|token)\\s*=\\s*[\"'][^\"']+[\"']` in `.py`, `.sh`, `.js` files, excluding test files and comments; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_hardcoded_credentials","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-5","description":"All subprocess calls use list-mode arguments (not string commands)","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Grep `scripts/**/*.py` for `subprocess.run(` and `subprocess.call(`; verify each call's first argument is a list literal `[`, not a string","test_file":"dev/test_security.py","test_name":"test_subprocess_uses_list_args","tier":"unit","status":"pass","audit":""}]}],"audit":null},{"name":"pre_push_hook","category":"hooks","type":"feature","is_global":false,"source_url":null,"pinned":null,"description":"Pre-push git hook that blocks pushes when tests are failing. Runs unit-tier tests and checks sync_status for proof coverage. Supports two modes: warn (default) blocks only on FAILING, while strict blocks anything not VERIFIED.","source_path":null,"stack":"shell/bash, python3 (sync_status invocation, config parsing)","ext_status":null,"proved":14,"total":14,"deferred":0,"status":"VERIFIED","vhash":"0e655efb","receipt":{"commit":"6cd4518e322d351ea3c91a7b877f1e59f90f2492","timestamp":"2026-04-07T13:46:48.452182+00:00","stale":false},"rules":[{"id":"RULE-1","description":"Blocks push with exit 1 when any feature has status FAILING in sync_status output","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-17","description":"e2e: Create external anchor; create feature requiring it; set anchor proof to FAIL; run pre-push; verify exit 1 blocked","test_file":"dev/test_e2e_external_refs.sh","test_name":"pre-push blocks on anchor fail","tier":"unit","status":"pass","audit":""},{"id":"PROOF-17","description":"e2e: Create external anchor; create feature requiring it; set anchor proof to FAIL; run pre-push; verify exit 1 blocked","test_file":"dev/test_e2e_external_refs.sh","test_name":"pre-push blocks on anchor fail","tier":"e2e","status":"pass","audit":""},{"id":"PROOF-1","description":"Set up temp project with .purlin/ and specs/; create a spec with 3 rules; create proof file with one FAIL entry; run pre-push.sh; verify exit code is 1 and stdout contains \"PUSH BLOCKED\"","test_file":"","test_name":"","tier":"integration","status":"planned","audit":""},{"id":"PROOF-8","description":"Full lifecycle test: create temp git repo with .purlin/ and specs/; create spec with 3 rules; create proof file with 1 PASS, 1 FAIL, 1 unproved; run hook and verify exit 1 (blocked by FAIL); fix FAIL to PASS; run hook and verify exit 0 with warning (PARTIAL \u2014 unproved rules remain); add missing proof as PASS; run hook and verify exit 0 silently (all PASSING)","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"RULE-2","description":"Allows push with exit 0 and prints a warning when any feature is PARTIAL (some behavioral rules proved, none failing)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Set up temp project with .purlin/ and specs/; create proof file covering some but not all behavioral rules with no FAIL entries; run pre-push.sh; verify exit code is 0 and stdout contains \"partial coverage\"","test_file":"dev/test_pre_push_hook.py","test_name":"test_partial_coverage_exits_0_with_warning","tier":"integration","status":"pass","audit":""}]},{"id":"RULE-3","description":"Allows push with exit 0 silently when no specs directory exists or specs directory contains no .md files","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Set up temp project with .purlin/ but no specs/ directory; run pre-push.sh; verify exit code is 0 and stdout is empty","test_file":"dev/test_pre_push_hook.py","test_name":"test_no_specs_dir_exits_0_silently","tier":"integration","status":"pass","audit":""}]},{"id":"RULE-4","description":"Allows push with exit 0 when all features are PASSING or VERIFIED (all behavioral rules proved, no failures)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Set up temp project with .purlin/ and specs/; create proof file with all PASS entries (PASSING status); run pre-push.sh; verify exit code is 0","test_file":"dev/test_pre_push_hook.py","test_name":"test_all_pass_exits_0","tier":"integration","status":"pass","audit":""},{"id":"PROOF-12","description":"Create spec with rule description containing \"FAIL\" (e.g. \"RULE-1: FAIL status badge is solid red pill\"); create all-pass proofs; run hook; verify exit 0 and no \"PUSH BLOCKED\" \u2014 description text must not false-positive trigger blocking","test_file":"dev/test_pre_push_hook.py","test_name":"test_fail_keyword_in_rule_description_does_not_block","tier":"integration","status":"pass","audit":""}]},{"id":"RULE-5","description":"Detects test framework from `.purlin/config.json` `test_framework` field, falling back to auto-detection (pytest if conftest.py or pyproject.toml [tool.pytest] exists, jest if package.json contains jest, shell otherwise)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Set up temp project with `.purlin/config.json` containing `{\"test_framework\": \"pytest\"}`; verify pre-push.sh selects pytest; repeat with `{\"test_framework\": \"jest\"}` and verify jest is selected","test_file":"dev/test_pre_push_hook.py","test_name":"test_detects_framework_from_config_and_auto","tier":"integration","status":"pass","audit":""}]},{"id":"RULE-6","description":"Runs only unit-tier tests (pytest excludes `not integration`, jest uses `--testPathPattern=unit`, shell runs `*.test.sh`)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Set up temp project with conftest.py and two test files (one unit, one @integration); run pre-push.sh; verify the unit test ran (sentinel file created) and the integration test was skipped (no sentinel)","test_file":"dev/test_pre_push_hook.py","test_name":"test_unit_test_runs_integration_skipped","tier":"integration","status":"pass","audit":""}]},{"id":"RULE-7","description":"Produces output showing which features passed, which have partial coverage, and which are blocked with FAIL proofs","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Set up temp project with specs containing PASSING, FAILING, and PARTIAL features; run pre-push.sh; verify stdout contains \"PASSING features\", \"partial coverage\", and \"PUSH BLOCKED\" sections","test_file":"dev/test_pre_push_hook.py","test_name":"test_output_shows_blocked_and_recovery_on_fail","tier":"integration","status":"pass","audit":""},{"id":"PROOF-13","description":"Create spec with one failing proof; run hook; verify recovery message contains `/purlin:unit-test test_feature`, `/purlin:status`, and `/purlin:build`","test_file":"dev/test_pre_push_hook.py","test_name":"test_recovery_message_lists_feature_specific_commands","tier":"integration","status":"pass","audit":""}]},{"id":"RULE-8","description":"In strict mode (`\"pre_push\": \"strict\"` in config), blocks push with exit 1 when any feature is not VERIFIED \u2014 this includes PASSING features (full coverage but no receipt) and PARTIAL features (incomplete behavioral rule coverage); allows push only when all features are VERIFIED","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-9","description":"Set strict mode in config; create proofs for 2 of 3 behavioral rules (PARTIAL \u2014 no FAIL, but not fully covered); run hook; verify exit 1 and output contains \"strict mode\"","test_file":"dev/test_pre_push_hook.py","test_name":"test_strict_mode_blocks_partial_coverage","tier":"integration","status":"pass","audit":""},{"id":"PROOF-10","description":"Set strict mode in config; create proofs for all behavioral rules (PASSING \u2014 full coverage, no receipt); run hook; verify exit 1 and output contains \"strict mode\" (PASSING blocked in strict, only VERIFIED allowed)","test_file":"dev/test_pre_push_hook.py","test_name":"test_strict_mode_allows_passing_blocks_partial","tier":"integration","status":"pass","audit":""},{"id":"PROOF-14","description":"Set strict mode; create partial proofs (no FAIL); run hook; verify exit 1 and output contains `RECOVERY STEPS`, `/purlin:unit-test`, and `/purlin:verify`","test_file":"dev/test_pre_push_hook.py","test_name":"test_strict_mode_recovery_includes_verify_command","tier":"integration","status":"pass","audit":""},{"id":"PROOF-15","description":"e2e: Strict mode with own rules proved but required rules unproved; verify exit 1 with strict mode block","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""},{"id":"PROOF-16","description":"e2e: Strict mode with all (own + required) rules proved; verify exit 0","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"RULE-9","description":"After `purlin:init`, `.git/hooks/pre-push` exists, is executable, and runs `scripts/hooks/pre-push.sh`","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-11","description":"Run purlin:init on a fresh git repo; verify .git/hooks/pre-push exists, is executable, and a git push with failing proofs is intercepted and blocked","test_file":"dev/test_pre_push_hook.py","test_name":"test_installed_hook_exists_is_executable_and_blocks","tier":"integration","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-1","description":"FORBIDDEN \u2014 No `eval()` or `exec()` calls in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Grep `scripts/` for `eval(` and `exec(` in `.py` files; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_eval_or_exec","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-2","description":"FORBIDDEN \u2014 No `subprocess` calls with `shell=True` in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Grep `scripts/**/*.py` for `shell\\s*=\\s*True`; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_shell_true","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-3","description":"FORBIDDEN \u2014 No `os.system()` calls in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Grep `scripts/**/*.py` for `os\\.system\\s*\\(`; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_os_system","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-4","description":"FORBIDDEN \u2014 No hardcoded credential assignments (password/secret/api_key/token literals) in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Grep `scripts/` for assignment patterns `(password|secret|api_key|token)\\s*=\\s*[\"'][^\"']+[\"']` in `.py`, `.sh`, `.js` files, excluding test files and comments; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_hardcoded_credentials","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-5","description":"All subprocess calls use list-mode arguments (not string commands)","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Grep `scripts/**/*.py` for `subprocess.run(` and `subprocess.call(`; verify each call's first argument is a list literal `[`, not a string","test_file":"dev/test_security.py","test_name":"test_subprocess_uses_list_args","tier":"unit","status":"pass","audit":""}]}],"audit":null},{"name":"proof_common","category":"_anchors","type":"anchor","is_global":false,"source_url":null,"pinned":null,"description":"The behavior every Purlin proof plugin implements regardless of language \u2014 spec-directory resolution, proof-file naming, fallback, feature-scoped overwrite, the 7 required fields, pass/fail status, the no-marker no-op, glob-based discovery, the fallback stderr warning, and purge-on-rerun. Each per-language proof plugin spec (proof_plugins_pytest, proof_plugins_jest, proof_plugins_shell, proof_plugins_c, proof_plugins_php, proof_plugins_sql, proof_plugins_vitest, proof_plugins_xunit) requires this anchor and adds only its framework-specific rules.","source_path":null,"stack":null,"ext_status":null,"proved":10,"total":10,"deferred":0,"status":"VERIFIED","vhash":"b7f9d949","receipt":{"commit":"ac522f60debf1f866e0a6acfb25bcb10fafd2057","timestamp":"2026-06-02T15:45:42.990726+00:00","stale":false},"rules":[{"id":"RULE-1","description":"Each plugin resolves the spec directory by scanning `specs/**/*.md` and matching the feature name to the spec filename stem","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Create `specs/hooks/gate_hook.md` and run a proof plugin with feature `gate_hook`; verify the proof file is written to `specs/hooks/`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"spec dir resolution","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-10","description":"When a test is removed from a re-run, the old proof entry is purged and not carried over from the previous proof file","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-13","description":"Write a proof file with 2 proofs, then re-run with only 1; verify the removed entry is purged and not carried over","test_file":"dev/test_multilang_proof_plugins.py","test_name":"test_removed_test_purged_on_rerun","tier":"integration","status":"pass","audit":""},{"id":"PROOF-12","description":"e2e: Write proof file with only 1 of 2 proofs (test deletion); verify coverage shows 1/2 not 2/2","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"RULE-2","description":"Proof files are written to the spec's directory as `.proofs-.json`","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Run a proof plugin for feature `gate_hook` tier `unit`; verify output file is named `gate_hook.proofs-unit.json`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"proof file naming","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-3","description":"When the spec directory for a feature is not found, the plugin falls back to writing to `specs/`","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Run a proof plugin for feature `nonexistent_feature` (no matching spec); verify proof file is written to `specs/`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"fallback to specs/","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-4","description":"Feature-scoped overwrite: existing entries for other features are preserved; only the current feature's entries are replaced","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Create a proof file with entries for features A and B; run the plugin for feature A only; verify feature B entries are preserved and feature A entries are replaced","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"feature-scoped overwrite","tier":"unit","status":"pass","audit":""},{"id":"PROOF-10","description":"e2e: Create 2 specs; write proofs for each separately; verify both PASSING and no cross-contamination","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""},{"id":"PROOF-11","description":"e2e: Overwrite one feature's proofs via shell harness; verify target feature updated, other feature untouched","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"RULE-5","description":"Each proof entry contains all 7 required fields: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Run a proof plugin and read the output JSON; verify each entry in `proofs` array contains all 7 fields: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","test_file":"dev/test_multilang_proof_plugins.py","test_name":"test_python_proof_plugin_real_execution","tier":"integration","status":"pass","audit":""},{"id":"PROOF-5","description":"Run a proof plugin and read the output JSON; verify each entry in `proofs` array contains all 7 fields: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"all 7 required fields","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-6","description":"`status` is `\"pass\"` when the test passes and `\"fail\"` when it fails \u2014 no other values","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Run a passing test and a failing test with proof markers; verify the passing test has `status: \"pass\"` and the failing test has `status: \"fail\"`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"pass/fail status","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-7","description":"If no proof markers are collected during a test run, no proof files are written (no-op)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Run a test suite with no proof markers; verify no `*.proofs-*.json` files are created","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"no markers no files","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-8","description":"Custom/community proof plugins installed to `.purlin/plugins/` require no registration \u2014 `sync_status` discovers proof files by globbing `specs/**/*.proofs-*.json`, so any plugin that writes files in that pattern works automatically","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-8","description":"Place a `.proofs-unit.json` file in a spec directory written by a non-built-in source; run `sync_status`; verify it reads the proofs","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"custom plugin proofs discovered by sync_status","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-9","description":"When spec directory lookup falls back to specs/ root, the plugin emits a warning to stderr naming the missing spec and suggesting purlin:spec ","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-9","description":"Run a proof plugin for a feature with no matching spec; verify stderr contains a warning naming the feature and suggesting purlin:spec","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"fallback warning to stderr","tier":"unit","status":"pass","audit":""}]}],"audit":null},{"name":"proof_plugins_c","category":"proof","type":"feature","is_global":false,"source_url":null,"pinned":null,"description":"The C proof plugin. A header-only collector prints accumulated proofs as JSON to stdout; a python emitter reads stdin and writes proof files. Inherits all shared proof-plugin behavior from proof_common.","source_path":null,"stack":"c/gcc (header-only collector), python3 emitter reading stdin","ext_status":null,"proved":24,"total":24,"deferred":0,"status":"VERIFIED","vhash":"36f70645","receipt":{"commit":"ac522f60debf1f866e0a6acfb25bcb10fafd2057","timestamp":"2026-06-02T15:45:42.990726+00:00","stale":false},"rules":[{"id":"RULE-1","description":"The C marker is `purlin_proof(\"feature\", \"PROOF-N\", \"RULE-N\", passed_bool, \"test_name\", __FILE__, \"tier\")` called from C source code","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Compile a C test with `purlin_proof()` calls using gcc; run the binary; verify stdout JSON contains all 7 required fields","test_file":"dev/test_multilang_proof_plugins.py","test_name":"test_c_proof_plugin_real_compilation","tier":"integration","status":"pass","audit":""},{"id":"PROOF-3","description":"Compile a C test where the `passed_bool` is false; run and pipe to emitter; verify `status: \"fail\"` in the proof file","test_file":"dev/test_multilang_proof_plugins.py","test_name":"test_c_proof_plugin_failing_test","tier":"integration","status":"pass","audit":""}]},{"id":"RULE-2","description":"`purlin_proof_finish()` prints accumulated proofs as JSON to stdout; `c_purlin_emit.py` reads stdin and performs feature-scoped overwrite to proof files","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Compile and run a C test; pipe stdout to `c_purlin_emit.py`; verify proof file is written to spec directory with correct entries","test_file":"dev/test_multilang_proof_plugins.py","test_name":"test_c_emit_pipeline_writes_to_spec_dir","tier":"integration","status":"pass","audit":""}]},{"id":"proof_common/RULE-1","description":"Each plugin resolves the spec directory by scanning `specs/**/*.md` and matching the feature name to the spec filename stem","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Create `specs/hooks/gate_hook.md` and run a proof plugin with feature `gate_hook`; verify the proof file is written to `specs/hooks/`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"spec dir resolution","tier":"unit","status":"pass","audit":""}]},{"id":"proof_common/RULE-10","description":"When a test is removed from a re-run, the old proof entry is purged and not carried over from the previous proof file","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-13","description":"Write a proof file with 2 proofs, then re-run with only 1; verify the removed entry is purged and not carried over","test_file":"dev/test_multilang_proof_plugins.py","test_name":"test_removed_test_purged_on_rerun","tier":"integration","status":"pass","audit":""},{"id":"PROOF-12","description":"e2e: Write proof file with only 1 of 2 proofs (test deletion); verify coverage shows 1/2 not 2/2","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"proof_common/RULE-2","description":"Proof files are written to the spec's directory as `.proofs-.json`","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Run a proof plugin for feature `gate_hook` tier `unit`; verify output file is named `gate_hook.proofs-unit.json`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"proof file naming","tier":"unit","status":"pass","audit":""}]},{"id":"proof_common/RULE-3","description":"When the spec directory for a feature is not found, the plugin falls back to writing to `specs/`","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Run a proof plugin for feature `nonexistent_feature` (no matching spec); verify proof file is written to `specs/`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"fallback to specs/","tier":"unit","status":"pass","audit":""}]},{"id":"proof_common/RULE-4","description":"Feature-scoped overwrite: existing entries for other features are preserved; only the current feature's entries are replaced","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Create a proof file with entries for features A and B; run the plugin for feature A only; verify feature B entries are preserved and feature A entries are replaced","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"feature-scoped overwrite","tier":"unit","status":"pass","audit":""},{"id":"PROOF-10","description":"e2e: Create 2 specs; write proofs for each separately; verify both PASSING and no cross-contamination","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""},{"id":"PROOF-11","description":"e2e: Overwrite one feature's proofs via shell harness; verify target feature updated, other feature untouched","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"proof_common/RULE-5","description":"Each proof entry contains all 7 required fields: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Run a proof plugin and read the output JSON; verify each entry in `proofs` array contains all 7 fields: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","test_file":"dev/test_multilang_proof_plugins.py","test_name":"test_python_proof_plugin_real_execution","tier":"integration","status":"pass","audit":""},{"id":"PROOF-5","description":"Run a proof plugin and read the output JSON; verify each entry in `proofs` array contains all 7 fields: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"all 7 required fields","tier":"unit","status":"pass","audit":""}]},{"id":"proof_common/RULE-6","description":"`status` is `\"pass\"` when the test passes and `\"fail\"` when it fails \u2014 no other values","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Run a passing test and a failing test with proof markers; verify the passing test has `status: \"pass\"` and the failing test has `status: \"fail\"`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"pass/fail status","tier":"unit","status":"pass","audit":""}]},{"id":"proof_common/RULE-7","description":"If no proof markers are collected during a test run, no proof files are written (no-op)","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Run a test suite with no proof markers; verify no `*.proofs-*.json` files are created","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"no markers no files","tier":"unit","status":"pass","audit":""}]},{"id":"proof_common/RULE-8","description":"Custom/community proof plugins installed to `.purlin/plugins/` require no registration \u2014 `sync_status` discovers proof files by globbing `specs/**/*.proofs-*.json`, so any plugin that writes files in that pattern works automatically","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-8","description":"Place a `.proofs-unit.json` file in a spec directory written by a non-built-in source; run `sync_status`; verify it reads the proofs","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"custom plugin proofs discovered by sync_status","tier":"unit","status":"pass","audit":""}]},{"id":"proof_common/RULE-9","description":"When spec directory lookup falls back to specs/ root, the plugin emits a warning to stderr naming the missing spec and suggesting purlin:spec ","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-9","description":"Run a proof plugin for a feature with no matching spec; verify stderr contains a warning naming the feature and suggesting purlin:spec","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"fallback warning to stderr","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-1","description":"Proof files are named `.proofs-.json` and live in the same directory as their spec","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Create a proof file at `specs/test/foo.proofs-unit.json`; run sync_status; verify it reads the file and associates it with the `foo` spec","test_file":"dev/test_schema_proof_format.py","test_name":"test_proof_file_read_by_sync_status","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-2","description":"The JSON schema has a top-level `tier` string and a `proofs` array. Each proof entry contains: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Validate a proof JSON file against the required fields; verify all 7 fields (`feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`) are present in each proof entry","test_file":"dev/test_schema_proof_format.py","test_name":"test_proof_entry_has_all_seven_fields","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-3","description":"`status` is either `\"pass\"` or `\"fail\"` \u2014 no other values","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Create a proof entry with `status: \"error\"`; run sync_status; verify it is not counted as a passing proof","test_file":"dev/test_schema_proof_format.py","test_name":"test_invalid_status_not_counted","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-4","description":"`tier` values are: `\"unit\"`, `\"integration\"`, `\"e2e\"`, and `\"windows\"` (a platform-gated tier proven only on a Windows runner) \u2014 no other automated tiers","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Scan all real `*.proofs-*.json` files; verify every tier value (top-level and per-entry) is in the valid set {unit, integration, e2e, windows}. Verify `spec_format.md` documents @integration, @e2e, and","test_file":"dev/test_schema_proof_format.py","test_name":"test_standard_tiers_documented","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-5","description":"Proof plugins use feature-scoped overwrite: load existing file, purge entries matching current feature, append new entries, write merged result","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Pre-seed a proof file with feature B entries; run the real pytest proof plugin for feature A via subprocess; verify feature B entries are preserved and feature A entries are added","test_file":"dev/test_schema_proof_format.py","test_name":"test_feature_scoped_overwrite","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-6","description":"Proof files are committed to git \u2014 they are project records, not ephemeral build artifacts","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Grep `.gitignore` for `*.proofs-*.json`; verify no gitignore rule excludes proof files","test_file":"dev/test_schema_proof_format.py","test_name":"test_proof_files_not_gitignored","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-7","description":"Manual proofs are stamped inline in the spec's `## Proof` section as `@manual(, , )`, not in proof JSON files","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Grep `references/formats/proofs_format.md` for `@manual`; verify manual stamp format is `@manual(, , )`","test_file":"dev/test_schema_proof_format.py","test_name":"test_manual_stamp_format_documented","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-1","description":"FORBIDDEN \u2014 No `eval()` or `exec()` calls in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Grep `scripts/` for `eval(` and `exec(` in `.py` files; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_eval_or_exec","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-2","description":"FORBIDDEN \u2014 No `subprocess` calls with `shell=True` in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Grep `scripts/**/*.py` for `shell\\s*=\\s*True`; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_shell_true","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-3","description":"FORBIDDEN \u2014 No `os.system()` calls in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Grep `scripts/**/*.py` for `os\\.system\\s*\\(`; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_os_system","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-4","description":"FORBIDDEN \u2014 No hardcoded credential assignments (password/secret/api_key/token literals) in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Grep `scripts/` for assignment patterns `(password|secret|api_key|token)\\s*=\\s*[\"'][^\"']+[\"']` in `.py`, `.sh`, `.js` files, excluding test files and comments; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_hardcoded_credentials","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-5","description":"All subprocess calls use list-mode arguments (not string commands)","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Grep `scripts/**/*.py` for `subprocess.run(` and `subprocess.call(`; verify each call's first argument is a list literal `[`, not a string","test_file":"dev/test_security.py","test_name":"test_subprocess_uses_list_args","tier":"unit","status":"pass","audit":""}]}],"audit":null},{"name":"proof_plugins_jest","category":"proof","type":"feature","is_global":false,"source_url":null,"pinned":null,"description":"The Jest proof reporter. Parses `[proof:...]` markers from test titles and emits standardized proof JSON. Inherits all shared proof-plugin behavior from proof_common.","source_path":null,"stack":"node/jest, custom reporter (onTestResult + onRunComplete hooks)","ext_status":null,"proved":26,"total":26,"deferred":0,"status":"VERIFIED","vhash":"2e0ab5ae","receipt":{"commit":"ac522f60debf1f866e0a6acfb25bcb10fafd2057","timestamp":"2026-06-02T15:45:42.990726+00:00","stale":false},"rules":[{"id":"RULE-1","description":"The marker is parsed from the test title using the pattern `[proof:feature:PROOF-N:RULE-N:tier]` where tier defaults to `\"unit\"`","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Create a test `it(\"works [proof:feat:PROOF-1:RULE-1:unit]\", ...)`; run Jest; verify proof entry has `feature: \"feat\"`, `id: \"PROOF-1\"`, `rule: \"RULE-1\"`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"jest marker parsing","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-2","description":"Test names without a `[proof:...]` marker are ignored","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Create a test without `[proof:...]` in the title; run Jest; verify no proof entry is emitted for that test","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"jest no marker ignored","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-3","description":"`test_file` is recorded as the path relative to Jest's `rootDir`","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Run Jest; verify `test_file` is relative to `rootDir`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"jest test_file relative","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-4","description":"Jest status `\"passed\"` maps to `\"pass\"` and all other statuses map to `\"fail\"`","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Create a failing Jest test with a proof marker; verify `status` is `\"fail\"`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"jest status mapping","tier":"unit","status":"pass","audit":""}]},{"id":"proof_common/RULE-1","description":"Each plugin resolves the spec directory by scanning `specs/**/*.md` and matching the feature name to the spec filename stem","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Create `specs/hooks/gate_hook.md` and run a proof plugin with feature `gate_hook`; verify the proof file is written to `specs/hooks/`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"spec dir resolution","tier":"unit","status":"pass","audit":""}]},{"id":"proof_common/RULE-10","description":"When a test is removed from a re-run, the old proof entry is purged and not carried over from the previous proof file","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-13","description":"Write a proof file with 2 proofs, then re-run with only 1; verify the removed entry is purged and not carried over","test_file":"dev/test_multilang_proof_plugins.py","test_name":"test_removed_test_purged_on_rerun","tier":"integration","status":"pass","audit":""},{"id":"PROOF-12","description":"e2e: Write proof file with only 1 of 2 proofs (test deletion); verify coverage shows 1/2 not 2/2","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"proof_common/RULE-2","description":"Proof files are written to the spec's directory as `.proofs-.json`","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Run a proof plugin for feature `gate_hook` tier `unit`; verify output file is named `gate_hook.proofs-unit.json`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"proof file naming","tier":"unit","status":"pass","audit":""}]},{"id":"proof_common/RULE-3","description":"When the spec directory for a feature is not found, the plugin falls back to writing to `specs/`","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Run a proof plugin for feature `nonexistent_feature` (no matching spec); verify proof file is written to `specs/`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"fallback to specs/","tier":"unit","status":"pass","audit":""}]},{"id":"proof_common/RULE-4","description":"Feature-scoped overwrite: existing entries for other features are preserved; only the current feature's entries are replaced","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Create a proof file with entries for features A and B; run the plugin for feature A only; verify feature B entries are preserved and feature A entries are replaced","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"feature-scoped overwrite","tier":"unit","status":"pass","audit":""},{"id":"PROOF-10","description":"e2e: Create 2 specs; write proofs for each separately; verify both PASSING and no cross-contamination","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""},{"id":"PROOF-11","description":"e2e: Overwrite one feature's proofs via shell harness; verify target feature updated, other feature untouched","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"proof_common/RULE-5","description":"Each proof entry contains all 7 required fields: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Run a proof plugin and read the output JSON; verify each entry in `proofs` array contains all 7 fields: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","test_file":"dev/test_multilang_proof_plugins.py","test_name":"test_python_proof_plugin_real_execution","tier":"integration","status":"pass","audit":""},{"id":"PROOF-5","description":"Run a proof plugin and read the output JSON; verify each entry in `proofs` array contains all 7 fields: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"all 7 required fields","tier":"unit","status":"pass","audit":""}]},{"id":"proof_common/RULE-6","description":"`status` is `\"pass\"` when the test passes and `\"fail\"` when it fails \u2014 no other values","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Run a passing test and a failing test with proof markers; verify the passing test has `status: \"pass\"` and the failing test has `status: \"fail\"`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"pass/fail status","tier":"unit","status":"pass","audit":""}]},{"id":"proof_common/RULE-7","description":"If no proof markers are collected during a test run, no proof files are written (no-op)","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Run a test suite with no proof markers; verify no `*.proofs-*.json` files are created","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"no markers no files","tier":"unit","status":"pass","audit":""}]},{"id":"proof_common/RULE-8","description":"Custom/community proof plugins installed to `.purlin/plugins/` require no registration \u2014 `sync_status` discovers proof files by globbing `specs/**/*.proofs-*.json`, so any plugin that writes files in that pattern works automatically","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-8","description":"Place a `.proofs-unit.json` file in a spec directory written by a non-built-in source; run `sync_status`; verify it reads the proofs","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"custom plugin proofs discovered by sync_status","tier":"unit","status":"pass","audit":""}]},{"id":"proof_common/RULE-9","description":"When spec directory lookup falls back to specs/ root, the plugin emits a warning to stderr naming the missing spec and suggesting purlin:spec ","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-9","description":"Run a proof plugin for a feature with no matching spec; verify stderr contains a warning naming the feature and suggesting purlin:spec","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"fallback warning to stderr","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-1","description":"Proof files are named `.proofs-.json` and live in the same directory as their spec","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Create a proof file at `specs/test/foo.proofs-unit.json`; run sync_status; verify it reads the file and associates it with the `foo` spec","test_file":"dev/test_schema_proof_format.py","test_name":"test_proof_file_read_by_sync_status","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-2","description":"The JSON schema has a top-level `tier` string and a `proofs` array. Each proof entry contains: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Validate a proof JSON file against the required fields; verify all 7 fields (`feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`) are present in each proof entry","test_file":"dev/test_schema_proof_format.py","test_name":"test_proof_entry_has_all_seven_fields","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-3","description":"`status` is either `\"pass\"` or `\"fail\"` \u2014 no other values","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Create a proof entry with `status: \"error\"`; run sync_status; verify it is not counted as a passing proof","test_file":"dev/test_schema_proof_format.py","test_name":"test_invalid_status_not_counted","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-4","description":"`tier` values are: `\"unit\"`, `\"integration\"`, `\"e2e\"`, and `\"windows\"` (a platform-gated tier proven only on a Windows runner) \u2014 no other automated tiers","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Scan all real `*.proofs-*.json` files; verify every tier value (top-level and per-entry) is in the valid set {unit, integration, e2e, windows}. Verify `spec_format.md` documents @integration, @e2e, and","test_file":"dev/test_schema_proof_format.py","test_name":"test_standard_tiers_documented","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-5","description":"Proof plugins use feature-scoped overwrite: load existing file, purge entries matching current feature, append new entries, write merged result","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Pre-seed a proof file with feature B entries; run the real pytest proof plugin for feature A via subprocess; verify feature B entries are preserved and feature A entries are added","test_file":"dev/test_schema_proof_format.py","test_name":"test_feature_scoped_overwrite","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-6","description":"Proof files are committed to git \u2014 they are project records, not ephemeral build artifacts","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Grep `.gitignore` for `*.proofs-*.json`; verify no gitignore rule excludes proof files","test_file":"dev/test_schema_proof_format.py","test_name":"test_proof_files_not_gitignored","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-7","description":"Manual proofs are stamped inline in the spec's `## Proof` section as `@manual(, , )`, not in proof JSON files","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Grep `references/formats/proofs_format.md` for `@manual`; verify manual stamp format is `@manual(, , )`","test_file":"dev/test_schema_proof_format.py","test_name":"test_manual_stamp_format_documented","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-1","description":"FORBIDDEN \u2014 No `eval()` or `exec()` calls in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Grep `scripts/` for `eval(` and `exec(` in `.py` files; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_eval_or_exec","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-2","description":"FORBIDDEN \u2014 No `subprocess` calls with `shell=True` in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Grep `scripts/**/*.py` for `shell\\s*=\\s*True`; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_shell_true","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-3","description":"FORBIDDEN \u2014 No `os.system()` calls in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Grep `scripts/**/*.py` for `os\\.system\\s*\\(`; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_os_system","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-4","description":"FORBIDDEN \u2014 No hardcoded credential assignments (password/secret/api_key/token literals) in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Grep `scripts/` for assignment patterns `(password|secret|api_key|token)\\s*=\\s*[\"'][^\"']+[\"']` in `.py`, `.sh`, `.js` files, excluding test files and comments; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_hardcoded_credentials","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-5","description":"All subprocess calls use list-mode arguments (not string commands)","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Grep `scripts/**/*.py` for `subprocess.run(` and `subprocess.call(`; verify each call's first argument is a list literal `[`, not a string","test_file":"dev/test_security.py","test_name":"test_subprocess_uses_list_args","tier":"unit","status":"pass","audit":""}]}],"audit":null},{"name":"proof_plugins_php","category":"proof","type":"feature","is_global":false,"source_url":null,"pinned":null,"description":"The PHP proof runner. Runs each `@purlin`-annotated function individually and maps thrown exceptions to fail. Inherits all shared proof-plugin behavior from proof_common.","source_path":null,"stack":"php (standalone runner, docblock annotations)","ext_status":null,"proved":24,"total":24,"deferred":0,"status":"VERIFIED","vhash":"f45dc3b5","receipt":{"commit":"ac522f60debf1f866e0a6acfb25bcb10fafd2057","timestamp":"2026-06-02T15:45:42.990726+00:00","stale":false},"rules":[{"id":"RULE-1","description":"The PHP marker is a `/** @purlin feature PROOF-N RULE-N [tier] */` docblock annotation before the test function","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Create a PHP test with `@purlin` docblock; run `phpunit_purlin.php`; verify proof entry has correct feature, id, rule from the docblock","test_file":"dev/test_multilang_proof_plugins.py","test_name":"test_php_proof_plugin_real_execution","tier":"integration","status":"pass","audit":""}]},{"id":"RULE-2","description":"The PHP plugin runs each annotated function individually; an unhandled exception maps to `status: \"fail\"`, no exception maps to `status: \"pass\"`","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Create a PHP test that throws an exception; run plugin; verify `status: \"fail\"`. Create one that doesn't throw; verify `status: \"pass\"`","test_file":"dev/test_multilang_proof_plugins.py","test_name":"test_php_proof_plugin_failing_test","tier":"integration","status":"pass","audit":""}]},{"id":"proof_common/RULE-1","description":"Each plugin resolves the spec directory by scanning `specs/**/*.md` and matching the feature name to the spec filename stem","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Create `specs/hooks/gate_hook.md` and run a proof plugin with feature `gate_hook`; verify the proof file is written to `specs/hooks/`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"spec dir resolution","tier":"unit","status":"pass","audit":""}]},{"id":"proof_common/RULE-10","description":"When a test is removed from a re-run, the old proof entry is purged and not carried over from the previous proof file","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-13","description":"Write a proof file with 2 proofs, then re-run with only 1; verify the removed entry is purged and not carried over","test_file":"dev/test_multilang_proof_plugins.py","test_name":"test_removed_test_purged_on_rerun","tier":"integration","status":"pass","audit":""},{"id":"PROOF-12","description":"e2e: Write proof file with only 1 of 2 proofs (test deletion); verify coverage shows 1/2 not 2/2","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"proof_common/RULE-2","description":"Proof files are written to the spec's directory as `.proofs-.json`","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Run a proof plugin for feature `gate_hook` tier `unit`; verify output file is named `gate_hook.proofs-unit.json`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"proof file naming","tier":"unit","status":"pass","audit":""}]},{"id":"proof_common/RULE-3","description":"When the spec directory for a feature is not found, the plugin falls back to writing to `specs/`","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Run a proof plugin for feature `nonexistent_feature` (no matching spec); verify proof file is written to `specs/`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"fallback to specs/","tier":"unit","status":"pass","audit":""}]},{"id":"proof_common/RULE-4","description":"Feature-scoped overwrite: existing entries for other features are preserved; only the current feature's entries are replaced","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Create a proof file with entries for features A and B; run the plugin for feature A only; verify feature B entries are preserved and feature A entries are replaced","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"feature-scoped overwrite","tier":"unit","status":"pass","audit":""},{"id":"PROOF-10","description":"e2e: Create 2 specs; write proofs for each separately; verify both PASSING and no cross-contamination","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""},{"id":"PROOF-11","description":"e2e: Overwrite one feature's proofs via shell harness; verify target feature updated, other feature untouched","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"proof_common/RULE-5","description":"Each proof entry contains all 7 required fields: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Run a proof plugin and read the output JSON; verify each entry in `proofs` array contains all 7 fields: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","test_file":"dev/test_multilang_proof_plugins.py","test_name":"test_python_proof_plugin_real_execution","tier":"integration","status":"pass","audit":""},{"id":"PROOF-5","description":"Run a proof plugin and read the output JSON; verify each entry in `proofs` array contains all 7 fields: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"all 7 required fields","tier":"unit","status":"pass","audit":""}]},{"id":"proof_common/RULE-6","description":"`status` is `\"pass\"` when the test passes and `\"fail\"` when it fails \u2014 no other values","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Run a passing test and a failing test with proof markers; verify the passing test has `status: \"pass\"` and the failing test has `status: \"fail\"`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"pass/fail status","tier":"unit","status":"pass","audit":""}]},{"id":"proof_common/RULE-7","description":"If no proof markers are collected during a test run, no proof files are written (no-op)","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Run a test suite with no proof markers; verify no `*.proofs-*.json` files are created","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"no markers no files","tier":"unit","status":"pass","audit":""}]},{"id":"proof_common/RULE-8","description":"Custom/community proof plugins installed to `.purlin/plugins/` require no registration \u2014 `sync_status` discovers proof files by globbing `specs/**/*.proofs-*.json`, so any plugin that writes files in that pattern works automatically","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-8","description":"Place a `.proofs-unit.json` file in a spec directory written by a non-built-in source; run `sync_status`; verify it reads the proofs","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"custom plugin proofs discovered by sync_status","tier":"unit","status":"pass","audit":""}]},{"id":"proof_common/RULE-9","description":"When spec directory lookup falls back to specs/ root, the plugin emits a warning to stderr naming the missing spec and suggesting purlin:spec ","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-9","description":"Run a proof plugin for a feature with no matching spec; verify stderr contains a warning naming the feature and suggesting purlin:spec","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"fallback warning to stderr","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-1","description":"Proof files are named `.proofs-.json` and live in the same directory as their spec","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Create a proof file at `specs/test/foo.proofs-unit.json`; run sync_status; verify it reads the file and associates it with the `foo` spec","test_file":"dev/test_schema_proof_format.py","test_name":"test_proof_file_read_by_sync_status","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-2","description":"The JSON schema has a top-level `tier` string and a `proofs` array. Each proof entry contains: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Validate a proof JSON file against the required fields; verify all 7 fields (`feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`) are present in each proof entry","test_file":"dev/test_schema_proof_format.py","test_name":"test_proof_entry_has_all_seven_fields","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-3","description":"`status` is either `\"pass\"` or `\"fail\"` \u2014 no other values","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Create a proof entry with `status: \"error\"`; run sync_status; verify it is not counted as a passing proof","test_file":"dev/test_schema_proof_format.py","test_name":"test_invalid_status_not_counted","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-4","description":"`tier` values are: `\"unit\"`, `\"integration\"`, `\"e2e\"`, and `\"windows\"` (a platform-gated tier proven only on a Windows runner) \u2014 no other automated tiers","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Scan all real `*.proofs-*.json` files; verify every tier value (top-level and per-entry) is in the valid set {unit, integration, e2e, windows}. Verify `spec_format.md` documents @integration, @e2e, and","test_file":"dev/test_schema_proof_format.py","test_name":"test_standard_tiers_documented","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-5","description":"Proof plugins use feature-scoped overwrite: load existing file, purge entries matching current feature, append new entries, write merged result","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Pre-seed a proof file with feature B entries; run the real pytest proof plugin for feature A via subprocess; verify feature B entries are preserved and feature A entries are added","test_file":"dev/test_schema_proof_format.py","test_name":"test_feature_scoped_overwrite","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-6","description":"Proof files are committed to git \u2014 they are project records, not ephemeral build artifacts","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Grep `.gitignore` for `*.proofs-*.json`; verify no gitignore rule excludes proof files","test_file":"dev/test_schema_proof_format.py","test_name":"test_proof_files_not_gitignored","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-7","description":"Manual proofs are stamped inline in the spec's `## Proof` section as `@manual(, , )`, not in proof JSON files","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Grep `references/formats/proofs_format.md` for `@manual`; verify manual stamp format is `@manual(, , )`","test_file":"dev/test_schema_proof_format.py","test_name":"test_manual_stamp_format_documented","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-1","description":"FORBIDDEN \u2014 No `eval()` or `exec()` calls in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Grep `scripts/` for `eval(` and `exec(` in `.py` files; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_eval_or_exec","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-2","description":"FORBIDDEN \u2014 No `subprocess` calls with `shell=True` in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Grep `scripts/**/*.py` for `shell\\s*=\\s*True`; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_shell_true","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-3","description":"FORBIDDEN \u2014 No `os.system()` calls in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Grep `scripts/**/*.py` for `os\\.system\\s*\\(`; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_os_system","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-4","description":"FORBIDDEN \u2014 No hardcoded credential assignments (password/secret/api_key/token literals) in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Grep `scripts/` for assignment patterns `(password|secret|api_key|token)\\s*=\\s*[\"'][^\"']+[\"']` in `.py`, `.sh`, `.js` files, excluding test files and comments; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_hardcoded_credentials","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-5","description":"All subprocess calls use list-mode arguments (not string commands)","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Grep `scripts/**/*.py` for `subprocess.run(` and `subprocess.call(`; verify each call's first argument is a list literal `[`, not a string","test_file":"dev/test_security.py","test_name":"test_subprocess_uses_list_args","tier":"unit","status":"pass","audit":""}]}],"audit":null},{"name":"proof_plugins_pytest","category":"proof","type":"feature","is_global":false,"source_url":null,"pinned":null,"description":"The pytest proof plugin. Collects `@pytest.mark.proof(...)` markers during the test call phase and emits standardized proof JSON. Inherits all shared proof-plugin behavior (spec-dir resolution, naming, fallback, feature-scoped overwrite, the 7 fields, status, no-op, discovery, stderr warning, purge) from proof_common.","source_path":null,"stack":"python/stdlib, pytest plugin (pytest_configure + pytest_runtest_makereport hooks)","ext_status":null,"proved":26,"total":26,"deferred":0,"status":"VERIFIED","vhash":"2e0ab5ae","receipt":{"commit":"ac522f60debf1f866e0a6acfb25bcb10fafd2057","timestamp":"2026-06-02T15:45:42.990726+00:00","stale":false},"rules":[{"id":"RULE-1","description":"The marker signature is `@pytest.mark.proof(\"feature\", \"PROOF-N\", \"RULE-N\", tier=\"unit\")` where tier defaults to `\"unit\"`","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Create a test with `@pytest.mark.proof(\"feat\", \"PROOF-1\", \"RULE-1\")`; run pytest; verify the proof entry has `feature: \"feat\"`, `tier: \"unit\"`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"pytest marker signature","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-2","description":"Markers with fewer than 3 positional args are silently skipped","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Create a test with `@pytest.mark.proof(\"feat\", \"PROOF-1\")` (only 2 args); run pytest; verify no proof entry is emitted for that test","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"pytest fewer args skipped","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-3","description":"`test_file` is recorded as the path relative to the pytest rootdir","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Run pytest from a project root; verify `test_file` in the proof entry is relative to the root (not absolute)","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"pytest test_file relative","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-4","description":"The plugin registers itself via `pytest_configure` and collects results in `pytest_runtest_makereport` during the `call` phase only","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Verify `pytest_configure` registers the `proof` marker and the `purlin_proof` plugin","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"pytest_configure registers marker + plugin","tier":"unit","status":"pass","audit":""}]},{"id":"proof_common/RULE-1","description":"Each plugin resolves the spec directory by scanning `specs/**/*.md` and matching the feature name to the spec filename stem","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Create `specs/hooks/gate_hook.md` and run a proof plugin with feature `gate_hook`; verify the proof file is written to `specs/hooks/`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"spec dir resolution","tier":"unit","status":"pass","audit":""}]},{"id":"proof_common/RULE-10","description":"When a test is removed from a re-run, the old proof entry is purged and not carried over from the previous proof file","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-13","description":"Write a proof file with 2 proofs, then re-run with only 1; verify the removed entry is purged and not carried over","test_file":"dev/test_multilang_proof_plugins.py","test_name":"test_removed_test_purged_on_rerun","tier":"integration","status":"pass","audit":""},{"id":"PROOF-12","description":"e2e: Write proof file with only 1 of 2 proofs (test deletion); verify coverage shows 1/2 not 2/2","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"proof_common/RULE-2","description":"Proof files are written to the spec's directory as `.proofs-.json`","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Run a proof plugin for feature `gate_hook` tier `unit`; verify output file is named `gate_hook.proofs-unit.json`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"proof file naming","tier":"unit","status":"pass","audit":""}]},{"id":"proof_common/RULE-3","description":"When the spec directory for a feature is not found, the plugin falls back to writing to `specs/`","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Run a proof plugin for feature `nonexistent_feature` (no matching spec); verify proof file is written to `specs/`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"fallback to specs/","tier":"unit","status":"pass","audit":""}]},{"id":"proof_common/RULE-4","description":"Feature-scoped overwrite: existing entries for other features are preserved; only the current feature's entries are replaced","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Create a proof file with entries for features A and B; run the plugin for feature A only; verify feature B entries are preserved and feature A entries are replaced","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"feature-scoped overwrite","tier":"unit","status":"pass","audit":""},{"id":"PROOF-10","description":"e2e: Create 2 specs; write proofs for each separately; verify both PASSING and no cross-contamination","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""},{"id":"PROOF-11","description":"e2e: Overwrite one feature's proofs via shell harness; verify target feature updated, other feature untouched","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"proof_common/RULE-5","description":"Each proof entry contains all 7 required fields: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Run a proof plugin and read the output JSON; verify each entry in `proofs` array contains all 7 fields: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","test_file":"dev/test_multilang_proof_plugins.py","test_name":"test_python_proof_plugin_real_execution","tier":"integration","status":"pass","audit":""},{"id":"PROOF-5","description":"Run a proof plugin and read the output JSON; verify each entry in `proofs` array contains all 7 fields: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"all 7 required fields","tier":"unit","status":"pass","audit":""}]},{"id":"proof_common/RULE-6","description":"`status` is `\"pass\"` when the test passes and `\"fail\"` when it fails \u2014 no other values","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Run a passing test and a failing test with proof markers; verify the passing test has `status: \"pass\"` and the failing test has `status: \"fail\"`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"pass/fail status","tier":"unit","status":"pass","audit":""}]},{"id":"proof_common/RULE-7","description":"If no proof markers are collected during a test run, no proof files are written (no-op)","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Run a test suite with no proof markers; verify no `*.proofs-*.json` files are created","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"no markers no files","tier":"unit","status":"pass","audit":""}]},{"id":"proof_common/RULE-8","description":"Custom/community proof plugins installed to `.purlin/plugins/` require no registration \u2014 `sync_status` discovers proof files by globbing `specs/**/*.proofs-*.json`, so any plugin that writes files in that pattern works automatically","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-8","description":"Place a `.proofs-unit.json` file in a spec directory written by a non-built-in source; run `sync_status`; verify it reads the proofs","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"custom plugin proofs discovered by sync_status","tier":"unit","status":"pass","audit":""}]},{"id":"proof_common/RULE-9","description":"When spec directory lookup falls back to specs/ root, the plugin emits a warning to stderr naming the missing spec and suggesting purlin:spec ","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-9","description":"Run a proof plugin for a feature with no matching spec; verify stderr contains a warning naming the feature and suggesting purlin:spec","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"fallback warning to stderr","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-1","description":"Proof files are named `.proofs-.json` and live in the same directory as their spec","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Create a proof file at `specs/test/foo.proofs-unit.json`; run sync_status; verify it reads the file and associates it with the `foo` spec","test_file":"dev/test_schema_proof_format.py","test_name":"test_proof_file_read_by_sync_status","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-2","description":"The JSON schema has a top-level `tier` string and a `proofs` array. Each proof entry contains: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Validate a proof JSON file against the required fields; verify all 7 fields (`feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`) are present in each proof entry","test_file":"dev/test_schema_proof_format.py","test_name":"test_proof_entry_has_all_seven_fields","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-3","description":"`status` is either `\"pass\"` or `\"fail\"` \u2014 no other values","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Create a proof entry with `status: \"error\"`; run sync_status; verify it is not counted as a passing proof","test_file":"dev/test_schema_proof_format.py","test_name":"test_invalid_status_not_counted","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-4","description":"`tier` values are: `\"unit\"`, `\"integration\"`, `\"e2e\"`, and `\"windows\"` (a platform-gated tier proven only on a Windows runner) \u2014 no other automated tiers","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Scan all real `*.proofs-*.json` files; verify every tier value (top-level and per-entry) is in the valid set {unit, integration, e2e, windows}. Verify `spec_format.md` documents @integration, @e2e, and","test_file":"dev/test_schema_proof_format.py","test_name":"test_standard_tiers_documented","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-5","description":"Proof plugins use feature-scoped overwrite: load existing file, purge entries matching current feature, append new entries, write merged result","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Pre-seed a proof file with feature B entries; run the real pytest proof plugin for feature A via subprocess; verify feature B entries are preserved and feature A entries are added","test_file":"dev/test_schema_proof_format.py","test_name":"test_feature_scoped_overwrite","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-6","description":"Proof files are committed to git \u2014 they are project records, not ephemeral build artifacts","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Grep `.gitignore` for `*.proofs-*.json`; verify no gitignore rule excludes proof files","test_file":"dev/test_schema_proof_format.py","test_name":"test_proof_files_not_gitignored","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-7","description":"Manual proofs are stamped inline in the spec's `## Proof` section as `@manual(, , )`, not in proof JSON files","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Grep `references/formats/proofs_format.md` for `@manual`; verify manual stamp format is `@manual(, , )`","test_file":"dev/test_schema_proof_format.py","test_name":"test_manual_stamp_format_documented","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-1","description":"FORBIDDEN \u2014 No `eval()` or `exec()` calls in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Grep `scripts/` for `eval(` and `exec(` in `.py` files; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_eval_or_exec","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-2","description":"FORBIDDEN \u2014 No `subprocess` calls with `shell=True` in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Grep `scripts/**/*.py` for `shell\\s*=\\s*True`; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_shell_true","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-3","description":"FORBIDDEN \u2014 No `os.system()` calls in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Grep `scripts/**/*.py` for `os\\.system\\s*\\(`; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_os_system","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-4","description":"FORBIDDEN \u2014 No hardcoded credential assignments (password/secret/api_key/token literals) in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Grep `scripts/` for assignment patterns `(password|secret|api_key|token)\\s*=\\s*[\"'][^\"']+[\"']` in `.py`, `.sh`, `.js` files, excluding test files and comments; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_hardcoded_credentials","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-5","description":"All subprocess calls use list-mode arguments (not string commands)","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Grep `scripts/**/*.py` for `subprocess.run(` and `subprocess.call(`; verify each call's first argument is a list literal `[`, not a string","test_file":"dev/test_security.py","test_name":"test_subprocess_uses_list_args","tier":"unit","status":"pass","audit":""}]}],"audit":null},{"name":"proof_plugins_shell","category":"proof","type":"feature","is_global":false,"source_url":null,"pinned":null,"description":"The shell proof harness. Sourced by bash test scripts; accumulates entries in memory and writes them on finish. Inherits all shared proof-plugin behavior from proof_common.","source_path":null,"stack":"shell/bash, inline python3 for JSON (no jq dependency)","ext_status":null,"proved":26,"total":26,"deferred":0,"status":"VERIFIED","vhash":"2e0ab5ae","receipt":{"commit":"ac522f60debf1f866e0a6acfb25bcb10fafd2057","timestamp":"2026-06-02T15:45:42.990726+00:00","stale":false},"rules":[{"id":"RULE-1","description":"`purlin_proof` accepts 5 args: `feature`, `proof_id`, `rule_id`, `status`, `test_name`; tier comes from `PURLIN_PROOF_TIER` env var (default: `\"unit\"`)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Call `purlin_proof \"feat\" \"PROOF-1\" \"RULE-1\" pass \"desc\"` with `PURLIN_PROOF_TIER=integration`; call `purlin_proof_finish`; verify the proof entry has `tier: \"integration\"`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"shell tier from PURLIN_PROOF_TIER","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-2","description":"`test_file` is recorded from `BASH_SOURCE[1]` (the caller's file)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Source `shell_purlin.sh` from a test script; call `purlin_proof`; verify `test_file` matches the caller's filename","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"shell test_file from BASH_SOURCE","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-3","description":"`purlin_proof_finish` must be called to write proof files \u2014 entries are accumulated in memory until then","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Call `purlin_proof` twice without calling `purlin_proof_finish`; verify no proof files exist yet. Then call `purlin_proof_finish`; verify files are written","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"shell finish required to write","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-4","description":"After `purlin_proof_finish`, the accumulated entries are cleared (reset for next batch)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Call `purlin_proof_finish`; verify `_PURLIN_PROOFS` is empty afterwards; call again; verify it's a no-op","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"shell entries cleared after finish","tier":"unit","status":"pass","audit":""}]},{"id":"proof_common/RULE-1","description":"Each plugin resolves the spec directory by scanning `specs/**/*.md` and matching the feature name to the spec filename stem","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Create `specs/hooks/gate_hook.md` and run a proof plugin with feature `gate_hook`; verify the proof file is written to `specs/hooks/`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"spec dir resolution","tier":"unit","status":"pass","audit":""}]},{"id":"proof_common/RULE-10","description":"When a test is removed from a re-run, the old proof entry is purged and not carried over from the previous proof file","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-13","description":"Write a proof file with 2 proofs, then re-run with only 1; verify the removed entry is purged and not carried over","test_file":"dev/test_multilang_proof_plugins.py","test_name":"test_removed_test_purged_on_rerun","tier":"integration","status":"pass","audit":""},{"id":"PROOF-12","description":"e2e: Write proof file with only 1 of 2 proofs (test deletion); verify coverage shows 1/2 not 2/2","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"proof_common/RULE-2","description":"Proof files are written to the spec's directory as `.proofs-.json`","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Run a proof plugin for feature `gate_hook` tier `unit`; verify output file is named `gate_hook.proofs-unit.json`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"proof file naming","tier":"unit","status":"pass","audit":""}]},{"id":"proof_common/RULE-3","description":"When the spec directory for a feature is not found, the plugin falls back to writing to `specs/`","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Run a proof plugin for feature `nonexistent_feature` (no matching spec); verify proof file is written to `specs/`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"fallback to specs/","tier":"unit","status":"pass","audit":""}]},{"id":"proof_common/RULE-4","description":"Feature-scoped overwrite: existing entries for other features are preserved; only the current feature's entries are replaced","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Create a proof file with entries for features A and B; run the plugin for feature A only; verify feature B entries are preserved and feature A entries are replaced","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"feature-scoped overwrite","tier":"unit","status":"pass","audit":""},{"id":"PROOF-10","description":"e2e: Create 2 specs; write proofs for each separately; verify both PASSING and no cross-contamination","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""},{"id":"PROOF-11","description":"e2e: Overwrite one feature's proofs via shell harness; verify target feature updated, other feature untouched","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"proof_common/RULE-5","description":"Each proof entry contains all 7 required fields: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Run a proof plugin and read the output JSON; verify each entry in `proofs` array contains all 7 fields: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","test_file":"dev/test_multilang_proof_plugins.py","test_name":"test_python_proof_plugin_real_execution","tier":"integration","status":"pass","audit":""},{"id":"PROOF-5","description":"Run a proof plugin and read the output JSON; verify each entry in `proofs` array contains all 7 fields: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"all 7 required fields","tier":"unit","status":"pass","audit":""}]},{"id":"proof_common/RULE-6","description":"`status` is `\"pass\"` when the test passes and `\"fail\"` when it fails \u2014 no other values","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Run a passing test and a failing test with proof markers; verify the passing test has `status: \"pass\"` and the failing test has `status: \"fail\"`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"pass/fail status","tier":"unit","status":"pass","audit":""}]},{"id":"proof_common/RULE-7","description":"If no proof markers are collected during a test run, no proof files are written (no-op)","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Run a test suite with no proof markers; verify no `*.proofs-*.json` files are created","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"no markers no files","tier":"unit","status":"pass","audit":""}]},{"id":"proof_common/RULE-8","description":"Custom/community proof plugins installed to `.purlin/plugins/` require no registration \u2014 `sync_status` discovers proof files by globbing `specs/**/*.proofs-*.json`, so any plugin that writes files in that pattern works automatically","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-8","description":"Place a `.proofs-unit.json` file in a spec directory written by a non-built-in source; run `sync_status`; verify it reads the proofs","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"custom plugin proofs discovered by sync_status","tier":"unit","status":"pass","audit":""}]},{"id":"proof_common/RULE-9","description":"When spec directory lookup falls back to specs/ root, the plugin emits a warning to stderr naming the missing spec and suggesting purlin:spec ","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-9","description":"Run a proof plugin for a feature with no matching spec; verify stderr contains a warning naming the feature and suggesting purlin:spec","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"fallback warning to stderr","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-1","description":"Proof files are named `.proofs-.json` and live in the same directory as their spec","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Create a proof file at `specs/test/foo.proofs-unit.json`; run sync_status; verify it reads the file and associates it with the `foo` spec","test_file":"dev/test_schema_proof_format.py","test_name":"test_proof_file_read_by_sync_status","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-2","description":"The JSON schema has a top-level `tier` string and a `proofs` array. Each proof entry contains: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Validate a proof JSON file against the required fields; verify all 7 fields (`feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`) are present in each proof entry","test_file":"dev/test_schema_proof_format.py","test_name":"test_proof_entry_has_all_seven_fields","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-3","description":"`status` is either `\"pass\"` or `\"fail\"` \u2014 no other values","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Create a proof entry with `status: \"error\"`; run sync_status; verify it is not counted as a passing proof","test_file":"dev/test_schema_proof_format.py","test_name":"test_invalid_status_not_counted","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-4","description":"`tier` values are: `\"unit\"`, `\"integration\"`, `\"e2e\"`, and `\"windows\"` (a platform-gated tier proven only on a Windows runner) \u2014 no other automated tiers","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Scan all real `*.proofs-*.json` files; verify every tier value (top-level and per-entry) is in the valid set {unit, integration, e2e, windows}. Verify `spec_format.md` documents @integration, @e2e, and","test_file":"dev/test_schema_proof_format.py","test_name":"test_standard_tiers_documented","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-5","description":"Proof plugins use feature-scoped overwrite: load existing file, purge entries matching current feature, append new entries, write merged result","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Pre-seed a proof file with feature B entries; run the real pytest proof plugin for feature A via subprocess; verify feature B entries are preserved and feature A entries are added","test_file":"dev/test_schema_proof_format.py","test_name":"test_feature_scoped_overwrite","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-6","description":"Proof files are committed to git \u2014 they are project records, not ephemeral build artifacts","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Grep `.gitignore` for `*.proofs-*.json`; verify no gitignore rule excludes proof files","test_file":"dev/test_schema_proof_format.py","test_name":"test_proof_files_not_gitignored","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-7","description":"Manual proofs are stamped inline in the spec's `## Proof` section as `@manual(, , )`, not in proof JSON files","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Grep `references/formats/proofs_format.md` for `@manual`; verify manual stamp format is `@manual(, , )`","test_file":"dev/test_schema_proof_format.py","test_name":"test_manual_stamp_format_documented","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-1","description":"FORBIDDEN \u2014 No `eval()` or `exec()` calls in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Grep `scripts/` for `eval(` and `exec(` in `.py` files; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_eval_or_exec","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-2","description":"FORBIDDEN \u2014 No `subprocess` calls with `shell=True` in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Grep `scripts/**/*.py` for `shell\\s*=\\s*True`; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_shell_true","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-3","description":"FORBIDDEN \u2014 No `os.system()` calls in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Grep `scripts/**/*.py` for `os\\.system\\s*\\(`; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_os_system","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-4","description":"FORBIDDEN \u2014 No hardcoded credential assignments (password/secret/api_key/token literals) in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Grep `scripts/` for assignment patterns `(password|secret|api_key|token)\\s*=\\s*[\"'][^\"']+[\"']` in `.py`, `.sh`, `.js` files, excluding test files and comments; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_hardcoded_credentials","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-5","description":"All subprocess calls use list-mode arguments (not string commands)","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Grep `scripts/**/*.py` for `subprocess.run(` and `subprocess.call(`; verify each call's first argument is a list literal `[`, not a string","test_file":"dev/test_security.py","test_name":"test_subprocess_uses_list_args","tier":"unit","status":"pass","audit":""}]}],"audit":null},{"name":"proof_plugins_sql","category":"proof","type":"feature","is_global":false,"source_url":null,"pinned":null,"description":"The SQL proof plugin. Runs `-- @purlin`-marked test blocks against sqlite3 and maps PASS/FAIL output to status. Inherits all shared proof-plugin behavior from proof_common.","source_path":null,"stack":"sql/sqlite3 (shell driver + inline python3)","ext_status":null,"proved":24,"total":24,"deferred":0,"status":"VERIFIED","vhash":"f45dc3b5","receipt":{"commit":"ac522f60debf1f866e0a6acfb25bcb10fafd2057","timestamp":"2026-06-02T15:45:42.990726+00:00","stale":false},"rules":[{"id":"RULE-1","description":"The SQL marker is `-- @purlin feature PROOF-N RULE-N [tier]` as a comment line preceding the test block","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Create a SQL file with `-- @purlin` markers; run `sql_purlin.sh`; verify proof entries match the marker fields","test_file":"dev/test_multilang_proof_plugins.py","test_name":"test_sql_proof_plugin_real_execution","tier":"integration","status":"pass","audit":""}]},{"id":"RULE-2","description":"Each SQL test block must produce output starting with `PASS` or `FAIL` when executed against sqlite3; any other output or error maps to `status: \"fail\"`","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Create a SQL test block that outputs `PASS`; verify `status: \"pass\"`. Create one that outputs `FAIL`; verify `status: \"fail\"`","test_file":"dev/test_multilang_proof_plugins.py","test_name":"test_sql_proof_plugin_failing_test","tier":"integration","status":"pass","audit":""}]},{"id":"proof_common/RULE-1","description":"Each plugin resolves the spec directory by scanning `specs/**/*.md` and matching the feature name to the spec filename stem","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Create `specs/hooks/gate_hook.md` and run a proof plugin with feature `gate_hook`; verify the proof file is written to `specs/hooks/`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"spec dir resolution","tier":"unit","status":"pass","audit":""}]},{"id":"proof_common/RULE-10","description":"When a test is removed from a re-run, the old proof entry is purged and not carried over from the previous proof file","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-13","description":"Write a proof file with 2 proofs, then re-run with only 1; verify the removed entry is purged and not carried over","test_file":"dev/test_multilang_proof_plugins.py","test_name":"test_removed_test_purged_on_rerun","tier":"integration","status":"pass","audit":""},{"id":"PROOF-12","description":"e2e: Write proof file with only 1 of 2 proofs (test deletion); verify coverage shows 1/2 not 2/2","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"proof_common/RULE-2","description":"Proof files are written to the spec's directory as `.proofs-.json`","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Run a proof plugin for feature `gate_hook` tier `unit`; verify output file is named `gate_hook.proofs-unit.json`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"proof file naming","tier":"unit","status":"pass","audit":""}]},{"id":"proof_common/RULE-3","description":"When the spec directory for a feature is not found, the plugin falls back to writing to `specs/`","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Run a proof plugin for feature `nonexistent_feature` (no matching spec); verify proof file is written to `specs/`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"fallback to specs/","tier":"unit","status":"pass","audit":""}]},{"id":"proof_common/RULE-4","description":"Feature-scoped overwrite: existing entries for other features are preserved; only the current feature's entries are replaced","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Create a proof file with entries for features A and B; run the plugin for feature A only; verify feature B entries are preserved and feature A entries are replaced","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"feature-scoped overwrite","tier":"unit","status":"pass","audit":""},{"id":"PROOF-10","description":"e2e: Create 2 specs; write proofs for each separately; verify both PASSING and no cross-contamination","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""},{"id":"PROOF-11","description":"e2e: Overwrite one feature's proofs via shell harness; verify target feature updated, other feature untouched","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"proof_common/RULE-5","description":"Each proof entry contains all 7 required fields: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Run a proof plugin and read the output JSON; verify each entry in `proofs` array contains all 7 fields: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","test_file":"dev/test_multilang_proof_plugins.py","test_name":"test_python_proof_plugin_real_execution","tier":"integration","status":"pass","audit":""},{"id":"PROOF-5","description":"Run a proof plugin and read the output JSON; verify each entry in `proofs` array contains all 7 fields: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"all 7 required fields","tier":"unit","status":"pass","audit":""}]},{"id":"proof_common/RULE-6","description":"`status` is `\"pass\"` when the test passes and `\"fail\"` when it fails \u2014 no other values","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Run a passing test and a failing test with proof markers; verify the passing test has `status: \"pass\"` and the failing test has `status: \"fail\"`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"pass/fail status","tier":"unit","status":"pass","audit":""}]},{"id":"proof_common/RULE-7","description":"If no proof markers are collected during a test run, no proof files are written (no-op)","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Run a test suite with no proof markers; verify no `*.proofs-*.json` files are created","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"no markers no files","tier":"unit","status":"pass","audit":""}]},{"id":"proof_common/RULE-8","description":"Custom/community proof plugins installed to `.purlin/plugins/` require no registration \u2014 `sync_status` discovers proof files by globbing `specs/**/*.proofs-*.json`, so any plugin that writes files in that pattern works automatically","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-8","description":"Place a `.proofs-unit.json` file in a spec directory written by a non-built-in source; run `sync_status`; verify it reads the proofs","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"custom plugin proofs discovered by sync_status","tier":"unit","status":"pass","audit":""}]},{"id":"proof_common/RULE-9","description":"When spec directory lookup falls back to specs/ root, the plugin emits a warning to stderr naming the missing spec and suggesting purlin:spec ","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-9","description":"Run a proof plugin for a feature with no matching spec; verify stderr contains a warning naming the feature and suggesting purlin:spec","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"fallback warning to stderr","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-1","description":"Proof files are named `.proofs-.json` and live in the same directory as their spec","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Create a proof file at `specs/test/foo.proofs-unit.json`; run sync_status; verify it reads the file and associates it with the `foo` spec","test_file":"dev/test_schema_proof_format.py","test_name":"test_proof_file_read_by_sync_status","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-2","description":"The JSON schema has a top-level `tier` string and a `proofs` array. Each proof entry contains: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Validate a proof JSON file against the required fields; verify all 7 fields (`feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`) are present in each proof entry","test_file":"dev/test_schema_proof_format.py","test_name":"test_proof_entry_has_all_seven_fields","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-3","description":"`status` is either `\"pass\"` or `\"fail\"` \u2014 no other values","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Create a proof entry with `status: \"error\"`; run sync_status; verify it is not counted as a passing proof","test_file":"dev/test_schema_proof_format.py","test_name":"test_invalid_status_not_counted","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-4","description":"`tier` values are: `\"unit\"`, `\"integration\"`, `\"e2e\"`, and `\"windows\"` (a platform-gated tier proven only on a Windows runner) \u2014 no other automated tiers","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Scan all real `*.proofs-*.json` files; verify every tier value (top-level and per-entry) is in the valid set {unit, integration, e2e, windows}. Verify `spec_format.md` documents @integration, @e2e, and","test_file":"dev/test_schema_proof_format.py","test_name":"test_standard_tiers_documented","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-5","description":"Proof plugins use feature-scoped overwrite: load existing file, purge entries matching current feature, append new entries, write merged result","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Pre-seed a proof file with feature B entries; run the real pytest proof plugin for feature A via subprocess; verify feature B entries are preserved and feature A entries are added","test_file":"dev/test_schema_proof_format.py","test_name":"test_feature_scoped_overwrite","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-6","description":"Proof files are committed to git \u2014 they are project records, not ephemeral build artifacts","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Grep `.gitignore` for `*.proofs-*.json`; verify no gitignore rule excludes proof files","test_file":"dev/test_schema_proof_format.py","test_name":"test_proof_files_not_gitignored","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-7","description":"Manual proofs are stamped inline in the spec's `## Proof` section as `@manual(, , )`, not in proof JSON files","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Grep `references/formats/proofs_format.md` for `@manual`; verify manual stamp format is `@manual(, , )`","test_file":"dev/test_schema_proof_format.py","test_name":"test_manual_stamp_format_documented","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-1","description":"FORBIDDEN \u2014 No `eval()` or `exec()` calls in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Grep `scripts/` for `eval(` and `exec(` in `.py` files; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_eval_or_exec","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-2","description":"FORBIDDEN \u2014 No `subprocess` calls with `shell=True` in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Grep `scripts/**/*.py` for `shell\\s*=\\s*True`; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_shell_true","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-3","description":"FORBIDDEN \u2014 No `os.system()` calls in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Grep `scripts/**/*.py` for `os\\.system\\s*\\(`; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_os_system","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-4","description":"FORBIDDEN \u2014 No hardcoded credential assignments (password/secret/api_key/token literals) in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Grep `scripts/` for assignment patterns `(password|secret|api_key|token)\\s*=\\s*[\"'][^\"']+[\"']` in `.py`, `.sh`, `.js` files, excluding test files and comments; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_hardcoded_credentials","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-5","description":"All subprocess calls use list-mode arguments (not string commands)","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Grep `scripts/**/*.py` for `subprocess.run(` and `subprocess.call(`; verify each call's first argument is a list literal `[`, not a string","test_file":"dev/test_security.py","test_name":"test_subprocess_uses_list_args","tier":"unit","status":"pass","audit":""}]}],"audit":null},{"name":"proof_plugins_vitest","category":"proof","type":"feature","is_global":false,"source_url":null,"pinned":null,"description":"The Vitest TypeScript reporter. Reuses the Jest marker syntax in test titles and collects proofs by walking the onFinished(files) task tree. Inherits all shared proof-plugin behavior from proof_common.","source_path":null,"stack":"typescript/vitest, custom reporter (onFinished(files) tree walk, Vitest 2.x\u20134.x)","ext_status":null,"proved":24,"total":24,"deferred":0,"status":"VERIFIED","vhash":"f45dc3b5","receipt":{"commit":"ac522f60debf1f866e0a6acfb25bcb10fafd2057","timestamp":"2026-06-02T15:45:42.990726+00:00","stale":false},"rules":[{"id":"RULE-1","description":"The TypeScript Vitest reporter uses the same `[proof:feature:PROOF-N:RULE-N:tier]` marker syntax in test titles as the Jest reporter","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Drive `vitest_purlin.ts` `onFinished` with a test task whose name contains `[proof:feat:PROOF-1:RULE-1:integration]`; verify the marker parses into `feature: \"feat\"`, `id: \"PROOF-1\"`, `rule: \"RULE-1\"`, `tier: \"integration\"` \u2014 identical fields to the Jest reporter's parsing of the same marker","test_file":"dev/test_multilang_proof_plugins.py","test_name":"test_vitest_reporter_marker_parsing","tier":"integration","status":"pass","audit":""}]},{"id":"RULE-2","description":"The Vitest reporter collects proofs by recursively walking the file/suite/test tree passed to `onFinished(files)` \u2014 the stable Vitest 2.x\u20134.x reporter API. For each task of type `test`/`custom` it reads `task.name`, maps `task.result.state === \"pass\"` to `\"pass\"` (all other terminal states to `\"fail\"`), skips tasks with no terminal result (`skip`/`todo`/unrun), and resolves `test_file` from the file task's `filepath`. It does not use `onTaskUpdate` (whose pack shape changed in Vitest 2 and silently yielded zero proofs)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Load `vitest_purlin.ts` (compiled with tsc, or run directly via Node TypeScript type-stripping); drive its `onFinished(files)` with a synthetic Vitest 2.x+ task tree (a file suite task carrying `filepath` and nested `test` tasks whose names contain `[proof:...]` markers and whose `result.state` is `pass`/`fail`/`skip`); verify the emitted proof JSON has all 7 fields, that `state: pass` \u2192 `status: \"pass\"` while `state: fail` \u2192 `status: \"fail\"`, that a `skip` task is not recorded, and that `test_file` is resolved from the file task's `filepath`","test_file":"dev/test_multilang_proof_plugins.py","test_name":"test_vitest_reporter_onfinished_walk","tier":"integration","status":"pass","audit":""}]},{"id":"proof_common/RULE-1","description":"Each plugin resolves the spec directory by scanning `specs/**/*.md` and matching the feature name to the spec filename stem","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Create `specs/hooks/gate_hook.md` and run a proof plugin with feature `gate_hook`; verify the proof file is written to `specs/hooks/`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"spec dir resolution","tier":"unit","status":"pass","audit":""}]},{"id":"proof_common/RULE-10","description":"When a test is removed from a re-run, the old proof entry is purged and not carried over from the previous proof file","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-13","description":"Write a proof file with 2 proofs, then re-run with only 1; verify the removed entry is purged and not carried over","test_file":"dev/test_multilang_proof_plugins.py","test_name":"test_removed_test_purged_on_rerun","tier":"integration","status":"pass","audit":""},{"id":"PROOF-12","description":"e2e: Write proof file with only 1 of 2 proofs (test deletion); verify coverage shows 1/2 not 2/2","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"proof_common/RULE-2","description":"Proof files are written to the spec's directory as `.proofs-.json`","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Run a proof plugin for feature `gate_hook` tier `unit`; verify output file is named `gate_hook.proofs-unit.json`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"proof file naming","tier":"unit","status":"pass","audit":""}]},{"id":"proof_common/RULE-3","description":"When the spec directory for a feature is not found, the plugin falls back to writing to `specs/`","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Run a proof plugin for feature `nonexistent_feature` (no matching spec); verify proof file is written to `specs/`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"fallback to specs/","tier":"unit","status":"pass","audit":""}]},{"id":"proof_common/RULE-4","description":"Feature-scoped overwrite: existing entries for other features are preserved; only the current feature's entries are replaced","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Create a proof file with entries for features A and B; run the plugin for feature A only; verify feature B entries are preserved and feature A entries are replaced","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"feature-scoped overwrite","tier":"unit","status":"pass","audit":""},{"id":"PROOF-10","description":"e2e: Create 2 specs; write proofs for each separately; verify both PASSING and no cross-contamination","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""},{"id":"PROOF-11","description":"e2e: Overwrite one feature's proofs via shell harness; verify target feature updated, other feature untouched","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"proof_common/RULE-5","description":"Each proof entry contains all 7 required fields: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Run a proof plugin and read the output JSON; verify each entry in `proofs` array contains all 7 fields: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","test_file":"dev/test_multilang_proof_plugins.py","test_name":"test_python_proof_plugin_real_execution","tier":"integration","status":"pass","audit":""},{"id":"PROOF-5","description":"Run a proof plugin and read the output JSON; verify each entry in `proofs` array contains all 7 fields: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"all 7 required fields","tier":"unit","status":"pass","audit":""}]},{"id":"proof_common/RULE-6","description":"`status` is `\"pass\"` when the test passes and `\"fail\"` when it fails \u2014 no other values","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Run a passing test and a failing test with proof markers; verify the passing test has `status: \"pass\"` and the failing test has `status: \"fail\"`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"pass/fail status","tier":"unit","status":"pass","audit":""}]},{"id":"proof_common/RULE-7","description":"If no proof markers are collected during a test run, no proof files are written (no-op)","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Run a test suite with no proof markers; verify no `*.proofs-*.json` files are created","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"no markers no files","tier":"unit","status":"pass","audit":""}]},{"id":"proof_common/RULE-8","description":"Custom/community proof plugins installed to `.purlin/plugins/` require no registration \u2014 `sync_status` discovers proof files by globbing `specs/**/*.proofs-*.json`, so any plugin that writes files in that pattern works automatically","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-8","description":"Place a `.proofs-unit.json` file in a spec directory written by a non-built-in source; run `sync_status`; verify it reads the proofs","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"custom plugin proofs discovered by sync_status","tier":"unit","status":"pass","audit":""}]},{"id":"proof_common/RULE-9","description":"When spec directory lookup falls back to specs/ root, the plugin emits a warning to stderr naming the missing spec and suggesting purlin:spec ","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-9","description":"Run a proof plugin for a feature with no matching spec; verify stderr contains a warning naming the feature and suggesting purlin:spec","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"fallback warning to stderr","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-1","description":"Proof files are named `.proofs-.json` and live in the same directory as their spec","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Create a proof file at `specs/test/foo.proofs-unit.json`; run sync_status; verify it reads the file and associates it with the `foo` spec","test_file":"dev/test_schema_proof_format.py","test_name":"test_proof_file_read_by_sync_status","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-2","description":"The JSON schema has a top-level `tier` string and a `proofs` array. Each proof entry contains: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Validate a proof JSON file against the required fields; verify all 7 fields (`feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`) are present in each proof entry","test_file":"dev/test_schema_proof_format.py","test_name":"test_proof_entry_has_all_seven_fields","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-3","description":"`status` is either `\"pass\"` or `\"fail\"` \u2014 no other values","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Create a proof entry with `status: \"error\"`; run sync_status; verify it is not counted as a passing proof","test_file":"dev/test_schema_proof_format.py","test_name":"test_invalid_status_not_counted","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-4","description":"`tier` values are: `\"unit\"`, `\"integration\"`, `\"e2e\"`, and `\"windows\"` (a platform-gated tier proven only on a Windows runner) \u2014 no other automated tiers","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Scan all real `*.proofs-*.json` files; verify every tier value (top-level and per-entry) is in the valid set {unit, integration, e2e, windows}. Verify `spec_format.md` documents @integration, @e2e, and","test_file":"dev/test_schema_proof_format.py","test_name":"test_standard_tiers_documented","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-5","description":"Proof plugins use feature-scoped overwrite: load existing file, purge entries matching current feature, append new entries, write merged result","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Pre-seed a proof file with feature B entries; run the real pytest proof plugin for feature A via subprocess; verify feature B entries are preserved and feature A entries are added","test_file":"dev/test_schema_proof_format.py","test_name":"test_feature_scoped_overwrite","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-6","description":"Proof files are committed to git \u2014 they are project records, not ephemeral build artifacts","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Grep `.gitignore` for `*.proofs-*.json`; verify no gitignore rule excludes proof files","test_file":"dev/test_schema_proof_format.py","test_name":"test_proof_files_not_gitignored","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-7","description":"Manual proofs are stamped inline in the spec's `## Proof` section as `@manual(, , )`, not in proof JSON files","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Grep `references/formats/proofs_format.md` for `@manual`; verify manual stamp format is `@manual(, , )`","test_file":"dev/test_schema_proof_format.py","test_name":"test_manual_stamp_format_documented","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-1","description":"FORBIDDEN \u2014 No `eval()` or `exec()` calls in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Grep `scripts/` for `eval(` and `exec(` in `.py` files; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_eval_or_exec","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-2","description":"FORBIDDEN \u2014 No `subprocess` calls with `shell=True` in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Grep `scripts/**/*.py` for `shell\\s*=\\s*True`; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_shell_true","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-3","description":"FORBIDDEN \u2014 No `os.system()` calls in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Grep `scripts/**/*.py` for `os\\.system\\s*\\(`; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_os_system","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-4","description":"FORBIDDEN \u2014 No hardcoded credential assignments (password/secret/api_key/token literals) in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Grep `scripts/` for assignment patterns `(password|secret|api_key|token)\\s*=\\s*[\"'][^\"']+[\"']` in `.py`, `.sh`, `.js` files, excluding test files and comments; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_hardcoded_credentials","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-5","description":"All subprocess calls use list-mode arguments (not string commands)","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Grep `scripts/**/*.py` for `subprocess.run(` and `subprocess.call(`; verify each call's first argument is a list literal `[`, not a string","test_file":"dev/test_security.py","test_name":"test_subprocess_uses_list_args","tier":"unit","status":"pass","audit":""}]}],"audit":null},{"name":"proof_plugins_xunit","category":"proof","type":"feature","is_global":false,"source_url":null,"pinned":null,"description":"The xUnit/.NET proof plugin (`scripts/proof/xunit_purlin.cs`). A custom `dotnet test` logger collects proof markers expressed as the `PurlinProof` test trait, maps the test outcome to pass/fail, and emits standardized proof JSON. Inherits all shared proof-plugin behavior (spec-dir resolution, naming, fallback, feature-scoped overwrite, the 7 fields, status, no-op, discovery, stderr warning, purge) from proof_common.","source_path":null,"stack":"dotnet/xunit, custom ITestLoggerWithParameters registered via `dotnet test --logger purlin`; trait-based marker, also compatible with NUnit/MSTest via TestCase.Traits","ext_status":null,"proved":28,"total":28,"deferred":0,"status":"VERIFIED","vhash":"352882e9","receipt":{"commit":"cf3a827f0291004d88a0b6941ad3608ccf631e80","timestamp":"2026-06-02T18:48:41.710705+00:00","stale":false},"rules":[{"id":"RULE-1","description":"The .NET marker is the test trait `[Trait(\"PurlinProof\", \"feature:PROOF-N:RULE-N:tier\")]` (xUnit), equivalently `[Category]`/`[TestProperty]` in NUnit/MSTest; the trait value is a colon-delimited `feature:PROOF-N:RULE-N:tier` string where tier defaults to `\"unit\"`","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Build an xUnit test annotated `[Trait(\"PurlinProof\", \"feat:PROOF-1:RULE-1:unit\")]`; run `dotnet test --logger purlin`; verify the proof entry has `feature: \"feat\"`, `id: \"PROOF-1\"`, `rule: \"RULE-1\"`, `tier: \"unit\"`","test_file":"dev/test_multilang_proof_plugins.py","test_name":"test_trait_marker_parses","tier":"integration","status":"pass","audit":""}]},{"id":"RULE-2","description":"The plugin is a custom `dotnet test` logger (`ITestLoggerWithParameters`) registered via `dotnet test --logger purlin`; it collects results during the run, not by post-parsing a `.trx` file","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Run `dotnet test --logger purlin` on a project with one marked test; verify the logger is invoked and produces proof output during the run (no separate `.trx` parse step required)","test_file":"dev/test_multilang_proof_plugins.py","test_name":"test_logger_runs_in_process","tier":"integration","status":"pass","audit":""}]},{"id":"RULE-3","description":"Tests without a `PurlinProof` trait are ignored \u2014 no proof entry is emitted for them","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Run a test with no `PurlinProof` trait; verify no proof entry is emitted for that test","test_file":"dev/test_multilang_proof_plugins.py","test_name":"test_untagged_ignored","tier":"integration","status":"pass","audit":""}]},{"id":"RULE-4","description":"A test `Outcome` of `Passed` maps to `status: \"pass\"`; `Failed` and all other non-skipped outcomes (e.g. `NotExecuted`) map to `status: \"fail\"`; a `Skipped` test is not recorded at all","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Run a passing marked test and a failing marked test; verify `status: \"pass\"` and `status: \"fail\"` respectively; add a `[Fact(Skip=\"...\")]` marked test and verify it is not recorded","test_file":"dev/test_multilang_proof_plugins.py","test_name":"test_status_mapping_and_skip_excluded","tier":"integration","status":"pass","audit":""}]},{"id":"RULE-5","description":"`test_file` is recorded as the source file path relative to the project root (resolved from `TestCase` source information / `CodeFilePath`); `test_name` is the fully-qualified test method name","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Run `dotnet test` from a project root; verify `test_file` is relative (not absolute) and `test_name` is the fully-qualified method name","test_file":"dev/test_multilang_proof_plugins.py","test_name":"test_relative_file_and_fq_name","tier":"integration","status":"pass","audit":""}]},{"id":"RULE-6","description":"On run completion the logger emits proof JSON to the resolved spec directory following the shared feature-scoped overwrite contract","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Pre-seed a proof file with feature B entries; run the logger for feature A; verify feature B entries are preserved and feature A entries are replaced (inherits the proof_common feature-scoped overwrite contract)","test_file":"dev/test_multilang_proof_plugins.py","test_name":"test_feature_scoped_overwrite","tier":"integration","status":"pass","audit":""}]},{"id":"proof_common/RULE-1","description":"Each plugin resolves the spec directory by scanning `specs/**/*.md` and matching the feature name to the spec filename stem","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Create `specs/hooks/gate_hook.md` and run a proof plugin with feature `gate_hook`; verify the proof file is written to `specs/hooks/`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"spec dir resolution","tier":"unit","status":"pass","audit":""}]},{"id":"proof_common/RULE-10","description":"When a test is removed from a re-run, the old proof entry is purged and not carried over from the previous proof file","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-13","description":"Write a proof file with 2 proofs, then re-run with only 1; verify the removed entry is purged and not carried over","test_file":"dev/test_multilang_proof_plugins.py","test_name":"test_removed_test_purged_on_rerun","tier":"integration","status":"pass","audit":""},{"id":"PROOF-12","description":"e2e: Write proof file with only 1 of 2 proofs (test deletion); verify coverage shows 1/2 not 2/2","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"proof_common/RULE-2","description":"Proof files are written to the spec's directory as `.proofs-.json`","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Run a proof plugin for feature `gate_hook` tier `unit`; verify output file is named `gate_hook.proofs-unit.json`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"proof file naming","tier":"unit","status":"pass","audit":""}]},{"id":"proof_common/RULE-3","description":"When the spec directory for a feature is not found, the plugin falls back to writing to `specs/`","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Run a proof plugin for feature `nonexistent_feature` (no matching spec); verify proof file is written to `specs/`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"fallback to specs/","tier":"unit","status":"pass","audit":""}]},{"id":"proof_common/RULE-4","description":"Feature-scoped overwrite: existing entries for other features are preserved; only the current feature's entries are replaced","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Create a proof file with entries for features A and B; run the plugin for feature A only; verify feature B entries are preserved and feature A entries are replaced","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"feature-scoped overwrite","tier":"unit","status":"pass","audit":""},{"id":"PROOF-10","description":"e2e: Create 2 specs; write proofs for each separately; verify both PASSING and no cross-contamination","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""},{"id":"PROOF-11","description":"e2e: Overwrite one feature's proofs via shell harness; verify target feature updated, other feature untouched","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"proof_common/RULE-5","description":"Each proof entry contains all 7 required fields: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Run a proof plugin and read the output JSON; verify each entry in `proofs` array contains all 7 fields: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","test_file":"dev/test_multilang_proof_plugins.py","test_name":"test_python_proof_plugin_real_execution","tier":"integration","status":"pass","audit":""},{"id":"PROOF-5","description":"Run a proof plugin and read the output JSON; verify each entry in `proofs` array contains all 7 fields: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"all 7 required fields","tier":"unit","status":"pass","audit":""}]},{"id":"proof_common/RULE-6","description":"`status` is `\"pass\"` when the test passes and `\"fail\"` when it fails \u2014 no other values","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Run a passing test and a failing test with proof markers; verify the passing test has `status: \"pass\"` and the failing test has `status: \"fail\"`","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"pass/fail status","tier":"unit","status":"pass","audit":""}]},{"id":"proof_common/RULE-7","description":"If no proof markers are collected during a test run, no proof files are written (no-op)","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Run a test suite with no proof markers; verify no `*.proofs-*.json` files are created","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"no markers no files","tier":"unit","status":"pass","audit":""}]},{"id":"proof_common/RULE-8","description":"Custom/community proof plugins installed to `.purlin/plugins/` require no registration \u2014 `sync_status` discovers proof files by globbing `specs/**/*.proofs-*.json`, so any plugin that writes files in that pattern works automatically","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-8","description":"Place a `.proofs-unit.json` file in a spec directory written by a non-built-in source; run `sync_status`; verify it reads the proofs","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"custom plugin proofs discovered by sync_status","tier":"unit","status":"pass","audit":""}]},{"id":"proof_common/RULE-9","description":"When spec directory lookup falls back to specs/ root, the plugin emits a warning to stderr naming the missing spec and suggesting purlin:spec ","label":"required","source":"proof_common","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-9","description":"Run a proof plugin for a feature with no matching spec; verify stderr contains a warning naming the feature and suggesting purlin:spec","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_proof_plugins.sh","test_name":"fallback warning to stderr","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-1","description":"Proof files are named `.proofs-.json` and live in the same directory as their spec","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Create a proof file at `specs/test/foo.proofs-unit.json`; run sync_status; verify it reads the file and associates it with the `foo` spec","test_file":"dev/test_schema_proof_format.py","test_name":"test_proof_file_read_by_sync_status","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-2","description":"The JSON schema has a top-level `tier` string and a `proofs` array. Each proof entry contains: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Validate a proof JSON file against the required fields; verify all 7 fields (`feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`) are present in each proof entry","test_file":"dev/test_schema_proof_format.py","test_name":"test_proof_entry_has_all_seven_fields","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-3","description":"`status` is either `\"pass\"` or `\"fail\"` \u2014 no other values","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Create a proof entry with `status: \"error\"`; run sync_status; verify it is not counted as a passing proof","test_file":"dev/test_schema_proof_format.py","test_name":"test_invalid_status_not_counted","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-4","description":"`tier` values are: `\"unit\"`, `\"integration\"`, `\"e2e\"`, and `\"windows\"` (a platform-gated tier proven only on a Windows runner) \u2014 no other automated tiers","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Scan all real `*.proofs-*.json` files; verify every tier value (top-level and per-entry) is in the valid set {unit, integration, e2e, windows}. Verify `spec_format.md` documents @integration, @e2e, and","test_file":"dev/test_schema_proof_format.py","test_name":"test_standard_tiers_documented","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-5","description":"Proof plugins use feature-scoped overwrite: load existing file, purge entries matching current feature, append new entries, write merged result","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Pre-seed a proof file with feature B entries; run the real pytest proof plugin for feature A via subprocess; verify feature B entries are preserved and feature A entries are added","test_file":"dev/test_schema_proof_format.py","test_name":"test_feature_scoped_overwrite","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-6","description":"Proof files are committed to git \u2014 they are project records, not ephemeral build artifacts","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Grep `.gitignore` for `*.proofs-*.json`; verify no gitignore rule excludes proof files","test_file":"dev/test_schema_proof_format.py","test_name":"test_proof_files_not_gitignored","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-7","description":"Manual proofs are stamped inline in the spec's `## Proof` section as `@manual(, , )`, not in proof JSON files","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Grep `references/formats/proofs_format.md` for `@manual`; verify manual stamp format is `@manual(, , )`","test_file":"dev/test_schema_proof_format.py","test_name":"test_manual_stamp_format_documented","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-1","description":"FORBIDDEN \u2014 No `eval()` or `exec()` calls in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Grep `scripts/` for `eval(` and `exec(` in `.py` files; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_eval_or_exec","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-2","description":"FORBIDDEN \u2014 No `subprocess` calls with `shell=True` in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Grep `scripts/**/*.py` for `shell\\s*=\\s*True`; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_shell_true","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-3","description":"FORBIDDEN \u2014 No `os.system()` calls in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Grep `scripts/**/*.py` for `os\\.system\\s*\\(`; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_os_system","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-4","description":"FORBIDDEN \u2014 No hardcoded credential assignments (password/secret/api_key/token literals) in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Grep `scripts/` for assignment patterns `(password|secret|api_key|token)\\s*=\\s*[\"'][^\"']+[\"']` in `.py`, `.sh`, `.js` files, excluding test files and comments; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_hardcoded_credentials","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-5","description":"All subprocess calls use list-mode arguments (not string commands)","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Grep `scripts/**/*.py` for `subprocess.run(` and `subprocess.call(`; verify each call's first argument is a list literal `[`, not a string","test_file":"dev/test_security.py","test_name":"test_subprocess_uses_list_args","tier":"unit","status":"pass","audit":""}]}],"audit":null},{"name":"purlin_agent","category":"instructions","type":"feature","is_global":false,"source_url":null,"pinned":null,"description":"Defines the Purlin Agent persona and behavior for Claude Code \u2014 the primary instruction document that shapes how Claude operates in spec-driven mode. Covers the core development loop, proof marker syntax, coverage gates, and routing between skills.","source_path":null,"stack":"markdown (agent definition)","ext_status":null,"proved":16,"total":16,"deferred":0,"status":"VERIFIED","vhash":"26603850","receipt":{"commit":"6cd4518e322d351ea3c91a7b877f1e59f90f2492","timestamp":"2026-04-07T13:46:48.452182+00:00","stale":false},"rules":[{"id":"RULE-1","description":"The agent definition contains a YAML frontmatter with `name`, `description`, and `model` fields","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Grep `agents/purlin.md` for YAML frontmatter delimiters (`---`); verify `name:`, `description:`, and `model:` fields exist between them","test_file":"dev/test_purlin_agent.py","test_name":"test_yaml_frontmatter","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-2","description":"The `## Core Loop` section defines exactly 4 numbered steps: do the work, call sync_status, follow directives, ship with verify","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Grep `agents/purlin.md` for `## Core Loop`; verify it contains exactly 4 numbered items and the keywords \"Do the work\", \"sync_status\", \"Follow\", and \"Ship\"","test_file":"dev/test_purlin_agent.py","test_name":"test_core_loop_four_steps","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-3","description":"The `## Specs` section includes the 2-section format template with `> Description:`, `## Rules`, `## Proof`","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Grep `agents/purlin.md` for `## Specs`; verify it contains `> Description:`, `## Rules`, `## Proof` within the template","test_file":"dev/test_purlin_agent.py","test_name":"test_specs_section_template","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-4","description":"The `## Proof Markers` section documents marker syntax for all 3 frameworks: pytest, Jest, and shell","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Grep `agents/purlin.md` for `## Proof Markers`; verify it contains `**pytest:`, `**Jest:`, and `**Shell:` framework subsections","test_file":"dev/test_purlin_agent.py","test_name":"test_proof_markers_three_frameworks","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-5","description":"The `## Hard Gates` section defines exactly 1 gate: proof coverage","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Grep `agents/purlin.md` for `## Hard Gates`; verify it lists \"Proof coverage\" and contains exactly 1 gate","test_file":"dev/test_purlin_agent.py","test_name":"test_hard_gates_exactly_one","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-6","description":"The `## Implicit Routing` section maps user intents to actions including role-based priority handling (test/build/fix \u2192 read spec + build, status \u2192 sync_status, drift \u2192 purlin:drift, spec \u2192 purlin:spec, verify \u2192 purlin:verify with independent audit, engineer items \u2192 drift + build/unit-test, QA items \u2192 drift + verify)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Grep `agents/purlin.md` for `## Implicit Routing`; verify it contains mappings for \"test\", \"status\", \"drift\", \"spec\", \"verify\", \"engineer\", and \"QA\"","test_file":"dev/test_purlin_agent.py","test_name":"test_implicit_routing","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-7","description":"The `## Skills` table lists all 12 skills with their purpose","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Grep `agents/purlin.md` for `## Skills`; count table rows; verify there are 12 skill entries","test_file":"dev/test_purlin_agent.py","test_name":"test_skills_table_twelve_entries","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-8","description":"The `## References` table lists all 12 reference entries with their topic","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-8","description":"Grep `agents/purlin.md` for `## References`; count table rows; verify there are 12 reference entries","test_file":"dev/test_purlin_agent.py","test_name":"test_references_table_twelve_entries","tier":"unit","status":"pass","audit":""}]},{"id":"schema_spec_format/RULE-1","description":"Every spec MUST contain exactly two required sections: `## Rules`, `## Proof`","label":"required","source":"schema_spec_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Parse `references/formats/spec_format.md`; find the `## Required Sections` area; verify both sections (`## Rules`, `## Proof`) appear within it","test_file":"dev/test_schema_spec_format.py","test_name":"test_format_documents_two_sections","tier":"unit","status":"pass","audit":""}]},{"id":"schema_spec_format/RULE-2","description":"Rules are numbered sequentially as `RULE-N` (RULE-1, RULE-2, ...). Unnumbered lines under `## Rules` trigger a sync_status WARNING","label":"required","source":"schema_spec_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Create a spec with an unnumbered line under `## Rules` (e.g., `- some constraint without RULE-N prefix`); run sync_status; verify it reports a WARNING about unnumbered rules","test_file":"dev/test_schema_spec_format.py","test_name":"test_unnumbered_rule_triggers_warning","tier":"unit","status":"pass","audit":""}]},{"id":"schema_spec_format/RULE-3","description":"Proofs are numbered as `PROOF-N (RULE-N)` \u2014 each proof references the rule it covers","label":"required","source":"schema_spec_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Grep `specs/**/*.md` for lines under `## Proof`; verify every proof line matches the pattern `PROOF-\\d+ \\(RULE-\\d+\\)`","test_file":"dev/test_schema_spec_format.py","test_name":"test_all_proof_lines_match_pattern","tier":"unit","status":"pass","audit":""}]},{"id":"schema_spec_format/RULE-4","description":"Every RULE must have at least one corresponding PROOF","label":"required","source":"schema_spec_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Create a spec with RULE-1 but no PROOF referencing RULE-1; run sync_status; verify it reports the rule as uncovered","test_file":"dev/test_schema_spec_format.py","test_name":"test_rule_without_proof_shows_uncovered","tier":"unit","status":"pass","audit":""}]},{"id":"schema_spec_format/RULE-5","description":"`> Requires:` metadata, when present, is a comma-separated list of anchor names whose rules also apply to this feature","label":"required","source":"schema_spec_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Create a spec with `> Requires: base_anchor`; run sync_status; verify the report includes `base_anchor/RULE-1` in the coverage output","test_file":"dev/test_schema_spec_format.py","test_name":"test_requires_includes_referenced_rules","tier":"unit","status":"pass","audit":""}]},{"id":"schema_spec_format/RULE-6","description":"`> Scope:` metadata, when present, is a comma-separated list of source file paths the feature touches","label":"required","source":"schema_spec_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Create a spec with `> Scope: scripts/mcp/purlin_server.py`; verify sync_status parses and displays the scope files","test_file":"dev/test_schema_spec_format.py","test_name":"test_scope_metadata_parsed","tier":"unit","status":"pass","audit":""}]},{"id":"schema_spec_format/RULE-7","description":"Feature specs use heading `# Feature: `, anchor specs use `# Anchor: `","label":"required","source":"schema_spec_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Grep `specs/**/*.md` for first-level headings; verify each starts with `# Feature:` or `# Anchor:`","test_file":"dev/test_schema_spec_format.py","test_name":"test_spec_headings_use_correct_prefix","tier":"unit","status":"pass","audit":""}]},{"id":"schema_spec_format/RULE-8","description":"`> Description:` metadata, when present, is a plain-language summary of the spec; supports multi-line via `>` continuation lines that do not match a metadata field pattern (`> FieldName:`)","label":"required","source":"schema_spec_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-8","description":"Create a spec with `> Description: First line\\n> second line\\n> Scope: src/`; parse description; verify it equals \"First line second line\" (continuation joined, Scope not included)","test_file":"dev/test_schema_spec_format.py","test_name":"test_multiline_description_continuation","tier":"unit","status":"pass","audit":""}]}],"audit":null},{"name":"purlin_config","category":"mcp","type":"feature","is_global":false,"source_url":null,"pinned":null,"description":"MCP tool for reading and writing Purlin configuration. Reads the full config or a single key, and writes single keys atomically.","source_path":null,"stack":"python/stdlib, json","ext_status":null,"proved":6,"total":6,"deferred":0,"status":"VERIFIED","vhash":"ddd0d4e8","receipt":{"commit":"6cd4518e322d351ea3c91a7b877f1e59f90f2492","timestamp":"2026-04-07T13:46:48.452182+00:00","stale":false},"rules":[{"id":"RULE-1","description":"Reads the full config or a single key, and writes single keys via `update_config`","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Call with action \"write\" then action \"read\"; verify the written value is returned","test_file":"dev/test_mcp_server.py","test_name":"test_read_write","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-1","description":"FORBIDDEN \u2014 No `eval()` or `exec()` calls in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Grep `scripts/` for `eval(` and `exec(` in `.py` files; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_eval_or_exec","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-2","description":"FORBIDDEN \u2014 No `subprocess` calls with `shell=True` in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Grep `scripts/**/*.py` for `shell\\s*=\\s*True`; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_shell_true","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-3","description":"FORBIDDEN \u2014 No `os.system()` calls in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Grep `scripts/**/*.py` for `os\\.system\\s*\\(`; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_os_system","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-4","description":"FORBIDDEN \u2014 No hardcoded credential assignments (password/secret/api_key/token literals) in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Grep `scripts/` for assignment patterns `(password|secret|api_key|token)\\s*=\\s*[\"'][^\"']+[\"']` in `.py`, `.sh`, `.js` files, excluding test files and comments; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_hardcoded_credentials","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-5","description":"All subprocess calls use list-mode arguments (not string commands)","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Grep `scripts/**/*.py` for `subprocess.run(` and `subprocess.call(`; verify each call's first argument is a list literal `[`, not a string","test_file":"dev/test_security.py","test_name":"test_subprocess_uses_list_args","tier":"unit","status":"pass","audit":""}]}],"audit":null},{"name":"purlin_references","category":"instructions","type":"feature","is_global":false,"source_url":null,"pinned":null,"description":"Ten reference documents that define Purlin's formats, conventions, and quality standards. These are the authoritative source that skills and agents reference, ensuring structural consistency across the framework.","source_path":null,"stack":"markdown (reference documentation)","ext_status":null,"proved":30,"total":30,"deferred":0,"status":"VERIFIED","vhash":"eebca835","receipt":{"commit":"8f4c47d21dd51e50f2994e2df67b48af5c2371f2","timestamp":"2026-06-05T13:43:25.311450+00:00","stale":false},"rules":[{"id":"RULE-1","description":"`spec_format.md` documents the 2 required sections (`## Rules`, `## Proof`), the `> Description:` metadata field, and the RULE-N/PROOF-N numbering convention","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Grep `references/formats/spec_format.md` for `## Rules`, `## Proof`; verify both appear as required sections. Grep for `> Description:` in the metadata fields table. Grep for `RULE-N` pattern documentation","test_file":"dev/test_purlin_references.py","test_name":"test_spec_format_required_sections","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-10","description":"`spec_quality_guide.md` includes test failure diagnosis guidance with the three categories (code bug, test bug, spec drift) and assertion integrity rules","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-10","description":"Grep `references/spec_quality_guide.md` for `Code bug`, `Test bug`, `Spec drift`, and `Assertion Integrity`; verify all appear","test_file":"dev/test_purlin_references.py","test_name":"test_quality_guide_test_failure_diagnosis","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-11","description":"`spec_quality_guide.md` includes audience-appropriate language guidance mapping artifacts to their intended readers","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-11","description":"Grep `references/spec_quality_guide.md` for `Audience-Appropriate Language`; verify the section exists","test_file":"dev/test_purlin_references.py","test_name":"test_quality_guide_audience_language","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-12","description":"`drift_criteria.md` documents file classification order, NO_IMPACT patterns, behavioral directory exclusions, significance mapping, structural-only drift indicators, and precomputed drift flags","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-12","description":"Grep `references/drift_criteria.md` for `File Classification`, `NO_IMPACT Patterns`, `Behavioral Directory Exclusions`, `Significance Classification`, `Structural-Only Drift`, `drift_flags`; verify all sections present","test_file":"dev/test_purlin_references.py","test_name":"test_drift_criteria_sections","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-13","description":"`spec_quality_guide.md` documents E2E proof descriptions as observable flows (arrange \u2192 act \u2192 observe through the real running app), forbids naming source files or internal functions in proof descriptions, and requires descriptions to stay tool-agnostic (executable by any e2e runner)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-13","description":"Grep `references/spec_quality_guide.md` for the \"E2E proof descriptions\" section; verify it contains arrange \u2192 act \u2192 observe flow language, the ban on naming source files/internal functions, and tool-agnostic phrasing","test_file":"dev/test_purlin_references.py","test_name":"test_quality_guide_e2e_proof_section","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-14","description":"`audit_criteria.md` defines E2E Proof Tier Integrity criteria \u2014 tier mismatch (an `@e2e` test that never drives the real UI) and source-constant assertion (asserting a config constant where the rule describes runtime behavior) \u2014 applying to ALL `@e2e` proofs, not just design anchors","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-14","description":"Grep `references/audit_criteria.md` for \"E2E Proof Tier Integrity\", \"tier mismatch\", and \"source-constant\"; verify the section applies to all `@e2e` proofs, not only design anchors","test_file":"dev/test_purlin_references.py","test_name":"test_audit_criteria_e2e_tier_integrity","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-15","description":"`supported_frameworks.md` documents end-to-end (browser) proofs: no dedicated e2e proof reporter ships, `@e2e` proofs are tool-agnostic, and proof emission wires through the existing plugins (Vitest/Jest markers or shell `purlin_proof` wrappers)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-15","description":"Grep `references/supported_frameworks.md` for the end-to-end proofs section; verify it states no dedicated e2e reporter ships, describes tool-agnostic `@e2e` proofs, and documents wiring through existing plugins","test_file":"dev/test_purlin_references.py","test_name":"test_supported_frameworks_e2e_section","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-2","description":"`proofs_format.md` documents the proof JSON schema with all 7 required fields and the feature-scoped overwrite merge behavior","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Grep `references/formats/proofs_format.md` for the 7 field names: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`; verify all appear. Grep for \"feature-scoped overwrite\"","test_file":"dev/test_purlin_references.py","test_name":"test_proofs_format_fields_and_merge","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-3","description":"`proofs_format.md` documents proof markers for all 3 frameworks: pytest, Jest, shell","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Grep `references/formats/proofs_format.md` for `### pytest`, `### Jest`, `### Shell`; verify all 3 framework subsections exist","test_file":"dev/test_purlin_references.py","test_name":"test_proofs_format_three_frameworks","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-4","description":"`anchor_format.md` documents anchor file location (`specs/_anchors/`), metadata fields (`> Source:`, `> Pinned:`, `> Global:`), sync protocol, and global anchor behavior","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Grep `references/formats/anchor_format.md` for `_anchors/`, `> Source:`, `> Pinned:`, `> Global:`; verify all appear","test_file":"dev/test_purlin_references.py","test_name":"test_anchor_format_metadata","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-5","description":"`anchor_format.md` documents all 8 type values: `design`, `api`, `security`, `brand`, `platform`, `schema`, `legal`, `prodbrief`","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Grep `references/formats/anchor_format.md` for all 8 type values: `design`, `api`, `security`, `brand`, `platform`, `schema`, `legal`, `prodbrief`; verify all appear in the type metadata documentation","test_file":"dev/test_purlin_references.py","test_name":"test_anchor_format_eight_type_values","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-6","description":"`hard_gates.md` documents exactly 1 gate: proof coverage","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Grep `references/hard_gates.md` for \"Proof coverage\"; verify it appears and no second gate is defined","test_file":"dev/test_purlin_references.py","test_name":"test_hard_gates_exactly_one","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-7","description":"`commit_conventions.md` documents all 8 commit prefixes: spec, feat, fix, test, verify, anchor, chore, docs","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Grep `references/commit_conventions.md` for the 8 prefixes: `spec`, `feat`, `fix`, `test`, `verify`, `anchor`, `chore`, `docs`; verify all 8 appear","test_file":"dev/test_purlin_references.py","test_name":"test_commit_conventions_eight_prefixes","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-8","description":"`purlin_commands.md` lists all 12 skills grouped by category (Authoring, Building, Quality, Reporting, Project)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-8","description":"Grep `references/purlin_commands.md` for `Authoring`, `Building`, `Quality`, `Reporting`, `Project`; verify all 5 category headers exist. Count skill entries; verify 12","test_file":"dev/test_purlin_references.py","test_name":"test_purlin_commands_categories_and_skills","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-9","description":"`spec_quality_guide.md` includes guidance on writing rules (rebuild test, contract boundaries, coverage dimensions), proof descriptions, tier assignment, and FORBIDDEN patterns","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-9","description":"Grep `references/spec_quality_guide.md` for \"rebuild test\", \"contract boundary/boundaries\", \"Coverage dimensions\", \"FORBIDDEN\", \"Tier\"; verify the guide covers the rebuild test, contract boundaries, coverage dimensions, forbidden patterns, and tier assignment","test_file":"dev/test_purlin_references.py","test_name":"test_spec_quality_guide_coverage","tier":"unit","status":"pass","audit":""}]},{"id":"schema_spec_format/RULE-1","description":"Every spec MUST contain exactly two required sections: `## Rules`, `## Proof`","label":"required","source":"schema_spec_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Parse `references/formats/spec_format.md`; find the `## Required Sections` area; verify both sections (`## Rules`, `## Proof`) appear within it","test_file":"dev/test_schema_spec_format.py","test_name":"test_format_documents_two_sections","tier":"unit","status":"pass","audit":""}]},{"id":"schema_spec_format/RULE-2","description":"Rules are numbered sequentially as `RULE-N` (RULE-1, RULE-2, ...). Unnumbered lines under `## Rules` trigger a sync_status WARNING","label":"required","source":"schema_spec_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Create a spec with an unnumbered line under `## Rules` (e.g., `- some constraint without RULE-N prefix`); run sync_status; verify it reports a WARNING about unnumbered rules","test_file":"dev/test_schema_spec_format.py","test_name":"test_unnumbered_rule_triggers_warning","tier":"unit","status":"pass","audit":""}]},{"id":"schema_spec_format/RULE-3","description":"Proofs are numbered as `PROOF-N (RULE-N)` \u2014 each proof references the rule it covers","label":"required","source":"schema_spec_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Grep `specs/**/*.md` for lines under `## Proof`; verify every proof line matches the pattern `PROOF-\\d+ \\(RULE-\\d+\\)`","test_file":"dev/test_schema_spec_format.py","test_name":"test_all_proof_lines_match_pattern","tier":"unit","status":"pass","audit":""}]},{"id":"schema_spec_format/RULE-4","description":"Every RULE must have at least one corresponding PROOF","label":"required","source":"schema_spec_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Create a spec with RULE-1 but no PROOF referencing RULE-1; run sync_status; verify it reports the rule as uncovered","test_file":"dev/test_schema_spec_format.py","test_name":"test_rule_without_proof_shows_uncovered","tier":"unit","status":"pass","audit":""}]},{"id":"schema_spec_format/RULE-5","description":"`> Requires:` metadata, when present, is a comma-separated list of anchor names whose rules also apply to this feature","label":"required","source":"schema_spec_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Create a spec with `> Requires: base_anchor`; run sync_status; verify the report includes `base_anchor/RULE-1` in the coverage output","test_file":"dev/test_schema_spec_format.py","test_name":"test_requires_includes_referenced_rules","tier":"unit","status":"pass","audit":""}]},{"id":"schema_spec_format/RULE-6","description":"`> Scope:` metadata, when present, is a comma-separated list of source file paths the feature touches","label":"required","source":"schema_spec_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Create a spec with `> Scope: scripts/mcp/purlin_server.py`; verify sync_status parses and displays the scope files","test_file":"dev/test_schema_spec_format.py","test_name":"test_scope_metadata_parsed","tier":"unit","status":"pass","audit":""}]},{"id":"schema_spec_format/RULE-7","description":"Feature specs use heading `# Feature: `, anchor specs use `# Anchor: `","label":"required","source":"schema_spec_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Grep `specs/**/*.md` for first-level headings; verify each starts with `# Feature:` or `# Anchor:`","test_file":"dev/test_schema_spec_format.py","test_name":"test_spec_headings_use_correct_prefix","tier":"unit","status":"pass","audit":""}]},{"id":"schema_spec_format/RULE-8","description":"`> Description:` metadata, when present, is a plain-language summary of the spec; supports multi-line via `>` continuation lines that do not match a metadata field pattern (`> FieldName:`)","label":"required","source":"schema_spec_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-8","description":"Create a spec with `> Description: First line\\n> second line\\n> Scope: src/`; parse description; verify it equals \"First line second line\" (continuation joined, Scope not included)","test_file":"dev/test_schema_spec_format.py","test_name":"test_multiline_description_continuation","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-1","description":"Proof files are named `.proofs-.json` and live in the same directory as their spec","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Create a proof file at `specs/test/foo.proofs-unit.json`; run sync_status; verify it reads the file and associates it with the `foo` spec","test_file":"dev/test_schema_proof_format.py","test_name":"test_proof_file_read_by_sync_status","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-2","description":"The JSON schema has a top-level `tier` string and a `proofs` array. Each proof entry contains: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Validate a proof JSON file against the required fields; verify all 7 fields (`feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`) are present in each proof entry","test_file":"dev/test_schema_proof_format.py","test_name":"test_proof_entry_has_all_seven_fields","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-3","description":"`status` is either `\"pass\"` or `\"fail\"` \u2014 no other values","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Create a proof entry with `status: \"error\"`; run sync_status; verify it is not counted as a passing proof","test_file":"dev/test_schema_proof_format.py","test_name":"test_invalid_status_not_counted","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-4","description":"`tier` values are: `\"unit\"`, `\"integration\"`, `\"e2e\"`, and `\"windows\"` (a platform-gated tier proven only on a Windows runner) \u2014 no other automated tiers","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Scan all real `*.proofs-*.json` files; verify every tier value (top-level and per-entry) is in the valid set {unit, integration, e2e, windows}. Verify `spec_format.md` documents @integration, @e2e, and","test_file":"dev/test_schema_proof_format.py","test_name":"test_standard_tiers_documented","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-5","description":"Proof plugins use feature-scoped overwrite: load existing file, purge entries matching current feature, append new entries, write merged result","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Pre-seed a proof file with feature B entries; run the real pytest proof plugin for feature A via subprocess; verify feature B entries are preserved and feature A entries are added","test_file":"dev/test_schema_proof_format.py","test_name":"test_feature_scoped_overwrite","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-6","description":"Proof files are committed to git \u2014 they are project records, not ephemeral build artifacts","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Grep `.gitignore` for `*.proofs-*.json`; verify no gitignore rule excludes proof files","test_file":"dev/test_schema_proof_format.py","test_name":"test_proof_files_not_gitignored","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-7","description":"Manual proofs are stamped inline in the spec's `## Proof` section as `@manual(, , )`, not in proof JSON files","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Grep `references/formats/proofs_format.md` for `@manual`; verify manual stamp format is `@manual(, , )`","test_file":"dev/test_schema_proof_format.py","test_name":"test_manual_stamp_format_documented","tier":"unit","status":"pass","audit":""}]}],"audit":null},{"name":"purlin_report","category":"dashboard","type":"feature","is_global":false,"source_url":null,"pinned":null,"description":"Static HTML dashboard that renders Purlin coverage data from `.purlin/report-data.js`. Opens via `file://` in any browser with no server or build step.","source_path":null,"stack":"html/css/javascript","ext_status":null,"proved":44,"total":44,"deferred":0,"status":"VERIFIED","vhash":"378f19d9","receipt":{"commit":"e8dbadf5c33ff73ac0c89efdf5d736078047d57d","timestamp":"2026-06-05T12:24:43.424415+00:00","stale":false},"rules":[{"id":"RULE-1","description":"HTML loads .purlin/report-data.js via a script tag before rendering","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Use Playwright to read page source; verify script tag with src=\".purlin/report-data.js\" is present","test_file":"dev/test_purlin_report.py","test_name":"test_script_tag_loads_data_js","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-10","description":"Anchor features display a type pill (anchor or global) next to their name","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-10","description":"Write report-data.js with anchor features (one global, one local); load in Playwright; verify elements with class \"tp-global\" and \"tp-anchor\" exist; take screenshot","test_file":"dev/test_purlin_report.py","test_name":"test_anchor_type_pills","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-11","description":"Anchors with source_url display an external link icon with the URL as tooltip","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-11","description":"Write report-data.js with anchor having source_url; load in Playwright; verify element with class \"ext-icon\" exists and has title attribute containing the URL","test_file":"dev/test_purlin_report.py","test_name":"test_anchor_external_link_icon","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-12","description":"Feature table columns are sortable by clicking column headers","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-12","description":"Load page; click \"Coverage\" column header; verify table row order changes (first row name differs from default sort); take screenshot","test_file":"dev/test_purlin_report.py","test_name":"test_table_sorting","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-13","description":"Footer docs link uses docs_url from PURLIN_DATA, not a hardcoded URL","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-13","description":"Write report-data.js with docs_url set to \"https://example.com/docs\"; load in Playwright; verify footer link href equals \"https://example.com/docs\"","test_file":"dev/test_purlin_report.py","test_name":"test_footer_docs_url","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-14","description":"Summary strip shows integrity percentage from audit_summary with color coding, or dash with \"run purlin:audit\" when null","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-14","description":"Write report-data.js with audit_summary.integrity=85; load in Playwright; verify a summary card contains \"85%\"; write another with audit_summary=null; reload; verify dash shown instead; take screenshots of both","test_file":"dev/test_purlin_report.py","test_name":"test_integrity_card_display","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-15","description":"Header shows last audit time from audit_summary, with amber warning when stale","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-15","description":"Write report-data.js with audit_summary.stale=true; load in Playwright; verify element with class \"audit-time stale\" exists; take screenshot","test_file":"dev/test_purlin_report.py","test_name":"test_audit_time_stale_class","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-16","description":"Status column is centered in both the feature table and the expanded rules sub-table at all viewport widths","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-16","description":"Load page in Playwright at 1920px and 1280px widths; expand a feature; verify the status badge td in the feature table has text-align:center; verify the status td in the rules sub-table has text-align:center; take screenshots at both widths","test_file":"dev/test_purlin_report.py","test_name":"test_status_column_centered_at_multiple_widths","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-17","description":"Dashboard uses full available width up to 2400px and is responsive down to 1100px","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-17","description":"Load page in Playwright at viewport width 2400px; verify dashboard container max-width is 2400px; resize to 1100px; verify summary strip reflows to 3 columns; take screenshots at both widths","test_file":"dev/test_purlin_report.py","test_name":"test_responsive_layout","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-18","description":"Coverage bar fill width matches the proved/total fraction \u2014 a feature with 2/6 coverage has a bar fill at ~33%, not 100%","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-18","description":"Create features with known coverage fractions (2/6 and 5/5); load in Playwright; measure the cov-fill element width as a percentage of the cov-bar; verify 2/6 bar is ~33% and 5/5 bar is 100%","test_file":"dev/test_purlin_report.py","test_name":"test_coverage_bar_width_matches_fraction","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-19","description":"Features are grouped by category with collapsible section headers showing rolled-up summaries (total count, coverage fraction, and status breakdown); category coverage bar is amber when any feature is untested or coverage is incomplete, red when any feature is failing, green only when all features are passing or verified; this grouping applies independently within both the Specs and Anchors sections","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-19","description":"Write report-data.js with features in 3 categories: \"skills\" (2 PASSING), \"mcp\" (1 PASSING + 1 UNTESTED), \"_anchors\" (1 VERIFIED anchor); load in Playwright; verify the Specs section has 2 category headers (\"skills\", \"mcp\"); verify the Anchors section has 1 category header (\"_anchors\"); verify \"mcp\" category has amber coverage bar; take screenshot","test_file":"dev/test_purlin_report.py","test_name":"test_category_headers_with_rolled_up_summaries","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-2","description":"When PURLIN_DATA is undefined, dashboard shows a no-data message with instructions","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Use Playwright to load purlin-report.html without report-data.js; take screenshot; verify page contains text \"No dashboard data\" and \"purlin:status\"","test_file":"dev/test_purlin_report.py","test_name":"test_no_data_message","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-20","description":"Category sections are expanded by default; a category renders collapsed only when localStorage explicitly records it as collapsed","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-20","description":"Load page with categorized features and empty localStorage; verify feature rows (.fr) are visible without any interaction; click a category header; verify its features become hidden; take screenshot","test_file":"dev/test_purlin_report.py","test_name":"test_categories_expanded_by_default","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-21","description":"Category and feature open/closed state is persisted to localStorage and restored on reload","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-21","description":"Load page (categories expanded by default); collapse a category; reload page; verify the same category is still collapsed and other categories remain expanded; expand it; reload; verify it is expanded","test_file":"dev/test_purlin_report.py","test_name":"test_category_state_persists_across_reloads","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-22","description":"Proof audit tags (STRONG/WEAK/HOLLOW) only appear when audit_summary has data; when audit_summary is null (integrity card shows dash), no audit tags render on any proof","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-22","description":"Load page with audit_summary having integrity=85 and features with proof-level audit tags; expand a feature; verify .atag elements appear on proofs; take screenshot. Then load with audit_summary=null but same per-feature audit data; expand the same feature; verify zero .atag elements render; take screenshot","test_file":"dev/test_purlin_report.py","test_name":"test_audit_tags_visible_with_audit_data_hidden_without","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-23","description":"Expanded detail view shows a status-colored action banner above the rules table: PARTIAL shows how many rules need proofs, FAILING shows how many tests are failing, PASSING shows \"run purlin:verify\", UNTESTED shows \"write tests to begin coverage\", VERIFIED shows no banner","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-23","description":"Write report-data.js with features in each status (PARTIAL with 2 NONE rules, FAILING with 1 FAIL rule, PASSING, UNTESTED, VERIFIED); expand each; verify PARTIAL has .ab-partial banner containing \"2 rules need proofs\"; FAILING has .ab-failing banner containing \"1 test failing\"; PASSING has .ab-passing banner containing \"purlin:verify\"; UNTESTED has .ab-untested banner containing \"write tests\"; VERIFIED has no .ab element; take screenshots","test_file":"dev/test_purlin_report.py","test_name":"test_action_banners_per_status","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-24","description":"Rules with NONE status display with an amber left border, visually distinguishing uncovered rules from proved ones","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-24","description":"Expand a PARTIAL feature with NONE rules; verify rule rows with NONE status have class \"rule-np\"; verify the first td of those rows has a computed border-left-color matching amber; take screenshot","test_file":"dev/test_purlin_report.py","test_name":"test_no_proof_rules_have_amber_border","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-25","description":"Rules with FAIL status display with a red left border, visually distinguishing failing rules from passing ones","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-25","description":"Expand a FAILING feature with FAIL rules; verify rule rows with FAIL status have class \"rule-fail\"; verify the first td of those rows has a computed border-left-color matching red; take screenshot","test_file":"dev/test_purlin_report.py","test_name":"test_fail_rules_have_red_border","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-26","description":"Expanded detail view for anchors with source_url shows an \"External Reference\" info block above the rules table, displaying Source URL (as clickable link), Path (if present, in code font), and Pinned version (SHA truncated to 7 chars for git, full timestamp for Figma); unpinned anchors show amber \"Unpinned\" text","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-26","description":"Write report-data.js with an anchor feature having source_url, pinned (40-char SHA), and source_path; expand the anchor; verify .ext-ref-block exists containing a Source link, Path in code element, and Pinned truncated to 7 chars; write another anchor with no pinned; expand it; verify amber \"Unpinned\" text; take screenshots","test_file":"dev/test_purlin_report.py","test_name":"test_external_reference_block","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-27","description":"External link icon tooltip includes Source URL, Path (if present), and Pinned value (truncated)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-27","description":"Write report-data.js with anchor having source_url, pinned, and source_path; verify .ext-icon title attribute contains all three values","test_file":"dev/test_purlin_report.py","test_name":"test_ext_icon_tooltip_includes_pinned","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-28","description":"Anchors with `ext_status` of \"stale\" or \"unpinned\" display an amber outline \"STALE\" badge next to the external link icon, with tooltip \"Run purlin:anchor sync to update\"; anchors with \"current\" or null ext_status show no badge","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-28","description":"Write report-data.js with one anchor having ext_status=\"stale\" and another with ext_status=null; load dashboard; verify stale anchor has .ext-stale badge with text \"STALE\" and tooltip containing \"sync\"; verify non-stale anchor has no .ext-stale badge","test_file":"dev/test_purlin_report.py","test_name":"test_stale_badge_on_stale_anchor","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-29","description":"When `uncommitted` array in PURLIN_DATA is non-empty, an expandable \"uncommitted work\" section appears between the summary strip and the feature table, showing the file count when collapsed and the file list when expanded; when the array is empty or absent, no section appears","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-29","description":"Write report-data.js with uncommitted=[\"M file1.py\",\"?? file2.js\"]; load dashboard; verify .uw-section exists with count \"2\"; click the header; verify .uw-files becomes visible with file list; reload with uncommitted=[]; verify no .uw-section exists","test_file":"dev/test_purlin_report.py","test_name":"test_uncommitted_section","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-3","description":"Summary strip shows total features, verified, passing, incomplete (partial + untested combined), failing counts from PURLIN_DATA.summary; the incomplete card shows a subtitle breakdown when both partial and untested are nonzero","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Write report-data.js with summary {total_features:10, verified:4, passing:2, partial:2, failing:1, untested:1}; load page in Playwright; verify summary cards show: Features=10, Verified=4, Passing=2, Incomplete=3 (with subtitle \"2 partial, 1 untested\"), Failing=1; take screenshot","test_file":"dev/test_purlin_report.py","test_name":"test_summary_strip_counts","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-30","description":"Expanded detail view shows the spec's `> Description:` content as a description block (`.desc-block`) using the full width of the detail container, positioned between the external reference block (or action banner if no external reference) and the \"Rules & Proofs\" header; the block is only rendered when the feature has a non-null, non-empty description","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-30","description":"Write report-data.js with one feature having description \"Handles user login\" and another with description null; expand each; verify the first shows a .desc-block containing \"Handles user login\" positioned before the rules table; verify the second has no .desc-block element","test_file":"dev/test_purlin_report.py","test_name":"test_description_block_rendering","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-31","description":"Proofs whose description matches structural patterns (grep, file exists, section present, verify contains) display a green \"Structural\" tag (solid green background, white text) next to the proof ID; structural tags render independently of audit data and are not counted in the integrity score","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-31","description":"Write report-data.js with audit_summary having integrity data and a feature with two proofs: one with a structural description (\"Grep scripts/ for eval(); verify zero matches\") and one behavioral (\"Returns 401 when token is expired\"); expand the feature; verify the structural proof has an .atag-st element with text \"Structural\" and computed background-color matching green (#22c55e); verify the behavioral proof does not have an .atag-st element; verify the structural proof's tag has white text color","test_file":"dev/test_purlin_report.py","test_name":"test_structural_proof_tag_rendering","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-32","description":"Anchor features render in a separate \"Anchors\" section below the Specs section, with its own section label and table; the Specs section contains only non-anchor features","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-32","description":"Write report-data.js with 2 regular features (category \"auth\") and 2 anchor features (category \"_anchors\"); load in Playwright; verify two distinct .section-label elements exist with text \"Specs\" and \"Anchors\"; verify anchor feature names appear only inside the Anchors section table; verify no anchor features appear in the Specs section table; take screenshot","test_file":"dev/test_purlin_report.py","test_name":"test_anchors_separate_section","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-33","description":"Planned proofs (proof entries with status \"planned\") render in the rule's proof cell greyed with a \"not run\" indicator, visually distinct from executed proofs and from the bare em dash shown when a rule has no proofs at all; planned proofs never show an audit tag","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-33","description":"Write report-data.js with audit_summary having integrity data and one rule whose proofs array contains one entry with status \"planned\" (empty test_file/test_name) and one executed entry, plus a second rule with an empty proofs array; expand the feature; verify the planned proof renders inside an element with class .rprf-planned containing \"not run\" and has no .atag or .atag-st element; verify the executed proof renders normally; verify the empty-proofs rule still shows an em dash; take screenshot","test_file":"dev/test_purlin_report.py","test_name":"test_planned_proof_renders_greyed_not_run","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-4","description":"Feature table renders one row per feature with name, coverage fraction, status badge, integrity, and verified columns","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Write report-data.js with 8 features in mixed states; load in Playwright; count table rows with class \"fr\"; verify count is 8; take screenshot","test_file":"dev/test_purlin_report.py","test_name":"test_feature_table_row_count","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-5","description":"Clicking a feature row expands it to show per-rule detail","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Load page with features; click first feature row; verify a detail row with class \"dr\" becomes visible; verify it contains a rules table; take screenshot of expanded state","test_file":"dev/test_purlin_report.py","test_name":"test_row_expand_shows_detail","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-6","description":"Expanded detail shows each rule with id, description, source (blank for own rules, \"required\" or \"global\" for others), status, and proof column; when a rule has multiple proofs, all proofs are stacked vertically in a single cell separated by a thin divider, not split across rowspan rows","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Expand a feature with own + global rules, one rule having 2 proofs; verify own rules have empty Source column; verify global rules show \"global\" in Source column; verify the multi-proof rule has both proofs stacked in a single td with a .rprf-sep divider between them; take screenshot","test_file":"dev/test_purlin_report.py","test_name":"test_expanded_rule_sources_and_multi_proof_stacking","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-7","description":"Toggling dark/light mode persists the preference to localStorage","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Load page; click theme toggle; verify data-theme attribute changes; reload page; verify theme persisted from localStorage","test_file":"dev/test_purlin_report.py","test_name":"test_theme_toggle_persists","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-8","description":"Staleness indicator shows amber warning when data is older than 1 hour","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-8","description":"Write report-data.js with timestamp set to 2 hours ago; load in Playwright; verify staleness text contains \"ago\" and has CSS class \"warning\"; take screenshot","test_file":"dev/test_purlin_report.py","test_name":"test_staleness_warning_amber","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-9","description":"Staleness indicator shows red warning when data is older than 24 hours","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-9","description":"Write report-data.js with timestamp set to 2 days ago; load in Playwright; verify staleness text has CSS class \"stale\"; take screenshot","test_file":"dev/test_purlin_report.py","test_name":"test_staleness_stale_red","tier":"unit","status":"pass","audit":""}]},{"id":"dashboard_visual/RULE-1","description":"Dark theme background is #0f172a, card background is #1e293b","label":"required","source":"dashboard_visual","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Load dashboard in Playwright with dark theme; verify computed background-color of body is #0f172a and card element is #1e293b","test_file":"dev/test_purlin_report.py","test_name":"test_dark_theme_backgrounds","tier":"unit","status":"pass","audit":""}]},{"id":"dashboard_visual/RULE-10","description":"Integrity color coding: green at 80%+, amber at 50-79%, red below 50%, gray when null","label":"required","source":"dashboard_visual","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-10","description":"Load dashboard with features at integrity 90%, 60%, and 30%; verify green, amber, and red color classes respectively","test_file":"dev/test_purlin_report.py","test_name":"test_integrity_color_coding","tier":"unit","status":"pass","audit":""}]},{"id":"dashboard_visual/RULE-11","description":"All colors and visual constants are defined as CSS custom properties in :root or theme selectors, not hardcoded in component styles","label":"required","source":"dashboard_visual","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-11","description":"Grep purlin-report.html for hardcoded hex colors outside of CSS custom property definitions; verify none exist in component style rules","test_file":"dev/test_purlin_report.py","test_name":"test_no_hardcoded_hex_outside_custom_properties","tier":"unit","status":"pass","audit":""}]},{"id":"dashboard_visual/RULE-2","description":"Light theme background is #f1f5f9, card background is #ffffff","label":"required","source":"dashboard_visual","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Load dashboard in Playwright with light theme; verify computed background-color of body is #f1f5f9 and card element is #ffffff","test_file":"dev/test_purlin_report.py","test_name":"test_light_theme_backgrounds","tier":"unit","status":"pass","audit":""}]},{"id":"dashboard_visual/RULE-3","description":"Status green is #22c55e, amber is #f59e0b, red is #ef4444, teal accent is #2dd4bf","label":"required","source":"dashboard_visual","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Grep purlin-report.html CSS for --green: #22c55e, --amber: #f59e0b, --red: #ef4444, --teal: #2dd4bf; verify all present","test_file":"dev/test_purlin_report.py","test_name":"test_status_colors_in_css","tier":"unit","status":"pass","audit":""}]},{"id":"dashboard_visual/RULE-4","description":"Font stack is -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif","label":"required","source":"dashboard_visual","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Grep purlin-report.html for the sans-serif font stack; verify it includes -apple-system and Roboto","test_file":"dev/test_purlin_report.py","test_name":"test_sans_serif_font_stack","tier":"unit","status":"pass","audit":""}]},{"id":"dashboard_visual/RULE-5","description":"Monospace font stack is \"SF Mono\", \"Fira Code\", \"Cascadia Code\", \"JetBrains Mono\", Consolas, monospace","label":"required","source":"dashboard_visual","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Grep purlin-report.html for the monospace font stack; verify it includes \"SF Mono\" and Consolas","test_file":"dev/test_purlin_report.py","test_name":"test_mono_font_stack","tier":"unit","status":"pass","audit":""}]},{"id":"dashboard_visual/RULE-6","description":"VERIFIED status badge is solid green pill with white text","label":"required","source":"dashboard_visual","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Load dashboard with a VERIFIED feature in Playwright; verify .sb-ready element has background-color matching --green and white text","test_file":"dev/test_purlin_report.py","test_name":"test_ready_badge_style","tier":"unit","status":"pass","audit":""}]},{"id":"dashboard_visual/RULE-7","description":"PARTIAL status badge is amber outline pill (transparent background, amber border)","label":"required","source":"dashboard_visual","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Load dashboard with a partial feature in Playwright; verify .sb-partial element has transparent background and amber border","test_file":"dev/test_purlin_report.py","test_name":"test_partial_badge_style","tier":"unit","status":"pass","audit":""}]},{"id":"dashboard_visual/RULE-8","description":"FAIL status badge is solid red pill with white text","label":"required","source":"dashboard_visual","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-8","description":"Load dashboard with a FAIL feature in Playwright; verify .sb-fail element has background-color matching --red","test_file":"dev/test_purlin_report.py","test_name":"test_fail_badge_style","tier":"unit","status":"pass","audit":""}]},{"id":"dashboard_visual/RULE-9","description":"UNTESTED status badge is gray pill with amber text; no-proofs badge (generic) is gray pill at reduced opacity","label":"required","source":"dashboard_visual","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-9","description":"Load dashboard with an UNTESTED feature in Playwright; verify .sb-untested element has gray background and amber text color; verify a separate no-proofs element (.sb-none) has reduced opacity","test_file":"dev/test_purlin_report.py","test_name":"test_untested_badge_and_no_proofs_opacity","tier":"unit","status":"pass","audit":""}]}],"audit":null},{"name":"purlin_skills","category":"instructions","type":"feature","is_global":false,"source_url":null,"pinned":null,"description":"Twelve skill definition files that define user-facing Purlin commands. Each skill is a SKILL.md with YAML frontmatter and structured instructions that Claude Code executes when invoked. These are the primary user interface to Purlin \u2014 they define what each command does, its usage syntax, and step-by-step workflow.","source_path":null,"stack":"markdown (skill definitions)","ext_status":null,"proved":15,"total":15,"deferred":0,"status":"VERIFIED","vhash":"5bd9ab85","receipt":{"commit":"18b76cee2076ec24a0af7173d8cb33cda81e1e2b","timestamp":"2026-04-03T23:42:43.968125+00:00","stale":false},"rules":[{"id":"RULE-1","description":"Each skill file has YAML frontmatter with `name` and `description` fields","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"For each `skills/*/SKILL.md`, grep for YAML frontmatter delimiters (`---`); verify `name:` and `description:` fields exist","test_file":"dev/test_purlin_skills.py","test_name":"test_each_skill_has_frontmatter","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-10","description":"Drift skill requires reading git diffs for behavioral changes, not just interpreting MCP categories","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-10","description":"Grep `skills/drift/SKILL.md` for `git diff`; verify the diff-reading requirement is present","test_file":"dev/test_purlin_skills.py","test_name":"test_drift_requires_reading_diffs","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-11","description":"Spec skill update workflow (Step 7) presents a delta report showing KEEPING/ADDING/UPDATING/REMOVING before applying changes","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-11","description":"Grep `skills/spec/SKILL.md` for `KEEPING`, `ADDING`, `UPDATING`, and `REMOVING`; verify the delta report structure is present","test_file":"dev/test_purlin_skills.py","test_name":"test_spec_has_delta_report_structure","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-12","description":"Skills that write proof descriptions (build, spec, spec-from-code) include mandatory tier tag review","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-12","description":"Grep `skills/build/SKILL.md`, `skills/spec/SKILL.md`, and `skills/spec-from-code/SKILL.md` for `tier`; verify all three contain tier review requirements","test_file":"dev/test_purlin_skills.py","test_name":"test_proof_writing_skills_have_tier_review","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-13","description":"Init skill `--add-plugin` validates plugin files against language-specific patterns (Python: `proofs`+`json`, JS: `proofs`+`JSON`, Shell: `purlin_proof`, Java: `proofs`+`Proof`) and warns if validation fails","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-13","description":"Grep `skills/init/SKILL.md` for the validation table language entries (`Python`, `JavaScript`, `Shell`, `Java`) and the warning text `doesn't look like a standard proof plugin`; verify all present","test_file":"dev/test_purlin_skills.py","test_name":"test_init_add_plugin_validates_by_language","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-14","description":"Init skill `--add-plugin` supports both local file paths and git URL sources with distinct handling for each","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-14","description":"Grep `skills/init/SKILL.md` for `local file path` and `git URL`; verify both source types are documented with distinct handling steps","test_file":"dev/test_purlin_skills.py","test_name":"test_init_add_plugin_supports_file_and_git","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-15","description":"Init skill `--list-plugins` identifies built-in plugins (`pytest_purlin`, `jest_purlin`, `purlin-proof`) by framework name and labels all others as `custom`","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-15","description":"Grep `skills/init/SKILL.md` for `pytest_purlin.py` with `Python/pytest`, `jest_purlin.js` with `JavaScript/Jest`, and the label `custom`; verify the labeling table exists","test_file":"dev/test_purlin_skills.py","test_name":"test_init_list_plugins_labels_builtin_and_custom","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-2","description":"Exactly 12 skill files exist, one per directory under `skills/`","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Glob `skills/*/SKILL.md`; verify exactly 12 files are returned","test_file":"dev/test_purlin_skills.py","test_name":"test_exactly_twelve_skill_files","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-3","description":"Each skill file contains a `## Usage` section documenting the command syntax","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"For each `skills/*/SKILL.md`, grep for `## Usage`; verify the section exists","test_file":"dev/test_purlin_skills.py","test_name":"test_each_skill_has_usage_section","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-4","description":"Skill names in frontmatter match their directory names (e.g., `skills/build/SKILL.md` has `name: build`)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"For each `skills/*/SKILL.md`, extract the `name:` from frontmatter and the directory name; verify they match","test_file":"dev/test_purlin_skills.py","test_name":"test_skill_name_matches_directory","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-5","description":"Skills that modify files (build, spec, unit-test, verify, init, anchor) include commit instructions or git operations","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Grep `skills/build/SKILL.md`, `skills/spec/SKILL.md`, `skills/unit-test/SKILL.md`, `skills/verify/SKILL.md`, `skills/init/SKILL.md`, `skills/anchor/SKILL.md` for `git commit` or `commit the` or `create.*commit`; verify each contains positive commit instructions","test_file":"dev/test_purlin_skills.py","test_name":"test_modify_skills_have_commit_instructions","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-6","description":"Skills that call MCP tools (status, drift, find) reference the tool by name (`sync_status`, `drift`)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Grep `skills/status/SKILL.md` for `sync_status`; grep `skills/drift/SKILL.md` for `drift`; grep `skills/find/SKILL.md` for `sync_status`; verify each references its MCP tool","test_file":"dev/test_purlin_skills.py","test_name":"test_mcp_skills_reference_tools","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-7","description":"Build and unit-test skills require calling `sync_status` after tests and state it is not optional","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Grep `skills/build/SKILL.md` and `skills/unit-test/SKILL.md` for `sync_status`; verify both contain the reference. Grep build for \"not optional\"; verify present.","test_file":"dev/test_purlin_skills.py","test_name":"test_build_and_unittest_require_sync_status","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-8","description":"Verify skill prohibits modifying code or test files during verification","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-8","description":"Grep `skills/verify/SKILL.md` for `NEVER modify`; verify the read-only constraint is present","test_file":"dev/test_purlin_skills.py","test_name":"test_verify_prohibits_modifying_files","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-9","description":"Build skill includes test failure diagnosis guidance requiring root cause analysis before fixing","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-9","description":"Grep `skills/build/SKILL.md` for `diagnose` and `Never weaken`; verify both are present","test_file":"dev/test_purlin_skills.py","test_name":"test_build_has_failure_diagnosis_guidance","tier":"unit","status":"pass","audit":""}]}],"audit":null},{"name":"purlin_teammate_definitions","category":"instructions","type":"feature","is_global":false,"source_url":null,"pinned":null,"description":"Defines three Claude Code agent roles for the Purlin verify-audit-build loop: an auditor that evaluates proof quality, a builder that fixes code/tests based on audit feedback, and a reviewer that validates specs and detects spec drift. These definitions live in `.claude/agents/` where Claude Code discovers them as available agent roles.","source_path":null,"stack":"markdown (agent definitions)","ext_status":null,"proved":4,"total":4,"deferred":0,"status":"VERIFIED","vhash":"b3ef613b","receipt":{"commit":"18b76cee2076ec24a0af7173d8cb33cda81e1e2b","timestamp":"2026-04-03T23:42:43.968125+00:00","stale":false},"rules":[{"id":"RULE-1","description":"purlin-auditor.md exists with YAML frontmatter containing `name: purlin-auditor`, `description`, and `model` fields","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Read `.claude/agents/purlin-auditor.md`; verify YAML frontmatter contains `name: purlin-auditor`, a `description:` field, and a `model:` field","test_file":"dev/test_purlin_teammate_definitions.py","test_name":"test_auditor_frontmatter","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-2","description":"purlin-builder.md exists with YAML frontmatter containing `name: purlin-builder`, `description`, and `model` fields","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Read `.claude/agents/purlin-builder.md`; verify YAML frontmatter contains `name: purlin-builder`, a `description:` field, and a `model:` field","test_file":"dev/test_purlin_teammate_definitions.py","test_name":"test_builder_frontmatter","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-3","description":"purlin-reviewer.md exists with YAML frontmatter containing `name: purlin-reviewer`, `description`, and `model` fields","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Read `.claude/agents/purlin-reviewer.md`; verify YAML frontmatter contains `name: purlin-reviewer`, a `description:` field, and a `model:` field","test_file":"dev/test_purlin_teammate_definitions.py","test_name":"test_reviewer_frontmatter","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-4","description":"All three agent definitions are located in the `.claude/agents/` directory","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Glob `.claude/agents/purlin-*.md`; verify exactly 3 files are found and all are in `.claude/agents/`","test_file":"dev/test_purlin_teammate_definitions.py","test_name":"test_all_three_in_agents_directory","tier":"unit","status":"pass","audit":""}]}],"audit":null},{"name":"purlin_version","category":"instructions","type":"feature","is_global":false,"source_url":null,"pinned":null,"description":"Ensures the Purlin version string is defined in exactly one place (the VERSION file) and all references to it read from that file or match its value. Prevents version drift when releasing new versions.","source_path":null,"stack":null,"ext_status":null,"proved":5,"total":5,"deferred":0,"status":"VERIFIED","vhash":"175608df","receipt":{"commit":"5336ff4a5d11a06dc1a5d9f9b75b8c2b10f473e2","timestamp":"2026-05-26T07:45:32.457582+00:00","stale":false},"rules":[{"id":"RULE-1","description":"A VERSION file exists at the project root containing a single semver string","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Read VERSION file; verify it exists and contains a valid semver string (X.Y.Z)","test_file":"dev/test_purlin_version.py","test_name":"test_version_file_exists_and_is_valid_semver","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-2","description":"purlin_server.py reads the version from the VERSION file via _read_version(), not a hardcoded string","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Grep purlin_server.py for _read_version; verify it's called to set PURLIN_VERSION; verify SERVER_INFO uses PURLIN_VERSION","test_file":"dev/test_purlin_version.py","test_name":"test_purlin_server_uses_read_version_function","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-3","description":"templates/config.json version field matches the VERSION file","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Read VERSION file and templates/config.json; verify the version field in the template matches the VERSION file content","test_file":"dev/test_purlin_version.py","test_name":"test_template_config_version_matches_version_file","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-4","description":"No hardcoded version strings in purlin_server.py (no literal \"0.9.0\" or similar version patterns)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Grep purlin_server.py for hardcoded version patterns like \"0.9.0\" or \"0.10.0\"; verify zero matches outside of comments","test_file":"dev/test_purlin_version.py","test_name":"test_no_hardcoded_version_strings_in_purlin_server","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-5","description":"The .claude-plugin/plugin.json version field matches the VERSION file","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Read VERSION file and .claude-plugin/plugin.json; verify the version field in the manifest matches the VERSION file content","test_file":"dev/test_purlin_version.py","test_name":"test_plugin_manifest_version_matches_version_file","tier":"unit","status":"pass","audit":""}]}],"audit":null},{"name":"report_data","category":"mcp","type":"feature","is_global":false,"source_url":null,"pinned":null,"description":"When `\"report\": true` is set in `.purlin/config.json`, `sync_status` writes a JS data file (`.purlin/report-data.js`) containing structured coverage, rule, and verification data for the HTML dashboard.","source_path":null,"stack":"python/stdlib, json","ext_status":null,"proved":22,"total":22,"deferred":0,"status":"VERIFIED","vhash":"629723ab","receipt":{"commit":"e8dbadf5c33ff73ac0c89efdf5d736078047d57d","timestamp":"2026-06-05T12:24:43.424415+00:00","stale":false},"rules":[{"id":"RULE-1","description":"When config has `\"report\": true`, sync_status writes `.purlin/report-data.js`","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Set report=true in config, call sync_status, verify .purlin/report-data.js exists","test_file":"dev/test_report_data.py","test_name":"test_report_written_when_enabled","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-10","description":"Rule label is one of own, required, or global","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-10","description":"Parse report data, verify all rule labels are valid enum values","test_file":"dev/test_report_data.py","test_name":"test_all_rule_labels_are_valid","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-11","description":"docs_url is dynamically derived from the Purlin plugin git remote","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-11","description":"Call _get_plugin_docs_url, verify it returns a URL derived from git remote","test_file":"dev/test_report_data.py","test_name":"test_docs_url_derived_from_git_remote","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-12","description":"Anchor features have type \"anchor\" and include source_url when `> Source:` is present in spec","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-12","description":"Create an anchor spec with > Source:, regenerate report, verify source_url in output","test_file":"dev/test_report_data.py","test_name":"test_anchor_with_source_includes_source_url","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-13","description":"anchors_summary.total matches the count of features with type \"anchor\"","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-13","description":"Parse report data, count anchor features, verify matches anchors_summary.total","test_file":"dev/test_report_data.py","test_name":"test_anchors_summary_total_matches_anchor_count","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-14","description":"sync_status output includes dashboard file URL when purlin-report.html exists at project root","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-14","description":"Place purlin-report.html at root, call sync_status with report=true, verify output contains file:// URL","test_file":"dev/test_report_data.py","test_name":"test_dashboard_url_in_output_when_html_exists","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-15","description":"report-data.js includes an audit_summary object with integrity percentage, assessment counts, last audit timestamp, relative time, and stale boolean; null when no audit cache exists","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-15","description":"Create an audit cache with STRONG/WEAK entries and timestamps, regenerate report, verify audit_summary fields; delete cache, regenerate, verify audit_summary is null","test_file":"dev/test_report_data.py","test_name":"test_audit_summary_fields_present_and_null_when_no_cache","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-16","description":"Per-feature audit data is populated from the audit cache when entries exist for that feature","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-16","description":"Create an audit cache with entries for a specific feature, regenerate report, verify that feature's audit field is populated with correct integrity and findings","test_file":"dev/test_report_data.py","test_name":"test_per_feature_audit_populated_from_cache","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-17","description":"All proved rules count toward the coverage fraction (proved/total) regardless of proof type \u2014 grep-based and behavioral proofs are treated equally. Proof quality is assessed by the auditor (STRONG/WEAK/HOLLOW), not the coverage system","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-17","description":"Create a feature with 3 rules (2 behavioral, 1 grep-based); write passing proofs for all 3; build report data; verify proved==3, total==3, and status==PASSING \u2014 all proofs count equally","test_file":"dev/test_report_data.py","test_name":"test_all_proofs_count_equally","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-18","description":"Features with partial behavioral coverage have status \"PARTIAL\" in report data, not \"PASSING\", even if all existing proofs pass \u2014 PASSING requires every behavioral rule to have a passing proof","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-18","description":"Create a feature with 3 behavioral rules; write passing proofs for only 2 of them; build report data; verify feature status is \"PARTIAL\" not \"PASSING\"","test_file":"dev/test_report_data.py","test_name":"test_partial_coverage_gets_partial_status_not_passing","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-19","description":"Every feature entry includes a `category` field derived from the spec's parent directory under `specs/` (e.g., `specs/skills/skill_build.md` has category `skills`)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-19","description":"","test_file":"dev/test_report_data.py","test_name":"test_feature_category_derived_from_spec_directory","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-2","description":"When config lacks `\"report\"` or it is false, no report file is written","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Call sync_status without report config, verify no report-data.js written","test_file":"dev/test_report_data.py","test_name":"test_no_report_written_when_disabled","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-20","description":"Coverage invariant \u2014 for every feature in report data, status PASSING or VERIFIED implies proved == total (100% coverage fraction). No feature may show PASSING or VERIFIED with proved < total","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-20","description":"Create multiple features \u2014 one PASSING (behavioral-only), one PASSING (mixed behavioral+structural), one VERIFIED with receipt, one PARTIAL; build report data; assert every PASSING/VERIFIED feature has proved == total and every PARTIAL feature has proved < total","test_file":"dev/test_report_data.py","test_name":"test_passing_verified_always_100pct_coverage","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-21","description":"Every feature entry includes a `description` field containing the text of the spec's `> Description:` metadata field (with multi-line continuations joined), or null if the field is absent","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-21","description":"Create a spec with `> Description: Handles user login.`; build report data; verify feature description equals \"Handles user login.\"; create a spec with no `> Description:` field; verify description is null","test_file":"dev/test_report_data.py","test_name":"test_description_from_metadata_field","tier":"unit","status":"pass","audit":""},{"id":"PROOF-21","description":"Create a spec with `> Description: Handles user login.`; build report data; verify feature description equals \"Handles user login.\"; create a spec with no `> Description:` field; verify description is null","test_file":"dev/test_report_data.py","test_name":"test_description_multiline","tier":"unit","status":"pass","audit":""},{"id":"PROOF-21","description":"Create a spec with `> Description: Handles user login.`; build report data; verify feature description equals \"Handles user login.\"; create a spec with no `> Description:` field; verify description is null","test_file":"dev/test_report_data.py","test_name":"test_description_null_when_absent","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-22","description":"Planned proofs do not affect coverage \u2014 proved/total counts, vhash, and feature status are computed from executed proofs only; a rule whose only proofs are planned has status NONE","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-23","description":"Create a feature with one rule whose only proof is planned (no executed result); build report data; verify proved==0, feature status is UNTESTED, vhash is null, and the rule status is NONE","test_file":"dev/test_report_data.py","test_name":"test_planned_proofs_do_not_affect_coverage","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-3","description":"Report file contains `const PURLIN_DATA = {...};` that parses as valid JavaScript","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Read report-data.js, strip JS wrapper, verify JSON parses successfully","test_file":"dev/test_report_data.py","test_name":"test_report_file_is_valid_js_with_json","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-4","description":"PURLIN_DATA.summary counts match the feature list (verified + passing + partial + failing + untested == total_features)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Parse report data, verify summary counts sum correctly","test_file":"dev/test_report_data.py","test_name":"test_summary_counts_sum_to_total","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-5","description":"Every feature entry has fields: name, type, is_global, proved, total, deferred, status, vhash, receipt, rules, audit","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Parse report data, verify every feature has all required fields","test_file":"dev/test_report_data.py","test_name":"test_every_feature_has_required_fields","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-6","description":"Features with all proofs passing (status \"PASSING\" or \"VERIFIED\") have a non-null vhash; others have null vhash","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Parse report data, verify PASSING/VERIFIED features have vhash and others don't","test_file":"dev/test_report_data.py","test_name":"test_passing_features_have_vhash_others_do_not","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-7","description":"Features with receipt files include commit, timestamp, and stale fields in receipt","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Create a receipt file, regenerate report, verify receipt fields present","test_file":"dev/test_report_data.py","test_name":"test_receipt_fields_present_when_receipt_file_exists","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-8","description":"Each rule entry has fields: id, description, label, source, is_deferred, is_assumed, status, proofs (array of proof objects where each proof has id, description, test_file, test_name, tier, status, audit). The proofs array contains executed proofs plus planned proofs \u2014 spec `PROOF-N` entries from the rule's source `## Proof` section with no executed result \u2014 distinguished by status \"planned\" with empty test_file, test_name, and audit, and tier parsed from the proof description's @tag (default unit); a proof id with an executed result for a rule is never also emitted as planned for that rule","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-8","description":"Parse report data, verify every rule entry has all required fields","test_file":"dev/test_report_data.py","test_name":"test_every_rule_entry_has_required_fields","tier":"unit","status":"pass","audit":""},{"id":"PROOF-22","description":"Create a spec whose `## Proof` section declares PROOF-1 (RULE-1) and PROOF-2 (RULE-1) `@integration`; write an executed proof result for PROOF-1 only; build report data; verify RULE-1's proofs array contains PROOF-1 with status pass and PROOF-2 with status \"planned\", empty test_file/test_name/audit, and tier \"integration\"; verify PROOF-1 does not also appear as planned","test_file":"dev/test_report_data.py","test_name":"test_planned_proofs_appear_in_proofs_array","tier":"unit","status":"pass","audit":""},{"id":"PROOF-22","description":"Create a spec whose `## Proof` section declares PROOF-1 (RULE-1) and PROOF-2 (RULE-1) `@integration`; write an executed proof result for PROOF-1 only; build report data; verify RULE-1's proofs array contains PROOF-1 with status pass and PROOF-2 with status \"planned\", empty test_file/test_name/audit, and tier \"integration\"; verify PROOF-1 does not also appear as planned","test_file":"dev/test_report_data.py","test_name":"test_planned_proofs_for_required_anchor_rules","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-9","description":"Rule status is one of PASS, FAIL, NONE, or DEFERRED","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-9","description":"Parse report data, verify all rule statuses are valid enum values","test_file":"dev/test_report_data.py","test_name":"test_all_rule_statuses_are_valid","tier":"unit","status":"pass","audit":""}]}],"audit":null},{"name":"schema_proof_format","category":"_anchors","type":"anchor","is_global":false,"source_url":null,"pinned":null,"description":"Defines the JSON schema for proof files emitted by test runners and how plugins merge results when re-running tests (feature-scoped overwrite). The `sync_status` tool reads these files to compute rule coverage.","source_path":null,"stack":null,"ext_status":null,"proved":7,"total":7,"deferred":0,"status":"VERIFIED","vhash":"e492d09a","receipt":{"commit":"18b76cee2076ec24a0af7173d8cb33cda81e1e2b","timestamp":"2026-04-03T23:42:43.968125+00:00","stale":false},"rules":[{"id":"RULE-1","description":"Proof files are named `.proofs-.json` and live in the same directory as their spec","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Create a proof file at `specs/test/foo.proofs-unit.json`; run sync_status; verify it reads the file and associates it with the `foo` spec","test_file":"dev/test_schema_proof_format.py","test_name":"test_proof_file_read_by_sync_status","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-2","description":"The JSON schema has a top-level `tier` string and a `proofs` array. Each proof entry contains: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Validate a proof JSON file against the required fields; verify all 7 fields (`feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`) are present in each proof entry","test_file":"dev/test_schema_proof_format.py","test_name":"test_proof_entry_has_all_seven_fields","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-3","description":"`status` is either `\"pass\"` or `\"fail\"` \u2014 no other values","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Create a proof entry with `status: \"error\"`; run sync_status; verify it is not counted as a passing proof","test_file":"dev/test_schema_proof_format.py","test_name":"test_invalid_status_not_counted","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-4","description":"`tier` values are: `\"unit\"`, `\"integration\"`, `\"e2e\"`, and `\"windows\"` (a platform-gated tier proven only on a Windows runner) \u2014 no other automated tiers","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Scan all real `*.proofs-*.json` files; verify every tier value (top-level and per-entry) is in the valid set {unit, integration, e2e, windows}. Verify `spec_format.md` documents @integration, @e2e, and","test_file":"dev/test_schema_proof_format.py","test_name":"test_standard_tiers_documented","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-5","description":"Proof plugins use feature-scoped overwrite: load existing file, purge entries matching current feature, append new entries, write merged result","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Pre-seed a proof file with feature B entries; run the real pytest proof plugin for feature A via subprocess; verify feature B entries are preserved and feature A entries are added","test_file":"dev/test_schema_proof_format.py","test_name":"test_feature_scoped_overwrite","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-6","description":"Proof files are committed to git \u2014 they are project records, not ephemeral build artifacts","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Grep `.gitignore` for `*.proofs-*.json`; verify no gitignore rule excludes proof files","test_file":"dev/test_schema_proof_format.py","test_name":"test_proof_files_not_gitignored","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-7","description":"Manual proofs are stamped inline in the spec's `## Proof` section as `@manual(, , )`, not in proof JSON files","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Grep `references/formats/proofs_format.md` for `@manual`; verify manual stamp format is `@manual(, , )`","test_file":"dev/test_schema_proof_format.py","test_name":"test_manual_stamp_format_documented","tier":"unit","status":"pass","audit":""}]}],"audit":null},{"name":"schema_spec_format","category":"_anchors","type":"anchor","is_global":false,"source_url":null,"pinned":null,"description":"Defines the canonical 2-section format for all Purlin specs: Rules and Proof. The `sync_status` tool parses specs against this format and reports violations as warnings or missing coverage.","source_path":null,"stack":null,"ext_status":null,"proved":8,"total":8,"deferred":0,"status":"VERIFIED","vhash":"45becb45","receipt":{"commit":"d460be568d744c38a89b9f8624a939a1d324c967","timestamp":"2026-04-07T13:47:29.481586+00:00","stale":false},"rules":[{"id":"RULE-1","description":"Every spec MUST contain exactly two required sections: `## Rules`, `## Proof`","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Parse `references/formats/spec_format.md`; find the `## Required Sections` area; verify both sections (`## Rules`, `## Proof`) appear within it","test_file":"dev/test_schema_spec_format.py","test_name":"test_format_documents_two_sections","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-2","description":"Rules are numbered sequentially as `RULE-N` (RULE-1, RULE-2, ...). Unnumbered lines under `## Rules` trigger a sync_status WARNING","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Create a spec with an unnumbered line under `## Rules` (e.g., `- some constraint without RULE-N prefix`); run sync_status; verify it reports a WARNING about unnumbered rules","test_file":"dev/test_schema_spec_format.py","test_name":"test_unnumbered_rule_triggers_warning","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-3","description":"Proofs are numbered as `PROOF-N (RULE-N)` \u2014 each proof references the rule it covers","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Grep `specs/**/*.md` for lines under `## Proof`; verify every proof line matches the pattern `PROOF-\\d+ \\(RULE-\\d+\\)`","test_file":"dev/test_schema_spec_format.py","test_name":"test_all_proof_lines_match_pattern","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-4","description":"Every RULE must have at least one corresponding PROOF","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Create a spec with RULE-1 but no PROOF referencing RULE-1; run sync_status; verify it reports the rule as uncovered","test_file":"dev/test_schema_spec_format.py","test_name":"test_rule_without_proof_shows_uncovered","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-5","description":"`> Requires:` metadata, when present, is a comma-separated list of anchor names whose rules also apply to this feature","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Create a spec with `> Requires: base_anchor`; run sync_status; verify the report includes `base_anchor/RULE-1` in the coverage output","test_file":"dev/test_schema_spec_format.py","test_name":"test_requires_includes_referenced_rules","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-6","description":"`> Scope:` metadata, when present, is a comma-separated list of source file paths the feature touches","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Create a spec with `> Scope: scripts/mcp/purlin_server.py`; verify sync_status parses and displays the scope files","test_file":"dev/test_schema_spec_format.py","test_name":"test_scope_metadata_parsed","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-7","description":"Feature specs use heading `# Feature: `, anchor specs use `# Anchor: `","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Grep `specs/**/*.md` for first-level headings; verify each starts with `# Feature:` or `# Anchor:`","test_file":"dev/test_schema_spec_format.py","test_name":"test_spec_headings_use_correct_prefix","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-8","description":"`> Description:` metadata, when present, is a plain-language summary of the spec; supports multi-line via `>` continuation lines that do not match a metadata field pattern (`> FieldName:`)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-8","description":"Create a spec with `> Description: First line\\n> second line\\n> Scope: src/`; parse description; verify it equals \"First line second line\" (continuation joined, Scope not included)","test_file":"dev/test_schema_spec_format.py","test_name":"test_multiline_description_continuation","tier":"unit","status":"pass","audit":""}]}],"audit":null},{"name":"security_no_dangerous_patterns","category":"_anchors","type":"anchor","is_global":false,"source_url":"/Users/richlabarca/LocalCode/purlin/dev/external-refs/security-policy.git","pinned":"e18cecd9932191ea24361f2c472bb2818307e4d6","description":"Enforces the absence of dangerous code patterns across all executable Purlin framework code. The codebase is currently clean \u2014 this anchor exists to prove and maintain that state. Any feature requiring this anchor inherits these constraints.","source_path":"security_policy.md","stack":null,"ext_status":"error","proved":5,"total":5,"deferred":0,"status":"VERIFIED","vhash":"175608df","receipt":{"commit":"18b76cee2076ec24a0af7173d8cb33cda81e1e2b","timestamp":"2026-04-03T23:42:43.968125+00:00","stale":false},"rules":[{"id":"RULE-1","description":"FORBIDDEN \u2014 No `eval()` or `exec()` calls in scripts/","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Grep `scripts/` for `eval(` and `exec(` in `.py` files; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_eval_or_exec","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-2","description":"FORBIDDEN \u2014 No `subprocess` calls with `shell=True` in scripts/","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Grep `scripts/**/*.py` for `shell\\s*=\\s*True`; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_shell_true","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-3","description":"FORBIDDEN \u2014 No `os.system()` calls in scripts/","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Grep `scripts/**/*.py` for `os\\.system\\s*\\(`; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_os_system","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-4","description":"FORBIDDEN \u2014 No hardcoded credential assignments (password/secret/api_key/token literals) in scripts/","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Grep `scripts/` for assignment patterns `(password|secret|api_key|token)\\s*=\\s*[\"'][^\"']+[\"']` in `.py`, `.sh`, `.js` files, excluding test files and comments; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_hardcoded_credentials","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-5","description":"All subprocess calls use list-mode arguments (not string commands)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Grep `scripts/**/*.py` for `subprocess.run(` and `subprocess.call(`; verify each call's first argument is a list literal `[`, not a string","test_file":"dev/test_security.py","test_name":"test_subprocess_uses_list_args","tier":"unit","status":"pass","audit":""}]}],"audit":null},{"name":"skill_anchor","category":"skills","type":"feature","is_global":false,"source_url":null,"pinned":null,"description":"The `purlin:anchor` skill creates, syncs, and manages anchor specs \u2014 cross-cutting constraints with optional external references. Anchors define shared rules that other features reference via `> Requires:`.","source_path":null,"stack":"markdown (skill definition)","ext_status":null,"proved":7,"total":7,"deferred":0,"status":"VERIFIED","vhash":"e492d09a","receipt":{"commit":"6cd4518e322d351ea3c91a7b877f1e59f90f2492","timestamp":"2026-04-07T13:46:48.452182+00:00","stale":false},"rules":[{"id":"RULE-1","description":"Skill file has YAML frontmatter with `name` and `description` fields","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Grep `skills/anchor/SKILL.md` for YAML frontmatter delimiters (`---`); verify `name:` and `description:` fields exist","test_file":"dev/test_skill_specs.py","test_name":"test_has_frontmatter","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-2","description":"Skill file contains a `## Usage` section documenting command syntax","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Grep `skills/anchor/SKILL.md` for `## Usage`; verify the section exists","test_file":"dev/test_skill_specs.py","test_name":"test_has_usage_section","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-3","description":"The `name` field in frontmatter is `anchor`, matching the directory name","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Extract `name:` from frontmatter; verify it equals `anchor`","test_file":"dev/test_skill_specs.py","test_name":"test_name_matches_directory","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-4","description":"Skill includes commit instructions or git operations for file modifications","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Grep `skills/anchor/SKILL.md` for commit instructions (`git commit`, `commit the`, `create.*commit`); verify present","test_file":"dev/test_skill_specs.py","test_name":"test_has_commit_instructions","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-5","description":"An anchor written with only Part 1 (authoring) fields \u2014 no `> Source:`, `> Path:`, or `> Pinned:` \u2014 is parsed as a local anchor: `source_url` is None, no staleness checks run, and sync_status output contains no Source/Pinned lines for that anchor","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"e2e: Create anchor with only Part 1 fields (Description, Type, rules, proofs \u2014 no Source/Path/Pinned); call _scan_specs; verify source_url is None and pinned is None. Run sync_status; verify output does NOT contain \"Source:\" or \"Pinned:\" for that anchor","test_file":"dev/test_e2e_external_refs.sh","test_name":"Part 1-only anchor parsed as local","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-6","description":"An anchor with Part 2 tracking fields (`> Source:`, `> Path:`, `> Pinned:`) added is parsed as externally-referenced: `source_url`, `pinned`, and `source_path` are all populated, and sync_status output shows Source/Path/Pinned lines for that anchor","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"e2e: Create anchor with Part 1 fields plus Part 2 tracking fields (Source, Path, Pinned pointing to a local bare git repo); call _scan_specs; verify source_url, pinned, and source_path are all populated with correct values. Run sync_status; verify output contains \"Source:\", \"Path:\", and \"Pinned:\" lines","test_file":"dev/test_e2e_external_refs.sh","test_name":"Part 2 tracking fields fully parsed","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-7","description":"Adding Part 2 tracking fields to a Part 1-only anchor transitions it from local to externally-referenced \u2014 the same anchor file, before and after adding `> Source:` and `> Pinned:`, produces different `_scan_specs` results for `source_url` and `pinned`","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"e2e: Create a Part 1-only anchor; call _scan_specs and capture source_url (None). Then add Part 2 fields to the same file; call _scan_specs again; verify source_url is now populated and pinned matches the written value","test_file":"dev/test_e2e_external_refs.sh","test_name":"Part 1\u21922 transition changes scan results","tier":"e2e","status":"pass","audit":""}]}],"audit":null},{"name":"skill_audit","category":"skills","type":"feature","is_global":false,"source_url":null,"pinned":null,"description":"The `purlin:audit` skill evaluates proof quality with STRONG/WEAK/HOLLOW assessments. It is read-only \u2014 it never modifies code or test files.","source_path":null,"stack":"markdown (skill definition)","ext_status":null,"proved":15,"total":15,"deferred":0,"status":"VERIFIED","vhash":"5bd9ab85","receipt":{"commit":"2259011e512dffa219356892dc1187651a58b6ba","timestamp":"2026-06-24T20:17:54.345616+00:00","stale":false},"rules":[{"id":"RULE-1","description":"Skill file has YAML frontmatter with `name` and `description` fields","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Grep `skills/audit/SKILL.md` for YAML frontmatter delimiters (`---`); verify `name:` and `description:` fields exist","test_file":"dev/test_skill_specs.py","test_name":"test_has_frontmatter","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-10","description":"External LLM rates well-structured tests with real assertions as STRONG or WEAK (never HOLLOW)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-10","description":"e2e: Audit strong test code with external LLM; verify STRONG or WEAK (not HOLLOW)","test_file":"dev/test_skill_specs.py","test_name":"test_static_checks_passes_well_structured_test","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-11","description":"Response parsing extracts all required fields (PROOF-ID, ASSESSMENT, CRITERION, WHY, FIX) from external LLM output","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-11","description":"e2e: Parse external LLM response; verify ASSESSMENT, CRITERION, WHY, FIX fields extracted","test_file":"dev/test_skill_specs.py","test_name":"test_skill_documents_external_llm_response_fields","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-12","description":"Two-pass flow works end-to-end: static_checks catches HOLLOW in Pass 1, only surviving proofs go to external LLM in Pass 2","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-12","description":"e2e: Mixed-quality test file; static_checks catches assert True; valid test goes to external LLM","test_file":"dev/test_skill_specs.py","test_name":"test_two_pass_flow_hollow_caught_in_pass1_valid_passes_through","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-13","description":"Custom audit LLM command in config responds to ping, config stores audit_llm and audit_llm_name, and the two-pass audit completes","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-13","description":"e2e: Write config with fake LLM command; verify ping, config fields, and two-pass audit","test_file":"dev/test_skill_specs.py","test_name":"test_config_stores_audit_llm_fields_and_skill_documents_external_llm_mode","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-14","description":"Criteria are loaded via the single `load_criteria()` function (`--load-criteria` CLI); built-in criteria always apply, additional team criteria are appended \u2014 never replaced","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-14","description":"e2e: Create fake git repo with additional criteria; configure project; verify load_criteria returns built-in + additional with separator; verify Pass 1 still catches assert True; verify additional criteria reach fake LLM prompt","test_file":"dev/test_skill_specs.py","test_name":"test_load_criteria_assembles_builtin_plus_additional_and_pass1_still_works","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-15","description":"The skill documents a portable Python interpreter for invoking static_checks.py \u2014 falling back from `python3` to `python` to `py -3` \u2014 rather than assuming `python3` is always on PATH (it is not on stock Windows)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-15","description":"Grep `skills/audit/SKILL.md` for the interpreter-fallback guidance; verify it documents `python` and `py -3` as fallbacks for `python3`","test_file":"dev/test_skill_specs.py","test_name":"test_documents_portable_interpreter_fallback","tier":"unit","status":"pass","audit":"EXCLUDED"}]},{"id":"RULE-2","description":"Skill file contains a `## Usage` section documenting command syntax","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Grep `skills/audit/SKILL.md` for `## Usage`; verify the section exists","test_file":"dev/test_skill_specs.py","test_name":"test_has_usage_section","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-3","description":"The `name` field in frontmatter is `audit`, matching the directory name","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Extract `name:` from frontmatter; verify it equals `audit`","test_file":"dev/test_skill_specs.py","test_name":"test_name_matches_directory","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-4","description":"Independent auditor mode documents instructions to read audit criteria and assess proofs as STRONG/WEAK/HOLLOW","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"e2e: Grep skills/audit/SKILL.md for independent auditor section; verify audit_criteria.md and STRONG/WEAK/HOLLOW","test_file":"dev/test_skill_specs.py","test_name":"test_independent_auditor_reads_criteria_and_assesses","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-5","description":"Independent auditor mode documents spawning a builder with HOLLOW/WEAK findings","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"e2e: Grep independent auditor section for purlin-builder and Spawn; verify builder protocol","test_file":"dev/test_skill_specs.py","test_name":"test_independent_auditor_spawns_builder","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-6","description":"Independent auditor mode documents re-auditing fixed proofs after builder responds","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"e2e: Grep independent auditor section for re-audit; verify re-check loop","test_file":"dev/test_skill_specs.py","test_name":"test_independent_auditor_re_audits_after_builder","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-7","description":"Independent auditor mode terminates after all findings addressed or after 3 rounds on any single proof","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"e2e: Grep independent auditor section for 3 rounds; verify termination condition","test_file":"dev/test_skill_specs.py","test_name":"test_independent_auditor_terminates_after_3_rounds","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-8","description":"Anchor rule handling documents reporting to the lead for ambiguous anchor rules","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-8","description":"e2e: Grep anchor rule handling for report to lead; verify ambiguous anchor protocol","test_file":"dev/test_skill_specs.py","test_name":"test_anchor_rule_handling_reports_to_lead","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-9","description":"External LLM detects deliberately hollow tests (assert True, assert None) as non-STRONG","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-9","description":"e2e: Audit hollow test code with external LLM; verify returns HOLLOW or WEAK","test_file":"dev/test_skill_specs.py","test_name":"test_static_checks_detects_hollow_test_as_non_strong","tier":"e2e","status":"pass","audit":""}]}],"audit":null},{"name":"skill_build","category":"skills","type":"feature","is_global":false,"source_url":null,"pinned":null,"description":"The `purlin:build` skill reads a spec, loads all its rules (including `> Requires:` dependencies), and implements the feature. It handles test execution, failure diagnosis, and proof generation.","source_path":null,"stack":"markdown (skill definition)","ext_status":null,"proved":12,"total":12,"deferred":0,"status":"VERIFIED","vhash":"40c0eaa6","receipt":{"commit":"1eaaf8bec1ee53d2f944fb79774699f7d68f8ab1","timestamp":"2026-06-02T15:47:23.471069+00:00","stale":false},"rules":[{"id":"RULE-1","description":"Skill file has YAML frontmatter with `name` and `description` fields","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Grep `skills/build/SKILL.md` for YAML frontmatter delimiters (`---`); verify `name:` and `description:` fields exist","test_file":"dev/test_skill_specs.py","test_name":"test_has_frontmatter","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-10","description":"The changeset summary is included as the commit message body in the build commit","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-10","description":"Grep `skills/build/SKILL.md` for commit message body instructions that reference the changeset summary and commit_conventions.md; verify present","test_file":"dev/test_skill_specs.py","test_name":"test_changeset_summary_in_commit_body","tier":"unit","status":"pass","audit":""},{"id":"PROOF-14","description":"In the temp project, commit with changeset summary as body; verify commit subject uses feat(): prefix and body contains all 3 changeset sections","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_e2e_build_changeset.sh","test_name":"git commit body contains changeset summary with feat prefix and all 3 sections","tier":"e2e","status":"pass","audit":""},{"id":"PROOF-18","description":"After the agent build session, verify git log shows a feat(): commit whose body contains RULE references and the changeset summary sections","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"RULE-11","description":"When running as proof fixer, the changeset summary maps fixed proofs instead of rules and omits the Decisions section","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-11","description":"Grep `skills/build/SKILL.md` for proof fixer changeset instructions in the \"When Running as Proof Fixer\" section; verify it documents mapping fixed proofs and skipping Decisions","test_file":"dev/test_skill_specs.py","test_name":"test_proof_fixer_changeset_maps_proofs","tier":"unit","status":"pass","audit":""},{"id":"PROOF-15","description":"Build a proof-fixer-style changeset; verify it maps PROOF-N instead of RULE-N and has no Decisions section","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_e2e_build_changeset.sh","test_name":"proof fixer changeset uses PROOF-N mappings, no Decisions section","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-12","description":"Build skill has exit criteria requiring tests pass, changeset summary printed, all changes committed, and no uncommitted proof files before the skill can complete","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-12","description":"Grep `skills/build/SKILL.md` for \"Exit Criteria\" section; verify it requires tests pass, changeset summary, committed changes, and no uncommitted proof files","test_file":"dev/test_skill_specs.py","test_name":"test_has_exit_criteria","tier":"unit","status":"pass","audit":""},{"id":"PROOF-16","description":"After the build session in the temp project, verify exit criteria: all tests pass, changeset summary valid, all proofs committed, git status clean","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_e2e_build_changeset.sh","test_name":"exit criteria verified: tests pass, changeset printed, all committed, no uncommitted proofs","tier":"e2e","status":"pass","audit":""},{"id":"PROOF-19","description":"After the agent build session, verify git status has no uncommitted proof files or source files, and proof files are tracked in git","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"RULE-2","description":"Skill file contains a `## Usage` section documenting command syntax","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Grep `skills/build/SKILL.md` for `## Usage`; verify the section exists","test_file":"dev/test_skill_specs.py","test_name":"test_has_usage_section","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-3","description":"The `name` field in frontmatter is `build`, matching the directory name","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Extract `name:` from frontmatter; verify it equals `build`","test_file":"dev/test_skill_specs.py","test_name":"test_name_matches_directory","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-4","description":"Skill includes commit instructions or git operations for file modifications","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Grep `skills/build/SKILL.md` for commit instructions (`git commit`, `commit the`, `create.*commit`); verify present","test_file":"dev/test_skill_specs.py","test_name":"test_has_commit_instructions","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-5","description":"Skill requires calling `sync_status` after tests and states it is not optional","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Grep `skills/build/SKILL.md` for `sync_status` and `not optional`; verify both present","test_file":"dev/test_skill_specs.py","test_name":"test_requires_sync_status_not_optional","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-6","description":"Skill includes test failure diagnosis guidance requiring root cause analysis before fixing","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Grep `skills/build/SKILL.md` for `diagnose` and `Never weaken`; verify both present","test_file":"dev/test_skill_specs.py","test_name":"test_has_failure_diagnosis_guidance","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-7","description":"Skill includes mandatory tier tag review for proof descriptions","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Grep `skills/build/SKILL.md` for tier review instructions and tier tag references (`@integration`/`@e2e`/unit tier); verify present","test_file":"dev/test_skill_specs.py","test_name":"test_has_tier_review","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-8","description":"Build skill documents proof fixer mode with instructions to fix proofs based on audit feedback and report back","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-8","description":"e2e: Grep skills/build/SKILL.md for proof fixer mode; verify fix proofs and report instructions","test_file":"dev/test_skill_specs.py","test_name":"test_documents_proof_fixer_mode","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-9","description":"Build skill produces a changeset summary after tests pass, with three sections: Changeset (rule\u2192file:line mapping), Decisions (judgment calls), and Review (focus areas)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-9","description":"Grep `skills/build/SKILL.md` for \"Changeset Summary\", \"Changeset\", \"Decisions\", \"Review\" section headers; verify all three summary sections are documented with format examples","test_file":"dev/test_skill_specs.py","test_name":"test_documents_changeset_summary","tier":"unit","status":"pass","audit":""},{"id":"PROOF-13","description":"Create a temp project with spec+code+tests, run pytest to emit proofs, build a changeset summary, validate it has all 3 sections with RULE\u2192file:line mappings","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_e2e_build_changeset.sh","test_name":"changeset summary has Changeset, Decisions, Review sections with RULE\u2192file:line mappings","tier":"e2e","status":"pass","audit":""},{"id":"PROOF-17","description":"Run purlin:build via claude -p with a spec containing ambiguous rules; verify agent output contains Changeset with RULE mappings, non-empty Decisions with real judgment calls, and non-empty Review with risk areas","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]}],"audit":null},{"name":"skill_drift","category":"skills","type":"feature","is_global":false,"source_url":null,"pinned":null,"description":"The `purlin:drift` skill detects spec drift by calling the `drift` MCP tool and summarizing changes since last verification, cross-referenced with specs. It produces PM/QA/eng-readable reports.","source_path":null,"stack":"markdown (skill definition)","ext_status":null,"proved":5,"total":5,"deferred":0,"status":"VERIFIED","vhash":"175608df","receipt":{"commit":"5f42a16ba84ca8b20a9185eabe1d7a8f73a84b72","timestamp":"2026-04-04T22:48:45.094616+00:00","stale":false},"rules":[{"id":"RULE-1","description":"Skill file has YAML frontmatter with `name` and `description` fields","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Grep `skills/drift/SKILL.md` for YAML frontmatter delimiters (`---`); verify `name:` and `description:` fields exist","test_file":"dev/test_skill_specs.py","test_name":"test_has_frontmatter","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-2","description":"Skill file contains a `## Usage` section documenting command syntax","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Grep `skills/drift/SKILL.md` for `## Usage`; verify the section exists","test_file":"dev/test_skill_specs.py","test_name":"test_has_usage_section","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-3","description":"The `name` field in frontmatter is `drift`, matching the directory name","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Extract `name:` from frontmatter; verify it equals `drift`","test_file":"dev/test_skill_specs.py","test_name":"test_name_matches_directory","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-4","description":"Skill references the `drift` MCP tool by name","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Grep `skills/drift/SKILL.md` for `drift`; verify the MCP tool is referenced","test_file":"dev/test_skill_specs.py","test_name":"test_references_drift_mcp_tool","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-5","description":"Skill requires reading git diffs for behavioral changes, not just interpreting MCP categories","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Grep `skills/drift/SKILL.md` for `git diff`; verify the diff-reading requirement is present","test_file":"dev/test_skill_specs.py","test_name":"test_requires_reading_diffs","tier":"unit","status":"pass","audit":""}]}],"audit":null},{"name":"skill_find","category":"skills","type":"feature","is_global":false,"source_url":null,"pinned":null,"description":"The `purlin:find` skill searches specs by name and displays rule coverage from `sync_status`.","source_path":null,"stack":"markdown (skill definition)","ext_status":null,"proved":4,"total":4,"deferred":0,"status":"VERIFIED","vhash":"b3ef613b","receipt":{"commit":"5f42a16ba84ca8b20a9185eabe1d7a8f73a84b72","timestamp":"2026-04-04T22:48:45.094616+00:00","stale":false},"rules":[{"id":"RULE-1","description":"Skill file has YAML frontmatter with `name` and `description` fields","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Grep `skills/find/SKILL.md` for YAML frontmatter delimiters (`---`); verify `name:` and `description:` fields exist","test_file":"dev/test_skill_specs.py","test_name":"test_has_frontmatter","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-2","description":"Skill file contains a `## Usage` section documenting command syntax","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Grep `skills/find/SKILL.md` for `## Usage`; verify the section exists","test_file":"dev/test_skill_specs.py","test_name":"test_has_usage_section","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-3","description":"The `name` field in frontmatter is `find`, matching the directory name","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Extract `name:` from frontmatter; verify it equals `find`","test_file":"dev/test_skill_specs.py","test_name":"test_name_matches_directory","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-4","description":"Skill references the `sync_status` MCP tool by name","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Grep `skills/find/SKILL.md` for `sync_status`; verify the MCP tool is referenced","test_file":"dev/test_skill_specs.py","test_name":"test_references_sync_status_mcp_tool","tier":"unit","status":"pass","audit":""}]}],"audit":null},{"name":"skill_init","category":"skills","type":"feature","is_global":false,"source_url":null,"pinned":null,"description":"The `purlin:init` skill initializes a project for spec-driven development. It creates `.purlin/`, `specs/`, detects the test framework, scaffolds the proof plugin, and manages plugin configuration.","source_path":null,"stack":"markdown (skill definition)","ext_status":null,"proved":48,"total":48,"deferred":0,"status":"VERIFIED","vhash":"4bfdc3c0","receipt":{"commit":"7a6ecc730ea1d7eb9ed95f00f5875dbb307e134c","timestamp":"2026-06-05T13:07:52.525362+00:00","stale":false},"rules":[{"id":"RULE-1","description":"Skill file has YAML frontmatter with `name` and `description` fields","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Grep `skills/init/SKILL.md` for YAML frontmatter delimiters (`---`); verify `name:` and `description:` fields exist","test_file":"dev/test_skill_specs.py","test_name":"test_has_frontmatter","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-10","description":"The version field in config.json matches the contents of the VERSION file","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-10","description":"e2e: Read VERSION file and config.json version; verify identical","test_file":"dev/test_skill_specs.py","test_name":"test_config_version_matches_version_file","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-11","description":"Default config values are test_framework: auto, spec_dir: specs, pre_push: warn, report: true, digest: auto","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-11","description":"e2e: Create project with no overrides; verify default values","test_file":"dev/test_skill_specs.py","test_name":"test_config_template_default_values","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-12","description":"When conftest.py exists at project root, auto-detection selects pytest","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-12","description":"e2e: Create project with conftest.py; run pre-push; verify pytest detected","test_file":"dev/test_skill_specs.py","test_name":"test_documents_conftest_py_detects_pytest","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-13","description":"When pyproject.toml contains [tool.pytest], auto-detection selects pytest","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-13","description":"e2e: Create project with pyproject.toml [tool.pytest]; verify pytest detected","test_file":"dev/test_skill_specs.py","test_name":"test_documents_pyproject_toml_detects_pytest","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-14","description":"When package.json contains jest, auto-detection selects jest","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-14","description":"e2e: Create project with package.json jest; verify jest detected","test_file":"dev/test_skill_specs.py","test_name":"test_documents_package_json_jest_detects_jest","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-15","description":"When package.json contains vitest, init scaffolds vitest_purlin.ts (Vitest's reporter API differs from Jest's \u2014 Vitest never calls onTestResult/onRunComplete \u2014 so the native TypeScript reporter is used for both JS and TS Vitest projects; Vitest loads .ts reporters natively via Vite)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-15","description":"e2e: Create project with vitest; verify vitest_purlin.ts scaffolded","test_file":"dev/test_skill_specs.py","test_name":"test_documents_vitest_maps_to_vitest_plugin","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-16","description":"When both conftest.py and package.json with jest exist, both plugins are scaffolded and test_framework is pytest,jest","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-16","description":"e2e: Create project with conftest.py + package.json jest; verify both plugins and pytest,jest config","test_file":"dev/test_skill_specs.py","test_name":"test_documents_multi_framework_scaffolding","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-17","description":"When no framework indicators are present, shell plugin is scaffolded as fallback","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-17","description":"e2e: Create project with no framework indicators; verify purlin-proof.sh scaffolded","test_file":"dev/test_skill_specs.py","test_name":"test_documents_no_framework_asks_user_not_silent_shell","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-18","description":"Scaffolded pytest_purlin.py in .purlin/plugins/ is byte-identical to scripts/proof/pytest_purlin.py","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-18","description":"e2e: Diff scaffolded pytest_purlin.py against source; verify byte-identical","test_file":"dev/test_skill_specs.py","test_name":"test_pytest_plugin_source_exists_and_is_copy_source","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-19","description":"Scaffolded jest_purlin.js in .purlin/plugins/ is byte-identical to scripts/proof/jest_purlin.js","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-19","description":"e2e: Diff scaffolded jest_purlin.js against source; verify byte-identical","test_file":"dev/test_skill_specs.py","test_name":"test_jest_plugin_source_exists_and_is_copy_source","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-2","description":"Skill file contains a `## Usage` section documenting command syntax","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Grep `skills/init/SKILL.md` for `## Usage`; verify the section exists","test_file":"dev/test_skill_specs.py","test_name":"test_has_usage_section","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-20","description":"Scaffolded purlin-proof.sh in .purlin/plugins/ is byte-identical to scripts/proof/shell_purlin.sh","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-20","description":"e2e: Diff scaffolded purlin-proof.sh against source; verify byte-identical","test_file":"dev/test_skill_specs.py","test_name":"test_shell_plugin_source_exists_and_is_copy_source","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-21","description":"Scaffolded pytest_purlin.py produces valid .proofs-unit.json when run with @pytest.mark.proof markers","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-21","description":"e2e: Scaffold pytest plugin; create spec; run pytest with markers; verify valid proofs emitted","test_file":"dev/test_skill_specs.py","test_name":"test_pytest_plugin_emits_valid_proofs_json","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-22","description":"Scaffolded jest_purlin.js produces valid .proofs-unit.json when invoked as reporter with [proof:...] markers","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-22","description":"e2e: Scaffold jest reporter; create spec; exercise via node; verify valid proofs emitted","test_file":"dev/test_skill_specs.py","test_name":"test_jest_plugin_has_proof_emission_logic","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-23","description":"Scaffolded purlin-proof.sh produces valid .proofs-unit.json when purlin_proof + purlin_proof_finish are called","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-23","description":"e2e: Scaffold shell harness; create spec; call purlin_proof + purlin_proof_finish; verify valid proofs","test_file":"dev/test_skill_specs.py","test_name":"test_shell_plugin_emits_valid_proofs_json","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-24","description":"After init with empty specs dir, sync_status returns No specs found without errors","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-24","description":"e2e: Create project with empty specs/; run sync_status; verify No specs found","test_file":"dev/test_skill_specs.py","test_name":"test_sync_status_returns_no_specs_found_for_empty_project","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-25","description":"Status progression: no proofs reports UNTESTED, all passing reports PASSING, one failing reports FAILING","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-25","description":"e2e: Status progression: no proofs UNTESTED, passing proofs PASSING, failing proof FAILING","test_file":"dev/test_skill_specs.py","test_name":"test_status_progression_untested_passing_failing","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-26","description":"When report: true in config, sync_status generates .purlin/report-data.js containing valid PURLIN_DATA","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-26","description":"e2e: Create project with report:true and passing proofs; run sync_status; verify report-data.js generated","test_file":"dev/test_skill_specs.py","test_name":"test_sync_status_generates_report_data_js_when_report_true","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-27","description":".gitignore contains all required purlin entries and does NOT contain `.purlin/report-data.js` (the digest is committed)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-27","description":"e2e: Verify .gitignore contains all required purlin entries","test_file":"dev/test_skill_specs.py","test_name":"test_documents_required_gitignore_entries","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-28","description":"Running init twice (re-init) does not create duplicate entries in .gitignore","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-28","description":"e2e: Run init twice; verify no duplicate gitignore entries","test_file":"dev/test_skill_specs.py","test_name":"test_documents_reinit_does_not_duplicate_gitignore","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-29","description":"After init, .git/hooks/pre-push exists, is executable, and contains purlin","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-29","description":"e2e: Verify .git/hooks/pre-push exists, is executable, contains purlin","test_file":"dev/test_skill_specs.py","test_name":"test_documents_pre_push_hook_installation","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-3","description":"The `name` field in frontmatter is `init`, matching the directory name","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Extract `name:` from frontmatter; verify it equals `init`","test_file":"dev/test_skill_specs.py","test_name":"test_name_matches_directory","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-30","description":"When a non-purlin pre-push hook exists before init, the existing hook is preserved","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-30","description":"e2e: Create existing non-purlin hook; run init; verify hook preserved","test_file":"dev/test_skill_specs.py","test_name":"test_documents_existing_hook_preservation","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-31","description":"When report: true, purlin-report.html exists at project root; when report: false, it does not","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-31","description":"e2e: Verify report:true creates purlin-report.html, report:false does not","test_file":"dev/test_skill_specs.py","test_name":"test_documents_report_html_toggle","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-32","description":"Full lifecycle works end-to-end: init creates structure, spec created, proof plugin runs, proofs emitted, sync_status reports PASSING, pre-push hook allows push","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-32","description":"e2e: Python lifecycle: init, spec, pytest, proofs, PASSING, hook ok","test_file":"dev/test_skill_specs.py","test_name":"test_full_lifecycle_init_spec_proof_passing","tier":"e2e","status":"pass","audit":""},{"id":"PROOF-33","description":"e2e: Shell lifecycle: init, spec, shell proof, proofs, PASSING","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""},{"id":"PROOF-34","description":"e2e: Jest lifecycle: init, spec, reporter, proofs, PASSING","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"RULE-33","description":"Init prints DETECTING CODEBASE before scanning for test frameworks","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-35","description":"e2e: Verify SKILL.md contains \"DETECTING CODEBASE\" print instruction before framework scan","test_file":"dev/test_skill_specs.py","test_name":"test_skill_prints_detecting_codebase_before_scan","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-34","description":"Init always presents the full framework selection list to the user, even when auto-detection succeeds, with detected frameworks pre-selected","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-36","description":"e2e: Verify SKILL.md documents always presenting the framework selection list with [x] and [ ] markers, including when auto-detection succeeds","test_file":"dev/test_skill_specs.py","test_name":"test_skill_always_presents_framework_selection_list","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-35","description":"When a single framework is detected, the selection list shows it pre-selected with [x] and all others unselected with [ ]","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-37","description":"e2e: Verify SKILL.md shows a single-detection example with one [x] pre-selected and remaining [ ] unselected","test_file":"dev/test_skill_specs.py","test_name":"test_skill_shows_single_detection_preselected","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-36","description":"When multiple frameworks are detected, all detected frameworks are pre-selected in the list","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-38","description":"e2e: Verify SKILL.md shows a multi-detection example with multiple [x] pre-selected","test_file":"dev/test_skill_specs.py","test_name":"test_skill_shows_multi_detection_preselected","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-37","description":"When no frameworks are detected, the selection list shows all options unselected","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-39","description":"e2e: Verify SKILL.md shows a no-detection example with all [ ] unselected","test_file":"dev/test_skill_specs.py","test_name":"test_skill_shows_no_detection_all_unselected","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-38","description":"The Purlin MCP server is bundled with the plugin \u2014 `.claude-plugin/plugin.json` declares a `purlin` entry under `mcpServers`; init does NOT write a `purlin` entry into the project's `.mcp.json` (a project-scope entry would shadow the plugin server with a version-pinned path that breaks on every plugin update)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-40","description":"Parse `.claude-plugin/plugin.json`; verify `mcpServers.purlin` exists. Grep `skills/init/SKILL.md`; verify it does NOT instruct creating a `purlin` entry in the project's `.mcp.json` (no init-time path resolution remains)","test_file":"dev/test_skill_specs.py","test_name":"test_mcp_server_bundled_in_plugin_manifest","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-39","description":"The bundled MCP server entry uses the `python3` command with `args` pointing to `${CLAUDE_PLUGIN_ROOT}/scripts/mcp/purlin_server.py`, so the path always resolves to the currently installed plugin version","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-41","description":"Parse `.claude-plugin/plugin.json`; verify `mcpServers.purlin.command` is `python3` and `args` contains `${CLAUDE_PLUGIN_ROOT}/scripts/mcp/purlin_server.py`","test_file":"dev/test_skill_specs.py","test_name":"test_bundled_mcp_server_uses_plugin_root_variable","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-4","description":"Skill includes commit instructions or git operations for file modifications","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Grep `skills/init/SKILL.md` for commit instructions (`git commit`, `commit the`, `create.*commit`); verify present","test_file":"dev/test_skill_specs.py","test_name":"test_has_commit_instructions","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-40","description":"Init migrates legacy MCP config: when the project's `.mcp.json` has a `purlin` entry under `mcpServers` (pre-0.9.4 version-pinned path), init removes that entry, preserves all other server entries, deletes the file only when nothing else remains in it, and tells the user to reload plugins or restart the session","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-42","description":"Grep `skills/init/SKILL.md` for the legacy migration step; verify it documents removing the `purlin` entry from the project's `.mcp.json`, preserving other server entries, and reloading plugins","test_file":"dev/test_skill_specs.py","test_name":"test_documents_legacy_mcp_json_migration","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-41","description":"Config has `\"digest\"` field with value `\"auto\"`, `\"warn\"`, or `\"off\"`","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-43","description":"e2e: Read config.json after init; verify `digest` field exists with value `auto`","test_file":"dev/test_skill_specs.py","test_name":"test_config_template_has_digest_field","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-42","description":"After init, `.git/hooks/pre-commit` exists, is executable, and contains `purlin`","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-44","description":"e2e: Verify `.git/hooks/pre-commit` exists, is executable, and contains `purlin`","test_file":"dev/test_skill_specs.py","test_name":"test_documents_pre_commit_hook_installation","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-43","description":"When a non-purlin pre-commit hook exists before init, the existing hook is preserved","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-45","description":"e2e: Create existing non-purlin pre-commit hook; run init; verify hook preserved","test_file":"dev/test_skill_specs.py","test_name":"test_documents_existing_pre_commit_hook_preservation","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-44","description":"In digest `auto` mode, running `git commit` triggers the pre-commit hook which regenerates `.purlin/report-data.js` and stages it into the commit","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-46","description":"e2e: Create repo with spec+proofs, run `git commit`; verify `.purlin/report-data.js` is tracked in the commit via `git show HEAD:.purlin/report-data.js`","test_file":"dev/test_skill_specs.py","test_name":"test_pre_commit_hook_generates_and_stages_digest","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-45","description":"After digest generation, `report-data.js` contains a `timestamp` field with a recent ISO timestamp and a `git_sha` field","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-47","description":"e2e: After commit, parse `report-data.js`; verify `timestamp` is within last 60s and `git_sha` field is present","test_file":"dev/test_skill_specs.py","test_name":"test_digest_has_timestamp_and_git_sha","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-46","description":"Digest generation does NOT trigger a new audit \u2014 `audit_summary` reflects only cached data (null when no cache exists)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-48","description":"e2e: After commit in fresh project (no audit cache), verify `audit_summary` in digest is null","test_file":"dev/test_skill_specs.py","test_name":"test_digest_does_not_trigger_audit","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-47","description":"Scaffolded vitest_purlin.ts in .purlin/plugins/ is byte-identical to scripts/proof/vitest_purlin.ts","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-49","description":"e2e: Diff scaffolded vitest_purlin.ts against scripts/proof/vitest_purlin.ts; verify byte-identical","test_file":"dev/test_skill_specs.py","test_name":"test_vitest_reporter_scaffold_byte_identical","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-48","description":"The framework selection list is built dynamically from the framework registry (`references/supported_frameworks.md`) and presents an option for every proof plugin shipped in `scripts/proof/`; the skill does not hardcode framework names, so a newly shipped plugin (e.g. xUnit) appears in the selection once it is registered","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-51","description":"Cross-reference distribution and registry: every proof plugin file in `scripts/proof/` is referenced in `references/supported_frameworks.md`, and every `scripts/proof/` path the registry names exists on disk \u2014 so the registry-built selection list covers exactly the shipped frameworks","test_file":"dev/test_skill_specs.py","test_name":"test_registry_covers_every_shipped_plugin","tier":"integration","status":"pass","audit":""},{"id":"PROOF-50","description":"Grep `skills/init/SKILL.md`; verify it instructs building the selection list dynamically from `references/supported_frameworks.md` and explicitly says not to hardcode framework names","test_file":"dev/test_skill_specs.py","test_name":"test_selection_list_built_from_registry_not_hardcoded","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-5","description":"`--add-plugin` validates plugin files against language-specific patterns (Python: `proofs`+`json`, JS: `proofs`+`JSON`, Shell: `purlin_proof`, Java: `proofs`+`Proof`) and warns if validation fails","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Grep `skills/init/SKILL.md` for language validation entries (`Python`, `JavaScript`, `Shell`, `Java`) and warning text `doesn't look like a standard proof plugin`; verify all present","test_file":"dev/test_skill_specs.py","test_name":"test_add_plugin_validates_by_language","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-6","description":"`--add-plugin` supports both local file paths and git URL sources with distinct handling for each","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Grep `skills/init/SKILL.md` for `local file path` and `git URL`; verify both source types are documented with distinct handling steps","test_file":"dev/test_skill_specs.py","test_name":"test_add_plugin_supports_file_and_git","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-7","description":"`--list-plugins` identifies built-in plugins (`pytest_purlin`, `jest_purlin`, `purlin-proof`) by framework name and labels all others as `custom`","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Grep `skills/init/SKILL.md` for `pytest_purlin.py` with `Python/pytest`, `jest_purlin.js` with `JavaScript/Jest`, and the label `custom`; verify the labeling table exists","test_file":"dev/test_skill_specs.py","test_name":"test_list_plugins_labels_builtin_and_custom","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-8","description":"After init, `.purlin/`, `.purlin/plugins/`, `specs/`, and `specs/_anchors/` directories all exist","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-8","description":"e2e: Create init-equivalent structure; verify .purlin/, .purlin/plugins/, specs/, specs/_anchors/ all exist","test_file":"dev/test_skill_specs.py","test_name":"test_documents_required_directory_structure","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-9","description":"`config.json` is valid JSON containing all 6 required fields: version, test_framework, spec_dir, pre_push, report, digest","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-9","description":"e2e: Read config.json; parse as JSON; verify all 5 required fields present","test_file":"dev/test_skill_specs.py","test_name":"test_config_template_has_six_required_fields","tier":"unit","status":"pass","audit":""}]}],"audit":null},{"name":"skill_rename","category":"skills","type":"feature","is_global":false,"source_url":null,"pinned":null,"description":"The `purlin:rename` skill renames a feature across all Purlin artifacts \u2014 specs, proofs, markers, and references \u2014 in one atomic operation.","source_path":null,"stack":"markdown (skill definition)","ext_status":null,"proved":3,"total":3,"deferred":0,"status":"VERIFIED","vhash":"7748e8cb","receipt":{"commit":"5f42a16ba84ca8b20a9185eabe1d7a8f73a84b72","timestamp":"2026-04-04T22:48:45.094616+00:00","stale":false},"rules":[{"id":"RULE-1","description":"Skill file has YAML frontmatter with `name` and `description` fields","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Grep `skills/rename/SKILL.md` for YAML frontmatter delimiters (`---`); verify `name:` and `description:` fields exist","test_file":"dev/test_skill_specs.py","test_name":"test_has_frontmatter","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-2","description":"Skill file contains a `## Usage` section documenting command syntax","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Grep `skills/rename/SKILL.md` for `## Usage`; verify the section exists","test_file":"dev/test_skill_specs.py","test_name":"test_has_usage_section","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-3","description":"The `name` field in frontmatter is `rename`, matching the directory name","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Extract `name:` from frontmatter; verify it equals `rename`","test_file":"dev/test_skill_specs.py","test_name":"test_name_matches_directory","tier":"unit","status":"pass","audit":""}]}],"audit":null},{"name":"skill_spec","category":"skills","type":"feature","is_global":false,"source_url":null,"pinned":null,"description":"The `purlin:spec` skill scaffolds or edits feature specs in 3-section format. It accepts any input \u2014 plain English, PRDs, customer feedback, code files, images \u2014 and extracts structured rules.","source_path":null,"stack":"markdown (skill definition)","ext_status":null,"proved":8,"total":8,"deferred":0,"status":"VERIFIED","vhash":"69092c78","receipt":{"commit":"8f4c47d21dd51e50f2994e2df67b48af5c2371f2","timestamp":"2026-06-05T13:43:25.311450+00:00","stale":false},"rules":[{"id":"RULE-1","description":"Skill file has YAML frontmatter with `name` and `description` fields","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Grep `skills/spec/SKILL.md` for YAML frontmatter delimiters (`---`); verify `name:` and `description:` fields exist","test_file":"dev/test_skill_specs.py","test_name":"test_has_frontmatter","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-2","description":"Skill file contains a `## Usage` section documenting command syntax","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Grep `skills/spec/SKILL.md` for `## Usage`; verify the section exists","test_file":"dev/test_skill_specs.py","test_name":"test_has_usage_section","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-3","description":"The `name` field in frontmatter is `spec`, matching the directory name","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Extract `name:` from frontmatter; verify it equals `spec`","test_file":"dev/test_skill_specs.py","test_name":"test_name_matches_directory","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-4","description":"Skill includes commit instructions or git operations for file modifications","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Grep `skills/spec/SKILL.md` for commit instructions (`git commit`, `commit the`, `create.*commit`); verify present","test_file":"dev/test_skill_specs.py","test_name":"test_has_commit_instructions","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-5","description":"Update workflow (Step 7) presents a delta report showing KEEPING/ADDING/UPDATING/REMOVING before applying changes","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Grep `skills/spec/SKILL.md` for `KEEPING`, `ADDING`, `UPDATING`, and `REMOVING`; verify the delta report structure is present","test_file":"dev/test_skill_specs.py","test_name":"test_has_delta_report_structure","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-6","description":"Skill includes mandatory tier tag review for proof descriptions","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Grep `skills/spec/SKILL.md` for tier review instructions and tier tag references (`@integration`/`@e2e`/unit tier); verify present","test_file":"dev/test_skill_specs.py","test_name":"test_has_tier_review","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-7","description":"Spec skill has exit criteria requiring the spec file is committed and no uncommitted spec files remain before the skill can complete","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Grep `skills/spec/SKILL.md` for \"Exit Criteria\" section; verify it requires spec committed and no uncommitted spec files","test_file":"dev/test_skill_specs.py","test_name":"test_has_exit_criteria","tier":"unit","status":"pass","audit":""},{"id":"PROOF-8","description":"Run purlin:spec via claude -p in a temp project; verify spec .md is committed, git status clean for specs/, and git log shows spec(): commit","test_file":"dev/test_e2e_build_agent.py","test_name":"test_spec_committed","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-8","description":"Validate-Before-Commit includes an e2e proof-description check: `@e2e` proofs must describe an observable flow and must not name source files or internal functions, per `references/spec_quality_guide.md` (\"E2E proof descriptions\")","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-9","description":"Grep `skills/spec/SKILL.md` Validate-Before-Commit for the `@e2e` observable-flow check and its pointer to `spec_quality_guide.md` \"E2E proof descriptions\"; verify present","test_file":"dev/test_skill_specs.py","test_name":"test_validate_e2e_observable_flow","tier":"unit","status":"pass","audit":""}]}],"audit":null},{"name":"skill_spec_from_code","category":"skills","type":"feature","is_global":false,"source_url":null,"pinned":null,"description":"The `purlin:spec-from-code` skill scans codebases and migrates existing specs in any format to the current compliant 3-section format.","source_path":null,"stack":"markdown (skill definition)","ext_status":null,"proved":32,"total":32,"deferred":0,"status":"VERIFIED","vhash":"9e996e68","receipt":{"commit":"8f4c47d21dd51e50f2994e2df67b48af5c2371f2","timestamp":"2026-06-05T13:43:25.311450+00:00","stale":false},"rules":[{"id":"RULE-1","description":"Skill file has YAML frontmatter with `name` and `description` fields","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Grep `skills/spec-from-code/SKILL.md` for YAML frontmatter delimiters (`---`); verify `name:` and `description:` fields exist","test_file":"dev/test_skill_specs.py","test_name":"test_has_frontmatter","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-10","description":"A spec generated from a plain description contains sequentially numbered RULE-N lines starting at RULE-1 with no gaps","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-19","description":"e2e: Create spec with 4 sequentially numbered rules; verify numbers are 1,2,3,4 with no gaps","test_file":"dev/test_e2e_spec_from_input.py","test_name":"test_sequential_rule_numbering","tier":"e2e","status":"pass","audit":""},{"id":"PROOF-19","description":"e2e: Create spec with 4 sequentially numbered rules; verify numbers are 1,2,3,4 with no gaps","test_file":"dev/test_e2e_spec_migration.py","test_name":"test_sequential_rule_numbering","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-11","description":"A spec generated from a plain description contains PROOF-N (RULE-N) lines where every rule has at least one proof","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-20","description":"e2e: Verify every RULE-N has at least one PROOF referencing it","test_file":"dev/test_e2e_spec_from_input.py","test_name":"test_every_rule_has_proof","tier":"e2e","status":"pass","audit":""},{"id":"PROOF-20","description":"e2e: Verify every RULE-N has at least one PROOF referencing it","test_file":"dev/test_e2e_spec_migration.py","test_name":"test_every_rule_has_proof","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-12","description":"A spec generated from a plain description contains no (assumed) tags when all values were explicitly stated","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-21","description":"e2e: Verify plain-description spec with explicit values has no (assumed) tags","test_file":"dev/test_e2e_spec_from_input.py","test_name":"test_no_assumed_tags_on_explicit_values","tier":"e2e","status":"pass","audit":""},{"id":"PROOF-21","description":"e2e: Verify plain-description spec with explicit values has no (assumed) tags","test_file":"dev/test_e2e_spec_migration.py","test_name":"test_no_assumed_tags_with_explicit_values","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-13","description":"A spec generated from a PRD extracts every testable constraint as a RULE-N line \u2014 at least 5 rules for a multi-requirement PRD","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-22","description":"e2e: Create PRD spec with 6 constraints; verify at least 5 RULE-N lines","test_file":"dev/test_e2e_spec_from_input.py","test_name":"test_prd_extracts_at_least_5_rules","tier":"e2e","status":"pass","audit":""},{"id":"PROOF-22","description":"e2e: Create PRD spec with 6 constraints; verify at least 5 RULE-N lines","test_file":"dev/test_e2e_spec_migration.py","test_name":"test_prd_extracts_at_least_5_rules","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-14","description":"A spec generated from a PRD contains valid metadata: `> Description:`, `> Scope:`, and `> Stack:` fields are present","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-23","description":"e2e: Verify PRD spec has Description, Scope, and Stack metadata","test_file":"dev/test_e2e_spec_from_input.py","test_name":"test_prd_metadata_present","tier":"e2e","status":"pass","audit":""},{"id":"PROOF-23","description":"e2e: Verify PRD spec has Description, Scope, and Stack metadata","test_file":"dev/test_e2e_spec_migration.py","test_name":"test_prd_has_valid_metadata","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-15","description":"A spec generated from a PRD includes `> Requires:` referencing anchors whose scope overlaps with the feature's scope","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-24","description":"e2e: Create project with overlapping anchor; verify Requires references it and sync_status shows required rules","test_file":"dev/test_e2e_spec_from_input.py","test_name":"test_requires_anchor_in_coverage","tier":"e2e","status":"pass","audit":""},{"id":"PROOF-24","description":"e2e: Create project with overlapping anchor; verify Requires references it and sync_status shows required rules","test_file":"dev/test_e2e_spec_migration.py","test_name":"test_prd_requires_overlapping_anchor","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-16","description":"A spec generated from a vague description adds `(assumed \u2014 )` tags to rules where the agent inferred specific values","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-25","description":"e2e: Create vague-input spec; verify (assumed) tags present","test_file":"dev/test_e2e_spec_from_input.py","test_name":"test_assumed_tags_present","tier":"e2e","status":"pass","audit":""},{"id":"PROOF-25","description":"e2e: Create vague-input spec; verify (assumed) tags present","test_file":"dev/test_e2e_spec_migration.py","test_name":"test_vague_input_has_assumed_tags","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-17","description":"Rules with (assumed) tags still follow RULE-N format and are parseable by sync_status","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-26","description":"e2e: Run sync_status on vague-input spec; verify parses without errors with correct rule count","test_file":"dev/test_e2e_spec_from_input.py","test_name":"test_assumed_rules_parseable_by_sync_status","tier":"e2e","status":"pass","audit":""},{"id":"PROOF-26","description":"e2e: Run sync_status on vague-input spec; verify parses without errors with correct rule count","test_file":"dev/test_e2e_spec_migration.py","test_name":"test_assumed_tags_parseable_by_sync_status","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-18","description":"A spec generated from customer feedback translates complaints into testable RULE-N constraints with specific thresholds or behaviors","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-27","description":"e2e: Create customer feedback spec; verify rules have specific thresholds","test_file":"dev/test_e2e_spec_from_input.py","test_name":"test_feedback_translated_to_specific_rules","tier":"e2e","status":"pass","audit":""},{"id":"PROOF-27","description":"e2e: Create customer feedback spec; verify rules have specific thresholds","test_file":"dev/test_e2e_spec_migration.py","test_name":"test_customer_feedback_has_specific_thresholds","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-19","description":"Every proof line across all four scenarios ends with an appropriate tier tag (@integration, @e2e, @manual, or no tag for unit)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-28","description":"e2e: Parse proof lines across all four scenarios; verify tier tags present","test_file":"dev/test_e2e_spec_from_input.py","test_name":"test_all_proofs_have_tier_tags","tier":"e2e","status":"pass","audit":""},{"id":"PROOF-28","description":"e2e: Parse proof lines across all four scenarios; verify tier tags present","test_file":"dev/test_e2e_spec_migration.py","test_name":"test_all_scenarios_have_tier_tags","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-2","description":"Skill file contains a `## Usage` section documenting command syntax","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Grep `skills/spec-from-code/SKILL.md` for `## Usage`; verify the section exists","test_file":"dev/test_skill_specs.py","test_name":"test_has_usage_section","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-20","description":"sync_status successfully parses specs from all four scenarios without errors, reporting correct rule counts and UNTESTED status","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-29","description":"e2e: Run sync_status on all four scenarios; verify UNTESTED status and correct rule counts","test_file":"dev/test_e2e_spec_from_input.py","test_name":"test_sync_status_parses_all_scenarios","tier":"e2e","status":"pass","audit":""},{"id":"PROOF-29","description":"e2e: Run sync_status on all four scenarios; verify UNTESTED status and correct rule counts","test_file":"dev/test_e2e_spec_migration.py","test_name":"test_sync_status_parses_all_scenarios","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-21","description":"The `## Rules` and `## Proof` sections are both present in specs from all four scenarios","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-30","description":"e2e: Verify ## Rules and ## Proof sections exist in all four scenario specs","test_file":"dev/test_e2e_spec_from_input.py","test_name":"test_all_specs_have_rules_and_proof_sections","tier":"e2e","status":"pass","audit":""},{"id":"PROOF-30","description":"e2e: Verify ## Rules and ## Proof sections exist in all four scenario specs","test_file":"dev/test_e2e_spec_migration.py","test_name":"test_all_scenarios_have_rules_and_proof_sections","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-22","description":"When a vague-input spec's (assumed) rule is updated with an explicit value, the (assumed) tag is removed and the rule remains valid","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-31","description":"e2e: Update (assumed) rule with explicit value; verify tag removed and RULE-N format valid","test_file":"dev/test_e2e_spec_from_input.py","test_name":"test_corrected_assumed_rule_is_valid","tier":"e2e","status":"pass","audit":""},{"id":"PROOF-31","description":"e2e: Update (assumed) rule with explicit value; verify tag removed and RULE-N format valid","test_file":"dev/test_e2e_spec_migration.py","test_name":"test_assumed_tag_removal_on_explicit_update","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-23","description":"Skill contains data contract extraction mandatory for ALL features (not just UI) with five categories: inbound contracts, outbound contracts, transformation rules, state transitions, and access contracts","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-33","description":"e2e: Create a simulated component with API field consumption, filter/sort transformations, conditional gates by user segment, and missing-data fallbacks; verify the extraction identifies inbound fields, transformations, access gates, and failure modes","test_file":"dev/test_e2e_ui_extraction.py","test_name":"test_contract_extraction_identifies_all_categories","tier":"e2e","status":"pass","audit":""},{"id":"PROOF-32","description":"Grep SKILL.md for \"Inbound contracts\", \"Outbound contracts\", \"Transformation rules\", \"State transitions\", \"Access contracts\" as subsections of step 4; verify all five exist and step is mandatory for ALL features","test_file":"","test_name":"","tier":"unit","status":"planned","audit":""}]},{"id":"RULE-24","description":"Skill contains a mandatory draft-and-evaluate step that applies rebuild/behavior/overlap tests to every candidate rule, and a rebuild-risk filter that verifies contract coverage across all five categories before presenting specs","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-34","description":"Grep SKILL.md for \"Draft and evaluate\" AND \"rebuild test\" in Phase 3; verify the step applies rebuild, behavior, and overlap tests. Grep for \"Verify contract coverage\" in the rebuild-risk filter; verify it checks all five contract categories","test_file":"dev/test_skill_specs.py","test_name":"test_draft_and_evaluate_with_rebuild_test","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-25","description":"Migration from `features/` reads `.impl.md` companion files and extracts active deviations as rules reflecting actual behavior","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-36","description":"e2e: Create a features/ directory with a spec and .impl.md containing a PM-ACCEPTED deviation; verify the deviation's actual behavior becomes a RULE-N in the migrated spec","test_file":"dev/test_e2e_ui_extraction.py","test_name":"test_impl_deviation_extraction","tier":"e2e","status":"pass","audit":""},{"id":"PROOF-35","description":"Grep SKILL.md for \".impl.md\" AND \"Active Deviations\" AND \"PM-ACCEPTED\"; verify the skill reads deviations and converts PM-accepted deviations to rules reflecting actual behavior","test_file":"","test_name":"","tier":"unit","status":"planned","audit":""}]},{"id":"RULE-26","description":"Migration from `features/` reads `.discoveries.md` companion files and converts resolved bugs to regression rules and open bugs to `(deferred)` rules","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-38","description":"e2e: Create features/ with .discoveries.md containing one RESOLVED bug and one OPEN bug; verify the resolved bug becomes a RULE-N and the open bug becomes a RULE-N with (deferred) tag","test_file":"dev/test_e2e_ui_extraction.py","test_name":"test_discoveries_bug_extraction","tier":"e2e","status":"pass","audit":""},{"id":"PROOF-37","description":"Grep SKILL.md for \".discoveries.md\" AND \"Resolved bugs\" AND \"(deferred)\"; verify resolved bugs become rules and open bugs become deferred rules","test_file":"","test_name":"","tier":"unit","status":"planned","audit":""}]},{"id":"RULE-27","description":"`.discoveries.md` Figma/design references are preserved as `> Visual-Reference:` metadata or `@manual` proof references during migration","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-39","description":"Grep SKILL.md for \"Visual-Reference\" AND \"Figma\" in the .discoveries.md migration section; verify design references are preserved as metadata or manual proof references","test_file":"dev/test_skill_specs.py","test_name":"test_discoveries_figma_preserved_as_visual_reference","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-28","description":"Quality guide references coverage dimensions instead of a fixed rule count target","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-40","description":"Grep spec_quality_guide.md for \"Coverage dimensions\"; verify section exists. Grep for \"5\u201310 rules per feature\"; verify the fixed target no longer exists","test_file":"dev/test_skill_specs.py","test_name":"test_quality_guide_coverage_dimensions_not_fixed_count","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-29","description":"The taxonomy phase never produces a category folder containing a single spec \u2014 single-feature categories are merged into a related category during taxonomy review, or the spec is placed directly at `specs/.md` when no category fits","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-41","description":"Grep `skills/spec-from-code/SKILL.md` for the Phase 2 single-feature category check; verify it instructs merging single-feature categories into a related category or placing the spec at `specs/.md` without a folder","test_file":"dev/test_skill_specs.py","test_name":"test_no_single_spec_category_folders","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-3","description":"The `name` field in frontmatter is `spec-from-code`, matching the directory name","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Extract `name:` from frontmatter; verify it equals `spec-from-code`","test_file":"dev/test_skill_specs.py","test_name":"test_name_matches_directory","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-30","description":"The tier review pass (Phase 3 step 7) includes an inverse check: every `@e2e` proof description must read as an observable flow (arrange \u2192 act \u2192 observe through the real running app) and must not name a source file or internal function; mis-tagged proofs are rewritten as boundary observations or retagged, per `references/spec_quality_guide.md` (\"E2E proof descriptions\")","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-42","description":"Grep `skills/spec-from-code/SKILL.md` step 7 for the inverse check: \"observable flow\", arrange \u2192 act \u2192 observe, and \"must not name a source file or internal function\"; verify the pointer to `spec_quality_guide.md` \"E2E proof descriptions\" is present","test_file":"dev/test_skill_specs.py","test_name":"test_tier_review_inverse_check","tier":"unit","status":"pass","audit":""},{"id":"PROOF-45","description":"Grep `references/spec_quality_guide.md` for the \"E2E proof descriptions\" section; verify the canonical guidance the skill points to exists","test_file":"dev/test_skill_specs.py","test_name":"test_quality_guide_has_e2e_flow_section","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-31","description":"Spec validation (Phase 3 step 11) includes a proof implementation-coupling check that rejects proof descriptions naming source files or internal symbols as the asserted target","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-43","description":"Grep `skills/spec-from-code/SKILL.md` step 11 for the proof implementation-coupling check; verify it rejects proof descriptions naming source files or internal symbols as the asserted target","test_file":"dev/test_skill_specs.py","test_name":"test_step11_proof_coupling_check","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-32","description":"When a category's generated proofs include `@e2e` and no e2e-capable test runner was detected in Phase 1, the skill surfaces a warning in the category review block and in the Phase 4 summary (tool-agnostic \u2014 Playwright, Cypress, MCP-driven browser, etc.)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-44","description":"Grep `skills/spec-from-code/SKILL.md` for the e2e-runner warning in BOTH the step 12 review block and the Phase 4 summary; verify both locations mention `@e2e` proofs with no e2e runner detected","test_file":"dev/test_skill_specs.py","test_name":"test_e2e_runner_reality_check","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-4","description":"Skill includes mandatory tier tag review for proof descriptions","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Grep `skills/spec-from-code/SKILL.md` for tier review instructions and tier tag references (`@integration`/`@e2e`/unit tier); verify present","test_file":"dev/test_skill_specs.py","test_name":"test_has_tier_review","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-5","description":"Phase 1 detects existing specs in both `features/` (legacy) and `specs/` (non-compliant) as migration candidates","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-10","description":"e2e: Create features/auth/login.md with Given/When/Then; verify 3 scenarios detected for conversion","test_file":"dev/test_e2e_spec_migration.py","test_name":"test_legacy_given_when_then_converted_to_rules","tier":"e2e","status":"pass","audit":""},{"id":"PROOF-17","description":"e2e: Create features/auth/login.md; verify category=auth name=login maps to specs/auth/login.md","test_file":"dev/test_e2e_spec_migration.py","test_name":"test_legacy_feature_name_and_category_preserved","tier":"e2e","status":"pass","audit":""},{"id":"PROOF-5","description":"Grep SKILL.md for `features/` detection AND `specs/` non-compliant detection in Phase 1; verify both paths exist","test_file":"","test_name":"","tier":"unit","status":"planned","audit":""}]},{"id":"RULE-6","description":"Non-compliant specs in `specs/` are detected by checking for: missing `## Rules` section, unnumbered rules, missing `## Proof` section, or missing `> Description:` metadata","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-11","description":"e2e: Create spec with unnumbered rules; verify sync_status warns about non-numbered rules","test_file":"dev/test_e2e_spec_migration.py","test_name":"test_unnumbered_rules_get_renumbered","tier":"e2e","status":"pass","audit":""},{"id":"PROOF-12","description":"e2e: Create spec missing > Description:; verify field absent and ## What it does present for derivation","test_file":"dev/test_e2e_spec_migration.py","test_name":"test_missing_description_detected","tier":"e2e","status":"pass","audit":""},{"id":"PROOF-13","description":"e2e: Create spec with Rules but no Proof section; verify Proof section absent","test_file":"dev/test_e2e_spec_migration.py","test_name":"test_missing_proof_section_detected","tier":"e2e","status":"pass","audit":""},{"id":"PROOF-6","description":"Grep SKILL.md for compliance checks: \"Missing `## Rules`\", \"unnumbered\", \"Missing `## Proof`\", \"Missing `> Description:`\"; verify all four criteria are documented","test_file":"","test_name":"","tier":"unit","status":"planned","audit":""}]},{"id":"RULE-7","description":"Compliant specs (numbered rules, proofs, proper sections) are left untouched during migration","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-14","description":"e2e: Create fully compliant spec; verify sync_status reports zero warnings","test_file":"dev/test_e2e_spec_migration.py","test_name":"test_compliant_spec_passes_sync_status","tier":"e2e","status":"pass","audit":""},{"id":"PROOF-15","description":"e2e: Verify compliant spec has Description, numbered rules, and proofs (excluded from migration)","test_file":"dev/test_e2e_spec_migration.py","test_name":"test_compliant_spec_left_untouched","tier":"e2e","status":"pass","audit":""},{"id":"PROOF-7","description":"Grep SKILL.md for \"Compliant specs\" or \"left untouched\"; verify compliant specs are explicitly excluded from migration","test_file":"","test_name":"","tier":"unit","status":"planned","audit":""}]},{"id":"RULE-8","description":"Migration preserves the original spec's rules, descriptions, and metadata with minimal loss of fidelity","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-16","description":"e2e: Create spec with Scope, Stack metadata but missing Description; verify Scope and Stack preserved verbatim","test_file":"dev/test_e2e_spec_migration.py","test_name":"test_existing_metadata_preserved","tier":"e2e","status":"pass","audit":""},{"id":"PROOF-18","description":"e2e: Migrate unnumbered spec; verify all original rule content preserved in numbered format with proofs","test_file":"dev/test_e2e_spec_migration.py","test_name":"test_llm_evaluates_migration_fidelity","tier":"e2e","status":"pass","audit":""},{"id":"PROOF-8","description":"Grep SKILL.md for \"primary input\" and \"preserve\"; verify migration uses old spec as primary input","test_file":"","test_name":"","tier":"integration","status":"planned","audit":""}]},{"id":"RULE-9","description":"Phase 4 offers to remove `features/` after migration but does NOT remove non-compliant specs from `specs/` (they are overwritten in place)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Grep SKILL.md for `features/` detection AND `specs/` non-compliant detection in Phase 1; verify both paths exist","test_file":"dev/test_skill_specs.py","test_name":"test_phase4_cleanup_offer_and_overwrite_in_place","tier":"unit","status":"pass","audit":""},{"id":"PROOF-9","description":"Grep SKILL.md for `features/` cleanup offer AND \"overwritten in place\" for specs/; verify both paths exist","test_file":"","test_name":"","tier":"unit","status":"planned","audit":""}]}],"audit":null},{"name":"skill_status","category":"skills","type":"feature","is_global":false,"source_url":null,"pinned":null,"description":"The `purlin:status` skill calls the `sync_status` MCP tool and displays per-feature rule coverage with actionable directives.","source_path":null,"stack":"markdown (skill definition)","ext_status":null,"proved":4,"total":4,"deferred":0,"status":"VERIFIED","vhash":"b3ef613b","receipt":{"commit":"5f42a16ba84ca8b20a9185eabe1d7a8f73a84b72","timestamp":"2026-04-04T22:48:45.094616+00:00","stale":false},"rules":[{"id":"RULE-1","description":"Skill file has YAML frontmatter with `name` and `description` fields","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Grep `skills/status/SKILL.md` for YAML frontmatter delimiters (`---`); verify `name:` and `description:` fields exist","test_file":"dev/test_skill_specs.py","test_name":"test_has_frontmatter","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-2","description":"Skill file contains a `## Usage` section documenting command syntax","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Grep `skills/status/SKILL.md` for `## Usage`; verify the section exists","test_file":"dev/test_skill_specs.py","test_name":"test_has_usage_section","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-3","description":"The `name` field in frontmatter is `status`, matching the directory name","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Extract `name:` from frontmatter; verify it equals `status`","test_file":"dev/test_skill_specs.py","test_name":"test_name_matches_directory","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-4","description":"Skill references the `sync_status` MCP tool by name","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Grep `skills/status/SKILL.md` for `sync_status`; verify the MCP tool is referenced","test_file":"dev/test_skill_specs.py","test_name":"test_references_sync_status_mcp_tool","tier":"unit","status":"pass","audit":""}]}],"audit":null},{"name":"skill_unit_test","category":"skills","type":"feature","is_global":false,"source_url":null,"pinned":null,"description":"The `purlin:unit-test` skill runs tests (unit tier unless `--all`), emits proof files via feature-scoped overwrite, and reports coverage per feature.","source_path":null,"stack":"markdown (skill definition)","ext_status":null,"proved":5,"total":5,"deferred":0,"status":"VERIFIED","vhash":"175608df","receipt":{"commit":"5f42a16ba84ca8b20a9185eabe1d7a8f73a84b72","timestamp":"2026-04-04T22:48:45.094616+00:00","stale":false},"rules":[{"id":"RULE-1","description":"Skill file has YAML frontmatter with `name` and `description` fields","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Grep `skills/unit-test/SKILL.md` for YAML frontmatter delimiters (`---`); verify `name:` and `description:` fields exist","test_file":"dev/test_skill_specs.py","test_name":"test_has_frontmatter","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-2","description":"Skill file contains a `## Usage` section documenting command syntax","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Grep `skills/unit-test/SKILL.md` for `## Usage`; verify the section exists","test_file":"dev/test_skill_specs.py","test_name":"test_has_usage_section","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-3","description":"The `name` field in frontmatter is `unit-test`, matching the directory name","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Extract `name:` from frontmatter; verify it equals `unit-test`","test_file":"dev/test_skill_specs.py","test_name":"test_name_matches_directory","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-4","description":"Skill includes commit instructions or git operations for file modifications","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Grep `skills/unit-test/SKILL.md` for commit instructions (`git commit`, `commit the`, `create.*commit`); verify present","test_file":"dev/test_skill_specs.py","test_name":"test_has_commit_instructions","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-5","description":"Skill requires calling `sync_status` after tests and states it is not optional","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Grep `skills/unit-test/SKILL.md` for `sync_status` and `not optional`; verify both present","test_file":"dev/test_skill_specs.py","test_name":"test_requires_sync_status_not_optional","tier":"unit","status":"pass","audit":""}]}],"audit":null},{"name":"skill_verify","category":"skills","type":"feature","is_global":false,"source_url":null,"pinned":null,"description":"The `purlin:verify` skill runs the full test suite across all tiers, then issues verification receipts for every feature with complete rule coverage.","source_path":null,"stack":"markdown (skill definition)","ext_status":null,"proved":6,"total":6,"deferred":0,"status":"VERIFIED","vhash":"e13ac1f4","receipt":{"commit":"1eaaf8bec1ee53d2f944fb79774699f7d68f8ab1","timestamp":"2026-06-02T15:47:23.471069+00:00","stale":false},"rules":[{"id":"RULE-1","description":"Skill file has YAML frontmatter with `name` and `description` fields","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Grep `skills/verify/SKILL.md` for YAML frontmatter delimiters (`---`); verify `name:` and `description:` fields exist","test_file":"dev/test_skill_specs.py","test_name":"test_has_frontmatter","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-2","description":"Skill file contains a `## Usage` section documenting command syntax","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Grep `skills/verify/SKILL.md` for `## Usage`; verify the section exists","test_file":"dev/test_skill_specs.py","test_name":"test_has_usage_section","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-3","description":"The `name` field in frontmatter is `verify`, matching the directory name","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Extract `name:` from frontmatter; verify it equals `verify`","test_file":"dev/test_skill_specs.py","test_name":"test_name_matches_directory","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-4","description":"Skill includes commit instructions or git operations for file modifications","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Grep `skills/verify/SKILL.md` for commit instructions (`git commit`, `commit the`, `create.*commit`); verify present","test_file":"dev/test_skill_specs.py","test_name":"test_has_commit_instructions","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-5","description":"Skill prohibits modifying code or test files during verification","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Grep `skills/verify/SKILL.md` for `NEVER modify`; verify the read-only constraint is present","test_file":"dev/test_skill_specs.py","test_name":"test_verify_prohibits_modifying_files","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-6","description":"Verify skill Step 4e documents independent audit that reports the final integrity score","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"e2e: Grep skills/verify/SKILL.md for independent audit; verify integrity score and purlin-auditor reference","test_file":"/Users/richlabarca/LocalCode/purlin/dev/test_e2e_teammate_audit_loop.sh","test_name":"verify Step 4e documents independent audit with integrity score and purlin-auditor","tier":"e2e","status":"pass","audit":""},{"id":"PROOF-6","description":"e2e: Grep skills/verify/SKILL.md for independent audit; verify integrity score and purlin-auditor reference","test_file":"dev/test_skill_specs.py","test_name":"test_step_4e_documents_independent_audit","tier":"unit","status":"pass","audit":""}]}],"audit":null},{"name":"static_checks","category":"audit","type":"feature","is_global":false,"source_url":null,"pinned":null,"description":"Deterministic pre-filter that catches structural test problems without any LLM. Uses Python's `ast` module for Python tests, a brace-balancing tokenizer for JS/TS tests, regex for Shell tests, and language-agnostic proof-file checks (proof ID collisions, orphan rules) that operate on JSON regardless of source language. Runs before the LLM audit pass so that structural issues like `assert True` are always caught regardless of which LLM performs the semantic evaluation.","source_path":null,"stack":null,"ext_status":null,"proved":29,"total":29,"deferred":0,"status":"PASSING","vhash":"68530817","receipt":{"commit":"2259011e512dffa219356892dc1187651a58b6ba","timestamp":"2026-06-24T20:17:54.345616+00:00","stale":true},"rules":[{"id":"RULE-1","description":"Detects assert True / tautological assertions in Python test functions","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Run static_checks on a file with assert True; verify status=fail check=assert_true","test_file":"dev/test_static_checks.py","test_name":"test_detects_assert_true","tier":"unit","status":"pass","audit":""},{"id":"PROOF-1","description":"Run static_checks on a file with assert True; verify status=fail check=assert_true","test_file":"dev/test_static_checks.py","test_name":"test_detects_assert_is_not_none","tier":"unit","status":"pass","audit":""},{"id":"PROOF-1","description":"Run static_checks on a file with assert True; verify status=fail check=assert_true","test_file":"dev/test_static_checks.py","test_name":"test_detects_assert_len_gte_zero","tier":"unit","status":"pass","audit":""},{"id":"PROOF-17","description":"e2e: Create test with assert True and a valid test; verify assert_true detected on first, pass on second","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"RULE-10","description":"compute_proof_hash returns a deterministic 16-char hex hash from (rule text, proof description, test code)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-10","description":"Call compute_proof_hash with same inputs twice and verify identical 16-char hex output; call with different inputs and verify different hash","test_file":"dev/test_static_checks.py","test_name":"test_compute_proof_hash_deterministic","tier":"unit","status":"pass","audit":""},{"id":"PROOF-10","description":"Call compute_proof_hash with same inputs twice and verify identical 16-char hex output; call with different inputs and verify different hash","test_file":"dev/test_static_checks.py","test_name":"test_compute_proof_hash_different_inputs","tier":"unit","status":"pass","audit":""},{"id":"PROOF-10","description":"Call compute_proof_hash with same inputs twice and verify identical 16-char hex output; call with different inputs and verify different hash","test_file":"dev/test_static_checks.py","test_name":"test_compute_proof_hash_no_separator_collision","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-11","description":"read_audit_cache returns an empty dict when no cache file exists and parses valid JSON when it does","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-11","description":"Call read_audit_cache on a nonexistent path and verify empty dict; write valid JSON to the cache path and verify it parses correctly","test_file":"dev/test_static_checks.py","test_name":"test_read_cache_missing_file","tier":"unit","status":"pass","audit":""},{"id":"PROOF-11","description":"Call read_audit_cache on a nonexistent path and verify empty dict; write valid JSON to the cache path and verify it parses correctly","test_file":"dev/test_static_checks.py","test_name":"test_read_cache_valid","tier":"unit","status":"pass","audit":""},{"id":"PROOF-11","description":"Call read_audit_cache on a nonexistent path and verify empty dict; write valid JSON to the cache path and verify it parses correctly","test_file":"dev/test_static_checks.py","test_name":"test_read_cache_non_dict_returns_empty","tier":"unit","status":"pass","audit":""},{"id":"PROOF-11","description":"Call read_audit_cache on a nonexistent path and verify empty dict; write valid JSON to the cache path and verify it parses correctly","test_file":"dev/test_static_checks.py","test_name":"test_read_cache_corrupt_json_returns_empty","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-12","description":"write_audit_cache writes atomically via tmp + os.replace","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-28","description":"e2e: Call write_audit_cache with 3 entries; verify audit_cache.json created with 3 keys","test_file":"dev/test_e2e_audit_cache_pipeline.py","test_name":"test_write_cache_creates_file","tier":"e2e","status":"pass","audit":""},{"id":"PROOF-12","description":"Call write_audit_cache, then read the file back and verify contents match the written dict","test_file":"dev/test_static_checks.py","test_name":"test_write_cache_atomic","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-13","description":"Shell if/else proof pairs (same proof_id and rule_id with one pass and one fail branch) are recognized as a single conditional proof where the if-condition is the assertion, not flagged as hardcoded pass","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-13","description":"Create shell test with if/else purlin_proof pair; run static_checks; verify status=pass (not flagged). Also verify a bare hardcoded pass without if/else is still caught","test_file":"dev/test_static_checks.py","test_name":"test_if_else_pair_not_flagged","tier":"unit","status":"pass","audit":""},{"id":"PROOF-13","description":"Create shell test with if/else purlin_proof pair; run static_checks; verify status=pass (not flagged). Also verify a bare hardcoded pass without if/else is still caught","test_file":"dev/test_static_checks.py","test_name":"test_hardcoded_pass_still_caught","tier":"unit","status":"pass","audit":""},{"id":"PROOF-13","description":"Create shell test with if/else purlin_proof pair; run static_checks; verify status=pass (not flagged). Also verify a bare hardcoded pass without if/else is still caught","test_file":"dev/test_static_checks.py","test_name":"test_if_else_pair_no_condition_flagged","tier":"unit","status":"pass","audit":""},{"id":"PROOF-27","description":"e2e: Create shell test with if/else purlin_proof pair; verify pass; verify bare hardcoded pass still caught","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"RULE-14","description":"Python assert_true results include a literal field (true for assert True/assertTrue(True), false for heuristic patterns like assert x is not None)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-14","description":"Run static_checks on file with assert True; verify literal=true. Run on file with assert x is not None; verify literal=false","test_file":"dev/test_static_checks.py","test_name":"test_literal_assert_true_has_literal_true","tier":"unit","status":"pass","audit":""},{"id":"PROOF-14","description":"Run static_checks on file with assert True; verify literal=true. Run on file with assert x is not None; verify literal=false","test_file":"dev/test_static_checks.py","test_name":"test_heuristic_assert_has_literal_false","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-15","description":"Proof ID collisions within a feature are detected \u2014 same PROOF-N targeting different RULE-N values in a proof JSON file","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-15","description":"Create proof JSON with two entries sharing PROOF-1 but targeting RULE-1 and RULE-2; call check_proof_file; verify result contains check='proof_id_collision' with both rules listed. Test with proof JSON from multiple language contexts (Python pytest, JavaScript Jest, Shell, C, PHP, SQL, TypeScript) to verify language-agnostic detection","test_file":"dev/test_static_checks.py","test_name":"test_detects_collision_per_language[python-_python_proof]","tier":"unit","status":"pass","audit":""},{"id":"PROOF-15","description":"Create proof JSON with two entries sharing PROOF-1 but targeting RULE-1 and RULE-2; call check_proof_file; verify result contains check='proof_id_collision' with both rules listed. Test with proof JSON from multiple language contexts (Python pytest, JavaScript Jest, Shell, C, PHP, SQL, TypeScript) to verify language-agnostic detection","test_file":"dev/test_static_checks.py","test_name":"test_detects_collision_per_language[javascript-_jest_proof]","tier":"unit","status":"pass","audit":""},{"id":"PROOF-15","description":"Create proof JSON with two entries sharing PROOF-1 but targeting RULE-1 and RULE-2; call check_proof_file; verify result contains check='proof_id_collision' with both rules listed. Test with proof JSON from multiple language contexts (Python pytest, JavaScript Jest, Shell, C, PHP, SQL, TypeScript) to verify language-agnostic detection","test_file":"dev/test_static_checks.py","test_name":"test_detects_collision_per_language[shell-_shell_proof]","tier":"unit","status":"pass","audit":""},{"id":"PROOF-15","description":"Create proof JSON with two entries sharing PROOF-1 but targeting RULE-1 and RULE-2; call check_proof_file; verify result contains check='proof_id_collision' with both rules listed. Test with proof JSON from multiple language contexts (Python pytest, JavaScript Jest, Shell, C, PHP, SQL, TypeScript) to verify language-agnostic detection","test_file":"dev/test_static_checks.py","test_name":"test_detects_collision_per_language[c-_c_proof]","tier":"unit","status":"pass","audit":""},{"id":"PROOF-15","description":"Create proof JSON with two entries sharing PROOF-1 but targeting RULE-1 and RULE-2; call check_proof_file; verify result contains check='proof_id_collision' with both rules listed. Test with proof JSON from multiple language contexts (Python pytest, JavaScript Jest, Shell, C, PHP, SQL, TypeScript) to verify language-agnostic detection","test_file":"dev/test_static_checks.py","test_name":"test_detects_collision_per_language[php-_php_proof]","tier":"unit","status":"pass","audit":""},{"id":"PROOF-15","description":"Create proof JSON with two entries sharing PROOF-1 but targeting RULE-1 and RULE-2; call check_proof_file; verify result contains check='proof_id_collision' with both rules listed. Test with proof JSON from multiple language contexts (Python pytest, JavaScript Jest, Shell, C, PHP, SQL, TypeScript) to verify language-agnostic detection","test_file":"dev/test_static_checks.py","test_name":"test_detects_collision_per_language[sql-_sql_proof]","tier":"unit","status":"pass","audit":""},{"id":"PROOF-15","description":"Create proof JSON with two entries sharing PROOF-1 but targeting RULE-1 and RULE-2; call check_proof_file; verify result contains check='proof_id_collision' with both rules listed. Test with proof JSON from multiple language contexts (Python pytest, JavaScript Jest, Shell, C, PHP, SQL, TypeScript) to verify language-agnostic detection","test_file":"dev/test_static_checks.py","test_name":"test_detects_collision_per_language[typescript-_typescript_proof]","tier":"unit","status":"pass","audit":""},{"id":"PROOF-15","description":"Create proof JSON with two entries sharing PROOF-1 but targeting RULE-1 and RULE-2; call check_proof_file; verify result contains check='proof_id_collision' with both rules listed. Test with proof JSON from multiple language contexts (Python pytest, JavaScript Jest, Shell, C, PHP, SQL, TypeScript) to verify language-agnostic detection","test_file":"dev/test_static_checks.py","test_name":"test_no_collision_when_ids_unique","tier":"unit","status":"pass","audit":""},{"id":"PROOF-15","description":"Create proof JSON with two entries sharing PROOF-1 but targeting RULE-1 and RULE-2; call check_proof_file; verify result contains check='proof_id_collision' with both rules listed. Test with proof JSON from multiple language contexts (Python pytest, JavaScript Jest, Shell, C, PHP, SQL, TypeScript) to verify language-agnostic detection","test_file":"dev/test_static_checks.py","test_name":"test_collision_with_mixed_languages","tier":"unit","status":"pass","audit":""},{"id":"PROOF-15","description":"Create proof JSON with two entries sharing PROOF-1 but targeting RULE-1 and RULE-2; call check_proof_file; verify result contains check='proof_id_collision' with both rules listed. Test with proof JSON from multiple language contexts (Python pytest, JavaScript Jest, Shell, C, PHP, SQL, TypeScript) to verify language-agnostic detection","test_file":"dev/test_static_checks.py","test_name":"test_multiple_collisions_detected","tier":"unit","status":"pass","audit":""},{"id":"PROOF-15","description":"Create proof JSON with two entries sharing PROOF-1 but targeting RULE-1 and RULE-2; call check_proof_file; verify result contains check='proof_id_collision' with both rules listed. Test with proof JSON from multiple language contexts (Python pytest, JavaScript Jest, Shell, C, PHP, SQL, TypeScript) to verify language-agnostic detection","test_file":"dev/test_multilang_proof_plugins.py","test_name":"test_collision_detected_in_c_output","tier":"integration","status":"pass","audit":""}]},{"id":"RULE-16","description":"Proof entries referencing non-existent rules in the spec are flagged as orphans","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-16","description":"Create proof JSON with entry targeting RULE-99 on a spec with only RULE-1 through RULE-3; call check_proof_file with spec_path; verify result contains check='proof_rule_orphan'. Test with proof JSON from multiple language contexts to verify language-agnostic detection","test_file":"dev/test_static_checks.py","test_name":"test_detects_orphan_per_language[python-_python_proof]","tier":"unit","status":"pass","audit":""},{"id":"PROOF-16","description":"Create proof JSON with entry targeting RULE-99 on a spec with only RULE-1 through RULE-3; call check_proof_file with spec_path; verify result contains check='proof_rule_orphan'. Test with proof JSON from multiple language contexts to verify language-agnostic detection","test_file":"dev/test_static_checks.py","test_name":"test_detects_orphan_per_language[javascript-_jest_proof]","tier":"unit","status":"pass","audit":""},{"id":"PROOF-16","description":"Create proof JSON with entry targeting RULE-99 on a spec with only RULE-1 through RULE-3; call check_proof_file with spec_path; verify result contains check='proof_rule_orphan'. Test with proof JSON from multiple language contexts to verify language-agnostic detection","test_file":"dev/test_static_checks.py","test_name":"test_detects_orphan_per_language[shell-_shell_proof]","tier":"unit","status":"pass","audit":""},{"id":"PROOF-16","description":"Create proof JSON with entry targeting RULE-99 on a spec with only RULE-1 through RULE-3; call check_proof_file with spec_path; verify result contains check='proof_rule_orphan'. Test with proof JSON from multiple language contexts to verify language-agnostic detection","test_file":"dev/test_static_checks.py","test_name":"test_detects_orphan_per_language[c-_c_proof]","tier":"unit","status":"pass","audit":""},{"id":"PROOF-16","description":"Create proof JSON with entry targeting RULE-99 on a spec with only RULE-1 through RULE-3; call check_proof_file with spec_path; verify result contains check='proof_rule_orphan'. Test with proof JSON from multiple language contexts to verify language-agnostic detection","test_file":"dev/test_static_checks.py","test_name":"test_detects_orphan_per_language[php-_php_proof]","tier":"unit","status":"pass","audit":""},{"id":"PROOF-16","description":"Create proof JSON with entry targeting RULE-99 on a spec with only RULE-1 through RULE-3; call check_proof_file with spec_path; verify result contains check='proof_rule_orphan'. Test with proof JSON from multiple language contexts to verify language-agnostic detection","test_file":"dev/test_static_checks.py","test_name":"test_detects_orphan_per_language[sql-_sql_proof]","tier":"unit","status":"pass","audit":""},{"id":"PROOF-16","description":"Create proof JSON with entry targeting RULE-99 on a spec with only RULE-1 through RULE-3; call check_proof_file with spec_path; verify result contains check='proof_rule_orphan'. Test with proof JSON from multiple language contexts to verify language-agnostic detection","test_file":"dev/test_static_checks.py","test_name":"test_detects_orphan_per_language[typescript-_typescript_proof]","tier":"unit","status":"pass","audit":""},{"id":"PROOF-16","description":"Create proof JSON with entry targeting RULE-99 on a spec with only RULE-1 through RULE-3; call check_proof_file with spec_path; verify result contains check='proof_rule_orphan'. Test with proof JSON from multiple language contexts to verify language-agnostic detection","test_file":"dev/test_static_checks.py","test_name":"test_no_orphan_when_all_rules_exist","tier":"unit","status":"pass","audit":""},{"id":"PROOF-16","description":"Create proof JSON with entry targeting RULE-99 on a spec with only RULE-1 through RULE-3; call check_proof_file with spec_path; verify result contains check='proof_rule_orphan'. Test with proof JSON from multiple language contexts to verify language-agnostic detection","test_file":"dev/test_static_checks.py","test_name":"test_required_anchor_rules_not_flagged","tier":"unit","status":"pass","audit":""},{"id":"PROOF-16","description":"Create proof JSON with entry targeting RULE-99 on a spec with only RULE-1 through RULE-3; call check_proof_file with spec_path; verify result contains check='proof_rule_orphan'. Test with proof JSON from multiple language contexts to verify language-agnostic detection","test_file":"dev/test_static_checks.py","test_name":"test_no_orphan_check_without_spec","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-17","description":"Each audit cache entry contains all required fields: assessment, criterion, why, fix, feature, proof_id, rule_id, priority, cached_at","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-29","description":"e2e: Write cache; verify every entry has all required fields and cached_at is valid ISO 8601","test_file":"dev/test_e2e_audit_cache_pipeline.py","test_name":"test_cache_entry_required_fields","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-18","description":"clear_audit_cache atomically replaces the cache file with an empty dict {}","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-32","description":"e2e: Write cache with entries; call clear_audit_cache; read back; verify empty dict","test_file":"dev/test_e2e_audit_cache_pipeline.py","test_name":"test_clear_cache_produces_empty_dict","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-19","description":"write_audit_cache stamps every entry with the real current UTC time, overwriting any caller-provided cached_at","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-33","description":"e2e: Write cache with stale cached_at (midnight UTC); read back; verify cached_at is within 5 seconds of real current time","test_file":"dev/test_e2e_audit_cache_pipeline.py","test_name":"test_write_cache_stamps_real_utc_time","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-2","description":"Detects test functions with no assertion statements","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Run static_checks on a file with no assertions; verify status=fail check=no_assertions","test_file":"dev/test_static_checks.py","test_name":"test_detects_no_assertions","tier":"unit","status":"pass","audit":""},{"id":"PROOF-18","description":"e2e: Create test with no assertions; verify no_assertions detected","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"RULE-21","description":"load_criteria returns built-in criteria always, appends cached additional criteria from `.purlin/cache/additional_criteria.md` if present, appends extra path if provided; no other function in static_checks.py assembles criteria text","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-35","description":"Call load_criteria with no config; verify only built-in content. Save additional file to cache; call again; verify built-in + separator + additional. Pass extra_path; verify all three present","test_file":"dev/test_static_checks.py","test_name":"test_builtin_only","tier":"unit","status":"pass","audit":""},{"id":"PROOF-35","description":"Call load_criteria with no config; verify only built-in content. Save additional file to cache; call again; verify built-in + separator + additional. Pass extra_path; verify all three present","test_file":"dev/test_static_checks.py","test_name":"test_with_additional_criteria","tier":"unit","status":"pass","audit":""},{"id":"PROOF-35","description":"Call load_criteria with no config; verify only built-in content. Save additional file to cache; call again; verify built-in + separator + additional. Pass extra_path; verify all three present","test_file":"dev/test_static_checks.py","test_name":"test_with_extra_path","tier":"unit","status":"pass","audit":""},{"id":"PROOF-35","description":"Call load_criteria with no config; verify only built-in content. Save additional file to cache; call again; verify built-in + separator + additional. Pass extra_path; verify all three present","test_file":"dev/test_static_checks.py","test_name":"test_all_three_sources","tier":"unit","status":"pass","audit":""},{"id":"PROOF-35","description":"Call load_criteria with no config; verify only built-in content. Save additional file to cache; call again; verify built-in + separator + additional. Pass extra_path; verify all three present","test_file":"dev/test_static_checks.py","test_name":"test_no_additional_without_cache_file","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-22","description":"prune_audit_cache removes all cache entries whose hash key is not in the provided live_keys set, preserving entries whose key IS in live_keys with all fields intact","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-38","description":"e2e: Write 5 cache entries via write_audit_cache; write 3 live keys to a temp file; call --prune-cache --live-keys-file; verify JSON output shows pruned=2, kept=3; read cache back and confirm exactly 3 entries remain","test_file":"dev/test_e2e_audit_cache_pipeline.py","test_name":"test_prune_cache_via_cli","tier":"e2e","status":"pass","audit":""},{"id":"PROOF-36","description":"Write cache with 3 entries (keys \"aaa\", \"bbb\", \"ccc\"); call prune_audit_cache with live_keys={\"aaa\",\"ccc\"}; read back; verify \"bbb\" removed, \"aaa\" and \"ccc\" preserved with all original fields intact","test_file":"dev/test_static_checks.py","test_name":"test_prune_removes_dead_preserves_live","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-23","description":"prune_audit_cache with an empty live_keys set on a non-empty cache produces an empty cache (full sweep), and with all keys live produces an identical cache (no false pruning)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-37","description":"Write cache with 3 entries; call prune_audit_cache with live_keys=set(); read back; verify empty dict. Write cache with 3 entries; call prune_audit_cache with all 3 keys as live; read back; verify all 3 entries preserved with identical content","test_file":"dev/test_static_checks.py","test_name":"test_prune_empty_live_keys_clears_all","tier":"unit","status":"pass","audit":""},{"id":"PROOF-37","description":"Write cache with 3 entries; call prune_audit_cache with live_keys=set(); read back; verify empty dict. Write cache with 3 entries; call prune_audit_cache with all 3 keys as live; read back; verify all 3 entries preserved with identical content","test_file":"dev/test_static_checks.py","test_name":"test_prune_all_keys_live_preserves_all","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-24","description":"write_audit_cache merges new entries into the existing cache on disk \u2014 entries from prior writes for different features are preserved, not overwritten. Entries for the same (feature, proof_id) are deduplicated by keeping the latest cached_at","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-30","description":"e2e: Write cache with 3 entries for same (feature, proof_id) at different timestamps; call _read_audit_cache_by_feature; verify dedup to 1 entry keeping the latest cached_at","test_file":"dev/test_e2e_audit_cache_pipeline.py","test_name":"test_read_side_dedup_keeps_latest_entry","tier":"e2e","status":"pass","audit":""},{"id":"PROOF-31","description":"e2e: Write cache with 2 entries for same (feature, proof_id) \u2014 HOLLOW older, STRONG newer \u2014 plus a distinct entry; verify only the latest (STRONG) per (feature, proof_id) is kept and the unique entry survives","test_file":"dev/test_e2e_audit_cache_pipeline.py","test_name":"test_write_side_pruning_removes_stale_duplicates","tier":"e2e","status":"pass","audit":""},{"id":"PROOF-39","description":"Write 3 entries for feature_a via write_audit_cache; then write 2 entries for feature_b via a second call; read cache back; verify all 5 entries are present. Then write 1 updated entry for feature_a (same proof_id, newer assessment); read back; verify feature_b entries are untouched and feature_a has the updated entry","test_file":"dev/test_static_checks.py","test_name":"test_second_write_preserves_first_write_entries","tier":"unit","status":"pass","audit":""},{"id":"PROOF-39","description":"Write 3 entries for feature_a via write_audit_cache; then write 2 entries for feature_b via a second call; read cache back; verify all 5 entries are present. Then write 1 updated entry for feature_a (same proof_id, newer assessment); read back; verify feature_b entries are untouched and feature_a has the updated entry","test_file":"dev/test_static_checks.py","test_name":"test_update_entry_preserves_other_features","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-25","description":"write_audit_cache protects the entire read\u2192merge\u2192write cycle with an exclusive OS file lock on `audit_cache.json.lock` (`fcntl.flock` on POSIX, `msvcrt.locking` on Windows) so that concurrent subagent writers serialize correctly and no entries are lost","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-40","description":"Run two threads calling write_audit_cache concurrently with entries for different features; verify all entries from both threads survive in the final cache. Verify the lock file is created adjacent to the cache file during the write","test_file":"dev/test_static_checks.py","test_name":"test_lock_file_created_alongside_cache","tier":"unit","status":"pass","audit":""},{"id":"PROOF-40","description":"Run two threads calling write_audit_cache concurrently with entries for different features; verify all entries from both threads survive in the final cache. Verify the lock file is created adjacent to the cache file during the write","test_file":"dev/test_static_checks.py","test_name":"test_concurrent_writes_preserve_all_entries","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-26","description":"`--write-cache` CLI flag reads a JSON dict of cache entries from stdin and merges them into the existing audit cache via write_audit_cache, printing a JSON status response with `status: \"merged\"` and entry count","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-41","description":"Call `--write-cache` via CLI with JSON on stdin; verify entries are merged and status response is correct. Seed cache first, then call `--write-cache` with entries for a different feature; verify both old and new entries survive","test_file":"dev/test_static_checks.py","test_name":"test_write_cache_cli_merges_entries","tier":"unit","status":"pass","audit":""},{"id":"PROOF-41","description":"Call `--write-cache` via CLI with JSON on stdin; verify entries are merged and status response is correct. Seed cache first, then call `--write-cache` with entries for a different feature; verify both old and new entries survive","test_file":"dev/test_static_checks.py","test_name":"test_write_cache_cli_merges_with_existing","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-27","description":"check_js detects tautological assertions (`expect(true).toBe(true)`) and JS/TS test bodies with no `expect()` calls, returning the same JSON shape (proof_id, rule_id, test_name, status, reason) as check_python","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-42","description":"e2e: Run the real static_checks.py CLI on a `.ts` file containing a `[proof:...]` test with `expect(true).toBe(true)`; verify status=fail check=assert_true. Run on one whose body has no `expect()`; verify status=fail check=no_assertions. Run on a clean test; verify status=pass","test_file":"dev/test_static_checks.py","test_name":"test_check_js_assertion_detection","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-28","description":"check_js parses JS/TS test files with a brace-balancing tokenizer that (a) matches test titles containing apostrophes regardless of quote style, and (b) captures full test bodies containing nested braces \u2014 options objects, destructured parameters, type assertions \u2014 without truncating at the first inner `}`","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-43","description":"e2e: Run the real static_checks.py CLI on the issue #2 repro \u2014 `it(\"execSync options trigger early-truncation [proof:demo:PROOF-1:RULE-1]\", () => { execSync(\"ls\", { cwd: \".\", encoding: \"utf8\" }); expect(out).toMatch(/./); })` and `it(\"cd's into a sibling [proof:demo:PROOF-2:RULE-2]\", () => { expect(1).toBe(1); })`; verify BOTH PROOF-1 and PROOF-2 appear in the output (apostrophe title matched) and PROOF-1 is NOT flagged no_assertions (options-object body fully captured, expect() seen)","test_file":"dev/test_static_checks.py","test_name":"test_check_js_tokenizer_handles_braces_and_apostrophes","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-29","description":"static_checks.py imports and runs on Windows as well as POSIX \u2014 no Unix-only module is imported unconditionally at module load. `fcntl` is imported under `try/except ImportError` and a module-level flag (`_HAS_FCNTL`) selects between `fcntl.flock` and an `msvcrt.locking` fallback, so write_audit_cache acquires and releases its exclusive lock through that flag and the Windows lock path is exercisable without `fcntl`. The native lock path is verified for real on a `windows-latest` GitHub Actions runner (where `_HAS_FCNTL` is genuinely False and `msvcrt` is real), not only by host simulation","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-51","description":"e2e: Invoke the real static_checks.py CLI as a subprocess on the host (`--write-cache` via stdin, then `--read-cache`, then Pass-1 on a hollow `.cs` fixture); verify valid JSON is emitted on stdout at each step and `.purlin/cache/audit_cache.json` is written to disk","test_file":"dev/test_e2e_audit_cache_pipeline.py","test_name":"test_cli_pipeline_subprocess","tier":"e2e","status":"pass","audit":"STRONG"},{"id":"PROOF-53","description":"On a real `windows-latest` runner, assert `_HAS_FCNTL` is False (POSIX `fcntl` genuinely absent, real `msvcrt` in use), then call write_audit_cache with no fake/monkeypatch into a temp dir; read the cache back and verify the entries round-trip intact and the `audit_cache.json.lock` file was created adjacent to the cache \u2014 exercising the native `msvcrt.locking` path on real Windows","test_file":"dev\\test_windows_native.py","test_name":"test_real_msvcrt_lock_path","tier":"windows","status":"pass","audit":""},{"id":"PROOF-44","description":"Parse static_checks.py with the `ast` module; verify there is no unconditional top-level `import fcntl` (the import sits inside a `try/except ImportError`) and that a module-level `_HAS_FCNTL` assignment exists in both the try and except branches","test_file":"dev/test_static_checks.py","test_name":"test_no_unconditional_fcntl_import","tier":"unit","status":"pass","audit":"EXCLUDED"},{"id":"PROOF-50","description":"With `fcntl` forced unavailable (`_HAS_FCNTL=False`) and a recording fake `msvcrt` injected into `sys.modules`, call write_audit_cache; verify the cache round-trips intact AND the fake `msvcrt.locking` was invoked with `LK_LOCK` then `LK_UNLCK`","test_file":"dev/test_e2e_audit_cache_pipeline.py","test_name":"test_windows_lock_path_via_fake_msvcrt","tier":"integration","status":"pass","audit":"STRONG"}]},{"id":"RULE-3","description":"Detects bare except:pass around code under test","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Run static_checks on a file with except Exception: pass; verify status=fail check=bare_except","test_file":"dev/test_static_checks.py","test_name":"test_detects_bare_except_pass","tier":"unit","status":"pass","audit":""},{"id":"PROOF-3","description":"Run static_checks on a file with except Exception: pass; verify status=fail check=bare_except","test_file":"dev/test_static_checks.py","test_name":"test_detects_bare_except_no_type","tier":"unit","status":"pass","audit":""},{"id":"PROOF-25","description":"e2e: Create test with bare except:pass; verify bare_except detected","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"RULE-30","description":"static_checks.py performs all text I/O as UTF-8 regardless of the OS locale: every text-mode `open()` specifies `encoding='utf-8'`, and stdout/stderr are reconfigured to UTF-8 at startup, so the tool's own UTF-8 specs/proofs/criteria/config/cache files both parse (read) and print (stdout) correctly even when the platform codec is cp1252/ASCII (e.g. on Windows). Without this, `--load-criteria` raises `UnicodeDecodeError` reading `audit_criteria.md` and `UnicodeEncodeError` printing its glyphs. This is verified for real under the native Windows console codec on a `windows-latest` GitHub Actions runner, not only by host locale simulation","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-52","description":"e2e: Invoke the real static_checks.py CLI `--load-criteria` as a subprocess with `env PYTHONUTF8=0`, `LC_ALL=C`, and `LANG=C` (simulating the Windows cp1252/ASCII default), reading the tool's own `references/audit_criteria.md` which contains non-ASCII glyphs; verify it exits 0 (no `UnicodeDecodeError` on read, no `UnicodeEncodeError` on stdout) and the printed criteria text contains a non-ASCII character","test_file":"dev/test_e2e_audit_cache_pipeline.py","test_name":"test_load_criteria_under_ascii_locale","tier":"e2e","status":"pass","audit":"STRONG"},{"id":"PROOF-54","description":"On a real `windows-latest` runner under the native console codec (no `PYTHONUTF8`/`LC_ALL`/`LANG` overrides), invoke the real static_checks.py CLI `--load-criteria` as a subprocess over the tool's own non-ASCII `references/audit_criteria.md`; verify it exits 0 (no `UnicodeDecodeError` on read, no `UnicodeEncodeError` on stdout) and the printed criteria text contains a non-ASCII character","test_file":"dev\\test_windows_native.py","test_name":"test_load_criteria_native_console","tier":"windows","status":"pass","audit":""},{"id":"PROOF-45","description":"Parse static_checks.py with the `ast` module; collect every call to `open(...)` opened in text mode (no `'b'` in the mode argument) and verify each passes an `encoding='utf-8'` keyword","test_file":"dev/test_static_checks.py","test_name":"test_all_text_open_calls_specify_utf8","tier":"unit","status":"pass","audit":"EXCLUDED"}]},{"id":"RULE-31","description":"For `.cs` test files, Pass 1 parses `[Trait(\"PurlinProof\", \"feature:PROOF-N:RULE-N:tier\")]` traits, associates each with its `[Fact]`/`[Theory]` method body, and applies assert-true and no-assertion detection \u2014 returning the same JSON shape (proof_id, rule_id, test_name, status, reason) as check_python/check_js. xUnit `Assert.*`, NUnit `Assert.That`, MSTest `Assert.*`, FluentAssertions `.Should()`, and Playwright fluent assertions \u2014 `Expect(...)`/`Assertions.Expect(...)` chained to a `To*Async()` matcher (`ToBeVisibleAsync`, `ToHaveTextAsync`, `ToContainTextAsync`, \u2026) \u2014 all count as assertions. A bare `Expect(x)` with no matcher chain is not an assertion (still flagged no_assertions)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-46","description":"Run check_csharp on a `.cs` file whose `[Trait(\"PurlinProof\", ...)]`-marked test body is `Assert.True(true);`; verify status=fail check=assert_true","test_file":"dev/test_static_checks.py","test_name":"test_detects_assert_true","tier":"unit","status":"pass","audit":"STRONG"},{"id":"PROOF-47","description":"Run check_csharp on a `.cs` file whose marked test body contains no assertion call; verify status=fail check=no_assertions","test_file":"dev/test_static_checks.py","test_name":"test_detects_no_assertions","tier":"unit","status":"pass","audit":"STRONG"},{"id":"PROOF-48","description":"Run check_csharp on marked `.cs` tests whose bodies use xUnit `Assert.Equal`, NUnit `Assert.That`, MSTest `Assert.IsTrue`, and FluentAssertions `.Should()` respectively; verify each is status=pass (the assertion is recognized, not flagged no_assertions)","test_file":"dev/test_static_checks.py","test_name":"test_recognizes_all_assertion_frameworks","tier":"unit","status":"pass","audit":"STRONG"},{"id":"PROOF-49","description":"Run the static_checks main dispatch (by file extension) on a `.cs` test file; verify it routes to check_csharp and returns a non-empty proofs array rather than the empty-`[]` fallback for unknown extensions","test_file":"dev/test_static_checks.py","test_name":"test_dispatch_routes_cs_to_check_csharp","tier":"unit","status":"pass","audit":"STRONG"},{"id":"PROOF-55","description":"Run check_csharp on a `.cs` test whose only assertion is `await Assertions.Expect(page.Locator(...)).ToBeVisibleAsync();`; verify status=pass (recognized as an assertion, NOT flagged no_assertions). Also run on a body containing a bare `Expect(x)` with no `To*Async()` matcher and no Assert/Should/Verify; verify status=fail check=no_assertions","test_file":"dev/test_static_checks.py","test_name":"test_recognizes_playwright_fluent_assertions","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-4","description":"Detects logic mirroring (expected value from same function as SUT)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Run static_checks on a file with logic mirroring; verify status=fail check=logic_mirroring","test_file":"dev/test_static_checks.py","test_name":"test_detects_logic_mirroring","tier":"unit","status":"pass","audit":""},{"id":"PROOF-4","description":"Run static_checks on a file with logic mirroring; verify status=fail check=logic_mirroring","test_file":"dev/test_static_checks.py","test_name":"test_no_mirroring_with_literal","tier":"unit","status":"pass","audit":""},{"id":"PROOF-19","description":"e2e: Create test with logic mirroring (expected from same function as SUT); verify logic_mirroring detected","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"RULE-5","description":"Detects mock target matching the function being tested (requires --spec-path)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Run static_checks with --spec-path on a file mocking the rule's function; verify status=fail check=mock_target_match","test_file":"dev/test_static_checks.py","test_name":"test_detects_mock_matching_rule","tier":"unit","status":"pass","audit":""},{"id":"PROOF-5","description":"Run static_checks with --spec-path on a file mocking the rule's function; verify status=fail check=mock_target_match","test_file":"dev/test_static_checks.py","test_name":"test_no_match_when_mock_unrelated","tier":"unit","status":"pass","audit":""},{"id":"PROOF-24","description":"e2e: Create test mocking bcrypt on rule about bcrypt; verify mock_target_match detected","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"RULE-6","description":"Returns JSON with proof_id, rule_id, test_name, status, reason for each proof","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Run static_checks on any file; verify JSON output has proofs array with required fields","test_file":"dev/test_static_checks.py","test_name":"test_json_has_required_fields","tier":"unit","status":"pass","audit":""},{"id":"PROOF-22","description":"e2e: Parse JSON output; verify proofs array has proof_id, rule_id, test_name, status, reason fields","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"RULE-7","description":"Always exits 0 for completed analysis; defects are reported via JSON output status=fail, not exit codes. Non-zero exits (2) are reserved for real errors (bad args, missing files)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Run static_checks on a clean file and verify exit 0; run on a flawed file and verify exit 0 with status=fail in JSON output","test_file":"dev/test_static_checks.py","test_name":"test_exit_0_when_all_pass","tier":"unit","status":"pass","audit":""},{"id":"PROOF-7","description":"Run static_checks on a clean file and verify exit 0; run on a flawed file and verify exit 0 with status=fail in JSON output","test_file":"dev/test_static_checks.py","test_name":"test_exit_0_with_fail_status_when_defects_found","tier":"unit","status":"pass","audit":""},{"id":"PROOF-20","description":"e2e: Create structurally valid but semantically weak test; verify passes structural checks","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""},{"id":"PROOF-21","description":"e2e: Create 3 strong tests; verify all pass structural checks with exit 0","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""},{"id":"PROOF-23","description":"e2e: Run on clean and flawed files; verify exit 0 for both; verify flawed has status=fail in JSON","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"RULE-8","description":"check_spec_coverage returns rule_count and proof_count for the spec","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-8","description":"Create spec with rules and proofs; call check_spec_coverage; verify rule_count and proof_count are correct","test_file":"dev/test_static_checks.py","test_name":"test_spec_coverage_counts","tier":"unit","status":"pass","audit":""},{"id":"PROOF-8","description":"Create spec with rules and proofs; call check_spec_coverage; verify rule_count and proof_count are correct","test_file":"dev/test_static_checks.py","test_name":"test_spec_coverage_via_cli","tier":"unit","status":"pass","audit":""},{"id":"PROOF-8","description":"Create spec with rules and proofs; call check_spec_coverage; verify rule_count and proof_count are correct","test_file":"dev/test_static_checks.py","test_name":"test_spec_coverage_empty","tier":"unit","status":"pass","audit":""},{"id":"PROOF-26","description":"e2e: Create specs with rules and proofs; call check_spec_coverage; verify rule_count and proof_count","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]}],"audit":{"integrity":100,"strong":7,"weak":0,"hollow":0,"manual":0,"findings":[]}},{"name":"sync_status","category":"mcp","type":"feature","is_global":false,"source_url":null,"pinned":null,"description":"Rule coverage reporting tool. Scans specs and proof files, computes per-feature coverage including own, required, and global anchor rules. Reports actionable directives and computes verification hashes.","source_path":null,"stack":"python/stdlib, json, glob, re, hashlib","ext_status":null,"proved":58,"total":58,"deferred":0,"status":"VERIFIED","vhash":"3a6de590","receipt":{"commit":"fb2089313b93d8f101a8b571039b7765a4583a2d","timestamp":"2026-06-05T14:37:11.418976+00:00","stale":false},"rules":[{"id":"RULE-1","description":"Scans `specs/**/*.md` for RULE-N lines, reads `*.proofs-*.json`, and reports per-feature coverage including own rules, required rules (from `> Requires:`), and global anchor rules, with actionable directives","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Create a spec with rules plus a required anchor; run sync_status; verify coverage includes own and required rules with directives","test_file":"dev/test_mcp_server.py","test_name":"test_rules_with_required_no_proofs","tier":"unit","status":"pass","audit":""},{"id":"PROOF-34","description":"e2e: Create anchor with Source/Pinned/Path metadata; run _scan_specs; verify pinned and source_path extracted","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"RULE-10","description":"Displays rule labels: `(own)` for the feature's own rules, `(required)` for `> Requires:` rules, `(global)` for global anchor rules","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-10","description":"Create a feature requiring an anchor; verify (own) and (required) labels","test_file":"dev/test_mcp_server.py","test_name":"test_rule_labels","tier":"unit","status":"pass","audit":""},{"id":"PROOF-23","description":"e2e: Run sync_status on feature with required and global rules; verify (own), (required), (global) labels on rule lines","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"RULE-11","description":"Shows a scope overlap advisory when an anchor's `> Scope:` overlaps a feature's scope but is not in `> Requires:`","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-11","description":"Create an anchor with overlapping scope but no Requires; verify scope overlap advisory","test_file":"dev/test_mcp_server.py","test_name":"test_scope_overlap_suggestion","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-12","description":"Warns when a `> Requires:` target does not match any known spec name","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-12","description":"Create a feature spec with `> Requires: does_not_exist`; run sync_status; verify warning about unresolved requires target","test_file":"dev/test_mcp_server.py","test_name":"test_unresolved_requires_warning","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-13","description":"Warns when a spec has manual proofs but no `> Scope:` metadata","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-13","description":"Create a spec with manual proof but no `> Scope:`; verify warning about staleness detection; create another with `> Scope:` and verify no warning","test_file":"dev/test_mcp_server.py","test_name":"test_manual_proof_without_scope_warning","tier":"unit","status":"pass","audit":""},{"id":"PROOF-13","description":"Create a spec with manual proof but no `> Scope:`; verify warning about staleness detection; create another with `> Scope:` and verify no warning","test_file":"dev/test_mcp_server.py","test_name":"test_manual_proof_with_scope_no_warning","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-14","description":"Prefers proof files adjacent to their spec over proof files in the specs/ root directory","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-14","description":"Create a proof file at specs/ root and a matching proof in a subdirectory next to its spec; run sync_status; verify the subdirectory proof is used","test_file":"dev/test_mcp_server.py","test_name":"test_prefers_subdirectory_proofs","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-15","description":"Explains receipt staleness cause: distinguishes own rule changes from required/global anchor rule changes","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-15","description":"Create a feature requiring an anchor; verify and write receipt; add a rule to the anchor; run sync_status; verify output explains staleness is from the anchor change","test_file":"dev/test_mcp_server.py","test_name":"test_receipt_staleness_from_anchor_change","tier":"unit","status":"pass","audit":""},{"id":"PROOF-31","description":"e2e: Add rule to spec; recompute vhash; verify mismatch with stale receipt","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"RULE-16","description":"Warns when `specs/` contains uncommitted changes to `.md` or `.proofs-*.json` files, listing the affected files and recommending a commit before drift or verify","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-16","description":"Create a temp git repo with a committed spec; modify the spec without committing; run sync_status; verify warning appears with filename; commit the change; run sync_status again; verify no warning","test_file":"dev/test_mcp_server.py","test_name":"test_warns_uncommitted_spec_changes","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-17","description":"All proofs are displayed uniformly \u2014 grep-based and behavioral proofs both show PASS/FAIL status with no visual distinction in coverage reporting","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-64","description":"","test_file":"dev/test_mcp_server.py","test_name":"test_proof_types_display_uniformly","tier":"unit","status":"pass","audit":""},{"id":"PROOF-17","description":"Create a spec with structural-only proofs; verify output shows structural checks with \"not counted\" label and summary line","test_file":"","test_name":"","tier":"integration","status":"planned","audit":""}]},{"id":"RULE-18","description":"sync_status output begins with a summary table showing feature name, coverage fraction, and status (VERIFIED/PASSING/FAILING/PARTIAL/UNTESTED) for all features, sorted by status priority (FAILING, PARTIAL, PASSING, VERIFIED, UNTESTED)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-18","description":"Create 3 features (one VERIFIED, one PARTIAL, one with no proofs); run sync_status; verify output starts with summary table, correct ready count, and detail follows","test_file":"dev/test_mcp_server.py","test_name":"test_summary_table","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-19","description":"sync_status appends an integrity summary after the features VERIFIED line showing the integrity percentage and relative time since last purlin:audit, sourced from the audit cache; when no cache exists, shows a prompt to run purlin:audit","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-43","description":"e2e: Write audit cache with STRONG/WEAK entries; run sync_status; verify Integrity percentage and relative time","test_file":"dev/test_e2e_audit_cache_pipeline.py","test_name":"test_sync_status_shows_integrity_line","tier":"e2e","status":"pass","audit":""},{"id":"PROOF-44","description":"e2e: No audit cache; run sync_status; verify \"No audit data\" message","test_file":"dev/test_e2e_audit_cache_pipeline.py","test_name":"test_sync_status_no_cache_shows_no_audit_data","tier":"e2e","status":"pass","audit":""},{"id":"PROOF-50","description":"e2e: Write cache then delete; verify sync_status reverts to no-audit state","test_file":"dev/test_e2e_audit_cache_pipeline.py","test_name":"test_deleting_cache_reverts_to_no_audit_state","tier":"e2e","status":"pass","audit":""},{"id":"PROOF-19","description":"Create a temp project with an audit cache containing STRONG and WEAK entries with timestamps; run sync_status; verify output contains \"Integrity: NN%\" and \"last purlin:audit:\" with relative time; delete the cache; run sync_status; verify output contains \"No audit data\"","test_file":"","test_name":"","tier":"integration","status":"planned","audit":""}]},{"id":"RULE-2","description":"Status is determined by proof coverage: VERIFIED (all rules proved + passing + non-stale receipt exists), PASSING (all rules proved + passing + no current receipt), PARTIAL (some but not all rules proved, none failing), FAILING (any proof has status FAIL), UNTESTED (zero proofs). Partial coverage never earns PASSING \u2014 every rule must have a passing proof","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Create a spec with all behavioral rules proved but no receipt; verify \"PASSING\" and vhash in output","test_file":"dev/test_mcp_server.py","test_name":"test_ready_with_vhash","tier":"unit","status":"pass","audit":""},{"id":"PROOF-25","description":"e2e: Add proofs for all 5 rules (own + required + global); run sync_status; verify 5/5 and PASSING","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""},{"id":"PROOF-33","description":"e2e: Create specs with proofs; verify both PASSING and check_spec_coverage returns correct counts","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""},{"id":"PROOF-42","description":"e2e: Coverage progression UNTESTED to PARTIAL to PASSING with external anchor rules","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"RULE-20","description":"Reports UNTESTED when a feature has zero behavioral proofs \u2014 this replaces the em-dash display for features with no proofs","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-20","description":"Create a spec with 2 behavioral rules but zero proof entries; run sync_status; verify status is \"UNTESTED\" in the summary table","test_file":"dev/test_mcp_server.py","test_name":"test_untested_status_for_zero_proofs","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-21","description":"Reports PARTIAL (not PASSING) when a feature has some rules proved and passing but not all rules are covered \u2014 partial coverage never earns PASSING status","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-21","description":"Create a spec with 3 behavioral rules; write proof file with 2 passing proofs covering RULE-1 and RULE-2 only; run sync_status; verify status is \"PARTIAL\" not \"PASSING\" despite both proofs passing","test_file":"dev/test_mcp_server.py","test_name":"test_partial_status_when_not_all_rules_proved","tier":"unit","status":"pass","audit":""},{"id":"PROOF-24","description":"e2e: Add proofs for feature's 2 own rules only; run sync_status; verify 2/5 and not VERIFIED","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"RULE-22","description":"Anchor detail shows Source URL, Path (if present), and Pinned value (SHA truncated to 7 chars) for anchors with `> Source:` metadata","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-65","description":"","test_file":"dev/test_mcp_server.py","test_name":"test_anchor_detail_shows_source_path_and_pinned","tier":"unit","status":"pass","audit":""},{"id":"PROOF-35","description":"e2e: Create external anchor; run sync_status; verify Source, Path, and Pinned lines in output","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"RULE-23","description":"Shows unpinned warning for anchors with `> Source:` but no `> Pinned:`","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-66","description":"","test_file":"dev/test_mcp_server.py","test_name":"test_anchor_unpinned_warning","tier":"unit","status":"pass","audit":""},{"id":"PROOF-36","description":"e2e: Create anchor with Source but no Pinned; run sync_status; verify unpinned warning","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"RULE-24","description":"report-data.js feature entries include `pinned` and `source_path` fields when the anchor spec has `> Pinned:` and `> Path:` metadata","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-67","description":"","test_file":"dev/test_mcp_server.py","test_name":"test_report_data_includes_pinned_and_source_path","tier":"unit","status":"pass","audit":""},{"id":"PROOF-37","description":"e2e: Create external anchor with report=true; verify report-data.js has pinned and source_path fields","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"RULE-25","description":"Shows \"consider re-auditing\" when the audit cache is older than 24 hours","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-45","description":"e2e: Write cache with timestamps from 3 days ago; verify \"consider re-auditing\"","test_file":"dev/test_e2e_audit_cache_pipeline.py","test_name":"test_sync_status_stale_cache_warns","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-26","description":"report-data.js includes audit_summary with integrity, assessment counts, last_audit, last_audit_relative, and stale fields when cache exists; audit_summary is null when no cache exists","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-46","description":"e2e: Write cache with report=true; parse report-data.js; verify audit_summary fields","test_file":"dev/test_e2e_audit_cache_pipeline.py","test_name":"test_report_data_audit_summary_fields","tier":"e2e","status":"pass","audit":""},{"id":"PROOF-47","description":"e2e: No cache with report=true; verify report-data.js audit_summary is null","test_file":"dev/test_e2e_audit_cache_pipeline.py","test_name":"test_report_data_audit_summary_null_when_no_cache","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-27","description":"report-data.js per-feature audit data is populated from cache entries matching the feature name","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-48","description":"e2e: Write cache for feature login; verify per-feature audit data with correct integrity and findings","test_file":"dev/test_e2e_audit_cache_pipeline.py","test_name":"test_report_data_per_feature_audit_data","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-28","description":"Cache entries without a feature field are excluded from per-feature audit data but counted in project-wide summary","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-49","description":"e2e: Write cache with/without feature field; verify per-feature excludes no-feature entries; verify project-wide counts both","test_file":"dev/test_e2e_audit_cache_pipeline.py","test_name":"test_cache_entries_without_feature_excluded_from_per_feature_but_counted_globally","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-29","description":"Per-feature integrity = (STRONG + MANUAL) / (STRONG + WEAK + HOLLOW + MANUAL) \u2014 measures proof quality only; NONE rules do not affect the integrity denominator","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-51","description":"e2e: Feature with 5 rules, 3 proved (2 STRONG, 1 WEAK); verify integrity = 2/3 = 67% (quality only, NONE excluded)","test_file":"dev/test_e2e_audit_cache_pipeline.py","test_name":"test_integrity_is_quality_only","tier":"e2e","status":"pass","audit":""},{"id":"PROOF-54","description":"e2e: Feature with 4 rules, 2 STRONG proofs + 2 NONE; verify integrity = 100% not 50%","test_file":"dev/test_e2e_audit_cache_pipeline.py","test_name":"test_integrity_ignores_no_proof_rules_completely","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-3","description":"Warns about unnumbered lines under `## Rules` and missing `## Rules` sections","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Create a spec with unnumbered rule; verify WARNING in output","test_file":"dev/test_mcp_server.py","test_name":"test_warns_unnumbered_rules","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-30","description":"Per-feature integrity counts only proofs cached under that feature name \u2014 required/global anchor proofs are counted under the anchor's own feature, not the consuming feature","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-52","description":"e2e: Feature with 2 own STRONG rules requiring anchor with 3 rules; verify integrity = 2/2 = 100% (anchor proofs counted under anchor)","test_file":"dev/test_e2e_audit_cache_pipeline.py","test_name":"test_integrity_counts_only_own_feature_proofs","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-31","description":"Global integrity (project-wide) = (STRONG + MANUAL) / (STRONG + WEAK + HOLLOW + MANUAL) across all features \u2014 NONE rules do not affect the denominator","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-53","description":"e2e: Two features each with 1 NONE rule; write 2 STRONG each; verify global integrity = 4/4 = 100% (NONE excluded)","test_file":"dev/test_e2e_audit_cache_pipeline.py","test_name":"test_global_integrity_quality_only","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-32","description":"sync_status CLI integrity percentage matches the audit_summary.integrity value in report-data.js","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-55","description":"e2e: Write cache; run sync_status with report=true; verify CLI integrity percentage matches report-data.js audit_summary.integrity","test_file":"dev/test_e2e_audit_cache_pipeline.py","test_name":"test_cli_integrity_matches_report_data","tier":"e2e","status":"pass","audit":""},{"id":"PROOF-57","description":"e2e: Create isolated project with 5-rule feature, 3 proved (1 STRONG, 1 WEAK, 1 HOLLOW) + 2 NONE; run sync_status with report=true; verify CLI, dashboard, and computed integrity all equal 33% (NONE excluded from denominator)","test_file":"dev/test_e2e_audit_cache_pipeline.py","test_name":"test_integrity_with_no_proof_rules_all_sources_match","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-33","description":"The integrity formula `(STRONG + MANUAL) / (STRONG + WEAK + HOLLOW + MANUAL)` is consistent across `references/audit_criteria.md`, `skills/audit/SKILL.md`, and this spec; none of these files include NONE in the integrity formula denominator","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-56","description":"Grep `references/audit_criteria.md`, `skills/audit/SKILL.md`, and `specs/mcp/sync_status.md` for the integrity formula; verify all three contain `(STRONG + MANUAL) / (STRONG + WEAK + HOLLOW + MANUAL)` and none include NONE in the integrity denominator","test_file":"dev/test_mcp_server.py","test_name":"test_integrity_formula_consistent_across_files","tier":"unit","status":"pass","audit":""},{"id":"PROOF-56","description":"Grep `references/audit_criteria.md`, `skills/audit/SKILL.md`, and `specs/mcp/sync_status.md` for the integrity formula; verify all three contain `(STRONG + MANUAL) / (STRONG + WEAK + HOLLOW + MANUAL)` and none include NONE in the integrity denominator","test_file":"dev/test_e2e_audit_cache_pipeline.py","test_name":"test_integrity_formula_consistent_across_sources","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-34","description":"Integrity is computed by exactly one function (`_compute_integrity`) \u2014 both `_read_audit_summary` and `_build_feature_audit` delegate to it; no other function in purlin_server.py contains the integrity formula","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-58","description":"Grep purlin_server.py for the integrity computation pattern; verify `_compute_integrity` is the only function containing the formula and that both `_read_audit_summary` and `_build_feature_audit` call it","test_file":"dev/test_e2e_audit_cache_pipeline.py","test_name":"test_compute_integrity_is_single_function","tier":"unit","status":"pass","audit":""},{"id":"PROOF-60","description":"e2e: Create isolated project with 2 features, each with different audit mixes; run sync_status with report=true; verify per-feature and global integrity in CLI output match report-data.js per-feature audit.integrity and audit_summary.integrity","test_file":"dev/test_e2e_audit_cache_pipeline.py","test_name":"test_multi_feature_integrity_cli_matches_dashboard","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-35","description":"Feature status is determined by exactly one function (`_determine_status`) \u2014 all call sites in purlin_server.py delegate to it; no other function contains the status determination logic","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-59","description":"Grep purlin_server.py for the status determination pattern; verify `_determine_status` is the only function containing the if/elif chain and that all three call sites delegate to it","test_file":"dev/test_e2e_audit_cache_pipeline.py","test_name":"test_determine_status_is_single_function","tier":"unit","status":"pass","audit":""},{"id":"PROOF-61","description":"e2e: Create isolated project with features in every status (VERIFIED, PASSING, PARTIAL, FAILING, UNTESTED); verify CLI summary table status matches report-data.js per-feature status for all five","test_file":"dev/test_e2e_audit_cache_pipeline.py","test_name":"test_all_statuses_cli_matches_dashboard","tier":"e2e","status":"pass","audit":""}]},{"id":"RULE-36","description":"`_scan_specs` parses `> Stack:` metadata from spec files and includes it in feature info; report-data.js includes `stack` field when present","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-62","description":"Create spec with `> Stack: python/stdlib, json`; run `_scan_specs`; verify features dict has `stack == \"python/stdlib, json\"`. Create spec without Stack; verify `stack is None`","test_file":"dev/test_mcp_server.py","test_name":"test_scan_specs_parses_stack","tier":"unit","status":"pass","audit":""},{"id":"PROOF-62","description":"Create spec with `> Stack: python/stdlib, json`; run `_scan_specs`; verify features dict has `stack == \"python/stdlib, json\"`. Create spec without Stack; verify `stack is None`","test_file":"dev/test_mcp_server.py","test_name":"test_scan_specs_stack_absent","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-37","description":"Does NOT warn about rule count \u2014 rule count scales with feature complexity per `references/spec_quality_guide.md` (\"Coverage dimensions\")","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-63","description":"Create feature with 3 rules; verify NO rule-count warning. Create feature with 12 rules; verify NO rule-count warning. Create anchor with 2 rules; verify NO warning. Create instruction spec with 3 rules; verify NO warning","test_file":"dev/test_mcp_server.py","test_name":"test_no_rule_count_warning_for_few_rules","tier":"unit","status":"pass","audit":""},{"id":"PROOF-63","description":"Create feature with 3 rules; verify NO rule-count warning. Create feature with 12 rules; verify NO rule-count warning. Create anchor with 2 rules; verify NO warning. Create instruction spec with 3 rules; verify NO warning","test_file":"dev/test_mcp_server.py","test_name":"test_no_rule_count_warning_for_many_rules","tier":"unit","status":"pass","audit":""},{"id":"PROOF-63","description":"Create feature with 3 rules; verify NO rule-count warning. Create feature with 12 rules; verify NO rule-count warning. Create anchor with 2 rules; verify NO warning. Create instruction spec with 3 rules; verify NO warning","test_file":"dev/test_mcp_server.py","test_name":"test_no_rule_count_warning_for_anchors","tier":"unit","status":"pass","audit":""},{"id":"PROOF-63","description":"Create feature with 3 rules; verify NO rule-count warning. Create feature with 12 rules; verify NO rule-count warning. Create anchor with 2 rules; verify NO warning. Create instruction spec with 3 rules; verify NO warning","test_file":"dev/test_mcp_server.py","test_name":"test_no_rule_count_warning_for_instructions","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-38","description":"When the project's `.mcp.json` defines a `purlin` server whose command or args path points into the Claude plugin cache (`.claude/plugins/cache/`), sync_status prepends a preamble advisory warning that the entry shadows the plugin-bundled MCP server and is pinned to an old plugin version, with a `\u2192 Run: purlin:init --mcp` directive; no advisory when `.mcp.json` is absent, has no `purlin` entry, or the `purlin` entry points elsewhere (e.g., a dev checkout)","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-68","description":"Create a temp project with `.mcp.json` defining `mcpServers.purlin` with an args path containing `.claude/plugins/cache/purlin/`; run sync_status; verify the preamble contains the legacy-entry advisory and `\u2192 Run: purlin:init --mcp`. Rewrite the entry with a non-cache path (dev checkout); verify no advisory. Delete `.mcp.json`; verify no advisory","test_file":"dev/test_mcp_server.py","test_name":"test_legacy_mcp_entry_advisory","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-4","description":"Required rules from `> Requires:` specs count toward a feature's coverage total (X/total) with `(required)` label; proofs are looked up under the source spec's feature name","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Create spec A with RULE-1 and spec B requiring A; verify B's total includes A's rule with (required) label","test_file":"dev/test_mcp_server.py","test_name":"test_requires_counts_for_coverage","tier":"unit","status":"pass","audit":""},{"id":"PROOF-22","description":"e2e: Create anchor (2 rules), global anchor (1 rule), feature (2 own + Requires); run sync_status; verify 0/5 total","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""},{"id":"PROOF-38","description":"e2e: Create 1 external anchor (2 rules) + feature (1 own); verify coverage 0/3","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""},{"id":"PROOF-39","description":"e2e: Create 2 external anchors + feature; verify coverage 0/5","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""},{"id":"PROOF-40","description":"e2e: Create 1 external + 1 local anchor + feature; verify coverage 0/4","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"RULE-5","description":"Detects manual proof staleness by checking if scope files have commits newer than the stamp's commit SHA","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Create a spec with manual stamp at old SHA and modified scope file; verify MANUAL PROOF STALE","test_file":"dev/test_mcp_server.py","test_name":"test_manual_proof_staleness","tier":"unit","status":"pass","audit":""},{"id":"PROOF-26","description":"e2e: Create spec with @manual stamp at current HEAD; verify PASS with verified date","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""},{"id":"PROOF-27","description":"e2e: Edit scope file and commit; verify MANUAL PROOF STALE with re-verify directive","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""},{"id":"PROOF-28","description":"e2e: Re-stamp with new HEAD SHA; verify PASS again","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"RULE-6","description":"`vhash` is computed as `sha256(sorted rule IDs including prefixed required/global keys + \"|\" + sorted proof ID:status pairs for all relevant proofs)[:8]`","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Compute vhash with prefixed required key; verify it includes the prefix and differs from hash without it","test_file":"dev/test_mcp_server.py","test_name":"test_vhash_with_prefixed_keys","tier":"unit","status":"pass","audit":""},{"id":"PROOF-29","description":"e2e: Compute vhash and write receipt; verify receipt has correct vhash, commit, rules, proofs","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""},{"id":"PROOF-30","description":"e2e: Recompute vhash with no changes; verify matches receipt on disk","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""},{"id":"PROOF-32","description":"e2e: Add proof for new rule; write new receipt; verify different vhash and audit matches","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"RULE-7","description":"All proofs count equally toward coverage regardless of proof type (grep-based, behavioral, etc.). Proof quality (STRONG/WEAK/HOLLOW) is assessed by the auditor, not the coverage system","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Create a spec with all grep-based proofs; verify structural checks are reported separately and feature is not VERIFIED","test_file":"dev/test_mcp_server.py","test_name":"test_structural_only_detection","tier":"unit","status":"pass","audit":""},{"id":"PROOF-7","description":"Create a spec with all grep-based proofs; verify structural checks are reported separately and feature is not VERIFIED","test_file":"dev/test_mcp_server.py","test_name":"test_all_proof_types_count_equally","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-8","description":"Detects `> Global: true` metadata on anchor specs and sets the `is_global` flag","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-8","description":"Create an anchor with `> Global: true`; verify is_global flag in scan result","test_file":"dev/test_mcp_server.py","test_name":"test_scan_specs_detects_global","tier":"unit","status":"pass","audit":""}]},{"id":"RULE-9","description":"Global anchor rules auto-apply to all non-anchor feature specs without needing `> Requires:`; they appear in coverage with `(global)` label","label":"own","source":null,"is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-9","description":"Create a global anchor and a feature with no Requires; verify global rule appears with (global) label","test_file":"dev/test_mcp_server.py","test_name":"test_global_anchor_auto_applies","tier":"unit","status":"pass","audit":""},{"id":"PROOF-41","description":"e2e: Create global external anchor; verify auto-applies to all features","test_file":"","test_name":"","tier":"e2e","status":"planned","audit":""}]},{"id":"schema_spec_format/RULE-1","description":"Every spec MUST contain exactly two required sections: `## Rules`, `## Proof`","label":"required","source":"schema_spec_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Parse `references/formats/spec_format.md`; find the `## Required Sections` area; verify both sections (`## Rules`, `## Proof`) appear within it","test_file":"dev/test_schema_spec_format.py","test_name":"test_format_documents_two_sections","tier":"unit","status":"pass","audit":""}]},{"id":"schema_spec_format/RULE-2","description":"Rules are numbered sequentially as `RULE-N` (RULE-1, RULE-2, ...). Unnumbered lines under `## Rules` trigger a sync_status WARNING","label":"required","source":"schema_spec_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Create a spec with an unnumbered line under `## Rules` (e.g., `- some constraint without RULE-N prefix`); run sync_status; verify it reports a WARNING about unnumbered rules","test_file":"dev/test_schema_spec_format.py","test_name":"test_unnumbered_rule_triggers_warning","tier":"unit","status":"pass","audit":""}]},{"id":"schema_spec_format/RULE-3","description":"Proofs are numbered as `PROOF-N (RULE-N)` \u2014 each proof references the rule it covers","label":"required","source":"schema_spec_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Grep `specs/**/*.md` for lines under `## Proof`; verify every proof line matches the pattern `PROOF-\\d+ \\(RULE-\\d+\\)`","test_file":"dev/test_schema_spec_format.py","test_name":"test_all_proof_lines_match_pattern","tier":"unit","status":"pass","audit":""}]},{"id":"schema_spec_format/RULE-4","description":"Every RULE must have at least one corresponding PROOF","label":"required","source":"schema_spec_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Create a spec with RULE-1 but no PROOF referencing RULE-1; run sync_status; verify it reports the rule as uncovered","test_file":"dev/test_schema_spec_format.py","test_name":"test_rule_without_proof_shows_uncovered","tier":"unit","status":"pass","audit":""}]},{"id":"schema_spec_format/RULE-5","description":"`> Requires:` metadata, when present, is a comma-separated list of anchor names whose rules also apply to this feature","label":"required","source":"schema_spec_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Create a spec with `> Requires: base_anchor`; run sync_status; verify the report includes `base_anchor/RULE-1` in the coverage output","test_file":"dev/test_schema_spec_format.py","test_name":"test_requires_includes_referenced_rules","tier":"unit","status":"pass","audit":""}]},{"id":"schema_spec_format/RULE-6","description":"`> Scope:` metadata, when present, is a comma-separated list of source file paths the feature touches","label":"required","source":"schema_spec_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Create a spec with `> Scope: scripts/mcp/purlin_server.py`; verify sync_status parses and displays the scope files","test_file":"dev/test_schema_spec_format.py","test_name":"test_scope_metadata_parsed","tier":"unit","status":"pass","audit":""}]},{"id":"schema_spec_format/RULE-7","description":"Feature specs use heading `# Feature: `, anchor specs use `# Anchor: `","label":"required","source":"schema_spec_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Grep `specs/**/*.md` for first-level headings; verify each starts with `# Feature:` or `# Anchor:`","test_file":"dev/test_schema_spec_format.py","test_name":"test_spec_headings_use_correct_prefix","tier":"unit","status":"pass","audit":""}]},{"id":"schema_spec_format/RULE-8","description":"`> Description:` metadata, when present, is a plain-language summary of the spec; supports multi-line via `>` continuation lines that do not match a metadata field pattern (`> FieldName:`)","label":"required","source":"schema_spec_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-8","description":"Create a spec with `> Description: First line\\n> second line\\n> Scope: src/`; parse description; verify it equals \"First line second line\" (continuation joined, Scope not included)","test_file":"dev/test_schema_spec_format.py","test_name":"test_multiline_description_continuation","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-1","description":"Proof files are named `.proofs-.json` and live in the same directory as their spec","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Create a proof file at `specs/test/foo.proofs-unit.json`; run sync_status; verify it reads the file and associates it with the `foo` spec","test_file":"dev/test_schema_proof_format.py","test_name":"test_proof_file_read_by_sync_status","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-2","description":"The JSON schema has a top-level `tier` string and a `proofs` array. Each proof entry contains: `feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Validate a proof JSON file against the required fields; verify all 7 fields (`feature`, `id`, `rule`, `test_file`, `test_name`, `status`, `tier`) are present in each proof entry","test_file":"dev/test_schema_proof_format.py","test_name":"test_proof_entry_has_all_seven_fields","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-3","description":"`status` is either `\"pass\"` or `\"fail\"` \u2014 no other values","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Create a proof entry with `status: \"error\"`; run sync_status; verify it is not counted as a passing proof","test_file":"dev/test_schema_proof_format.py","test_name":"test_invalid_status_not_counted","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-4","description":"`tier` values are: `\"unit\"`, `\"integration\"`, `\"e2e\"`, and `\"windows\"` (a platform-gated tier proven only on a Windows runner) \u2014 no other automated tiers","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Scan all real `*.proofs-*.json` files; verify every tier value (top-level and per-entry) is in the valid set {unit, integration, e2e, windows}. Verify `spec_format.md` documents @integration, @e2e, and","test_file":"dev/test_schema_proof_format.py","test_name":"test_standard_tiers_documented","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-5","description":"Proof plugins use feature-scoped overwrite: load existing file, purge entries matching current feature, append new entries, write merged result","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Pre-seed a proof file with feature B entries; run the real pytest proof plugin for feature A via subprocess; verify feature B entries are preserved and feature A entries are added","test_file":"dev/test_schema_proof_format.py","test_name":"test_feature_scoped_overwrite","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-6","description":"Proof files are committed to git \u2014 they are project records, not ephemeral build artifacts","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-6","description":"Grep `.gitignore` for `*.proofs-*.json`; verify no gitignore rule excludes proof files","test_file":"dev/test_schema_proof_format.py","test_name":"test_proof_files_not_gitignored","tier":"unit","status":"pass","audit":""}]},{"id":"schema_proof_format/RULE-7","description":"Manual proofs are stamped inline in the spec's `## Proof` section as `@manual(, , )`, not in proof JSON files","label":"required","source":"schema_proof_format","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-7","description":"Grep `references/formats/proofs_format.md` for `@manual`; verify manual stamp format is `@manual(, , )`","test_file":"dev/test_schema_proof_format.py","test_name":"test_manual_stamp_format_documented","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-1","description":"FORBIDDEN \u2014 No `eval()` or `exec()` calls in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-1","description":"Grep `scripts/` for `eval(` and `exec(` in `.py` files; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_eval_or_exec","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-2","description":"FORBIDDEN \u2014 No `subprocess` calls with `shell=True` in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-2","description":"Grep `scripts/**/*.py` for `shell\\s*=\\s*True`; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_shell_true","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-3","description":"FORBIDDEN \u2014 No `os.system()` calls in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-3","description":"Grep `scripts/**/*.py` for `os\\.system\\s*\\(`; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_os_system","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-4","description":"FORBIDDEN \u2014 No hardcoded credential assignments (password/secret/api_key/token literals) in scripts/","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-4","description":"Grep `scripts/` for assignment patterns `(password|secret|api_key|token)\\s*=\\s*[\"'][^\"']+[\"']` in `.py`, `.sh`, `.js` files, excluding test files and comments; verify zero matches","test_file":"dev/test_security.py","test_name":"test_no_hardcoded_credentials","tier":"unit","status":"pass","audit":""}]},{"id":"security_no_dangerous_patterns/RULE-5","description":"All subprocess calls use list-mode arguments (not string commands)","label":"required","source":"security_no_dangerous_patterns","is_deferred":false,"is_assumed":false,"status":"PASS","proofs":[{"id":"PROOF-5","description":"Grep `scripts/**/*.py` for `subprocess.run(` and `subprocess.call(`; verify each call's first argument is a list literal `[`, not a string","test_file":"dev/test_security.py","test_name":"test_subprocess_uses_list_args","tier":"unit","status":"pass","audit":""}]}],"audit":null}],"anchors_summary":{"total":5,"with_source":1,"global":0},"audit_summary":{"integrity":100,"strong":7,"weak":0,"hollow":0,"manual":0,"behavioral_total":7,"last_audit":"2026-06-24T20:22:16.500661+00:00","last_audit_relative":"20 hours ago","stale":false},"drift":null,"uncommitted":[]}; diff --git a/specs/audit/static_checks.receipt.json b/specs/audit/static_checks.receipt.json index c1a14699..5ec317e4 100644 --- a/specs/audit/static_checks.receipt.json +++ b/specs/audit/static_checks.receipt.json @@ -1,8 +1,8 @@ { "feature": "static_checks", - "vhash": "268ba07c", - "commit": "2259011e512dffa219356892dc1187651a58b6ba", - "timestamp": "2026-06-24T20:17:54.345616+00:00", + "vhash": "68530817", + "commit": "f56eaa37270ab3cc1599892b5ebe9f7732397fde", + "timestamp": "2026-06-25T16:49:50.864312+00:00", "rules": [ "RULE-1", "RULE-10", @@ -35,61 +35,6 @@ "RULE-8" ], "proofs": [ - { - "id": "PROOF-42", - "rule": "RULE-27", - "status": "pass" - }, - { - "id": "PROOF-43", - "rule": "RULE-28", - "status": "pass" - }, - { - "id": "PROOF-28", - "rule": "RULE-12", - "status": "pass" - }, - { - "id": "PROOF-29", - "rule": "RULE-17", - "status": "pass" - }, - { - "id": "PROOF-30", - "rule": "RULE-24", - "status": "pass" - }, - { - "id": "PROOF-31", - "rule": "RULE-24", - "status": "pass" - }, - { - "id": "PROOF-32", - "rule": "RULE-18", - "status": "pass" - }, - { - "id": "PROOF-38", - "rule": "RULE-22", - "status": "pass" - }, - { - "id": "PROOF-33", - "rule": "RULE-19", - "status": "pass" - }, - { - "id": "PROOF-51", - "rule": "RULE-29", - "status": "pass" - }, - { - "id": "PROOF-52", - "rule": "RULE-30", - "status": "pass" - }, { "id": "PROOF-1", "rule": "RULE-1", @@ -210,31 +155,6 @@ "rule": "RULE-12", "status": "pass" }, - { - "id": "PROOF-39", - "rule": "RULE-24", - "status": "pass" - }, - { - "id": "PROOF-39", - "rule": "RULE-24", - "status": "pass" - }, - { - "id": "PROOF-36", - "rule": "RULE-22", - "status": "pass" - }, - { - "id": "PROOF-37", - "rule": "RULE-23", - "status": "pass" - }, - { - "id": "PROOF-37", - "rule": "RULE-23", - "status": "pass" - }, { "id": "PROOF-13", "rule": "RULE-13", @@ -310,6 +230,11 @@ "rule": "RULE-15", "status": "pass" }, + { + "id": "PROOF-15", + "rule": "RULE-15", + "status": "pass" + }, { "id": "PROOF-16", "rule": "RULE-16", @@ -360,6 +285,36 @@ "rule": "RULE-16", "status": "pass" }, + { + "id": "PROOF-28", + "rule": "RULE-12", + "status": "pass" + }, + { + "id": "PROOF-29", + "rule": "RULE-17", + "status": "pass" + }, + { + "id": "PROOF-30", + "rule": "RULE-24", + "status": "pass" + }, + { + "id": "PROOF-31", + "rule": "RULE-24", + "status": "pass" + }, + { + "id": "PROOF-32", + "rule": "RULE-18", + "status": "pass" + }, + { + "id": "PROOF-33", + "rule": "RULE-19", + "status": "pass" + }, { "id": "PROOF-35", "rule": "RULE-21", @@ -385,6 +340,36 @@ "rule": "RULE-21", "status": "pass" }, + { + "id": "PROOF-36", + "rule": "RULE-22", + "status": "pass" + }, + { + "id": "PROOF-37", + "rule": "RULE-23", + "status": "pass" + }, + { + "id": "PROOF-37", + "rule": "RULE-23", + "status": "pass" + }, + { + "id": "PROOF-38", + "rule": "RULE-22", + "status": "pass" + }, + { + "id": "PROOF-39", + "rule": "RULE-24", + "status": "pass" + }, + { + "id": "PROOF-39", + "rule": "RULE-24", + "status": "pass" + }, { "id": "PROOF-40", "rule": "RULE-25", @@ -405,6 +390,16 @@ "rule": "RULE-26", "status": "pass" }, + { + "id": "PROOF-42", + "rule": "RULE-27", + "status": "pass" + }, + { + "id": "PROOF-43", + "rule": "RULE-28", + "status": "pass" + }, { "id": "PROOF-44", "rule": "RULE-29", @@ -441,8 +436,28 @@ "status": "pass" }, { - "id": "PROOF-15", - "rule": "RULE-15", + "id": "PROOF-51", + "rule": "RULE-29", + "status": "pass" + }, + { + "id": "PROOF-52", + "rule": "RULE-30", + "status": "pass" + }, + { + "id": "PROOF-53", + "rule": "RULE-29", + "status": "pass" + }, + { + "id": "PROOF-54", + "rule": "RULE-30", + "status": "pass" + }, + { + "id": "PROOF-55", + "rule": "RULE-31", "status": "pass" } ] From a5593d3fc5003804f7f67632504bfc17a8397adc Mon Sep 17 00:00:00 2001 From: rlabarca Date: Thu, 25 Jun 2026 13:03:40 -0400 Subject: [PATCH 4/5] fix(static_checks): resolve C# test_file from test_name when empty (audit reachability) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 ` 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) --- RELEASE_NOTES.md | 1 + dev/test_skill_specs.py | 12 ++++ dev/test_static_checks.py | 37 ++++++++++ docs/testing-workflow-guide.md | 2 + scripts/audit/static_checks.py | 71 +++++++++++++++++++ scripts/proof/pytest_purlin.py | 2 +- skills/audit/SKILL.md | 7 ++ specs/audit/static_checks.md | 2 + specs/audit/static_checks.proofs-unit.json | 9 +++ specs/audit/static_checks.proofs-windows.json | 4 +- specs/skills/skill_audit.md | 2 + specs/skills/skill_audit.proofs-unit.json | 9 +++ 12 files changed, 155 insertions(+), 3 deletions(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 03d2c416..0c77a1c9 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -13,6 +13,7 @@ - **Deterministic Pass-1 coverage for C#/.NET tests.** Pass 1 previously recognized only pytest/Jest/Shell markers, so xUnit/NUnit/MSTest tests carrying `[Trait("PurlinProof", "feature:PROOF-N:RULE-N:tier")]` produced zero proofs and the structural checker silently no-op'd on `.cs` files. The new `check_csharp` analyzer parses those trait markers, locates each `[Fact]`/`[Theory]` method body via a C#-aware brace/string scanner, and runs assert-true / no-assertion detection — recognizing xUnit `Assert.*`, NUnit `Assert.That`, MSTest `Assert.*`, FluentAssertions `.Should()`, and Playwright fluent assertions (`Expect(...)`/`Assertions.Expect(...)` chained to a `To*Async()` matcher) as assertions (`static_checks` RULE-31). A bare `Expect(x)` with no matcher chain is still flagged `no_assertions`. The dispatch was extracted into `analyze_test_file()` and now routes `.cs`. `references/supported_frameworks.md` documents the new coverage. To keep this broader structural acceptance from masking weak tests, the Pass-2 audit criteria gained a "presence/visibility-only assertion" WEAK check (`references/audit_criteria.md` → Criteria-Version 17). - **Portable interpreter guidance in `purlin:audit`.** `skills/audit/SKILL.md` documents a `python3 → python → py -3` fallback for invoking `static_checks.py`, since stock Windows does not put `python3` on PATH (`skill_audit` RULE-15). +- **`purlin:audit` reaches C# tests even when `test_file` is empty.** Under `dotnet test`, `TestCase.CodeFilePath` is often null (no surfaced source info), so the xUnit logger emits proof entries with a blank `test_file` — which previously blocked the audit skill from locating the `.cs` code to scan (Pass 1) or read (Pass 2). `static_checks.py` now exposes `--resolve-source `, which derives the declaring type from the fully-qualified `test_name` and locates its `.cs` file (skipping `bin`/`obj`), and `skills/audit/SKILL.md` uses it as a fallback (`static_checks` RULE-32, `skill_audit` RULE-16). Populating `test_file` natively still requires `RunConfiguration.CollectSourceInformation=true` with full PDBs — now documented. ### Testing diff --git a/dev/test_skill_specs.py b/dev/test_skill_specs.py index ccefd874..70361ee4 100644 --- a/dev/test_skill_specs.py +++ b/dev/test_skill_specs.py @@ -342,6 +342,18 @@ def test_documents_portable_interpreter_fallback(self): assert '`python`' in content or 'then `python`' in content or 'python`,' in content, \ "audit SKILL.md must document the 'python' interpreter fallback" + @pytest.mark.proof("skill_audit", "PROOF-16", "RULE-16") + def test_documents_empty_test_file_fallback(self): + """SKILL.md documents resolving an empty test_file from test_name via + --resolve-source, and names CollectSourceInformation as the native fix.""" + content = _read('audit') + assert '--resolve-source' in content, \ + "audit SKILL.md must document the --resolve-source fallback" + assert 'test_file' in content and 'test_name' in content, \ + "audit SKILL.md must explain resolving an empty test_file from test_name" + assert 'CollectSourceInformation' in content, \ + "audit SKILL.md must name CollectSourceInformation as the native way to populate test_file" + # ── skill_build ─────────────────────────────────────────────────────── diff --git a/dev/test_static_checks.py b/dev/test_static_checks.py index 613ff019..97d1d30e 100644 --- a/dev/test_static_checks.py +++ b/dev/test_static_checks.py @@ -28,11 +28,16 @@ compute_proof_hash, prune_audit_cache, read_audit_cache, + resolve_test_file_from_name, write_audit_cache, _read_rule_descriptions, load_criteria, ) +_STATIC_CHECKS_PY = os.path.join( + os.path.dirname(__file__), '..', 'scripts', 'audit', 'static_checks.py' +) + STATIC_CHECKS_PY = os.path.join( os.path.dirname(__file__), '..', 'scripts', 'audit', 'static_checks.py' ) @@ -1496,3 +1501,35 @@ def test_recognizes_playwright_fluent_assertions(self): f"bare Expect(x) should be no_assertions — {results[0]}" finally: os.unlink(bare) + + @pytest.mark.proof("static_checks", "PROOF-56", "RULE-32") + def test_resolve_test_file_from_name(self): + """When a proof's test_file is empty (C#/xUnit under dotnet test), the + source file is resolved from the fully-qualified test_name by locating the + declaring class — preferring the authored file over a bin/ build copy.""" + with tempfile.TemporaryDirectory() as root: + os.makedirs(os.path.join(root, 'tests')) + os.makedirs(os.path.join(root, 'bin')) + authored = os.path.join(root, 'tests', 'AuthLogicTests.cs') + with open(authored, 'w', encoding='utf-8') as f: + f.write('namespace Demo.Tests {\n public class AuthLogicTests {\n' + ' [Fact] public void Evaluate_NullRow() { }\n }\n}\n') + # A build-output copy that must be skipped. + with open(os.path.join(root, 'bin', 'AuthLogicTests.cs'), 'w', encoding='utf-8') as f: + f.write('public class AuthLogicTests { }\n') + + got = resolve_test_file_from_name( + 'Demo.Tests.AuthLogicTests.Evaluate_NullRow', root) + assert got == 'tests/AuthLogicTests.cs', f"resolved to {got!r}" + + # A test_name whose class is not declared anywhere resolves to ''. + assert resolve_test_file_from_name('Demo.Tests.MissingClass.X', root) == '' + + # The --resolve-source CLI prints JSON with the resolved test_file. + r = subprocess.run( + [sys.executable, _STATIC_CHECKS_PY, '--resolve-source', + 'Demo.Tests.AuthLogicTests.Evaluate_NullRow', '--project-root', root], + capture_output=True, text=True) + assert r.returncode == 0, r.stderr + payload = json.loads(r.stdout) + assert payload['test_file'] == 'tests/AuthLogicTests.cs', payload diff --git a/docs/testing-workflow-guide.md b/docs/testing-workflow-guide.md index 03159192..cc80ef29 100644 --- a/docs/testing-workflow-guide.md +++ b/docs/testing-workflow-guide.md @@ -69,6 +69,8 @@ public void ValidLoginReturns200() NUnit `[Category]`/`[Property]` and MSTest `[TestProperty]` surface the same way. Run with `dotnet test --logger purlin -- RunConfiguration.CollectSourceInformation=true`. Setup is manual — see [proofs_format.md](../references/formats/proofs_format.md) for wiring the `Purlin.TestLogger` assembly. +`CollectSourceInformation=true` (plus full PDBs) is what populates each proof's `test_file`; without surfaced source info `dotnet test` leaves it empty. `purlin:audit` handles that case anyway — it resolves the source from the fully-qualified `test_name` via `static_checks.py --resolve-source`, so C# Pass-1/Pass-2 works even when `test_file` is blank. + ### Shell (Bash) ```bash diff --git a/scripts/audit/static_checks.py b/scripts/audit/static_checks.py index 7793918e..fcedae17 100755 --- a/scripts/audit/static_checks.py +++ b/scripts/audit/static_checks.py @@ -795,6 +795,55 @@ def check_csharp(filepath, feature_name, rule_descs=None): return results +# Build-output / vendored directories that never contain authored test source. +_SOURCE_SCAN_SKIP_DIRS = {'bin', 'obj', 'node_modules', '.git', '.purlin', 'dist', 'build'} + + +def resolve_test_file_from_name(test_name, project_root, ext='.cs'): + """Resolve a test's source file from its fully-qualified test_name when the + proof's test_file is empty. + + Some runners cannot surface a source path: the xUnit logger emits + `MakeRelative(_root, tc.CodeFilePath ?? "")`, and under `dotnet test` + CodeFilePath is often null (no source info / full PDBs), so test_file is "". + The fully-qualified test_name (e.g. `Ns.Sub.AuthLogicTests.Evaluate_NullRow`) + still identifies the declaring type, so we derive the type (the segment before + the final `.method`) and search the project's `ext` files for its declaration. + + Returns the repo-relative POSIX path of the best match — preferring a file + whose stem equals the type name — or '' if no declaration is found. Skips + build-output and vendored directories. + """ + if not test_name: + return '' + parts = test_name.split('.') + if len(parts) < 2: + return '' + type_name = parts[-2] # final segment is the method; the one before is the type + if not type_name: + return '' + decl = re.compile(r'\b(?:class|struct|record|interface)\s+' + re.escape(type_name) + r'\b') + matches = [] + for root, dirs, files in os.walk(project_root): + dirs[:] = [d for d in dirs if d not in _SOURCE_SCAN_SKIP_DIRS] + for fn in files: + if not fn.endswith(ext): + continue + path = os.path.join(root, fn) + try: + with open(path, encoding='utf-8') as f: + if decl.search(f.read()): + matches.append(path) + except (OSError, UnicodeDecodeError): + continue + if not matches: + return '' + matches.sort() # determinism across platforms + best = next((m for m in matches + if os.path.splitext(os.path.basename(m))[0] == type_name), matches[0]) + return os.path.relpath(best, project_root).replace(os.sep, '/') + + def analyze_test_file(test_file, feature_name, rule_descs=None): """Dispatch a test file to the language checker matching its extension. @@ -1199,6 +1248,28 @@ def main(): print(load_criteria(project_root, extra_path=extra_path)) sys.exit(0) + # --resolve-source mode: locate a test's source file from its fully-qualified + # test_name when the proof's test_file is empty (e.g. C#/xUnit under dotnet + # test, where CodeFilePath is null). Prints JSON {test_name, test_file}. + if '--resolve-source' in sys.argv: + idx = sys.argv.index('--resolve-source') + test_name = sys.argv[idx + 1] if idx + 1 < len(sys.argv) else '' + project_root = os.getcwd() + if '--project-root' in sys.argv: + j = sys.argv.index('--project-root') + if j + 1 < len(sys.argv): + project_root = sys.argv[j + 1] + ext = '.cs' + if '--ext' in sys.argv: + k = sys.argv.index('--ext') + if k + 1 < len(sys.argv): + ext = sys.argv[k + 1] + print(json.dumps({ + "test_name": test_name, + "test_file": resolve_test_file_from_name(test_name, project_root, ext=ext), + })) + sys.exit(0) + # --compute-proof-hash mode: hash inputs for cache key if '--compute-proof-hash' in sys.argv: rule_text = '' diff --git a/scripts/proof/pytest_purlin.py b/scripts/proof/pytest_purlin.py index 4c5c9064..b20ccba6 100644 --- a/scripts/proof/pytest_purlin.py +++ b/scripts/proof/pytest_purlin.py @@ -49,7 +49,7 @@ def pytest_runtest_makereport(self, item, call): "feature": feature, "id": proof_id, "rule": rule_id, - "test_file": str(item.fspath.relto(item.config.rootdir)), + "test_file": str(item.fspath.relto(item.config.rootdir)).replace(os.sep, "/"), "test_name": item.name, "status": "pass" if call.excinfo is None else "fail", "tier": tier, diff --git a/skills/audit/SKILL.md b/skills/audit/SKILL.md index 670dfce0..3adbf303 100644 --- a/skills/audit/SKILL.md +++ b/skills/audit/SKILL.md @@ -128,6 +128,13 @@ For each feature being audited: 1. Read the spec's `## Proof` section — get every proof description. 2. For each proof, find the test file and test function from `.proofs-*.json` entries. + - **Empty `test_file` fallback:** some runners cannot supply a source path — the xUnit logger emits `MakeRelative(_root, tc.CodeFilePath ?? "")`, and under `dotnet test` `CodeFilePath` is often null (no source info), so C# proof entries arrive with `test_file: ""`. When `test_file` is empty, resolve it from the fully-qualified `test_name` before Pass 1 and Pass 2: + + ```bash + python3 ${CLAUDE_PLUGIN_ROOT}/scripts/audit/static_checks.py --resolve-source "" --project-root [--ext .cs] + ``` + + This derives the declaring type from `test_name` (the segment before the final `.method`) and searches the project's source files for its declaration, printing JSON `{test_name, test_file}`. Use the resolved `test_file` for both the Pass 1 command and the Pass-2 code read. To populate `test_file` natively instead, the consumer's test project must surface source info — run `dotnet test` with `RunConfiguration.CollectSourceInformation=true` and full PDBs. 3. Read the actual test code (the function body, not just the marker). 4. **Read fixture/setup code** — if the test references a class-scoped or module-scoped fixture (e.g. via `self` parameter or `@pytest.fixture(scope="class")`), include the fixture code in the prompt. This is critical for e2e tests where the "act" step is in the fixture. 5. Drop any proof already rated HOLLOW by Pass 1 or resolved by cache hit. diff --git a/specs/audit/static_checks.md b/specs/audit/static_checks.md index 0d7a0fb8..61382702 100644 --- a/specs/audit/static_checks.md +++ b/specs/audit/static_checks.md @@ -33,6 +33,7 @@ - RULE-28: check_js parses JS/TS test files with a brace-balancing tokenizer that (a) matches test titles containing apostrophes regardless of quote style, and (b) captures full test bodies containing nested braces — options objects, destructured parameters, type assertions — without truncating at the first inner `}` - RULE-29: static_checks.py imports and runs on Windows as well as POSIX — no Unix-only module is imported unconditionally at module load. `fcntl` is imported under `try/except ImportError` and a module-level flag (`_HAS_FCNTL`) selects between `fcntl.flock` and an `msvcrt.locking` fallback, so write_audit_cache acquires and releases its exclusive lock through that flag and the Windows lock path is exercisable without `fcntl`. The native lock path is verified for real on a `windows-latest` GitHub Actions runner (where `_HAS_FCNTL` is genuinely False and `msvcrt` is real), not only by host simulation - RULE-30: static_checks.py performs all text I/O as UTF-8 regardless of the OS locale: every text-mode `open()` specifies `encoding='utf-8'`, and stdout/stderr are reconfigured to UTF-8 at startup, so the tool's own UTF-8 specs/proofs/criteria/config/cache files both parse (read) and print (stdout) correctly even when the platform codec is cp1252/ASCII (e.g. on Windows). Without this, `--load-criteria` raises `UnicodeDecodeError` reading `audit_criteria.md` and `UnicodeEncodeError` printing its glyphs. This is verified for real under the native Windows console codec on a `windows-latest` GitHub Actions runner, not only by host locale simulation +- RULE-32: resolve_test_file_from_name locates a test's source file from its fully-qualified test_name (e.g. `Ns.Sub.AuthLogicTests.Evaluate_NullRow`) when the proof's test_file is empty — deriving the declaring type (the segment before the final `.method`) and searching the project's source files (skipping bin/obj/node_modules/.git and other build/vendored dirs) for its `class`/`struct`/`record`/`interface` declaration. Returns the repo-relative POSIX path of the best match (preferring a file whose stem equals the type name) or `''` if none. Exposed via the `--resolve-source [--project-root P] [--ext .cs]` CLI flag (printing JSON `{test_name, test_file}`), so purlin:audit can reach C# Pass-1/Pass-2 even when the xUnit logger emits an empty test_file (TestCase.CodeFilePath is null under `dotnet test` without source info) - RULE-31: For `.cs` test files, Pass 1 parses `[Trait("PurlinProof", "feature:PROOF-N:RULE-N:tier")]` traits, associates each with its `[Fact]`/`[Theory]` method body, and applies assert-true and no-assertion detection — returning the same JSON shape (proof_id, rule_id, test_name, status, reason) as check_python/check_js. xUnit `Assert.*`, NUnit `Assert.That`, MSTest `Assert.*`, FluentAssertions `.Should()`, and Playwright fluent assertions — `Expect(...)`/`Assertions.Expect(...)` chained to a `To*Async()` matcher (`ToBeVisibleAsync`, `ToHaveTextAsync`, `ToContainTextAsync`, …) — all count as assertions. A bare `Expect(x)` with no matcher chain is not an assertion (still flagged no_assertions) ## Proof @@ -90,3 +91,4 @@ - PROOF-55 (RULE-31): Run check_csharp on a `.cs` test whose only assertion is `await Assertions.Expect(page.Locator(...)).ToBeVisibleAsync();`; verify status=pass (recognized as an assertion, NOT flagged no_assertions). Also run on a body containing a bare `Expect(x)` with no `To*Async()` matcher and no Assert/Should/Verify; verify status=fail check=no_assertions - PROOF-53 (RULE-29): On a real `windows-latest` runner, assert `_HAS_FCNTL` is False (POSIX `fcntl` genuinely absent, real `msvcrt` in use), then call write_audit_cache with no fake/monkeypatch into a temp dir; read the cache back and verify the entries round-trip intact and the `audit_cache.json.lock` file was created adjacent to the cache — exercising the native `msvcrt.locking` path on real Windows @windows - PROOF-54 (RULE-30): On a real `windows-latest` runner under the native console codec (no `PYTHONUTF8`/`LC_ALL`/`LANG` overrides), invoke the real static_checks.py CLI `--load-criteria` as a subprocess over the tool's own non-ASCII `references/audit_criteria.md`; verify it exits 0 (no `UnicodeDecodeError` on read, no `UnicodeEncodeError` on stdout) and the printed criteria text contains a non-ASCII character @windows +- PROOF-56 (RULE-32): Create a temp project containing `tests/AuthLogicTests.cs` declaring `public class AuthLogicTests`; call resolve_test_file_from_name with `Demo.Tests.AuthLogicTests.Evaluate_NullRow` and the project root; verify it returns the relative POSIX path `tests/AuthLogicTests.cs`. Verify a `bin/`-buried copy is skipped (the authored file wins). Call with a test_name whose class is absent; verify `''`. Drive the real `--resolve-source` CLI as a subprocess and verify it prints JSON with the resolved `test_file` diff --git a/specs/audit/static_checks.proofs-unit.json b/specs/audit/static_checks.proofs-unit.json index 5186d275..68f78c55 100644 --- a/specs/audit/static_checks.proofs-unit.json +++ b/specs/audit/static_checks.proofs-unit.json @@ -630,6 +630,15 @@ "test_name": "test_recognizes_playwright_fluent_assertions", "status": "pass", "tier": "unit" + }, + { + "feature": "static_checks", + "id": "PROOF-56", + "rule": "RULE-32", + "test_file": "dev/test_static_checks.py", + "test_name": "test_resolve_test_file_from_name", + "status": "pass", + "tier": "unit" } ] } diff --git a/specs/audit/static_checks.proofs-windows.json b/specs/audit/static_checks.proofs-windows.json index 6e2dda20..530dd0dc 100644 --- a/specs/audit/static_checks.proofs-windows.json +++ b/specs/audit/static_checks.proofs-windows.json @@ -5,7 +5,7 @@ "feature": "static_checks", "id": "PROOF-53", "rule": "RULE-29", - "test_file": "dev\\test_windows_native.py", + "test_file": "dev/test_windows_native.py", "test_name": "test_real_msvcrt_lock_path", "status": "pass", "tier": "windows" @@ -14,7 +14,7 @@ "feature": "static_checks", "id": "PROOF-54", "rule": "RULE-30", - "test_file": "dev\\test_windows_native.py", + "test_file": "dev/test_windows_native.py", "test_name": "test_load_criteria_native_console", "status": "pass", "tier": "windows" diff --git a/specs/skills/skill_audit.md b/specs/skills/skill_audit.md index dff875fd..45002379 100644 --- a/specs/skills/skill_audit.md +++ b/specs/skills/skill_audit.md @@ -21,6 +21,7 @@ - RULE-13: Custom audit LLM command in config responds to ping, config stores audit_llm and audit_llm_name, and the two-pass audit completes - RULE-14: Criteria are loaded via the single `load_criteria()` function (`--load-criteria` CLI); built-in criteria always apply, additional team criteria are appended — never replaced - RULE-15: The skill documents a portable Python interpreter for invoking static_checks.py — falling back from `python3` to `python` to `py -3` — rather than assuming `python3` is always on PATH (it is not on stock Windows) +- RULE-16: When a proof's `test_file` is empty (e.g. C#/xUnit, where `dotnet test` leaves `TestCase.CodeFilePath` null), the skill resolves the source file from the fully-qualified `test_name` via `static_checks.py --resolve-source` before Pass 1 and Pass 2, so C# Pass-1/Pass-2 is reachable through purlin:audit without a populated path. The skill also documents that populating it natively requires source info (`RunConfiguration.CollectSourceInformation=true` with full PDBs) ## Proof @@ -39,3 +40,4 @@ - PROOF-13 (RULE-13): e2e: Write config with fake LLM command; verify ping, config fields, and two-pass audit @e2e - PROOF-14 (RULE-14): e2e: Create fake git repo with additional criteria; configure project; verify load_criteria returns built-in + additional with separator; verify Pass 1 still catches assert True; verify additional criteria reach fake LLM prompt @e2e - PROOF-15 (RULE-15): Grep `skills/audit/SKILL.md` for the interpreter-fallback guidance; verify it documents `python` and `py -3` as fallbacks for `python3` +- PROOF-16 (RULE-16): Grep `skills/audit/SKILL.md` for the empty-`test_file` fallback; verify it documents resolving the source from `test_name` via `--resolve-source` and mentions `CollectSourceInformation` as the native way to populate it diff --git a/specs/skills/skill_audit.proofs-unit.json b/specs/skills/skill_audit.proofs-unit.json index 099dbe71..f47c33a8 100644 --- a/specs/skills/skill_audit.proofs-unit.json +++ b/specs/skills/skill_audit.proofs-unit.json @@ -36,6 +36,15 @@ "test_name": "test_documents_portable_interpreter_fallback", "status": "pass", "tier": "unit" + }, + { + "feature": "skill_audit", + "id": "PROOF-16", + "rule": "RULE-16", + "test_file": "dev/test_skill_specs.py", + "test_name": "test_documents_empty_test_file_fallback", + "status": "pass", + "tier": "unit" } ] } From d161c46d1153c91142c41e245e58d6bb96a1300b Mon Sep 17 00:00:00 2001 From: rlabarca Date: Thu, 25 Jun 2026 13:04:02 -0400 Subject: [PATCH 5/5] verify: [Complete:static_checks,skill_audit] features=2/2 vhash=5cc1fc28 static_checks 30/30 (vhash=01ad747d), skill_audit 16/16 (vhash=f670fc7a). Adds RULE-32/PROOF-56 (C# --resolve-source) and RULE-16/PROOF-16 (audit fallback docs). Co-Authored-By: Claude Opus 4.8 (1M context) --- specs/audit/static_checks.receipt.json | 12 +++++++++--- specs/skills/skill_audit.receipt.json | 22 ++++++++++++++-------- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/specs/audit/static_checks.receipt.json b/specs/audit/static_checks.receipt.json index 5ec317e4..217135cd 100644 --- a/specs/audit/static_checks.receipt.json +++ b/specs/audit/static_checks.receipt.json @@ -1,8 +1,8 @@ { "feature": "static_checks", - "vhash": "68530817", - "commit": "f56eaa37270ab3cc1599892b5ebe9f7732397fde", - "timestamp": "2026-06-25T16:49:50.864312+00:00", + "vhash": "01ad747d", + "commit": "2fc2cfe5a898606714fa3ebb01008873bab9f831", + "timestamp": "2026-06-25T17:03:50.994334+00:00", "rules": [ "RULE-1", "RULE-10", @@ -28,6 +28,7 @@ "RULE-3", "RULE-30", "RULE-31", + "RULE-32", "RULE-4", "RULE-5", "RULE-6", @@ -459,6 +460,11 @@ "id": "PROOF-55", "rule": "RULE-31", "status": "pass" + }, + { + "id": "PROOF-56", + "rule": "RULE-32", + "status": "pass" } ] } diff --git a/specs/skills/skill_audit.receipt.json b/specs/skills/skill_audit.receipt.json index 01048e04..9a4e8570 100644 --- a/specs/skills/skill_audit.receipt.json +++ b/specs/skills/skill_audit.receipt.json @@ -1,8 +1,8 @@ { "feature": "skill_audit", - "vhash": "5bd9ab85", - "commit": "2259011e512dffa219356892dc1187651a58b6ba", - "timestamp": "2026-06-24T20:17:54.345616+00:00", + "vhash": "f670fc7a", + "commit": "2fc2cfe5a898606714fa3ebb01008873bab9f831", + "timestamp": "2026-06-25T17:03:50.994334+00:00", "rules": [ "RULE-1", "RULE-10", @@ -11,6 +11,7 @@ "RULE-13", "RULE-14", "RULE-15", + "RULE-16", "RULE-2", "RULE-3", "RULE-4", @@ -36,11 +37,6 @@ "rule": "RULE-3", "status": "pass" }, - { - "id": "PROOF-15", - "rule": "RULE-15", - "status": "pass" - }, { "id": "PROOF-4", "rule": "RULE-4", @@ -95,6 +91,16 @@ "id": "PROOF-14", "rule": "RULE-14", "status": "pass" + }, + { + "id": "PROOF-15", + "rule": "RULE-15", + "status": "pass" + }, + { + "id": "PROOF-16", + "rule": "RULE-16", + "status": "pass" } ] }