Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
150 changes: 80 additions & 70 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
@@ -1,6 +1,6 @@
---
name: nullable-pragma-gate-mechanics
description: How to actually verify the epic's per-file #nullable enable gate on UtilitiesCS — solution-wide TWAE aborts on vendored non-nullable warnings; use an isolated csproj rebuild and grep CS86xx
description: How to verify the epic's per-file #nullable enable gate on UtilitiesCS via an isolated csproj rebuild + grep CS86xx — but re-measure first, the solution-wide TWAE gate now passes
metadata:
type: project
---
Expand All @@ -11,3 +11,5 @@ The utilitiescs-nullable-remediation epic (#363 and its Wave-1 siblings) remedia

**How to apply:** Verify with the ISOLATED csproj gate that recompiles all target sources in one assembly:
`msbuild UtilitiesCS/UtilitiesCS.csproj -t:Rebuild -p:Configuration=Debug -p:Platform=AnyCPU -p:TreatWarningsAsErrors=true -p:BuildProjectReferences=false` (dash-switches + MSYS_NO_PATHCONV=1 in git-bash; a single legacy csproj needs `Platform=AnyCPU` no-space, NOT "Any CPU"; `BuildProjectReferences=false` uses already-built vendored DLLs so their warnings don't derail you — first do a normal `msbuild TaskMaster.sln -t:Build` to produce SVGControl.dll etc.). The AC1 metric is `grep -coE "error CS86[0-9]{2}"` on the log (must be 0); the non-zero build exit from CS0168/CS0618 is expected pre-existing noise, not a nullable failure. Record both the literal solution-command result AND the isolated-gate CS86xx=0 as evidence. See [[nullable_remediation_annotation_patterns]].

**STATUS — re-measured 2026-08-07 (#230 preflight):** the blocker above is HISTORICAL and no longer reproduces. `msbuild TaskMaster.sln /t:Build /p:Configuration=Debug /p:Platform="Any CPU" /p:Nullable=enable /p:TreatWarningsAsErrors=true` now returns **EXIT 0, 0 errors** across a genuine full compile of all 18 projects (evidence: `docs/features/active/2026-08-06-quickfiler-high-confidence-queue-init-stall-424/evidence/baseline/nullable-build-baseline.2026-08-06T22-21.md`, which also proves non-vacuity — 18 CoreCompile executions, 0 up-to-date short-circuits). The SVGControl CS0649 and UtilitiesCS CS0618/CS0168 promotions were cleared by the nullable epic. Do NOT preflight-block a plan for specifying the CLAUDE.md solution-wide TWAE command; measure it. This entry supersedes the #364, epic-restore, and net481-mechanics variants of the same claim.
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
name: preflight-selfderived-gate-thresholds-are-blind
description: A plan gate whose threshold is computed from the same measurement it validates cannot detect the condition it exists for; check commensurability and derivation-independence during preflight
metadata:
type: project
---

When preflight-validating a plan gate of the form "later measurement >= threshold recorded
earlier", check two things before accepting it:

1. **Derivation independence** — is the threshold computed from a measurement that would
itself be degraded by the failure condition the gate detects? If yes, the gate is blind.
2. **Commensurability** — are the two numbers the same unit and the same scope?

**Why:** #230 cycle 3. A plan added a "silent unwired `*.Part2.cs` file" guard as
`P7-T8 records an expected-minimum test count = sum of the Phase 1-6 filtered-run executed
counts`, then `P8-T5 asserts full-run executed count >= that floor`. Both failure modes fired:
(a) an unwired file never compiles, so the *phase filtered run* already reports the deflated
count — floor and actual deflate together and the comparison passes; (b) `Invoke-MSTestWithCoverage.ps1`
discovers every first-party `*.Test.dll` repo-wide (thousands) while the floor came from
`QuickFiler.Test` filtered runs (tens), so the comparison was vacuous. Overlapping
`/TestCaseFilter` values across phases (three tasks shared `~InitializationTests`) also made
the sum triple-count. The script emits no TRX logger and hard-codes
`/TestCaseFilter:TestCategory!=LiveOutlook`, so a per-assembly count was not extractable.

**How to apply:** replace count-comparison gates with positive-existence proofs derived from
a source that does not change under the failure condition. For csproj-wiring guards in legacy
non-SDK projects the working pair is: (a) enumerate added files via `git status --porcelain` /
`git diff --name-only` and check each against `<Compile Include>`; (b) rebuild, then
`& $vstest <assembly>.dll /ListTests` and confirm every statically-enumerated `[TestMethod]`
name from source appears in discovery. Both are wiring-sensitive; the static source count does
not shrink when a file is unwired. `/ListTests` is valid on VSTest 18.8.0 in both the
positional (`<dll> /ListTests`) and colon (`/ListTests:<file>`) forms — verified by argument
parsing, which rejects only the missing file, not the switch. Related: [[project_legacy_csproj_no_transitive_compile_refs]],
[[project_vstest_testcasefilter_or_operator_and_env_setup]].
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
name: qfcitemcontroller-pump-harness-needs-saveparameters
description: A QfcItemController test harness built with SetField-only injection leaves the ??= factory defaults null; call SaveParameters instead
metadata:
type: project
---

When arranging a `QfcItemController` for a **full initialization** run, inject only
the behavioral seams (`_uiDispatcher`, `_webViewInitializer`) with
`QfcItemControllerTestSupport.SetField`, then call the real
`controller.SaveParameters(...)`. Do **not** inject every field one-by-one.

**Why:** `SaveParameters` is the single construction path every production route
funnels through, and its `??=` block is what supplies `_folderPredictorFactory`,
`_conversationResolverFactory`, `_folderPredictorEmptyFactory`, `_flagTasksFactory`,
`_emailFilerFactory` and `_mailActions`. A SetField-only harness leaves those null,
so the test fails deep inside `LoadFolderHandlerAsync` (NRE on
`_folderPredictorFactory`) instead of at the seam under test. #230 hit this on
`InitializeAsync`; `InitializeSequentialAsync`/`InitializeGraphicsAsync`/
`Initialize(bool)` did not reach that code and masked the gap, and the static
factories passed because they call `SaveParameters` themselves.

**How to apply:** also expect `InitializeAsync` (and therefore `CreateAsync`) to
drive `PopulateFolderComboBoxAsync`, which needs three extra `IApplicationGlobals`
mocks the other init members do not: `AF.CtfMap` (an empty `new CtfMap()` makes
`ContainsId` false), `AF.UseLcppnPredictor = true` + `AF.FolderPredictor` = a
`Mock<IFolderPredictor>` whose `Classify` returns an empty ordered sequence (this
selects the LCPPN seam and keeps the entire flat `Manager["Folder"]` Bayesian stack
out of the test), and `AF.RecentsList` = `new SloLinkedList<string>()` for
`FolderPredictor.FolderArray`. Widen the mock graph; never change production to
accommodate the test.
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
name: uithread-dispatcher-static-swap-race
description: Any test that drives QfcItemController init must swap the process-wide static UiThread.Dispatcher; two classes doing so concurrently deadlock on the parked dispatcher
metadata:
type: project
---

`QfcTipsDetails.ToggleAsync` marshals through the **process-wide static**
`UtilitiesCS.UiThread.Dispatcher` (`UtilitiesCS/Threading/UiThread.cs`, private
static `_dispatcher`, no fallback getter). Any test that drives
`QfcItemController.Initialize*/Create*` reaches it via `ToggleTipsAsync`.

In `QuickFiler.Test` that static is either unset (NullReferenceException) or holds
the deliberately **parked, never-pumped** dispatcher seeded by
`QfcItemControllerTestSupport.EnsureUiThreadDispatcher` — an `InvokeAsync` on it
**never completes**. So such a test must reflection-swap `_dispatcher` to a live
pumped dispatcher and restore it in `finally`.

**Why:** #230 hit this twice. First as an NRE in `ToggleTipsAsync` (fixed by the
swap). Then, only under the full-suite run, as two `[Timeout]` expiries — one from
each of the two test classes that both call the shared pump fixture. MSTest
class-level parallelization ran them concurrently, so class B's restore reverted
the static to the parked dispatcher while class A's member was still awaiting a
dispatcher operation. Filtered runs and even a two-class run passed; only the
full-suite run interleaved them.

**How to apply:** if more than one test class swaps a shared static, serialize the
whole swap-to-restore window with a static `SemaphoreSlim(1,1)` acquired in the
fixture builder and released in an **idempotent** restore; release it in a `catch`
if the builder throws. `[DoNotParallelize]` is not sufficient on its own (see
[[project_mstest_donotparallelize_overlaps_parallel_bucket]]). Symptom signature to
recognize: a `[Timeout]` expiry rather than an assertion failure, exactly one
failure per swapping class, and green in every filtered run.

Related: [[project_utilitiescs_test_parallelism_flakiness]],
[[project_dispatcherdelay_hangs_unit_tests]].
4 changes: 4 additions & 0 deletions .claude/agent-memory/atomic-planner/MEMORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,7 @@
- [#349 breadcrumb plan seams](project_349_efcviewer_breadcrumb_plan_seams.md) — P0-T6 halt-gate on 9101 provider; evidence/repro/ authorized; EfcViewer3 mechanical swap only; Newtonsoft in UtilitiesCS only
- [#424 QuickFiler deadline plan seams](project_424_quickfiler_deadline_plan_seams.md) — 12s const; Part2 partial no-[TestClass]; overload migration breaks loose-mock Setup/Verify (Issue218 "dormant" misclassification); grep old overload shape in ALL test files
- [vstest scoped-run + csharpier 1.2.6 commands](reference_vstest_scoped_run_command.md) — vswhere-resolved vstest.console.exe + /InIsolation + /TestCaseFilter (join `|`); every run task needs an explicit command; csharpier needs format/check subcommands
- [#230 WinForms pump seam plan facts](project_230_winforms_pump_seam_plan_facts.md) — 19 census sites verified; Initialization.cs 466/500; test-file headroom; factory seam params before SaveParameters; CreateAsync awaited-tail faults; 19→11 target
- [One AC per check-off task](feedback_ac_checkoff_one_per_task.md) — preflight rejects batched AC check-offs; one checkbox + own evidence pointer per task (#230 B4)
- [Post-format file-size audit + async-tail asymmetry](feedback_postformat_file_size_audit.md) — 500-line audit goes AFTER final csharpier format; awaited vs fire-and-forget tails decide completion-vs-fault test shape (#230 B2/B3)
- [Wiring gates must be wiring-sensitive](feedback_wiring_gates_must_be_wiring_sensitive.md) — count floors deflate with the defect they guard; use static [TestMethod] enumeration vs /ListTests discovery (#230 rev3)
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
name: feedback-ac-checkoff-one-per-task
description: Executor preflight rejects batched AC check-off tasks — the acceptance-criteria-tracking protocol requires exactly one AC per check-off task, each with its own evidence pointer
metadata:
type: feedback
---

AC check-off tasks in atomic plans must flip exactly ONE acceptance-criterion checkbox each, with that AC's own evidence pointer. A task that flips 2+ checkboxes (e.g. "Check off S-AC1, S-AC2, S-AC3 and U-AC1, U-AC2") is a blocking preflight finding.

**Why:** #230 preflight B4 (2026-08-07) — four batched check-off tasks (5+2+6+5 ACs) were all rejected; only the single-AC task passed. The `acceptance-criteria-tracking` protocol requires individual check-off as each AC is verified, and "individually" is enforced at task granularity, not just at ordering granularity.

**How to apply:** When distributing check-offs across phases, emit one `[P#-T#]` per AC (renumbering downstream tasks), plus one final reconciliation task verifying all boxes are `[x]`. Budget the task-count accordingly — a 19-AC feature adds ~20 check-off tasks. Related: [[plan-validator-task-id-sequential-constraint]].
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
name: feedback-postformat-file-size-audit
description: 500-line file-size audits must run AFTER the final csharpier format pass — pre-format counts are advisory only; also sequence async-factory fault-path tests per the fire-and-forget vs awaited-tail asymmetry
metadata:
type: feedback
---

Place the authoritative <=500-line file-size audit task in the final-QC phase, immediately AFTER the `csharpier format` step and before lint, with restart-from-format on violation. A Phase-N audit measured before the final format pass is invalid: csharpier reflows to its print width and routinely pushes hand-written ~480-line test files past 500.

**Why:** #230 preflight B3 (2026-08-07) — the plan's Phase 7 size audit + S-AC13 check-off were rejected because `csharpier format .` did not run until Phase 8. Same cycle, B2: a factory test acceptance ("assert returned controller state") was unachievable because `CreateAsync` → `InitializeAsync` ends with an AWAITED `InitializeWebViewAsync()` that always faults under a mocked seam, while `CreateSequentialAsync`'s tail is fire-and-forget `_ = InitializeWebViewAsync();` and returns normally — the awaited-vs-discarded tail asymmetry determines whether a member can be tested to completion or only to a controlled fault (partial per-member coverage by construction; carve the coverage gate to "> 0%").

**How to apply:** (1) Keep any earlier line-count checks labeled advisory; key the S-AC check-off to the post-format artifact. (2) Before writing "assert the returned value" acceptance for an async factory/orchestrator, read its terminal statement chain: awaited faulting tail → fault-path test mirroring the exception identity; discarded tail → normal-completion test. Related: [[enumerate-condition-outcomes-before-case-list]], [[named-coverage-exception-verify-member-body]].
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
name: feedback-wiring-gates-must-be-wiring-sensitive
description: Executed-test-count floors cannot detect unwired test files (both sides deflate equally); gate wiring via static [TestMethod] enumeration vs vstest /ListTests discovery instead
metadata:
type: feedback
---

Never gate csproj test-file wiring on an executed-test-count floor. Prove wiring with two wiring-sensitive checks: (1) enumerate feature-added `.cs` files from `git diff --name-only` and match each against `<Compile Include>` entries; (2) statically enumerate the new `[TestMethod]` names from source and confirm each appears in `vstest.console.exe <assembly> /ListTests` output. Source enumeration does not shrink when a file is unwired; `/ListTests` reflects only what compiled — a name in source but absent from discovery is the silent-failure condition.

**Why:** #230 preflight revision 3 (2026-08-07). The count floor failed three ways: (a) an unwired `*.Part2.cs` deflates the same-phase filtered run that feeds the floor, so floor and full-run figures drop identically and the comparison passes — the gate is degraded by exactly the condition it exists to detect; (b) incommensurable units — `Invoke-MSTestWithCoverage.ps1` counts repo-wide (thousands) vs per-assembly filtered runs (tens), making "full >= floor" vacuous; (c) overlapping `/TestCaseFilter` values across phases (three tasks shared `FullyQualifiedName~InitializationTests`) triple-count shared classes, so the sum is neither a measurement nor commensurable.

**How to apply:** In legacy packages.config projects (see [[project-legacy-csproj-explicit-compile-include]]), put the two-check wiring task before final QC; keep executed/passed counts in run artifacts only as `Output Summary:` audit-trail data, never as a wiring gate. Generalization: before pinning any numeric floor as a gate, ask whether the failure mode it guards against also deflates the floor's own measurement source.
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
name: project-230-winforms-pump-seam-plan-facts
description: "#230 verified plan seams: 19 census sites in QfcItemController partials; Initialization.cs at 466/500 lines; test-file headroom figures; new TestSupport files need csproj Compile Include; 8-of-9 de-exemption target (19->11)"
metadata:
type: project
---

Verified facts behind the #230 plan (`docs/features/active/2026-08-07-winforms-message-pump-test-seam-230/plan.2026-08-07T20-36.md`), measured 2026-08-07:

- `[ExcludeFromCodeCoverage]` census inside `QuickFiler/Controllers/QfcItemController.*.cs` = exactly 19 sites (Conversation 1, EventWiring 1, EventHandlers 5, Initialization 7, Navigation 2, ViewerSetup 3). `EnsureBreadcrumbPipeline` (ViewerSetup.cs:132, from #351) is the post-ratification 19th site vs the 18-member ratified controller boundary. Target 19 -> 11; `InitializeWebViewAsync` (ViewerSetup.cs:38) stays exempt (WebView2 runtime barrier).
- `QfcItemController.Initialization.cs` = 466 lines pre-change — the factory-seam optional-parameter edit (~+16-20 lines, offset by 7 attribute/comment removals) needs an explicit <=500 verification task (plan P5-T2).
- Test-file headroom: InitializationTests 193, SeamFactoryTests 284, ViewerSetupTests 407 (tight — overflow rule: `*.Part2.cs` partial continuation, no new `[TestClass]`).
- New `QuickFiler.Test/TestSupport/*.cs` files require `<Compile Include>` in the legacy `QuickFiler.Test.csproj` (see [[project-legacy-csproj-explicit-compile-include]]).
- Factory seam: `CreateAsync`/`CreateSequentialAsync` do `new QfcItemController(); SaveParameters(...)` with `??=` production defaults (incl. real `WebView2CoreInitializer`) — de-exempting them requires the 3 optional params (`IUiDispatcher`, `IWebViewCoreInitializer`, `Func<MailItem, ConversationResolver>`) assigned before `SaveParameters`, mirroring primary ctor lines 38-64 exactly (`= null` defaults are nullable-gate-safe because the primary ctor already passes with them).
- Factory tail asymmetry (preflight-verified, plan D13): `CreateSequentialAsync`/`InitializeSequentialAsync`/`InitializeGraphicsAsync` end with fire-and-forget `_ = InitializeWebViewAsync();` and return normally under a mocked seam; `CreateAsync` → `InitializeAsync` ends with an AWAITED `InitializeWebViewAsync()` that always faults (null `CoreWebView2`), so `CreateAsync` never reaches `return controller;` in a unit test — test it to the controlled fault, partial per-member coverage by construction (gate "> 0%").
- Preflight revision 2 (2026-08-07) also encoded: vswhere-by-explicit-path in D6 (see [[reference-vstest-scoped-run-command]]), post-format file-size audit in Phase 8 (see [[feedback-postformat-file-size-audit]]), one AC per check-off task (see [[feedback-ac-checkoff-one-per-task]]), Compile-Include wiring verification task (P7-T8), `Select-String` EXIT_CODE via `$?`, and Cobertura per-member aggregation from `QfcItemController+<Member>d__NN` state-machine classes.
- Preflight revision 3 (2026-08-07) retired the executed-test-count floor: P7-T8 now proves wiring via csproj enumeration against `git diff --name-only` PLUS `vstest /ListTests` discovery of every statically-enumerated new `[TestMethod]` name; P8-T5 records repo-wide counts as audit trail only (see [[feedback-wiring-gates-must-be-wiring-sensitive]]).
- **How to apply:** any revision pass on this plan must preserve the census scope (controller partials only), the D5 coverage gates (post >= baseline raw AND denominator-adjusted, changed lines >= 90%, each of the 8 members > 0% with the CreateAsync/InitializeAsync carve-out), and the WPF-dispatcher interop smoke test ordering (host self-test before any controller pump test relies on `Initialize(bool)`'s dispatcher tail).
Loading
Loading