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
5 changes: 4 additions & 1 deletion .claude/agent-memory/atomic-executor/MEMORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@
- [Inline-dispatch harness citation makes a test vacuous](project_inline_dispatch_harness_citation_makes_execution_time_test_vacuous.md) · ["Skip the pointless drain" note](project_preflight_drain_scope_optimization_note_makes_test_vacuous.md)
- [Multi-pattern gates detach shared qualifiers](project_multipattern_gate_shared_qualifier_detachment.md) · [Banned-API zero-hit gate hits doc comments](project_banned_api_zero_hit_gate_hits_doc_comments.md)
- [Follow-up promotion task is unexecutable](project_followup_promotion_task_is_unexecutable_by_executor.md) · [Supersede clause leaves a routing residual](project_supersede_clause_leaves_hard_routing_residual.md)
- [Delegation to csharp-typed-engineer with no dispatch tool](project_plan_delegation_to_typed_engineer_without_dispatch_tool.md) — edit inline, record the substituted worker
- [Plan check-off fixpoint breaks clean-tree gates](project_plan_checkoff_fixpoint_breaks_terminal_clean_tree_gate.md) · [Tracked agent-memory breaks unscoped git gates](project_agent_memory_tracked_breaks_unscoped_git_gates.md)
- [Merge-base diff gates need a commit cadence](project_preflight_mergebase_diff_gates_need_commit_cadence.md) · [BASELINE_SHA diff conflates the merged base](project_baseline_sha_diff_conflates_merged_base.md) — use `<base>..HEAD`
- [Moving-base two-dot diff needs an inertness test](project_preflight_moving_base_two_dot_diff_inertness_test.md) — only blocking if the delta actually hits those paths
- [Moving-base two-dot diff needs an inertness test](project_preflight_moving_base_two_dot_diff_inertness_test.md) — only blocking if the delta hits those paths; better, make it an `INHERITED PATHS:` diff the executor runs
- [Inserted plan tasks force renumbering](project_plan_task_ids_digit_only_forces_renumbering.md) · [Plan rationale clauses are evidence](project_418_plan_rationale_clauses_are_evidence.md) — blockers hide as prose
- [Bugfix phase grows the file anyway](project_bugfix_phase_grows_the_file_despite_dead_code_removal.md) · [#418 500-line gate vs mandated plan content](project_418_500line_gate_vs_plan_content.md)
- [AC check-off + artifacts/ tool-output paths](project_preflight_ac_checkoff_and_tooloutput_paths.md) · [Orchestrator override does not satisfy an AC](project_orchestrator_override_does_not_satisfy_an_ac.md)
Expand All @@ -35,6 +36,7 @@
- [VS18 build/test toolchain paths](project_vs18_build_toolchain_paths.md) · [Repo-local SDK install + nullable Rebuild](project_repo_sdk_and_nullable_rebuild.md) — .dotnet-sdk needs pwsh7
- [Start-Process -ArgumentList strips quoting](project_startprocess_arglist_array_strips_quoting.md) · [Relative paths in pwsh hit the wrong worktree](project_relative_path_in_pwsh_dotnet_io_hits_wrong_worktree.md)
- [QuickFiler.Test coverage hang + build flags](project_quickfiler_test_coverage_hang_and_build_flags.md) — testhost can hang
- [Dot-sourcing Invoke-MSTestWithCoverage clobbers $CoverageOutput](project_dotsourcing_invoke_mstest_clobbers_coverageoutput_param.md) — param-block default wins; Cobertura lands at coverage\coverage.cobertura.xml, exit 0
- [vstest TestCaseFilter needs `|` not OR](project_vstest_testcasefilter_or_operator_and_env_setup.md) · [Test file name != partial class name](project_test_file_name_vs_partial_class_name.md)
- [Analyzer HintPath skew breaks all four gates](project_analyzer_hintpath_skew_breaks_all_four_gates.md) · [Analyzer version skew on fresh worktree](project_analyzer_version_skew_fresh_worktree.md) — CS0006
- [SecurityCodeScan incompatible with Roslyn 5.6](project_securitycodescan_roslyn56_incompat.md) · [Missing VSTO runtime breaks baseline gates](project_missing_vsto_runtime_breaks_baseline_gates.md) — HISTORICAL
Expand Down Expand Up @@ -75,6 +77,7 @@
- [First-party coverage denominator (#197)](project_coverage_firstparty_denominator_method.md) · [dotnet-coverage denominator nondeterminism](project_dotnet_coverage_denominator_nondeterminism.md) — 47% vs 81%
- [Failed/red coverage run leaves RAW Cobertura](project_failed_coverage_run_leaves_raw_unprocessed_cobertura.md) · [runner throws before post-processing](project_coverage_runner_throws_before_postprocessing.md) · [Koverage post-processing shape](project_koverage_cobertura_postprocessing_shape.md) — not comparable with a processed one
- [C# canonical coverage artifact conversion](project_csharp_canonical_coverage_artifact_conversion.md) · [Cobertura runsettings `<Attributes>` override](project_cobertura_runsettings_attributes_override.md)
- [Package rollup must use the repo helper](project_cobertura_package_rollup_must_use_repo_helper.md) — a hand-written class-direct node count never equals the root attributes
- [Processed Cobertura filenames use backslashes](project_processed_cobertura_filenames_use_backslash.md) — forward-slash match returns zero rows; gate unevaluable
- [Cobertura hits vs MS-coverage partial](project_changed_line_coverage_cobertura_vs_mscoverage_partial.md) · [QFC #227 coverage tooling](project_qfc227_coverage_tooling.md)
- [#398 test-split gate gotchas](project_398_test_split_gate_gotchas.md) · [ExcludeFromCodeCoverage on partial = CS0579](project_excludefromcodecoverage_partial_class_cs0579.md)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
---
name: cobertura-package-rollup-must-use-repo-helper
description: Koverage root lines-covered/lines-valid come from a per-class dedup+max-hits merge of BOTH the class-direct and method-level line views, so a hand-written ./classes/class/lines/line node count cannot be asserted equal to them - call Get-CoberturaPackageLineSummary instead
metadata:
type: project
---

A plan that derives per-package coverage counters by hand and then asserts they equal the root
`<coverage>` attributes of a Koverage-processed Cobertura document states an equality that is not
guaranteed by either available node axis.

**Mechanism.** `ConvertTo-KoverageCoberturaXml` sets the root attributes from
`Get-CoberturaCoverageSummary` (`scripts/vscode/Invoke-MSTestWithCoverage.Helpers.ps1`), which sums
`Get-CoberturaPackageLineSummary` (`scripts/vscode/Invoke-MSTestWithCoverage.PackageRate.ps1`) over
`./package`, which sums `Get-CoberturaClassLineSummary` over `.//class`. That last helper builds a
map keyed by line **number** from the concatenation of `./lines/line` and
`./methods/method/lines/line`, keeps the **maximum** `hits` seen across the two views, and takes the
widest `condition-coverage` pair. Branches are counted only for lines carrying `branch="True"`.

Consequences for an assertion author:

- `./classes/class/lines/line` (class-direct only) misses any line that appears solely in the
method view, and reads a `hits` of 0 where the method view recorded a hit — common around async
state machines, which can emit method-level lines with no class-direct twin.
- `.//lines/line` double-counts, because the two views overlap.
- Counting a line as a branch whenever it carries a `condition-coverage` attribute over-counts
relative to the helper's `branch="True"` test.

**How to apply.** When a plan needs package-level counters that must reconcile with the root
attributes (for example a JaCoCo projection whose acceptance is exact equality), dot-source
`scripts/vscode/Invoke-MSTestWithCoverage.Helpers.ps1` — it dot-sources `PackageRate.ps1` and
`Threshold.ps1` for you — and call `Get-CoberturaPackageLineSummary -PackageNode $pkg`. It returns
`LineRate`, `BranchRate`, `LinesCovered`, `LinesValid`, `BranchesCovered`, `BranchesValid` as
strings, and the identity with the root attributes then holds by construction rather than by
assumption. Note both those files call `Set-StrictMode -Version Latest`, which applies to the
caller's scope after dot-sourcing, so use `GetAttribute()` rather than bare attribute property
access in the surrounding block.

Related: [[koverage-cobertura-postprocessing-shape]],
[[project_async_state_machine_emits_no_method_element]],
[[project_coverage_delta_reproduce_baseline_counting_method]].
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
name: dotsourcing-invoke-mstest-clobbers-coverageoutput-param
description: A wrapper script that dot-sources Invoke-MSTestWithCoverage.ps1 has its own $CoverageOutput parameter silently overwritten by that script's param-block default, so the Cobertura lands at coverage\coverage.cobertura.xml instead of the requested path
metadata:
type: project
---

A throwaway wrapper that takes `-CoverageOutput` and then dot-sources
`scripts/vscode/Invoke-MSTestWithCoverage.ps1` loses its own parameter value. That script's
`param` block declares `[string]$CoverageOutput = "coverage\coverage.cobertura.xml"` (line 9 as
of 2026-09-05), and dot-sourcing a script with a `param` block re-creates its parameter
variables in the CALLING scope with their defaults. Any later `dotnet-coverage collect --output
$CoverageOutput` therefore writes to the script's default path, not the caller's.

Observed on issue #781 [P0-T8]: run reported `Code coverage results: coverage\coverage.cobertura.xml.`
with `COLLECT_EXIT_CODE: 0` while `coverage\baseline-781.cobertura.xml` never existed. The
downstream post-processing task read the requested path and would have failed on a missing file.

**Why:** the collector exits 0 and the tests all pass, so nothing in the run signals the
deviation. Only the one-line `Code coverage results:` message names the real path, and a plan
that checks only the exit code and the test counts never reads it.

**How to apply:** a parameter name that also appears in a dot-sourced script's `param` block is
unsafe. Either capture the wrapper's value into a differently-named variable BEFORE the
dot-source (`$outPath = $CoverageOutput`) and use that at the collect line, or grep the run log
for `Code coverage results:` and copy the produced document to the path later tasks read. The
clobbering recurs on every run of the same wrapper shape, so the baseline and the final pass
both need the same correction. `$ResultsDirectory` is unaffected because that script has no such
parameter. Related: [[project_failed_coverage_run_leaves_raw_unprocessed_cobertura]],
[[project_coverage_runner_throws_before_postprocessing]].
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
name: plan-delegation-to-typed-engineer-without-dispatch-tool
description: A plan task that directs the executor to delegate code authoring to csharp-typed-engineer is unexecutable when the executor's tool surface has no sub-agent dispatch tool; perform the edits inline and record the substitution in the handoff artifact
metadata:
type: project
---

Minor-audit plans routinely open Phase 1 with a handoff task worded "the executor delegates the
code-authoring tasks to `csharp-typed-engineer` and re-verifies each acceptance condition
itself." When `atomic-executor` is launched as a sub-agent, its tool surface is Read, Grep, Glob,
Edit, Write, Bash, and the PoshQC MCP tools — there is no Task or Agent tool, so the delegation
cannot be performed.

**Why:** blocking is forbidden after execution begins, and the delegation is a means rather than
an independent outcome — the plan's real requirement is that the edits stay bounded by the task
text and that every acceptance condition is verified against the tree.

**How to apply:** perform the edits inline, bounded by the task text, and record the substitution
explicitly in the handoff artifact under a heading naming the executing worker, stating that the
re-verification obligation is unchanged and how it is discharged. Do not silently claim the
delegation happened, and do not treat the missing tool as a preflight blocker discovered
mid-plan. An orchestrator that wants a genuine delegation must either dispatch the engineer
itself or give the executor a dispatch tool. Related:
[[project_followup_promotion_task_is_unexecutable_by_executor]].
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: preflight-moving-base-two-dot-diff-inertness-test
description: A plan gate using `git diff origin/main -- <paths>` is only safe when origin/main has not touched those paths since the merge base; test inertness with `git diff --name-only <merge-base> origin/main -- <same paths>` before calling it blocking
description: A plan gate using `git diff origin/main -- <paths>` is only safe when origin/main has not touched those paths since the merge base; test inertness with `git diff --name-only <merge-base> origin/main -- <same paths>` before calling it blocking, and prefer making that test a second diff the executor runs and records under an INHERITED PATHS heading
metadata:
type: project
---
Expand All @@ -26,3 +26,34 @@ merge base `9b6aff2e` vs `origin/main` `2b85134b` differed by 77 files including
`QuickFiler/` production file, but zero of the six in-scope paths, so two `git diff origin/main`
gates stayed correct. Relates to [[baseline-sha-diff-conflates-merged-base]] and
[[preflight-mergebase-diff-gates-need-commit-cadence]].

## Better remedy: make the inertness test a task the executor runs

Do not settle for a preflight-time measurement plus a plan sentence asserting the result. The
preflight observation is about the tree at review time, and the branch can gain commits before the
task runs. Instead, have the plan carry BOTH diffs in the same task:

- `git diff --name-status <BASE_REF> -- <paths>` — two-dot, base commit against the WORKING TREE.
This is what the scope gate is actually about. Pair it with `git add --intent-to-add` so a
newly created file is visible, and with `git status --porcelain --untracked-files=all` as the
independent untracked observation.
- `git diff --name-only <BASE_REF>..HEAD -- <the same paths>` — the commit range, recorded under a
literal heading such as `INHERITED PATHS:`, with an acceptance clause requiring it to list no
path. This isolates what the branch's own commits already changed under those paths.

The union the gate evaluates must be taken from the worktree diff and the porcelain span ONLY. Folding
the commit-range diff into the union makes a non-empty inherited list fail the scope clause spuriously.

**Why:** it converts an assertion about commit contents into an observation the executor makes at run
time, so it stays correct if the branch advances between planning and execution. It also survives a
planner session that has no git tool available and therefore cannot re-derive a commit-contents claim
at authoring time — a real constraint seen on #781, where the planner replaced exactly such a claim
with this construction rather than assert it unverified.

**How to apply:** both diffs carry a ref operand, so neither trips G8; the `git add` and
`git status --porcelain` spans in the same task exonerate both under G8b. Keep the `--` pathspec
separator on every invocation, or G8b reads the pathspec as a ref operand.

Do NOT use the three-dot `<BASE_REF>...HEAD` form for the scope diff. It compares two commits and
never reads the working tree, so in a plan that stages without committing it returns an empty list
however the executor edits those files.
1 change: 1 addition & 0 deletions .claude/agent-memory/atomic-planner/MEMORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
- [#680](project_680_menu_mode_plan_seams.md) — HostTests.cs 499 not 500; "optional" fallback was load-bearing
- [#736 R1–R5](project_736_efc_archiveroot_boundary_sink_plan_seams.md) — 485-line test file voids the spec Write Set; R2: hardcoded `U=2` floor breaks when one item is a method body; R3: one shared `try` short-circuits the second delegate to zero invocations; R4: a name-shaped gate never measures a content-shaped rule, and the reviewer's own delta carried a false CI citation; R5: `*.log` evidence artifacts gated only on existence could never be committed
- [#731](project_731_lifecycle_disposal_plan_seams.md) — Defects468Tests 498/500 forces a partial continuation; QfcQueue.cs already 505; spec said ONE comment line not three; R3 = all 4 defects in gate wiring; R4 = worktree not bootstrapped (no `.dotnet-sdk/`, no `packages/`), `DebugType=full` leaks host paths, 1:1 line mapping unsatisfiable for a 1-to-20 hunk; R5 = case-insensitive token absence collides with a retained method name, `.//line` double-counts on two axes, unconditional AC17 check-off vs two degraded branches; R6 = document state is DERIVED from exit code (`:236`/`:341` throw before the `:343` write), an exclusion keyed on the regression's own definition is vacuous, `line-rate` is a fraction not a percentage, unanchored filename suffix also selects `IQfcDatamodel.cs`
- [#781](project_781_excludefromcodecoverage_guard_plan_seams.md) — `[ExcludeFromCodeCoverage]` on the partial class makes changed-line coverage unmeasurable; `CaptureCurrent` voids a null-ambient success test; guard swap voids its own sibling comments; R1: `"Any CPU"` fails a project-file build, `<ref>...HEAD` can't see an uncommitted plan, no node axis reproduces Cobertura root counts
- [#735 R1](project_735_evidence_content_sanitization_seams.md) — name-only sanitization gate can't fail; TRX `runUser=`/`computerName=` leak in content; csproj "between" clause self-contradictory

## Plan-structure traps
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
name: project-781-dispatcher-boundary-guard-plan-seams
description: SUPERSEDED — merged into project_781_excludefromcodecoverage_guard_plan_seams.md; do not add content here
metadata:
type: project
---

Superseded. This file was created in a pass that ran concurrently with the one that wrote
[[project-781-excludefromcodecoverage-guard-plan-seams]]. All of its content has been merged there,
including the `BreadcrumbUiDispatcher.CaptureCurrent()` unsatisfiability trap, the 480-line
correction, the threshold-assert ordering rule, and the concurrent-writer hazard.

Read `project_781_excludefromcodecoverage_guard_plan_seams.md` instead. This stub is retained only
so a stale link does not dead-end; it is not indexed in `MEMORY.md`.
Loading
Loading