Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
961479b
docs(epic): add quickfiler-suite-determinism-foundation manifest
drmoisan Aug 21, 2026
025b350
docs(epic): record stale doc references, child hard constraints, late…
drmoisan Aug 21, 2026
0a5f571
docs(prep): preserve incomplete preparation artifacts for #511
drmoisan Aug 21, 2026
64f8ca8
docs(prep): preserve incomplete preparation artifacts for #445
drmoisan Aug 21, 2026
1b2db4f
docs(prep): preserve incomplete preparation artifacts for #491
drmoisan Aug 21, 2026
8092f04
docs(prep): preserve incomplete preparation artifacts for #449
drmoisan Aug 21, 2026
0ccbe2a
docs(epic): back-fill verified feature_folder names from promotion re…
drmoisan Aug 21, 2026
6f94659
Merge main (790bd39e) into epic integration branch
drmoisan Aug 21, 2026
5711109
Merge epic integration branch (carrying main 790bd39e) into bug/winfo…
drmoisan Aug 21, 2026
5c82b6c
Merge epic integration branch (carrying main 790bd39e) into bug/quick…
drmoisan Aug 21, 2026
3dc2119
Merge epic integration branch (carrying main 790bd39e) into bug/quick…
drmoisan Aug 21, 2026
cd2f078
Merge epic integration branch (carrying main 790bd39e) into bug/quick…
drmoisan Aug 21, 2026
241cad3
docs(quickfiler-test): clear preflight on the Form1 removal plan (#491)
drmoisan Aug 22, 2026
10de2fa
docs(epic): correct the #491 complexity row and record two preparatio…
drmoisan Aug 22, 2026
6e2be9f
Fan in prepared feature for #491 (quickfiler-test-form1-live-form)
drmoisan Aug 22, 2026
0dc8c14
docs(445): clear atomic-executor preflight for keyboard-action contra…
drmoisan Aug 22, 2026
b8004bd
Fan in prepared feature for #445 (quickfiler-keyboard-action-contract…
drmoisan Aug 22, 2026
eae0ffa
docs(plan): clear atomic-executor preflight for issue #449
drmoisan Aug 22, 2026
ea2392a
Fan in prepared feature for #449 (quickfiler-explorer-controller-late…
drmoisan Aug 22, 2026
846d37d
docs(plan): clear atomic-executor preflight for #511/#571 determinism…
drmoisan Aug 22, 2026
ccdf278
Fan in prepared feature for #511 and #571 (winformspumphost-suite-det…
drmoisan Aug 22, 2026
44feaeb
docs(epic): emit epic-orchestrator kickoff artifact
drmoisan Aug 22, 2026
8e71d6e
chore(agent-memory): record preflight findings from issue #445 prepar…
drmoisan Aug 22, 2026
f80786c
chore(agent-memory): record orchestrator learnings from #445 preflight
drmoisan Aug 22, 2026
8e1b3fe
docs(memory): record two orchestrator corrections from the #449 prefl…
drmoisan Aug 22, 2026
3f6acdf
chore(agent-memory): record planner traps found during #511/#571 pref…
drmoisan Aug 22, 2026
7e25b47
chore(agent-memory): record C# agent-worktree bootstrap requirements
drmoisan Aug 22, 2026
c551eab
docs(epic): seed epic-status.md projection at wave-0 kickoff
drmoisan Aug 22, 2026
39f6797
docs(epic): record wave-0 worktree_created transition
drmoisan Aug 22, 2026
c7557c3
fix(quickfiler-test): remove dead Form1 from the test assembly and gu…
drmoisan Aug 22, 2026
5cec657
docs(quickfiler-test-form1-live-form-491): record P5-T5 scope-lock ve…
drmoisan Aug 22, 2026
3f2fb8d
docs(quickfiler-test-form1-live-form-491): check off P5-T2 checklist-…
drmoisan Aug 22, 2026
92d444c
fix(quickfiler-test): remove dead QfcFormViewerDerived nested class b…
drmoisan Aug 22, 2026
bec8339
docs(quickfiler-test-form1-live-form-491): check off P5-T1/P5-T2 in r…
drmoisan Aug 22, 2026
1292b4c
fix(quickfiler): repair three keyboard-action contract defects
drmoisan Aug 22, 2026
03b5011
fix(quickfiler): repair three QfcExplorerController latent defects (#…
drmoisan Aug 22, 2026
3512f95
docs(quickfiler): check off AC-1..AC-16 and add final QA evidence (#449)
drmoisan Aug 22, 2026
d341542
docs(quickfiler): add feature-review audit artifacts for #449
drmoisan Aug 22, 2026
f912640
docs(445): record feature-review audit artifacts
drmoisan Aug 22, 2026
76cbaa0
docs(quickfiler-test-form1-live-form-491): add feature-review audit a…
drmoisan Aug 22, 2026
0f3a3a2
docs(potential): promote UtilitiesCS.Test/Form1 live-form defect as i…
drmoisan Aug 22, 2026
5d1c207
Merge pull request #585 from drmoisan/bug/quickfiler-explorer-control…
drmoisan Aug 22, 2026
577270d
Merge pull request #587 from drmoisan/bug/quickfiler-keyboard-action-…
drmoisan Aug 22, 2026
b8b4a9e
Merge pull request #588 from drmoisan/bug/quickfiler-test-form1-live-…
drmoisan Aug 22, 2026
17a11eb
chore(agent-memory): record collect_pr_context cross-child contaminat…
drmoisan Aug 22, 2026
4108c91
Merge pull request #590 from drmoisan/bug/quickfiler-explorer-control…
drmoisan Aug 22, 2026
f91cef4
chore(agent-memory): record completion-gate receipt shape and collect…
drmoisan Aug 22, 2026
14a73be
docs(epic): record three wave-0 merges and carried findings
drmoisan Aug 22, 2026
7db3e8e
Merge remote-tracking branch 'origin/epic/quickfiler-suite-determinis…
drmoisan Aug 22, 2026
0bdad30
Merge pull request #591 from drmoisan/bug/quickfiler-keyboard-action-…
drmoisan Aug 22, 2026
68e33a2
docs(epic): record #445 follow-up PR #591 and the analyzer-skew mecha…
drmoisan Aug 22, 2026
e7b4824
docs(epic): record the #511 halt and the descope decision
drmoisan Aug 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 50 additions & 47 deletions .claude/agent-memory/atomic-executor/MEMORY.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
name: concurrent-dotnet-coverage-deadlock-and-doccomment-retention-gate
description: Two #445 execution hazards - a sibling worktree's dotnet-coverage collect deadlocks yours (diagnose by CPU delta, kill only your own chain), and an XML doc comment quoting a code literal breaks a retention count gate
metadata:
type: project
---

**1. A concurrent `dotnet-coverage collect` from a sibling agent worktree deadlocks yours.**

Symptom: `dotnet-coverage collect ... -- vstest.console.exe @assemblies` produces no output file and never returns. The `testhost.exe` accrued 0.02 s of CPU across a 60-second sample (32.046875 to 32.0625) — a hang, not slow progress.

Diagnose, do not guess:
```powershell
# progress test: sample CPU twice, 60 s apart. Delta ~0 means deadlocked.
# ownership test: read the command line, which carries the absolute worktree path.
Get-CimInstance Win32_Process | Where-Object { $_.Name -eq 'testhost.exe' -or $_.Name -eq 'vstest.console.exe' } |
ForEach-Object { "PID=$($_.ProcessId) PPID=$($_.ParentProcessId)"; $_.CommandLine }
```
`Get-CimInstance Win32_Process -Filter "Name='testhost.exe'"` throws `Invalid query` from bash; use `Where-Object` instead.

On #445 the contending workload was a full nine-assembly instrumented run from `agent-a28821f6e56934fc7` (issue #491). Two simultaneous full-solution instrumentation sessions on one machine is the cause.

Remedy: kill **only your own** chain — `dotnet-coverage` -> `vstest.console` -> `testhost` plus the parent `pwsh` runner (walk `ParentProcessId`; killing the runner matters, see [[project_timedout_mstest_leaves_detached_runner]]). Leave the sibling's processes running. Wait for the machine to clear, then re-run the **unaltered** command. It then completed in normal time with 6441/6441 passing.

**Never** respond by adding a sleep/retry/timeout to a test or by changing the command. The hang is machine contention, not a regression, and the plan forbids stabilising a test with timing hacks.

**2. An XML doc comment that quotes a code literal breaks a retention count gate.**

A plan can pin an out-of-scope expression with a retention gate, e.g. `Key.Substring(other.Length - 1, 1)` must stay at count 1 to prove a deferred defect was not "helpfully" fixed. Writing an XML doc comment that quotes that expression as `<c>Key.Substring(other.Length - 1, 1)</c>` raises the count to **2** and fails the gate — even though no code changed.

Fix by describing the expression instead of reproducing it ("branch 1's substring offset expression"). Re-run the count to confirm it returns to baseline.

**Why:** `git grep -F` is text-based and cannot distinguish a code occurrence from a comment occurrence. The gate is doing its job; the doc comment is the defect.

**How to apply:** After writing any comment or doc block on a file that carries retention gates, re-run every count gate for that file before checking the task off. Prefer prose descriptions over verbatim literals in comments on gated files. Related: [[project_multipattern_gate_shared_qualifier_detachment]], [[project_sibling_worktree_shared_tooling_hazard]].
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
name: count-idiom-pitfalls-csharpier-and-measureobject
description: Two measurement idioms that silently report the wrong number in plan gates - CSharpier's "Formatted N files" is a processed count not a rewrite count, and Measure-Object -Line omits blank lines
metadata:
type: project
---

Two counting idioms routinely used in atomic-plan acceptance gates report a different quantity than the gate assumes. Both were hit on #445 and both would have produced a wrong verdict if taken at face value.

**1. `csharpier format` prints a PROCESSED count, not a REWRITE count.**
`dotnet tool run csharpier format <5 files>` prints `Formatted 5 files in 2054ms.` even when it rewrote **zero** of them. A plan task that says "record how many of the five files the formatter rewrote" and that triggers a phase restart on a non-zero count will restart forever if it reads that line as the answer.
Measure the rewrite count directly:
```powershell
$before = @{}; foreach ($f in $files) { $before[$f] = (Get-FileHash -LiteralPath $f -Algorithm SHA256).Hash }
& $dotnet tool run csharpier format @files
foreach ($f in $files) { if ((Get-FileHash -LiteralPath $f -Algorithm SHA256).Hash -ne $before[$f]) { $rewritten++ } }
```
`csharpier check .` remains the right read-only verdict (`Checked 1517 files in ...` plus exit 0 means zero need formatting; non-conforming files are printed one per line before the summary).

**2. `Measure-Object -Line` does NOT count blank lines.**
`(Get-Content -LiteralPath $f | Measure-Object -Line).Lines` returned 86 / 84 / 84 / 17 / 143 for files whose true physical line counts are 95 / 99 / 99 / 18 / 168. Every figure is understated, by exactly the blank-line count. A file-size audit against a 500-line cap using this idiom under-reports and can pass a file that actually violates the cap.
Use `(Get-Content -LiteralPath $f).Count`, and cross-check with `wc -l`. The two agreed exactly on all five files.

Critically: baseline and final MUST use the same idiom, or the before/after comparison is incommensurable. The Uniform Count Idiom for `git grep` (`(git grep -n -F 'TOKEN' -- 'PATHSPEC' | Measure-Object -Line).Lines`) is fine because `git grep` never emits a blank line.

**Why:** Both idioms look authoritative and produce a plausible number, so neither failure announces itself. The CSharpier one caused a false restart signal; the Measure-Object one silently understated every file-size baseline.

**How to apply:** When a plan gate asks "how many files did the formatter change", hash before and after. When a plan gate asks for a file line count, use `(Get-Content).Count` and record the counting method in the baseline artifact so the final-QC task reproduces it. Related: [[project_csharpier_pipefiles_nonenforcing_gate]], [[feedback_verify_line_citations_with_numbered_output]].
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
---
name: preflight-absolute-zero-gate-on-sibling-owned-assembly
description: In an epic, a child plan that demands "Failed 0" on a whole test assembly can be unsatisfiable when a concurrent sibling child owns known intermittent failures in that same assembly; also, preparation-mode plans that pin an absolute WS path.
metadata:
type: project
---

Two preflight defect classes found on the #445 keyboard-action-contract plan
(`docs/features/active/2026-08-07-quickfiler-keyboard-action-contract-defects-445/plan.<TS>.md`),
both invisible unless you cross-read the epic manifest.

**1. Absolute-zero test gate on an assembly a sibling child owns.**
The plan required `QuickFiler.Test` `Failed 0` unconditionally in two tasks, while applying a
baseline-subset tolerance repo-wide. The epic manifest
(`docs/features/epics/quickfiler-suite-determinism-foundation/epic.md`) documents two
*intermittently failing* tests in that same assembly
(`QuickFiler.Test/Controllers/QfcItemController.InitializationTests.Part3.cs`) owned by a different
wave-0 child (#511/#571) that is NOT guaranteed to have merged first.

**Why:** the gate can fail for a reason the executing child cannot fix, and blocking is forbidden
after `[P0-T1]`, so the executor is deadlocked or pressured to weaken a test.

**How to apply:** when preflighting an epic child, list every assembly the child asserts an absolute
pass/fail count on, then grep the epic manifest for known-failing tests in those assemblies. Where a
sibling owns a failure, the delta is to extend the plan's own baseline-subset rule to that assembly
while keeping absolute zero on the test classes the child actually owns. See
[[project-511-is-a-testhost-crash-not-n-failing-tests]] and
[[winformspumphost-tests-load-flaky]] for why that assembly is unreliable.

**2. Preparation-mode plan pinning an absolute workspace root.**
The plan's `## Resolved Environment` pinned `WS` to the *planning* worktree and labelled the block
"verified; use these, do not re-derive". That worktree was already deleted, and preparation-mode
plans execute later in a third, not-yet-created worktree, so no absolute literal is ever correct.

**Why:** the "do not re-derive" instruction forbids the one micro-action that would repair it, which
is what turns a stale path from self-healing into blocking.

**How to apply:** on any preparation-mode preflight, diff the plan's pinned workspace root against
`git worktree list` first. The delta is to replace the literal with
`git rev-parse --show-toplevel` resolved at execution time. Repo-local tool paths that live in the
PRIMARY clone (for example `<primary>/.dotnet-sdk/dotnet.exe`) are NOT the same defect: they resolve
from any worktree and stay valid.

**3. Iteration-2 resolution shape (verified 2026-08-21).** Both defects cleared on re-preflight. Two
things were worth proving rather than assuming:

- A "failing set is a SUBSET of the baseline failing set" condition is by itself sufficient to catch
a genuine new regression anywhere in the assembly, including outside the classes the child owns:
a new failure is absent from the baseline set, so the subset test fails. Adding "and no failure in
<owned classes>" only converts the owned classes to absolute zero; it does not rescue the rest.
- Unpinning `EXIT_CODE:` on a vstest task that tolerates a pre-existing failure is coherent and NOT
a weakening, provided the downstream AC check-off still conjoins "all stage exit codes are 0".
vstest exits non-zero on any failure, so that conjunct silently re-imposes Failed 0 for the
check-off while the escape clause records the gap.

**Namespace gotcha:** `QuickFiler.Test`'s test classes live in namespace `QuickFiler.Controllers.Tests`,
so a fully-qualified test name from that assembly does NOT contain the string `QuickFiler.Test`. Any
gate phrased as "the `QuickFiler.Test` portion of the baseline failing set" cannot be evaluated by
FQN string match; partition by run scope instead.
5 changes: 5 additions & 0 deletions .claude/agent-memory/atomic-planner/MEMORY.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
# Atomic Planner Memory Index

- [Agent worktrees need SDK + NuGet + analyzer-backfill bootstrap](agent-worktrees-need-sdk-and-nuget-bootstrap.md) — no `.dotnet-sdk`, no `packages/`, and a clean restore still misses the skewed analyzer versions (CS0006, not a warning); three Phase 0 tasks
- [/Logger:trx needs /ResultsDirectory](trx-needs-resultsdirectory.md) — TRX lands in `TestResults\` relative to cwd; TRX-existence-under-evidence acceptance is unsatisfiable without it, and the clean-tree gate won't catch it
- [Per-task TRX subdirectory](trx-needs-resultsdirectory.md) — a shared `/ResultsDirectory:` makes "ten distinct TRX files" ambiguous once `[expect-fail]` runs deposit earlier TRX there; give each task a `p#-t#` segment
- [Terminal-phase planner traps](terminal-phase-planner-traps.md) — sweep the last phase for an unowned "a follow-up issue should carry it", artifacts written after the clean-tree commit task, and a false "clarification against the spec's wording"
- [#553 CI parallel-split plan seams](project_553_ci_parallel_split_plan_seams.md) — workflow-only scope: no C# toolchain; Phase 0 snapshot for byte-identity; ruleset PUT + gh pr create orchestrator-gated; no jq (ConvertTo-Json -Depth 20); pathspec anchoring; BRANCH/SCRATCH conventions

- [#445 keyboard-action plan seams](project_445_keyboard_action_plan_seams.md) — prepared-in-one-worktree plans must resolve WS at execution time; scope epic-child pass/fail gates to owned test classes (sibling-owned flaky tests share the assembly); retention gates need their own justification
- [Dead-code removal vs coverage exclusion](project_deadcode_removal_vs_coverage_exclusion.md) — coverage gate blocked by unreachable dead prod code → plan removal (shrink denominator), never exclusion/carve-out/forced-rethrow
- [Coverage gate on CLR-invoked private members](coverage-gate-clr-invoked-private-members.md) — never gate AssemblyResolve-style private members at >=90%; split newly-added vs changed per the AC's own wording
- [Nullable context mismatch: prod vs test](project_nullable_context_mismatch_prod_vs_test.md) — check `#nullable enable` in the prod file AND missing `<LangVersion>` (C# 7.3) in the test csproj; adding `<LangVersion>latest</LangVersion>` is never "one property"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
---
name: agent-worktrees-need-sdk-and-nuget-bootstrap
description: A fresh agent worktree has neither .dotnet-sdk nor packages/, and a clean nuget restore still leaves the analyzer version skew unresolved, so a C# plan needs THREE explicit Phase 0 bootstrap tasks
metadata:
type: project
---

A C# plan executing inside a `.claude/worktrees/<agent-id>` worktree needs THREE Phase 0 bootstrap tasks
before the first `dotnet tool restore` and before the first `msbuild`, in this order:

1. **Provision `.dotnet-sdk`.** `global.json` pins `sdk.version 8.0.205` with `rollForward: latestFeature`
and `paths: [".dotnet-sdk", "$host$"]`. A fresh worktree has no `.dotnet-sdk`, and a host that only
carries a 10.x SDK cannot satisfy `8.0.205` under `latestFeature`, so `dotnet --version` from the
worktree root prints the `global.json` `errorMessage` instead of a version. Remedy:
`pwsh -NoProfile -File .\scripts\vscode\Install-RepoDotNetSdk.ps1`, or mirror the populated
`.dotnet-sdk` tree from the main checkout. Falsifiable acceptance: `dotnet --version` prints
`8.0.205` AND `dotnet --list-sdks` includes a path ending `.dotnet-sdk\sdk`.
2. **`nuget restore TaskMaster.sln`.** A fresh worktree has no `packages/`. Every project declares
`<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">` whose `<Error>`
fires before compilation when the tree is missing (e.g. `QuickFiler.Test.csproj:452-466`), and every
`Reference` `HintPath` under `..\packages\` is unresolvable. CI does not hit this because
`.github/workflows/_build-analyzers.yml:45` runs `nuget restore` explicitly.
3. **Back-fill `Meziantou.Analyzer 3.0.156` and `Roslynator.Analyzers 4.16.0`.** Step 2 alone is NOT
enough. All 16 first-party `.csproj` files carry UNCONDITIONAL `<Analyzer Include>` items naming
`..\packages\Meziantou.Analyzer.3.0.156\...` and four `..\packages\Roslynator.Analyzers.4.16.0\...`
DLLs (`QuickFiler.Test.csproj:474-478`), while all 16 `packages.config` pin `3.0.174` and `4.16.1`.
Dependabot commit `f8e22af7` bumped only the NuGet-generated `Condition`-guarded `Import`/`Error`
lines and `packages.config`; the hand-authored Issue #181 `Analyzer` items were never realigned.
A missing `Analyzer` path is `error CS0006`, NOT a warning — the compile FAILS. Remedy:
`nuget install <id> -Version <v> -OutputDirectory packages`, or copy the folders from the main
checkout. Both versions exist there and are verifiable with a glob before you write the claim.

**Do not conclude from green CI that the compile tolerates the skew.**
`_build-analyzers.yml:38` caches `path: packages` with a PREFIX `restore-keys` fallback (lines 40-41).
The bump guarantees an exact-key miss (the key hashes `**/packages.config`), so the fallback restores a
pre-bump tree still holding the old versions and line 45 only adds the new ones beside them. The main
checkout shows the same accumulation (Meziantou `.101/.123/.156/.174`). Green CI is lingering folders.

None of the three dirties the tree: `.gitignore:350` is `.dotnet*/` and `.gitignore:191` is `**/[Pp]ackages/*`.
Note `.gitignore:191` is the packages pattern — NOT line 349, which is blank. Verify a `.gitignore`
line citation before writing it into an acceptance clause.

**Why:** Preflight on the #511 plan returned REVISIONS REQUIRED twice: iteration 1 for the missing SDK
and `nuget restore` steps, iteration 2 for the missing analyzer back-fill. In both cases every
`EXIT_CODE: 0` acceptance on a dotnet/msbuild task was unreachable by environment.

**How to apply:** Add all three as their own Phase 0 tasks with their own binary acceptance — never fold
them into the head of an existing task, which puts two independent outcomes under one task ID. See
[[project_csharp_phase0_toolchain_bootstrap]] and [[one-ac-per-checkoff-task]].
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
---
name: project-445-keyboard-action-plan-seams
description: "#445 epic-child plan revision: prepared-in-one-worktree/executed-in-another forbids absolute WS literals; assembly-wide `Failed 0` gates collide with sibling-owned intermittent tests; retention gates are not vacuous"
metadata:
type: project
---

Three seams surfaced when `atomic-executor` preflight rejected the #445 plan (epic
`quickfiler-suite-determinism-foundation`, wave-0 child).

**1. A prepared plan must not pin an absolute workspace root.** Preparation-mode epic children are
planned in one agent worktree and executed later by `epic-orchestrator` in a worktree that does not
exist yet. Any `C:\...\.claude\worktrees\agent-<id>` literal in a "Resolved Environment (verified;
use these, do not re-derive)" block is wrong on arrival — the planning worktree is gone by then.
Write `WS` as "resolve at execution time via `git rev-parse --show-toplevel`" and keep only the
genuinely worktree-independent literals (repo-local `.dotnet-sdk\dotnet.exe` in the PRIMARY clone,
VS-installed msbuild/vstest paths) under the do-not-re-derive header.

**Why:** the "do not re-derive" instruction forbids the one action that repairs a dead path.
**How to apply:** in any epic-child or preparation-mode plan, audit every absolute path for
worktree-dependence before writing the environment block. See [[worktree-root-breaks-dotclaude-exclusion]]
for the related trap that `WS` itself sits under `.claude\worktrees\`.

**2. An epic child must not gate on an assembly-wide `Failed 0`.** `QuickFiler.Test` contains two
intermittently failing pump tests owned by sibling child #511/#571 and a `Form1.cs` defect owned by
#491, all in the same wave with an empty dependency graph, so the sibling fixes are not guaranteed
merged when this child runs. The coverage runsettings (`scripts/vscode/TaskMaster.cli.runsettings`)
sets `<Workers>0</Workers>` with `<Scope>ClassLevel</Scope>` — full CPU parallelism, the exact load
condition those tests fail under. Scope the gate to the test CLASSES this child owns
(subset-of-baseline PLUS "no failing test in <owned classes>"), and drop the `EXIT_CODE: 0` clause
on the run, since vstest exits non-zero on any failure.

**Why:** the executor's only in-task recourses for an unfixable sibling failure are to weaken a
sibling's test or record a false pass, both prohibited.
**How to apply:** when planning any child of a multi-child epic, list which test classes in the
shared assembly the child owns and phrase every pass/fail gate against that set only. Thread the
same class-scoped condition through the AC check-off task's escape clause, or the discharge is
unreachable — see [[thread-granted-discharges-through-consumers]].

**3. Retention gates need their own justification sentence.** A literal register whose preamble
claims "every count moves" is false as soon as it holds any before==after entry. Say plainly that
the register mixes CHANGE gates (count must move) and RETENTION gates (count must hold on a file
the plan edits), and state why each retention gate can still fail. Related:
[[acceptance-edits-must-be-false-before-true-after]].

**4. Assertion form can collide with a retention gate.** A per-element FluentAssertions
`list[0].Should().Be("b")` would push the pinned `Be("b"` occurrence count from 1 to 2 and break the
plan's own AC19 gate. When a plan pins an occurrence count in a test file it also instructs the
executor to edit, name the assertion form explicitly (`.Should().Equal(new[] { ... })`).
Loading
Loading