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
1 change: 1 addition & 0 deletions .claude/agent-memory/atomic-executor/MEMORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
- [Merge-base diff gates need a commit cadence](project_preflight_mergebase_diff_gates_need_commit_cadence.md) — `<MERGE_BASE>..HEAD` gates are vacuous while HEAD == merge-base and unsatisfiable once HEAD is ahead; on a later cycle scope-audit via `git show --numstat --format= HEAD`
- [Inserted plan tasks force renumbering](project_plan_task_ids_digit_only_forces_renumbering.md) — suffixed IDs (`P3-T5a`) fail validation; say "insert + renumber downstream", then verify defs-vs-mentions mechanically
- [Plan rationale clauses are evidence](project_418_plan_rationale_clauses_are_evidence.md) — #418 needed 3 preflight passes; all blockers were unmeasured world-state claims in prose, never in the fix
- [Tracked agent-memory breaks unscoped git gates](project_agent_memory_tracked_breaks_unscoped_git_gates.md) — `.claude/agent-memory/**` is tracked + dirty at branch head; every git diff/status/grep gate needs an explicit pathspec or it is unsatisfiable / false-positive
- [#418 500-line gate vs mandated plan content](project_418_500line_gate_vs_plan_content.md) — P1-T19 unsatisfiable (193 new lines into 146 headroom); per-block logging clauses block centralizing; delta = extract helpers to a new file
- [#376 capstone scope-expansion layers](project_376_capstone_scope_expansion_layers.md) — 5 escalated layers past P2-T17 resolved via the 3 authorized patterns; stop-condition never triggered
- [#207 Hook() redesign breaks AppEventsTests](project_207_hook_redesign_breaks_appeventstests.md) — readiness-gate Hook() fails an out-of-scope test asserting superseded ordering; needs a plan revision, not a test weakening
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
name: agent-memory-tracked-breaks-unscoped-git-gates
description: .claude/agent-memory/** is tracked and dirty at branch head, so any unscoped git diff/status/grep gate in a plan is unsatisfiable or false-positive; every git gate needs an explicit pathspec
metadata:
type: project
---

`.claude/agent-memory/**` is a **tracked** directory in this repo, and agents write to it during
planning and execution. At the head of a fresh feature branch it is routinely already modified
versus the merge-base (verified on `bug/wpf-dispatcher-yield-test-order-dependent-508`: three
modified tracked files plus four untracked ones, while the scoped `.cs`/`.csproj`/`.sln` diff was
empty). Consequently:

- An unscoped `git diff --name-only` "lists exactly <the in-scope files>" gate is **unsatisfiable**.
- An unscoped `git status --porcelain` "is empty" gate is **unsatisfiable** (the untracked
`<FEATURE>/` folder and every evidence artifact the plan writes also land here).
- A prohibited-fix grep over an unscoped `git diff` produces **false positives**: the memory files
are prose about past defects, so `.claude/agent-memory/atomic-planner/MEMORY.md` literally
contains tokens like `DoNotParallelize`, `Thread.Sleep`, and `Task.Delay`.

**Why:** this defect class survived two full preflight revision rounds on issue #508 (three passes
total) because it is invisible when reading the plan prose — it only surfaces when you actually run
`git status --porcelain` in the worktree. Planners write the natural unscoped command and it looks
correct.

**How to apply:** during preflight, grep the plan for every `git diff` / `git status` / `git grep`
occurrence and confirm each **gating** one carries an explicit pathspec — either
`-- '*.cs' '*.csproj' '*.sln'` or the literal in-scope file paths. Unscoped forms are acceptable
only for record-only capture that the task text explicitly says is not a gate. Pair the scoped
`git diff` (catches modified/deleted) with the scoped `git status --porcelain` (catches added/
untracked) — neither alone proves "no file was added or removed". Note also that bare
`git diff --name-only` is worktree-vs-index; if anything gets staged mid-execution, switch to
`git diff --name-only HEAD -- <pathspec>`.

Related: [[project_preflight_selfderived_gate_thresholds_are_blind]],
[[project_418_plan_rationale_clauses_are_evidence]].
6 changes: 5 additions & 1 deletion .claude/agent-memory/atomic-planner/MEMORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
- [Coverage Evidence Path Normalization](evidence-path-normalization.md) — specs sometimes name evidence/coverage/; normalize to canonical baseline/ + qa-gates/
- [Stale build output is not evidence of existence](stale-build-output-is-not-evidence-of-existence.md) — obj/ cache filenames outlive tear-down commits; verify project/source files with git ls-files or a glob before writing an existence claim into acceptance text
- [Diff gates need a commit task](diff-gates-need-a-commit-task.md) — `git diff <MERGE_BASE>..HEAD` gates pass vacuously with no commit task; Phase 0 porcelain is non-empty by construction; whitelist docs/ + agent-memory in scope-lock diff gates
- [.claude/agent-memory is tracked — scope every git gate](agent-memory-is-tracked-scope-git-gates.md) — unscoped diff/status/grep gates are unsatisfiable; MEMORY.md prose even trips prohibited-token greps
- [Never pin a HEAD SHA as a plan expectation](never-pin-head-sha-as-plan-expectation.md) — record HEAD, gate on tree invariants (clean porcelain + no .cs/.csproj/packages.config/app.config diff vs the baseline-capture sha)
- [.csharpierignore scope: packages.config is NOT exempt](csharpierignore-scope-packages-config.md) — only *.csproj/*.props/*.targets are excluded; justify single-line package entries by character width, never by formatter exemption
- [Repo-wide csharpier format breaks zero-diff ACs](csharpier-repowide-format-breaks-zero-diff-acs.md) — scope the mutating pass to the plan's own path list; keep `check .` read-only; re-verify the zero-line diff AFTER formatting
Expand All @@ -23,7 +24,9 @@
- [Plan validator phase-heading constraint](plan-validator-phase-heading-constraint.md) — MCP plan validator requires exact `### Phase N — <Title>`; no tokens between Phase N and em-dash; H1 title line is exempt
- [Plan validator task-ID sequential constraint](plan-validator-task-id-sequential-constraint.md) — task IDs must be digit-only and sequential-by-appearance; mid-phase insertion forces renumbering all later tasks + cross-refs
- [Legacy csproj wiring](project_legacy_csproj_explicit_compile_include.md) — packages.config projects need `Compile Include` wiring and their own `Reference`; ProjectReference gives no compile-time flow (CS0012)
- [C# Phase 0 toolchain bootstrap](project_csharp_phase0_toolchain_bootstrap.md) — .dotnet-sdk/ absent + no dotnet tool restore + no dotnet-coverage; make it [P0-T1] or all csharpier/coverage tasks fail
- [C# Phase 0 toolchain bootstrap](project_csharp_phase0_toolchain_bootstrap.md) — `dotnet tool run` is broken (no .config manifest, no .dotnet-sdk); use global csharpier.exe/dotnet-coverage.exe + a mandatory NuGet restore task
- [Worktree root breaks the `\.claude\` exclusion](worktree-root-breaks-dotclaude-exclusion.md) — the agent worktree IS under .claude\worktrees\, so a substring assertion is unsatisfiable; assert a workspace-root prefix instead
- [Async state machines split the coverage denominator](async-state-machine-coverage-aggregation.md) — `<Method>d__N` and `<>c*` are separate Cobertura `<class>` elements; aggregate by `filename` or a >=90% gate fails for measurement reasons
- [#211 startup-lifetime heartbeat seam](project_211_startup_lifetime_heartbeat_seam.md) — Phase 3.3 [startup-lifetime-heartbeat] DispatcherTimer in ThisAddIn.cs (exempt), pure logic in StartupDiagnosticsProbe; AC15
- [#292 CurrentStoreContext parallel seam](project_292_currentstorecontext_parallel_seam.md) — process-global static; scope-opening store test classes must be [DoNotParallelize] or they pollute reader-baseline tests under UtilitiesCS.Test ClassLevel parallelization
- [WinForms STA-refinement exemption rule](project_winforms_sta_refinement_exemption_rule.md) — epic #295 STA refinement: remove HWND-only default-body + PerformClick-wiring exemptions via dedicated *.StaTests.cs; keep dialog/Form/launcher exemptions
Expand All @@ -33,6 +36,7 @@
- [C# coverage gate expects JaCoCo](project_csharp_coverage_gate_jacoco_format.md) — validate-feature-review-coverage.ps1 reads artifacts/csharp/coverage.xml as JaCoCo, not Cobertura; plan a conversion scoped to first-party
- [Durable script copy into feature folder](durable-script-copy-into-feature-folder.md) — copy scratchpad-supplied scripts into `<FEATURE>/scripts/` before referencing them in plan tasks (session-scoped temp paths aren't durable)
- [#351 QuickFiler breadcrumb plan seams](project_351_quickfiler_breadcrumb_plan_seams.md) — JSON code in UtilitiesCS only (QuickFiler lacks Newtonsoft); P2-T1 blocked-if-9101-absent; evidence/repro/ rejected; coordinator pattern
- [Dispatcher repro hang trap](dispatcher-repro-hang-trap.md) — a repro that touches Dispatcher.CurrentDispatcher on a pooled worker hangs on awaited InvokeAsync instead of failing; use an owned pumping STA thread
- [Invoke-MSTestWithCoverage.ps1 canonical coverage runner](reference_invoke_mstest_with_coverage_script.md) — full-suite *.Test.dll → Cobertura XML via dotnet-coverage+vstest /InIsolation; cite for baseline/final-QC coverage tasks
- [Invoke-MSTest.ps1 single-SearchRoot defect](reference_invoke_mstest_single_searchroot_defect.md) — scalar `.Count` under StrictMode throws when one assembly matches; always cite `-SearchRoot .`
- [Literal-call clauses block file-size tightening](literal-call-clauses-block-file-size-tightening.md) — clauses pinning a call in 2+ places + a near-500-line file = unsatisfiable; plan the type split up front (no waiver for .cs)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
name: agent-memory-is-tracked-scope-git-gates
description: .claude/agent-memory/** is a TRACKED, agent-written path — every plan gate built on git diff/status/grep must carry an explicit pathspec or it is unsatisfiable
metadata:
type: feedback
---

`.claude/agent-memory/**` is tracked in git, is routinely already modified at branch head, and is written to *further* by agents while the plan executes. Any plan task whose acceptance is an unscoped git command is therefore unsatisfiable by construction. Scope every such gate:

- `git diff --name-only -- '*.cs' '*.csproj' '*.sln'` instead of bare `git diff --name-only` for "lists exactly these files" gates.
- `git status --porcelain -- '*.cs' '*.csproj' '*.sln'` instead of bare porcelain for clean-tree gates.
- `git diff -- <the two in-scope file paths>` instead of a whole-tree `git diff` for prohibited-token grep gates.

The grep case is the non-obvious one. `.claude/agent-memory/atomic-planner/MEMORY.md` is *prose about* prohibited patterns, so its text literally contains tokens like `DoNotParallelize`, `Thread.Sleep`, and `Ignore]`. An unscoped `git diff | grep DoNotParallelize` fires a false positive on a memory-index line and fails a task that has nothing wrong with it.

Scoping a grep gate costs no coverage as long as a sibling task independently proves the scoped set *is* the whole source diff — pair the grep task with a scoped `git diff --name-only -- '*.cs' ...` "lists exactly" task and cite it in the grep task's text.

**Why:** #508 revision pass 1 scoped the two Phase 0 gates (P0-T3, P0-T14) but left the same defect in three Phase 1/2 tasks, costing an entire extra preflight pass. Scoping is not a per-task judgment call; it is a property of the repo layout and applies to every git-based gate in the plan.

**How to apply:** When writing or revising any plan, sweep for `git diff`, `git status`, and "grep the diff" across *all* phases at once and apply the pathspec uniformly. Add a `## Notes` entry stating the scoping rule once and marking it binding on the specific task IDs, so a later reviewer does not read the pathspec as a weakened gate. This refines, and does not contradict, [[never-pin-head-sha-as-plan-expectation]]: a pathspec is a source-tree invariant, not a permitted-dirt enumeration — never list specific dirty files as tolerated.
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
name: async-state-machine-coverage-aggregation
description: An async method's body lands in a separate Cobertura <class> element (<Method>d__N); a >=90% changed-class gate must aggregate by filename across compiler-generated nested types
metadata:
type: project
---

When a plan gates changed-class line coverage at `>= 90%`, do not instruct the executor to read the named `<class>` element. An `async` method compiles into a nested state machine (`Type/<Method>d__N`) and lambdas into display classes (`<>c*`), each of which appears as its **own `<class>` element** in Cobertura output.

Reading the named element alone leaves only constructors and field initializers in the denominator. On `WpfDispatcherYield` (#508) that was ~6 coverable lines with 1 uncovered, i.e. ~83% — a gate failure for a pure **measurement** reason while the real aggregated figure passed.

Correct instruction: aggregate every `<class>` element whose `filename` attribute equals the changed source file path, then derive the line rate from the summed line/covered-line counts.

**Why:** #508 preflight pass 1 flagged the coverage gate as unreachable-as-measured. The same trap applies to any C# class in this repo whose changed member is `async` or uses lambdas — which is most of them.

**How to apply:** Write the aggregation rule into the coverage task text itself, plus into the plan's coverage design-decision section, and name the specific lines expected to remain uncovered up front so a shortfall is distinguishable from a measurement error. Related: [[named-coverage-exception-verify-member-body]], [[csharp-coverage-gate-jacoco-format]].
14 changes: 14 additions & 0 deletions .claude/agent-memory/atomic-planner/dispatcher-repro-hang-trap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
name: dispatcher-repro-hang-trap
description: Never plan a WPF Dispatcher flake repro that touches Dispatcher.CurrentDispatcher on a pooled worker — an awaited InvokeAsync(Background) against that non-pumping dispatcher hangs forever instead of failing
metadata:
type: feedback
---

When planning a fail-before repro for a `System.Windows.Threading.Dispatcher` order-dependence defect, do NOT plan "touch `Dispatcher.CurrentDispatcher` on the current test thread, then call the code under test". `Dispatcher.CurrentDispatcher` creates and caches a dispatcher for the calling thread, but an MSTest pooled worker never runs `Dispatcher.Run()`, so any awaited `dispatcher.InvokeAsync(..., DispatcherPriority.Background, ...)` never completes. The repro deadlocks the test run instead of producing the failing assertion the plan needs.

The only hang-free deterministic shapes are: (a) supply a **pumping** dispatcher from an STA thread the test itself owns and shuts down (`Dispatcher.CurrentDispatcher` + `Dispatcher.Run()` on that owned thread, `BeginInvokeShutdown` + `Join` in `Dispose`), or (b) assert on synchronously observable resolution state without awaiting the yield at all.

**Why:** #508 (2026-08-08). The naive repro looked deterministic and was the obvious first choice, but it converts an intermittent `Failed` into an indefinite hang. Corroborating signal: the reported baseline flake manifested as `Failed`, not `Hang`, which means the accidentally-resolved dispatcher in those runs was already pumping — i.e. the real contributor was the process-global `UiThread.Dispatcher` populated by `UiThread.Init()` (which shows and pumps a `SyncContextForm`), not a bare pooled-thread dispatcher.

**How to apply:** Any `[expect-fail]` task involving `Dispatcher` must state the pumping requirement explicitly and pair it with a task recording the hang hazard and mitigation, so the executor does not substitute the naive shape. Also check whether the production fallback reads a plain static field (safe) or a property whose getter calls `Init()` (pops a form / touches COM — never acceptable in a unit test). Related: [[reference-invoke-mstest-with-coverage-script]].
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
---
name: csharp-phase0-toolchain-bootstrap
description: C# plans need a Phase 0 bootstrap task (Install-RepoDotNetSdk.ps1 + dotnet tool restore + dotnet-coverage) or every csharpier and coverage task fails on a fresh checkout
description: C# Phase 0 must resolve the toolchain explicitly — prefer the global csharpier/dotnet-coverage exes, never `dotnet tool run`, and always include a NuGet restore task in a fresh agent worktree
metadata:
type: project
---

Every C# atomic plan in this repo must open Phase 0 with a toolchain-bootstrap task before any csharpier or coverage command task. Three separate prerequisites are not satisfied by a fresh checkout:
Every C# atomic plan in this repo must resolve its toolchain explicitly in Phase 0 before any csharpier or coverage command task. Verify the current state each cycle; the shape below has changed at least once.

1. `global.json` pins SDK `8.0.205` with `"paths": [".dotnet-sdk", "$host$"]`, and `.dotnet-sdk/` is gitignored. Until `scripts/vscode/Install-RepoDotNetSdk.ps1` runs, `dotnet tool run csharpier --version` fails with an instruction to run that script.
2. `Install-RepoDotNetSdk.ps1` does NOT run `dotnet tool restore`, so csharpier (manifest at repo-root `dotnet-tools.json`) needs a separate `dotnet tool restore`.
3. `dotnet-coverage` is a global tool that is not installed by either of the above. `scripts/vscode/Invoke-MSTestWithCoverage.ps1` throws without it (guard near line 129).
**Verified 2026-08-08 (issue #508 preflight, agent worktree):**

Package restore itself is fine: `packages/` is gitignored and restored by `scripts/vscode/Invoke-Restore.ps1` (`msbuild /t:Restore /p:RestorePackagesConfig=true`); the `EnsureNuGetPackageBuildImports` target is `BeforeTargets="PrepareForBuild"` so it does not fire during restore.
1. `dotnet tool run csharpier` is **broken and must not be planned**. There is no `.config/dotnet-tools.json` (the manifest sits at repo root as `dotnet-tools.json`, which `dotnet tool run` does not read), and `global.json` pins an SDK under an absent `.dotnet-sdk`, so every `dotnet` SDK command fails with the missing-SDK error.
2. Prefer the **global tools**, which were confirmed on PATH: `C:\Users\DanMoisan\.dotnet\tools\csharpier.exe` (1.3.0) and `C:\Users\DanMoisan\.dotnet\tools\dotnet-coverage.exe` (18.5.2). CSharpier 1.x needs the `format` / `check` subcommand; bare `csharpier .` is invalid.
3. `vstest.console.exe` is NOT on PATH; resolve via `C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe`.
4. A **NuGet restore task is mandatory** in a fresh agent worktree: `packages/` does not exist and there is no `bin\Debug` output, so analyzer and nullable baselines are vacuous (or fail CS0006) without it. Use `pwsh -File scripts/vscode/Invoke-Restore.ps1` (`msbuild /t:Restore /p:RestorePackagesConfig=true`, no .NET SDK required); fall back to the WinGet `nuget.exe restore TaskMaster.sln`. Watch for analyzer version skew between `<Analyzer Include>` HintPaths and the `packages.config` pins — that is an environment issue, not a plan defect.

**Why:** #418 preflight pass 1 returned two blocking findings (B1, B2) because the plan's csharpier baseline, csharpier final-QC, coverage baseline, and coverage final-QC tasks were all unrunnable — and the two coverage tasks carry the mandatory numeric coverage evidence that a minor-audit plan cannot report PASS without.
**Why:** #418 preflight pass 1 blocked on unrunnable csharpier/coverage tasks; #508 preflight pass 1 blocked again on `dotnet tool run csharpier` plus a missing restore task. Coverage tasks carry the mandatory numeric evidence a minor-audit plan cannot report PASS without.

**How to apply:** Make it `[P0-T1]`, ahead of the policy reads, with acceptance requiring an `evidence/baseline/toolchain-bootstrap.<ts>.md` artifact that records `EXIT_CODE: 0` for all three commands plus a verified `csharpier --version` and a resolving `dotnet-coverage --version`. Related: [[evidence-path-normalization]], [[csharp-coverage-gate-jacoco-format]].
**How to apply:** Put the restore task in Phase 0 immediately after the formatter baseline, and write the literal resolved exe path into every command task rather than a generic tool name. Ask the caller for the resolved tool table if it was not supplied. Related: [[evidence-path-normalization]], [[csharp-coverage-gate-jacoco-format]], [[vstest-scoped-run-command]], [[csharpier-format-not-pipe-files-gate]].
Loading
Loading