Skip to content

Feature: quickfiler-coverage-ledger #432

Open

Description

@drmoisan
  • Work Mode: full-feature

Problem / Why

Epic #136 requires that every production .cs file compiled by QuickFiler/QuickFiler.csproj
reach at least 80% line coverage or sit on an explicitly ratified exemption ledger. Fifteen sibling
child features are blocked on three shared prerequisites that must be settled exactly once:

  1. The denominator is undefined. No authoritative per-file classification exists that says which
    of the 121 compiled files are testable and which are ratified-exempt. Without it, a child
    cannot state its own acceptance criteria, because it cannot tell whether a file such as
    ItemViewer.Designer.cs is inside its target set.
  2. The existing [ExcludeFromCodeCoverage] attributes are unratified. QuickFiler carries a
    population of these attributes that has never been judged against the irreducible-remainder
    standard. Per the epic manifest, an attribute sitting on a testable seam is a Blocking finding.
    Until each one has a recorded disposition, children would independently and inconsistently decide
    whether to remove or keep them, and would collide on shared configuration.
  3. There is no per-file coverage measurement. scripts/vscode/Invoke-MSTestWithCoverage.ps1
    emits a Cobertura report, but nothing derives per-file line-coverage percentages from it. Fifteen
    children each building their own reporting would produce fifteen inconsistent numbers and a
    capstone (F16) that cannot close.

Aggregate assembly coverage does not satisfy issue #136, which measures success per production file.

Proposed Behavior

