Skip to content
2 changes: 2 additions & 0 deletions .claude/agent-memory/atomic-executor/MEMORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
- [Blocked Bash command drops chained check-off](project_blocked_bash_command_silently_drops_chained_checkoff.md) — aborts the WHOLE
- [CSharpier chain-wrap defeats single-line search gates](project_csharpier_chain_wrap_defeats_singleline_search_gates.md) — zero-hit gate go
- [Verify line citations with numbered output](feedback_verify_line_citations_with_numbered_output.md) — never hand-count
- [Plan "observed while authoring" counts are undercounts](project_plan_authoring_time_token_counts_are_undercounts.md) — measure; 5 vs 7, 36 vs 37
- [Planner and executor observe different worktrees](project_planner_and_executor_observe_different_worktrees.md) — `git status` claims don't travel
- [Extract gate literals from the plan, never re-type](project_preflight_gate_literal_extract_from_plan_not_retype.md) — quoting drift
- [Tool layer collapses `\` in file content](project_tool_layer_collapses_double_backslash_in_file_content.md) — heredocs and Wri
Expand Down Expand Up @@ -107,6 +108,7 @@
## Coverage measurement
- [Exempt-forward extraction leaves call site uncovered](project_exempt_forward_extraction_leaves_call_site_uncovered.md) — >=90% gate unsat
- [Reproduce the baseline's counting method](project_coverage_delta_reproduce_baseline_counting_method.md) — deduped vs all-d
- [Async state machine emits no `<method>` element](project_async_state_machine_emits_no_method_element.md) — per-method union is empty; `.//line` double-counts
- [First-party coverage denominator (#197)](project_coverage_firstparty_denominator_method.md) — count per-`<line>`
- [dotnet-coverage denominator nondeterminism](project_dotnet_coverage_denominator_nondeterminism.md) — 47% vs 81% from
- [Koverage Cobertura post-processing shape](project_koverage_cobertura_postprocessing_shape.md) — passing run = proce
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
name: async-state-machine-emits-no-method-element
description: dotnet-coverage merges an async method's state-machine lines into the parent class's class-level <lines> list and emits NO named <method> element, so a plan's per-method coverage aggregation returns an empty union
metadata:
type: project
---

In this repo's Cobertura output, an `async` method produces **no `<method>` element at all** and **no separate state-machine `<class>` element**. Its lines are merged into the parent class's class-level `<lines>` list. A per-method aggregation written as "the union of `<method>` elements whose name is or contains `<MethodName>`" therefore returns an **empty set** — 0 covered of 0 valid, which is numeric but vacuous.

Two further traps in the same document:

- **`.//line` under a `<class>` double-counts.** The `<method>`-level `<line>` entries are a subset of the class-level `<lines>` list. Use `lines/line` (direct children) for a per-file figure. On `FileIO2.cs` the wrong idiom gave 189/223 and the right one 106/126.
- **A non-async method DOES get a `<method>` element.** After a fix converted a public overload from `async Task` to a plain `Task<bool>` forwarder, the method-element union went from 0 to 1 — so the same derivation silently changes shape across the change.

**Why:** the plan for #647 anticipated the *separate state-machine class* shape and wrote its per-method rule against it. The observed shape was merged-into-parent, so the stated rule was unsatisfiable and the AC's ">= 0.90 changed-method line rate" would have been unevaluable.

**How to apply:** when a plan defines a per-method coverage aggregation over `<method>` elements, measure the union *before* trusting it. If it is empty, substitute a span-based derivation — scan the source for the declaration, brace-match forward to the closing brace or terminating semicolon, and take the class-level `<line>` entries whose `number` falls in that span. Fix the substitute derivation in the baseline artifact and apply it identically at post-change, so both ends are one measurement. Record the substitution explicitly as a departure rather than reporting 0/0.

Related: [[project_coverage_delta_reproduce_baseline_counting_method]], [[project_koverage_cobertura_postprocessing_shape]], [[project_exempt_forward_extraction_leaves_call_site_uncovered]]
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
name: plan-authoring-time-token-counts-are-undercounts
description: a plan's "count observed while authoring" for a single-line token is an unmeasured claim; measure it yourself, because the authoring pass often counts only the occurrences that stand alone on their own line
metadata:
type: project
---

A plan task that says "the count observed while authoring this plan was N" is stating an authoring-time observation, not a binding acceptance value. Measure it. Two independent drifts in one execution of the #647 plan:

- `BASELINE_FILENAME_PARAM_COUNT` for the token `string filename,` in `FileIO2.cs`: plan said **5**, measured **7**. The authoring pass counted the five occurrences that sit alone on their own line in a wrapped parameter list and missed the two embedded inside single-line declarations (`DELETE_TextFile(string filename, string stagingPath)` and `WriteTextFile(string filename, ...)`).
- `BASELINE_IVT_COUNT` for `InternalsVisibleTo`: plan said **36**, measured **37**, because the branch was reconciled against `origin/main` after the plan was authored.

**Why:** the later gate is almost always phrased "equals the integer **recorded** in P<x>-T<y> plus 1", with a parenthetical naming the authoring-time number ("which is 6 when that recorded value is the 5 observed while authoring"). The *recorded* value governs and the parenthetical is a conditional that does not apply. Copying the plan's number into the artifact instead of measuring makes the artifact false AND can make the later gate unsatisfiable — the post-change count was 8, which satisfies "measured 7 + 1" but not "plan's 5 + 1 = 6".

**How to apply:** in any baseline task whose acceptance is "records an integer under this field name", run the count yourself and write the measured value. Record the divergence under a `DRIFT:` line naming both numbers and explaining the mechanism, and state which later gate reads the field and what it now requires. Never re-type a figure from plan prose into an evidence artifact.

Beware the counting-method mismatch too: `grep -c` counts matching *lines*, not matches, and driven through `xargs` it silently skips tracked paths containing a space (`UtilitiesCS/To Depricate/`). It reported 31 files where a PowerShell `[regex]::Matches` sweep found 35 files and 37 matches. Fix the counting method in the baseline artifact so the post-change gate reproduces it exactly.

Related: [[feedback_verify_line_citations_with_numbered_output]], [[project_preflight_gate_literal_extract_from_plan_not_retype]], [[project_preflight_selfderived_gate_thresholds_are_blind]]
2 changes: 2 additions & 0 deletions .claude/agent-memory/feature-review/MEMORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@
- [440-review-residuals](project_440-review-residuals.md) — PASS/0 blocking; a "corrected" defect-encoding test can be defect-NEUTRAL (check fail-before Totals); partial-class test files defeat filename FQN filters

- [644-review-residuals](project_644-review-residuals.md) — all 3 cycles PASS/0 blocking; AC-16 PARTIAL x3; rejecting the caller's `.claude/agent-memory` diff exclusion found the only new defect; SHA-256 beats mtime as compile proof
- [647-review-residuals](project_647-review-residuals.md) — PASS/0 blocking, 21/21 AC; AC20 PASS-with-deviation on in-spec provisions; footprint SHA-256 vs p6-t1 hash table binds all gates to head; evidence timestamps drifted +1h40m
- [measure every changed file, not just the AC-named one](feedback_measure-every-changed-file-not-just-the-ac-named-one.md) — per-file Cobertura aggregation exposed a call-site regression (77.05%, new lines uncovered) that no executor artifact reported

## Artifact hygiene
- [Never embed absolute host paths](../_shared_no_absolute_host_paths.md) — no `C:\Users\<account>\...`, bare account, or machine name in ANY artifact; use `<repo-root>` / `<user-profile>` / `<user>` / `<host>`. vstest names TRX `<account>_<HOST>_<ts>.trx` by default, so control `/ResultsDirectory:` + `LogFileName=` or rename before citing.
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
name: measure-every-changed-file-not-just-the-ac-named-one
description: Compute per-file coverage for EVERY changed production file from the Cobertura XML, not just the one the AC names; executor evidence routinely covers only the primary file and hides call-site regressions
metadata:
type: feedback
---

When auditing coverage, parse the Cobertura document yourself and compute line coverage for **every** changed production file in the branch diff, not only the file the acceptance criterion names.

**Why:** On #647 the spec's AC20 scoped its coverage obligation to `UtilitiesCS/To Depricate/FileIO2.cs` and the repository-wide figure, and every executor evidence artifact reported exactly those two. Measuring the two call-site files directly exposed what the evidence never showed: `QuickFiler/Controllers/QfcHomeController.Metrics.cs` sat at 77.05% (94/122) with the six lines the change *added* (the new `if (!metricsWritten) logger.Error(...)` block) reading `hits="0"`, a regression from roughly 80.18%. That is the single most valuable untested block in the whole diff — the one place a caller consumes the new failure signal through a testable seam — and no gate in the plan looked at it.

**How to apply:** After confirming the repo-wide root attributes, run a per-file aggregation over the `<class>`/`<line>` elements keyed by the `filename` attribute (a partial class spans several `<class>` elements, so aggregate by filename and take max hits per line number). Then, for each changed production file, select the diff's added line numbers and report their hit counts. Reconstruct the baseline per-file rate arithmetically from the diff when no baseline XML exists. Two follow-on judgments this enables:
- New uncovered lines are materially worse than pre-existing uncovered lines. [[677-review-residuals]] dispositioned a sub-80 modified file non-blocking *because* all uncovered lines were proven pre-existing; that leg does not hold when the change itself added them.
- Before escalating, check whether a covering test could assert anything. On #647 the uncovered lines were a `logger.Error` on a **static** log4net field, so a test would produce coverage without assertion power ([[501-review-residuals]]). Correct remedy is a promoted logging-seam issue, not a coverage-chasing test — which is why this stayed non-blocking.
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
name: 647-review-residuals
description: "Issue #647 (FileIO2 write-retry reports success) review outcome: PASS/0 blocking, 18 non-blocking; AC20 graded PASS-with-deviation on in-spec provisions; call-site coverage regression found only by measuring the non-primary changed files"
metadata:
type: project
---

Review 2026-08-31 (`policy-audit.2026-08-31T19-44.md` et al., head `8e773f35`, base `9b6aff2e` = `origin/main` tip = recomputed merge-base). **PASS, 0 blocking, 21/21 AC, GO.** 18 non-blocking (N-1..N-8 policy, C-1..C-7 code, F-1..F-3 feature).

**Why these adjudications are reusable:**

- **AC20 PASS-with-deviation.** Two literal AC sub-clauses failed: "every changed line ... exercised" (lines 74 and 101 of `FileIO2.cs` read `hits="0"`) and "repository-wide figure ... not lowered" (0.853296 -> 0.852919). Both are pre-authorized by provisions in `spec.md`'s **own Test Strategy section** — it pre-accepts an uncovered production-default line and states "no repository-wide figure is asserted as a blocking gate here. The blocking obligations are change-scoped." Graded PASS, checkbox left checked, deviations recorded in full under F-1 so a maintainer can overturn on the evidence. Distinguishes from a plan-only provision: the authorization is in the AC's own source document. Also: unchecking would have created a remediation loop with **no achievable remedy** (covering line 74 needs filesystem I/O, prohibited by UT4) — proportionality argued explicitly.
- **Provenance via SHA-256, not mtime.** The five footprint files' SHA-256 at head match, byte for byte, the ten post-format hashes in `evidence/qa-gates/p6-t1-format.md`. That single check binds the analyzer build, nullable build, 6899-test run and the Cobertura document to the reviewed tree without re-running anything. Cheapest strong provenance available; look for a `p*-format.md` hash table in every TaskMaster execution.
- **Evidence timestamps drifted ahead of the clock.** Recorded ISO timestamps run +1h15m to +1h40m ahead of file mtimes and commit dates (`p8-t4-commit.md` says 21:10; commit `8e773f35` is 19:32:56 -0400). Monotonic drift, not a timezone offset. Ordering preserved so sequencing arguments hold; absolute values are not citable as wall-clock facts. Cross-check evidence timestamps against `git log --date=iso` and `ls -la` on every review.
- **Toolchain restart taken in place.** First P6-T5 run exited 1 with 14 one-minute timeouts in `QuickFiler.Test` pump-host/dispatcher fixtures under `/EnableCodeCoverage`; byte-identical re-run passed 6899/6899. Accepted as substance-over-form because the footprint hashes prove steps 1-3 would have been no-ops. Same pattern as [[same-commit-differing-outcome-flake-check]]. That 14-test timeout class is pre-existing debt and will recur in future full-suite gates — worth promoting.

**Residuals owed:** (1) orchestrator still owes three MCP promotions recorded as *requests only* in `evidence/qa-gates/p8-t3-promotion-requests.md` (narrow retryable exception set; supported async text writer for the `To Depricate` migration; remove the method-local `Interlocked.Increment`); (2) C-3 logging seam on `QfcHomeController` so the new `if (!metricsWritten)` log becomes assertable; (3) C-7 pump-host timeout promotion; (4) C-1 `AppOlObjects.cs` is at 494/500 — extract the `TimedDiskWriter` construction before the next edit; (5) F-2 `spec.md` Test Strategy describes the accepted-uncovered line as being in the public forwarder, but the production defaults landed in the internal seam overload — correct at close-out.

**Post-647 same-session Cobertura baseline:** line 0.852919 (54835/64291), branch 0.792754 (13063/16478), 9 assemblies. Do not gate cross-session on these per [[csharp-coverage-constants-nondeterministic]].

Artifacts were mirrored into the session cwd worktree per [[review-worktree-differs-from-session-cwd-mirror-artifacts]]; the hook simulated `Ok=True` from **both** roots.
1 change: 1 addition & 0 deletions .claude/agent-memory/orchestrator/MEMORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@
- [Shared checkpoint: never read-modify-write](shared-checkpoint-read-modify-write-corrupts.md) - a sibling swaps the session-root file
- [External actor can merge your child PR mid-run](external-actor-can-merge-your-child-pr-midrun.md) — re-read PR state before the CI gate
- [Stale base anchor passes ancestry vacuously](stale-base-anchor-passes-ancestry-vacuously.md) — on a prep resume the pinned base stays an ancestor, so the check passes while diffs bill another issue's work to your plan
- [orchestrator-state.json is TRACKED in git](orchestrator-state-json-is-tracked-in-git.md) — .gitignore does not apply; writing your checkpoint pollutes the footprint. Fix with skip-worktree

## Artifact hygiene
- [Angle-bracket redaction breaks TRX XML](angle-bracket-redaction-breaks-trx-xml.md) — a `<placeholder>` in an XML attribute makes the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,21 @@ recorded a head SHA one commit behind and omitted all three review artifacts.

Two more defects confirmed in the same bundle: the summary reported "GitHub CLI (gh) is not installed" while `gh auth status` and `gh issue view` both worked in the same session; and the `author asserted` autoclose list contained `#AC-1`..`#AC-16` (acceptance-criterion IDs scraped as issue numbers) plus three issues that were not mine to close. Never emit `Closes` from that list. Note also that a child PR into an epic integration branch cannot auto-close anything — GitHub only honors closing keywords merging into the DEFAULT branch — so `Refs #NNN` is the correct form and the epic's final integration-to-main PR carries the close.

**It behaved correctly in a parallel-run child whose cwd WAS its own agent worktree (#647,
2026-08-31).** Session root was `TaskMaster-wt/2026-08-29T00-11`, my cwd was
`.claude/worktrees/agent-<id>`. `collect_pr_context` with `base: main` wrote `pr_context.summary.txt`
and `.appendix.txt` DIRECTLY into my worktree's `artifacts/`, freshly (mtime matched the call), with
`Head ref (resolved)` equal to my own `git rev-parse HEAD` and `Base ref (resolved)` equal to the
current `origin/main`. Both ownership assertions passed on the first call. `gh pr create --body-file`
from the worktree was then accepted, so the `enforce-pr-author-skill.ps1` hook read the WORKTREE copy,
not the session root — consistent with [[agent-worktree-hooks-resolve-to-agent-cwd]] and against
[[child-orchestrator-pr-hook-reads-session-root]], whose session-root behavior applies when the agent's
cwd is a DIFFERENT worktree from where the feature branch is checked out. Cheap insurance that cost
nothing: `cp -p` the summary, appendix, body and receipt to the session root as well, so either
resolution succeeds. Use `-p` so the preserved mtime keeps the receipt's `created_at` newer
([[pr-author-receipt-staleness-is-mtime-vs-created-at]]). The "GitHub CLI unavailable" line was
still false, as always.

**Independent confirmation and the simplest safe remedy (#445, 2026-08-22).** Same run, same wave:
`ok:true`, worktree paths returned, nothing written there, primary checkout freshly written. The
worktree copy I would have used was a decoy the feature-review subagent had hand-authored (quirk (a)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,16 @@ blocking, because every `EXIT_CODE: 0` acceptance downstream is unreachable with
fires at `BeforeTargets="PrepareForBuild"`, so msbuild hard-fails. Fix: `nuget restore TaskMaster.sln`
(what CI does at `.github/workflows/_build-analyzers.yml:45`). Restored content is ignored by
`.gitignore:191` (`**/[Pp]ackages/*`) — NOT by line 349, which is blank.
3. **A clean restore still breaks the build.** All 16 first-party `.csproj` files carry UNCONDITIONAL
3. **RESOLVED UPSTREAM 2026-08-31 (issue #647) — verify before acting on it.** The skew below is gone
on current main: every `.csproj` `<Analyzer Include>` and every `packages.config` now agree on
Meziantou.Analyzer **3.0.194** and Roslynator.Analyzers **5.0.0**. A plain
`pwsh -NoProfile -File scripts/vscode/Invoke-Restore.ps1` was sufficient and no hand-installed
analyzer package was needed. Re-measure with two greps (csproj analyzer paths vs `packages.config`
versions) before you plan around this item; steps 1 and 2 above still hold on a fresh worktree.
The historical description follows, because the failure mode can return with any Dependabot bump
that touches `packages.config` without touching the hand-authored Issue-#181 analyzer items.

All 16 first-party `.csproj` files carried UNCONDITIONAL
`<Analyzer Include>` items naming `Meziantou.Analyzer.3.0.156` and `Roslynator.Analyzers.4.16.0`,
while all 16 `packages.config` pin `3.0.174` and `4.16.1`. Dependabot `f8e22af7` updated only the
`Condition`-guarded `<Import>`/`<Error>` lines and `packages.config`, missing the hand-authored
Expand Down
Loading
Loading