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
2 changes: 2 additions & 0 deletions .claude/agent-memory/atomic-executor/MEMORY.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Atomic Executor Memory Index

## Plan validation & gates
- [Mid-plan commit needs a capture-time sanitisation gate](project_midplan_commit_needs_capture_time_sanitisation_gate.md) — a final whole-tree sweep cannot reach an earlier commit
- [Blocked Bash command drops chained check-off](project_blocked_bash_command_silently_drops_chained_checkoff.md) — aborts the WHOLE command
- [CSharpier chain-wrap defeats single-line search gates](project_csharpier_chain_wrap_defeats_singleline_search_gates.md) — zero-hit gate
- [Verify line citations with numbered output](feedback_verify_line_citations_with_numbered_output.md) — never hand-count
Expand Down Expand Up @@ -70,6 +71,7 @@
- [Async state machine emits no `<method>` element](project_async_state_machine_emits_no_method_element.md) — `.//line` double-counts
- [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 coverage run leaves RAW Cobertura](project_failed_coverage_run_leaves_raw_unprocessed_cobertura.md) · [Koverage Cobertura post-processing shape](project_koverage_cobertura_postprocessing_shape.md)
- [Coverage runner throws before post-processing](project_coverage_runner_throws_before_postprocessing.md) — a red suite leaves a RAW cobertura; 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)
- [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
Expand Up @@ -18,6 +18,17 @@ lines would have turned the search green by destroying documentation the plan it

The strip filter that worked: `grep -v -E '^\s*(///|//|\*|/\*)'` piped into `grep -F -c "$lit"`.

**PowerShell variant — `Select-String` is case-insensitive unless `-CaseSensitive` is passed.** A
zero-hit gate spelled `Select-String -SimpleMatch 'throw'` also matches `ThrowIfCancellationRequested`,
`NotThrowAsync`, `InvokesWithoutThrowing`, and `ThrowsAsync`. Issue #670's P1-T4 required zero `throw`
hits in a new fault-boundary file whose body — dictated by the same task — carried the comment
`// fault: InitializeWebViewAsync opens with Token.ThrowIfCancellationRequested().`, so the gate was
unsatisfiable as written. The fix is `-CaseSensitive`, not deleting the comment: a genuine rethrow is
always lower-case `throw;` or `throw ex;`, so the case-sensitive search still detects the thing the
gate exists to detect. Sweep every `Select-String` zero-match and exact-count condition in a plan for
this: the same trap fires on `'Task.Delay'` vs `task.delay`, and on any identifier whose PascalCase
form contains the banned lower-case token.

**How to apply:** at preflight, rewrite such a clause to name the executable-code measurement
explicitly. During execution, if the clause survives as written, record both counts side by side,
quote each raw hit with its file:line and the full comment text, and state plainly which reading is
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
name: coverage-runner-throws-before-postprocessing
description: Invoke-MSTestWithCoverage.ps1 throws on ANY non-zero vstest exit before it post-processes the Cobertura XML, so a red suite (or a sub-80% floor trip) leaves a raw, absolute-path, third-party-inclusive document that is not comparable with a processed one
metadata:
type: project
---

`scripts/vscode/Invoke-MSTestWithCoverage.ps1` reaches its post-processing block (`ConvertTo-KoverageCoberturaXml`
at line 340, `Assert-CoberturaLineCoverageThreshold` at 341, `Set-Content` at 343) only on a fully green run.
Two earlier exits bypass it:

- `Invoke-DotnetCoverageCollection` (same file, ~line 235) throws `MSTest with coverage failed with exit code N`
whenever the `dotnet-coverage`/vstest child exits non-zero — that is, on **any** failing test.
- `Assert-CoberturaLineCoverageThreshold` throws `Cobertura line coverage <p>% is below the required 80% threshold.`
at line 341, which is *before* the `Set-Content` at 343.

**Why it matters:** the artifact the runner leaves on disk is then the raw dotnet-coverage document —
absolute host filenames (account name included), third-party `<package>` nodes still present, no
`<sources>` node — whereas a green run leaves the processed document with repo-relative filenames and
third-party packages removed. A baseline captured in one state and a post-change run captured in the
other are **not comparable**: the denominators differ by the whole third-party surface, so any
"post-change covered lines >= baseline covered lines" or narrow percentage-band gate fails spuriously.

The two failure modes are distinguishable from captured stdout by their literals, and only the second
one is a coverage-floor trip rather than a test failure.

**How to apply:** in any plan that captures a baseline and a post-change Cobertura from this runner,
(1) record a `POSTPROCESSED: yes|no` flag per artifact (yes iff EXIT_CODE 0), (2) require the two flags
to agree before any delta gate is evaluated, and (3) supply the remedy — dot-source
`scripts/vscode/Invoke-MSTestWithCoverage.Helpers.ps1` and apply
`ConvertTo-KoverageCoberturaXml -XmlContent <raw> -RepoRoot <root>` to the unprocessed one. Also note
that a plan which tolerates a pre-existing baseline failure set (a `BASELINE_FAILURE_SET` / subset-relation
gate) must not simultaneously declare every non-zero stage-4 exit a failure requiring a restart: the
restart cannot clear a pre-existing failure. See [[exact-count-gate-vs-remediation-loop]] and
[[dotnet-coverage-denominator-nondeterminism]].
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
---
name: midplan-commit-needs-capture-time-sanitisation-gate
description: A plan with a mid-plan commit task cannot rely on a final whole-tree sanitisation sweep; every artifact the intermediate commit carries needs its own capture-time rewrite instruction plus a zero-sweep gate.
metadata:
type: project
---

When a plan commits evidence part-way through (for example a Phase 3 `git add`/`git commit`
that stages `evidence/` before Phase 4 runs), a whole-tree host-path sanitisation sweep placed
in the final phase does not protect those artifacts. Sanitisation in place cannot recover a
literal that is already in an earlier commit's tree.

The correct shape is per-artifact: each task that reproduces tool output into an artifact the
intermediate commit will carry must state (a) the rewrite instruction, naming the exact
placeholder spelling for each absolute root the tool prints, and (b) a capture-time zero-sweep
gate over that one artifact. State the gate even where the command's success-case output is
expected to name no path — the gate is cheap, it is failable, and its absence is what makes the
blanket "no artifact may contain an absolute host path" obligation unenforceable.

Two classes of command need this and are easy to miss:
- a command whose output enumerates roots the plan did not anticipate. `dotnet --list-sdks`
under a `global.json` with `paths: [".dotnet-sdk", "$host$"]` prints the machine-wide install
in addition to the repo-local one, so a repo-local-only rewrite leaves a bracketed absolute
path behind.
- a command that invokes a script which resolves a tool internally. The plan's binding text must
say it covers script-internal resolution, not only tasks that call `vswhere` directly.

A generated binary or XML artifact (`.trx`, Cobertura) cannot be authored with placeholders, so
it stays in the in-place sanitisation task's scope; the markdown artifacts move to capture-time
gating. Keep the two lists disjoint and keep the in-place task's file count arithmetic matching
its own enumeration.

**Why:** #670's plan needed three preflight rounds' worth of deltas to close this; rounds 2-4
each found another artifact whose only sanitisation reached it after the intermediate commit.

**How to apply:** when reviewing or authoring any plan that has both a mid-plan commit task and
a final sanitisation sweep, enumerate every artifact the mid-plan commit stages and check each
one has a gate of its own. See also [[project_sanitisation_task_cannot_sweep_its_own_record]]
and [[project_selftest_probe_literal_trips_the_next_sweep_pass]].
2 changes: 2 additions & 0 deletions .claude/agent-memory/atomic-planner/MEMORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
- [#662 R3](project_662_round3_trx_hygiene_and_verbatim_seams.md) — `*.trx` is NOT gitignored; sweep case-insensitively; exclude the plan file from the sweep's own zero-hit gate
- [#662 R2](project_662_banner_prefix_revision_round_seams.md) — `'*.xml'` scope gate hits the plan's own cobertura evidence; loop-restart needs a baseline-relative failure test
- [#663](project_663_qfc_alt_chord_plan_seams.md) — defect-preserving seam turns compile-red into runtime red; derive named-test outcomes from totals
- [#670](project_670_capture_time_sanitisation_seams.md) — a script that echoes a vswhere-resolved path leaks via an *indirect* invoker; gate sanitisation at capture time
- [#680](project_680_menu_mode_plan_seams.md) — HostTests.cs 499 not 500; TRX 5-shape identifiers, `grep -a`; exact line arithmetic

## Plan-structure traps
Expand All @@ -42,6 +43,7 @@
- [Fenced `#` comments look like headings](plan-fenced-powershell-comments-look-like-headings.md) — indent column-0 `#` inside code fences
- [One AC per check-off task](feedback_ac_checkoff_one_per_task.md) — preflight rejects batched AC check-offs
- [Terminal-phase planner traps](terminal-phase-planner-traps.md) — unowned follow-ups; artifacts written after the clean-tree commit task
- [A reviewer's enumeration may be deliberately narrow](reviewer-enumeration-may-be-deliberately-narrow.md) — omitted IDs are often bound by a sibling clause; "completing" the list can falsify its trailing predicate
- [Never plan a mid-plan halt on MCP availability](never-plan-a-mid-plan-halt-on-mcp-availability.md) — Phase 0 probe + record-and-continue
- [Thread granted discharges through consumers](thread-granted-discharges-through-consumers.md) — softening one task strands its discharge
- [Durable script copy into feature folder](durable-script-copy-into-feature-folder.md) — copy scratchpad scripts into `<FEATURE>/scripts/`
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
name: project-670-capture-time-sanitisation-seams
description: Issue #670 preflight rounds 2-5 — a script that echoes a vswhere-resolved path leaks a host literal into an intermediate commit; gate sanitisation at capture time, not in a later sweep
metadata:
type: project
---

Issue #670 (`QfcItemController.InitializeWebViewAsync` fault unobserved) took five preflight rounds.
Three of them were the same defect class, found one artifact at a time.

**The class:** a Phase 0 baseline task whose `Output Summary:` reproduces tool output that names an
absolute host path. If a Phase 3 commit task stages that artifact and the only sweep that reaches it
runs in Phase 4, the literal lands in an intermediate commit — and in-place sanitisation cannot
recover a literal already committed. The fix is always the same shape: a capture-time rewrite
instruction plus a capture-time zero-sweep gate in the task's own **Acceptance**, never a later
backstop.

**The non-obvious instance:** `scripts/vscode/Invoke-Restore.ps1` resolves MSBuild through `vswhere`
at `:27` and echoes it at `:32` (`Write-Host "Using MSBuild: $msbuildPath"`). A task that merely
*invokes* that script therefore leaks a resolved Program Files path even though the task text names
no `vswhere` call of its own. I initially flagged this as a judgment call and declined to act;
round 4 verified the mechanism and rejected the judgment.

**How to apply:** When auditing host-path obligations, enumerate tasks that resolve a tool
*indirectly* through a repo script, not just tasks whose command line contains `vswhere`. Grep the
invoked script for `Write-Host`/`Write-Output` of a resolved path. Also check what the invoked tool
*relays*: `Invoke-Restore.ps1:36` passes an absolute `$resolvedSolutionPath` to MSBuild `/t:Restore`,
whose own output then names project and package directories absolutely.

**Corollary that held:** gating the two artifacts at capture time left `P3-T14`'s four-file
in-scope list correct and unchanged — once they carry no host literal when P3-T14 runs, they are
not "artifacts produced so far that carry host identifiers". Fixing upstream avoided touching a
count downstream. Related: [[observation-scope-must-match-blast-radius]],
[[../_shared_no_absolute_host_paths]].
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
name: reviewer-enumeration-may-be-deliberately-narrow
description: Before "completing" a task-ID enumeration in a reviewer-supplied delta, check whether the omitted IDs are excluded because a different clause already binds them
metadata:
type: feedback
---

When a preflight delta hands you a verbatim enumeration of task IDs, do not add the IDs that look
missing until you have checked what predicate the enumeration's trailing clause asserts. The
omitted IDs are frequently omitted on purpose because a *sibling* clause already binds them, and
adding them makes the trailing clause false.

**Why:** On issue #670 round 4, a delta bound section 0's host-path obligation with
`P1-T6, P2-T6, P3-T3, P4-T3 and P4-T4 reuse the $msbuild variable ... so no resolved path enters
those artifacts`. I judged the list incomplete because P3-T5, P3-T6 and P3-T10 also rebuild with
P0-T10's `& $msbuild` command, and added them. That broke the clause: those three tasks ALSO run
vstest, so a resolved `vstest.console.exe` path does enter their artifacts — and the immediately
preceding sentence had already bound them via the `<vs-install>` test-runner placeholder. The
reviewer's five were exactly the msbuild-only artifacts. I caught it in self-review and reverted
to the verbatim text.

**How to apply:** For each ID you are tempted to add, read its task body for every command it
records, not just the one the enumeration is about. If it appears in an adjacent enumeration, it
is already bound and belongs there, not here. A generalizing tail such as "where an executor
records a resolved path instead, the same placeholder applies" is the reviewer's own signal that
the list is a positive claim about a subset, not an exhaustive partition — so an apparent gap is
not a defect. Related: [[acceptance-edits-must-be-false-before-true-after]],
[[single-numeral-gates-must-name-the-role]].
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 @@ -70,6 +70,8 @@
- [msbuild-log byte-equality gate adjudication](project_493-review-residuals-and-msbuild-log-gate-adjudication.md) — #493: how a structurally unsatisfiable msbuild-log byte-equality gate was adjudicated without downgrading the AC
- [StoreWrapperController absent from Cobertura](project_storewrapper-controller-absent-from-cobertura.md) — whole class missing from both baseline+post Cobertura XML despite only 2/N members `[ExcludeFromCodeCoverage]`; pre-existing (#287), don't treat absence as evidence either way
- [287-review-outcome](project_287-review-outcome.md) — PASS/0 blocking; 85.297%/79.293% repo-wide clears both coverage floors; positive base-anchor-divergence handling (contrast [[Stale caller-supplied merge-base]])
- [Cobertura `.//line` double-counts under a class](project_cobertura-class-line-double-count-trap.md) — #670: method rows re-count field initializers; use `lines/line` and cross-check `line-rate`
- [Verify the caller's factual "correction"](feedback_verify-the-callers-factual-correction.md) — #670: caller's ODE-absent claim was wrong; they'd found D3/D4, not D5's throw at Breadcrumb.cs:391-393
- [review-residuals index (misc closed issues)](project_review-residuals-index.md) — one-line PASS/0-blocking pointers for #442/#444/#446/#449/#457/#468/#476/#484/#488/#501/#511/#553/#614/#635/#663 with their specific residual/owed items, for lookup if any resurfaces

## Artifact hygiene
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
name: verify-the-callers-factual-correction
description: When a caller asserts the requirement docs are factually wrong and asks for your independent assessment, verify the assertion itself — at #670 the docs were right and the caller had located a different defect
metadata:
type: feedback
---

A delegating prompt that says "the requirement documents contain a known factual imprecision, give me
your independent assessment" is still a claim to be tested, not a premise to build on. Verify the
correction with the same rigor you would apply to the original.

**Why:** at #670 the caller stated that `issue.md`/`spec.md` wrongly describe the issue-#488 D5 path
as raising `ObjectDisposedException`, citing `ItemViewer.Breadcrumb.cs:420-436` (`ThrowIfOffUiBoundary`)
and `:64` (different-provider) as raising `InvalidOperationException`, and asserted "No
`ObjectDisposedException` is raised on that path." Both cited sites were real — but they are #488
defects **D4** (UI-thread affinity) and **D3** (fail-fast on provider substitution). D5 is a third,
separate guard at `:391-393`:

if (IsDisposed || Disposing) { throw new ObjectDisposedException(nameof(ItemViewer)); }

with an in-source comment at `:383-384` naming it "Issue #488 defect D5's `ObjectDisposedException`
throw". It is reachable: `ViewerSetup.cs:112` → `:150 InitializeBreadcrumbPipeline` →
`Breadcrumb.cs:74 EnsureBreadcrumbLifecycle` → `:361 EnsureBreadcrumbResourceOwnership` → `:393`.
The requirement documents were accurate and needed no correction.

**How to apply:**
- A `grep` for the exception type across the whole file, not just the caller's cited line range, is
the ten-second check that settles it. The caller's range excluded the throw.
- Where several numbered defects (D3/D4/D5) landed in one file from one upstream issue, confirm
WHICH defect a guard implements before attributing behaviour to it — adjacent guards in the same
file routinely throw different types.
- Report the contradiction plainly and give the call chain as evidence; do not soften it into
agreement. Then state the consequence — at #670 it was nil, because no AC depended on the
exception type and the boundary catches `Exception`.

Related: [[verify-parity-claims-in-remediation-inputs]], [[verify-the-asserted-evidence-mechanism]].
Loading
Loading