Deliver the wave-0 enabler for epic #136. No QuickFiler production behavior changes; no file under
QuickFiler/ is modified by this child.

  1. Per-file classification ledger at
    docs/features/epics/quickfiler-per-file-coverage/coverage-ledger.md, with one row for every
    file listed as <Compile Include=...> in QuickFiler/QuickFiler.csproj. The compiled list is
    derived from the csproj itself, not from a directory walk: QuickFiler/Legacy/** and
    QuickFiler/Notes/** exist on disk but are not compiled and are out of scope. Each row records
    file path, line count, owning child feature (per the epic manifest's "Feature File Assignments"),
    classification (testable or ratified-exempt), and, for every exempt row, a rationale tested
    against the irreducible-remainder standard.

  2. Disposition of every existing [ExcludeFromCodeCoverage] attribute in the compiled surface.
    Each is treated as unratified until this ledger judges it, and receives either ratified with a
    rationale, or remove naming the owning child that must remove it and cover the file.

  3. Repeatable per-file coverage report harness: a PowerShell script that consumes the Cobertura
    output already produced by scripts/vscode/Invoke-MSTestWithCoverage.ps1 and emits per-file
    line-coverage percentages for the QuickFiler assembly, flagging any testable file below 80% and
    exiting non-zero when one is found. Pure parsing and threshold logic is separated from file and
    process I/O so the logic is unit-testable without I/O.

Only three grounds justify a ratified-exempt classification:

  • generated WinForms *.Designer.cs files and generated Properties/ files;
  • interface-only declarations with no executable behavior (the type-only/interface-only clause in
    .claude/rules/general-unit-test.md);
  • irreducible WinForms/COM wiring where no interface seam, injectable delegate, or adapter can
    isolate the logic, with the row stating specifically why.

Acceptance Criteria

  • The ledger accounts for every compiled file in QuickFiler.csproj with no unassigned or
    unclassified file.
  • Every ratified-exempt row carries a rationale meeting one of the three permitted grounds.
  • Every existing [ExcludeFromCodeCoverage] attribute in the compiled surface has a recorded
    disposition naming the owning child.
  • The harness produces a deterministic per-file line-coverage report for the QuickFiler assembly
    from Cobertura input and exits non-zero when a testable file is below 80%.
  • The harness's pure logic is unit-tested with Pester at the mirrored tests/ path, with no
    temporary files.
  • The full PowerShell toolchain (PoshQC format, PSScriptAnalyzer, Pester) passes in final form.

Constraints & Risks

  • No production C# change. No file under QuickFiler/ may be modified by this child.
  • No policy re-legislation. .claude/rules/**, CLAUDE.md, and .github/instructions/** must
    not be modified. The policy reconciliation between the CLAUDE.md § UT2 COM/VSTO/WinForms
    exemption and the .claude/rules/general-unit-test.md Coverage Exclusion Policy is already
    recorded in the epic manifest; this child implements it.
  • No change to repository-wide coverage thresholds.
  • Manifest figures require ground-truthing. The epic manifest's counts and its [X] markers are
    authoring-time estimates. Preliminary inspection indicates the manifest's "33
    [ExcludeFromCodeCoverage]" figure counts files containing the string - including comment-only
    mentions and files that are not compiled - rather than attribute usages in the compiled surface.
    The ledger must be built from verified counts and must record the reconciliation against the
    manifest.
  • Cobertura schema must be confirmed, not assumed. Element and attribute names must be read from
    the real output of the existing script rather than inferred.
  • PowerShell file-size limit. Invoke-MSTestWithCoverage.Helpers.ps1 is already 357 lines
    against a 500-line cap, so the new logic belongs in its own file rather than appended there.
  • Blocking risk. Fifteen sibling children and the capstone are blocked until this child merges,
    so an incomplete or non-deterministic ledger propagates to every downstream feature.

Test Conditions

  • Pure per-file coverage computation from a Cobertura document with mixed hit and miss lines.
  • Threshold evaluation: file above 80%, file exactly at 80%, file below 80%.
  • Classification filtering: a ratified-exempt file below 80% must not trigger a failure.
  • Zero-executable-line file (interface-only) must not be reported as a 0% failure.
  • Malformed or empty Cobertura input produces an explicit error, not a silent pass.
  • Exit-code contract: non-zero when any testable file is below threshold; zero otherwise.
  • Determinism: identical input yields byte-identical report output and stable row ordering.

Source

From: docs/features/potential/2026-08-07-quickfiler-coverage-ledger.md

Activity

  1. drmoisan commented on Aug 8, 2026

    @drmoisan
    OwnerAuthor

    Findings from child F11 (#454) preparation that bear on this child's harness and ledger

    Preparation research for issue #454 (epic #136, child F11 — QfcCollectionController, the epic's
    largest single file) produced four measurement findings that are directly load-bearing for F1's
    harness and ledger. Recording them here so F1 does not have to rediscover them.

    Full analysis:
    docs/features/active/2026-08-07-quickfiler-collection-controller-coverage-454/research/coverage-harness-contract.md

    1. Per-file attribution survives a partial split — verified empirically

    Cobertura emits one <class> element per (type, source file) pair. Checked against
    docs/features/active/2026-08-06-quickfiler-high-confidence-queue-init-stall-424/evidence/qa-gates/coverage-final.cobertura.xml:

    • QfcItemController (10 partials) produces 10 <class> elements with 10 distinct filename values,
      all sharing name="QuickFiler.Controllers.QfcItemController"
      (:22740, :23126, :23519, :24004, :24222, :24601, :25411, :25754, :26058, :26662).
    • QfcHomeController (3 partials) produces 3 elements (:21643, :22314, :22612).
    • <method> elements are split per file, not grouped. The instrumenter even splits a single method
      across files: .ctor appears under both QfcHomeController.cs (:21645) and
      QfcHomeController.Metrics.cs (:22316).

    This matters for F1 because several children (F2, F9, F11) create partials mid-wave. The ledger's
    per-file rows remain meaningful after those splits.

    2. A second harness defect, now filed as #478

    Beyond #441's descendant-axis double count, Merge-CoberturaClassesByFilename unions the
    class-level <lines> correctly (Invoke-MSTestWithCoverage.Helpers.ps1:217-268) but never merges
    the non-primary group members' <methods> subtrees, then recomputes the rate over .//lines/line.
    The emitted line-rate blends the correct union with a primary-only method subtree.

    Worked example — QfcHomeController.Iteration.cs: true per-file is 45/56 = 80.36%; the emitted
    attribute is 0.8625 = 69/80.

    Consequence for F1: the harness must recompute per-file rates by unioning ./lines/line
    (class-level only, max hits per @number) and must never read the <class> line-rate attribute.
    That is what epic.md's directive already says; #478 documents why. #441 and #478 should be fixed
    together, since a fix for either alone leaves the attribute wrong.

    3. Branch coverage is fully emitted

    branch, condition-coverage, and <conditions> are present per line, and branch-rate per
    class/package/report. The 75% branch gate is enforceable today. One caveat for the ledger schema: a
    file with no branching lines yields 0/0 and must report N/A, never 0% — the same trap the
    third interface-only / not-measured bucket exists to prevent on the line axis.

    4. A like-for-like trap in the epic's repository-wide baseline

    #424's committed baseline artifact is raw (no <sources> element, line-rate=0.7019) while its
    final artifact is post-processed (0.856453). Epic #136's "Coverage-Target Reconciliation"
    section imported the raw 70.19% as the authoritative repository-wide baseline.

    Since the epic's repository-wide criterion is "retain or improve", comparing a raw baseline against a
    post-processed post-change figure would show a large apparent improvement that is purely an artifact
    of the comparison. F1 should either re-state the baseline in post-processed terms or record
    explicitly which form each child must compare against.

    Related: .github/workflows/ci.yml:134-140 produces no Cobertura output at all, so the
    repository-wide figure has to be produced locally. CI also does not filter .claude/worktrees from
    its test-assembly enumeration, so a local run needs a stale-worktree pre-flight assertion to avoid
    picking up agent-worktree builds.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    featureFeature request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions