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
20 changes: 20 additions & 0 deletions .claude/agent-memory/atomic-executor/MEMORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,3 +107,23 @@
- [Self-test probe literal trips the NEXT sweep pass](project_selftest_probe_literal_trips_the_next_sweep_pass.md) — describe probes, never quote them
- [TRX sanitisation must be case-insensitive](project_trx_sanitisation_must_be_case_insensitive.md) · [TRX/msbuild evidence needs a sanitisation micro-action](project_vstest_trx_evidence_needs_sanitisation_task.md)
- [PowerShell budget hook blocks scratch .ps1 helpers](project_powershell_scratch_script_budget_hook_blocks_helpers.md) — cap counts others
- [Appending a 2nd pass must not qualify schema fields](project_appending_a_second_pass_must_not_qualify_schema_fields.md) — `Output Summary (pass 1):` deletes the field
- [Agent Bash resets cwd; use `env -C`](project_bash_cwd_resets_use_env_dash_c.md) — standalone `cd` does not persist; dotnet reads the wrong global.json
- [Changed-line coverage branch gate invalidated by the fix](project_changed_line_coverage_branch_gate_invalidated_by_the_fix.md) — the fix makes the line analyzable; baseline-keyed branch is unsatisfiable
- [CSharpier forces a blank line before a comment](project_csharpier_requires_blank_line_before_comment_breaking_numstat_bounds.md) — one comment line costs 2 insertions; breaks a "1 insertion" numstat bound
- [dotnet global.json cwd-search vs no-cd Bash discipline](project_dotnet_global_json_cwd_search_vs_bash_discipline.md) — invoke pinned SDK exe by absolute path + `--tool-manifest`
- [ExcludeFromCodeCoverage misses `this`-capturing lambdas](project_excludefromcodecoverage_misses_this_capturing_lambdas.md) — lifted beside the member, stay at hits=0; prefix is entity-encoded in the XML
- [FakeTimeProvider zero due time fires at creation](project_faketimeprovider_zero_duetime_fires_at_creation.md) — "not completed before Advance" is false at TimeSpan.Zero
- [Koverage -RepoRoot needs native separators](project_koverage_reporoot_needs_native_separators.md) — a forward-slash root strips nothing; the "processed" doc stays raw
- [MSBuild logs leak TWO absolute roots](project_msbuild_log_has_two_absolute_path_leak_classes.md) — `/analyzerconfig:` hits the ancestor checkout; sanitise worktree root FIRST
- [MSTest Deploy_ dir leaks tokens on FAILING runs only](project_mstest_deploy_dir_leaks_tokens_on_failing_runs.md) — breaks "exactly one TRX" + the name gate
- [Plan-mandated .ps1 helpers hit the budget cap + a frozen porcelain gate](project_plan_mandated_ps1_helpers_collide_with_budget_cap_and_frozen_porcelain_gate.md) — fix ONE gitignored path
- [pwsh -File starts in the SESSION root](project_pwsh_file_starts_in_session_root_needs_workingdirectory.md) — script's own `git rev-parse` hits the wrong worktree; pass `-WorkingDirectory`
- [pwsh stdin is a REPL; non-ASCII mangled](project_pwsh_stdin_repl_mode_and_nonascii_mangling.md) — multi-line blocks output NOTHING; em-dash searches read false 0
- [Reflective property read escapes a member-expression grep](project_reflective_property_read_escapes_member_expression_grep.md) — `GetProperty("X")` is invisible to `git grep "Type.X"`; a new throwing guard broke 8 cleared tests
- [vstest leaves TWO .coverage files per run](project_vstest_emits_two_coverage_files_per_run.md) — an "exactly one attachment" gate is unsatisfiable by construction
- [ExpectedExitCode keyed off the baseline, not this run](project_expectedexitcode_declared_from_baseline_not_observed_run.md) — vacuous toward
- [Preparation mode flips anchored-diff membership](project_preparation_mode_flips_anchored_diff_gate_membership.md) — feature folder
- [FluentAssertions BeEmpty names only the first item](project_fluentassertions_beempty_names_only_first_item.md) — a gate demanding two type names in the BeEmpty failure text is unsatisfiable
- [vstest success run prints no Failed/Skipped line; TRX notExecuted is hard-coded 0](project_vstest_success_run_prints_no_failed_or_skipped_line.md) — derive Skipped as total minus executed; a gate that greps a Skipped: line on a green run is unsatisfiable
- [Worktree-isolation guard refuses pwsh launched from Bash in two shapes](project_worktree_isolation_guard_refuses_pwsh_from_bash.md) — a quoted absolute path as the command name is refused too; use a PATH= prefix and bootstrap the SDK from POSIX paths
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ When creating a new PowerShell file (e.g., `*.Tests.ps1`) in this repository, pr

**Why:** PSScriptAnalyzer is configured to enforce `PSUseBOMForUnicodeEncodedFile`. This bit cycle 0 of issue #25 (one loop restart) and again cycle 1 (one loop restart). The Write tool produces UTF-8 without BOM by default on Windows.

**Scope correction (measured 2026-09-03, issue #752):** the rule fires only on a file that actually contains non-ASCII bytes. A **pure-ASCII** new `.ps1` needs no BOM. All ten pre-existing files in `tests/scripts/vscode/` are `BOM=False NONASCII=0`, and a new pure-ASCII test file there drew zero PSSA diagnostics (16 before, 16 after, identical set). So: if the plan mandates pure ASCII, write it BOM-free and match the siblings; add the BOM only when the file genuinely needs a non-ASCII character. Do match the siblings' **CRLF** line endings, and note that `[System.IO.File]::ReadAllText/WriteAllBytes` ignore `Set-Location` — pass an absolute path or you silently operate on a same-named file in another worktree.

**How to apply:** After Write-ing a new .ps1 file, run a one-shot BOM-prepend command before invoking `mcp__drm-copilot__run_poshqc_analyze`:

```powershell
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
name: appending-a-second-pass-must-not-qualify-schema-fields
description: Relabelling "Output Summary:" to "Output Summary (pass 1):" when appending a re-run section deletes the machine-checkable field; the completeness gate reports the artifact INCOMPLETE.
metadata:
type: project
---

When a contingency forces a gate to run twice and you append a "pass 2" section to the existing
artifact, do not qualify the required schema field names to disambiguate the passes.

**Why:** `evidence-and-timestamp-conventions` requires the literal fields `Timestamp:`, `Command:`,
`EXIT_CODE:` and `Output Summary:`. Rewriting the original line to `Output Summary (pass 1):` and
adding `Output Summary (final, ...):` leaves the file with ZERO occurrences of the exact literal, so
a completeness audit reports the artifact incomplete even though it reads as complete to a human.
The parenthetical is the whole defect: an exact-literal scan does not tolerate it.

**How to apply:** keep exactly one unqualified `Output Summary:` line that covers BOTH passes, and
label the narrative sections instead ("Pass 1 result:", "## Pass 2 — the re-run"). Same rule for the
other three fields. If a second `EXIT_CODE:` is genuinely needed, put the re-run in its own artifact
rather than qualifying the field in a shared one — the schema is per-file, so one artifact carries
one field set.

Caught on issue #735 by the P5-T10 completeness gate, on the `file-line-counts` artifact after the
P4-T3 branch B extraction forced P4-T1 and P4-T2 to be re-run. The gate did its job; the point is
that the mistake is invisible on reading and only a literal scan finds it.

Related: [[project_artifact_output_summary_breaks_its_own_exact_count_gate]].
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
name: bash-cwd-resets-use-env-dash-c
description: Agent-thread Bash resets cwd between calls so a standalone `cd` does not persist; `env -C <dir> <cmd>` runs a plan's relative-path command from the worktree root with no `cd` and no chaining
metadata:
type: project
---

In an agent thread the Bash tool resets its working directory to the session root between calls. A
standalone `cd <worktree>` followed by `pwd` in the next call reports the session root again, so the
"working directory persists between calls" note in the tool description does not hold here.

Use `env -C "<ABSOLUTE WORKTREE PATH>" <command...>` as a uniform prefix instead. It is a single
unchained command, uses no `cd`, and satisfies a delegation's "never use `cd`" discipline literally
while still running the command from the worktree root.

**Why:** Atomic plans are written with relative operands (`TaskMaster.sln`,
`UtilitiesCS.Test/bin/Debug/UtilitiesCS.Test.dll`, `> TestResults/x.diff`) and say "run from the
worktree root". Rewriting them all to absolute paths would violate execute-verbatim AND break
acceptance clauses that assert the recorded command line. `dotnet` is the sharpest case: it resolves
`global.json` from cwd, so a bare `dotnet --version` from the wrong worktree exits 155 with
`The repo-local .NET SDK is missing` even when the item worktree has a working `.dotnet-sdk/`.

**How to apply:** Prefix every plan command block with `env -C <worktree> `, place it BEFORE any
`MSYS_NO_PATHCONV=1` or `PATH=` assignment, and record the prefix in the evidence artifact's
`Command:` field. Well-written plans already word command-line gates as "contains
`msbuild.exe TaskMaster.sln`" rather than "begins with", which accommodates the prefix. Combine with
[[project_pwsh_command_quoting_from_bash]] and
[[project_msys_slash_switch_conversion_rule]].
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
name: changed-line-coverage-branch-gate-invalidated-by-the-fix
description: A plan gate that keys its expected changed-line coverage off the BASELINE measurement is unsatisfiable when the fix itself makes the line analyzable for the first time.
metadata:
type: project
---

A two-branch acceptance gate of the form "if the baseline recorded the changed line as COVERED, assert COVERED; if it recorded NOT REPORTED, assert NOT REPORTED; any other combination is a stop-and-report" can be driven into its own stop-and-report clause by a correct fix.

**Why:** Pester's JaCoCo writer emits a `<line nr="N">` counter only for lines that carry an *analyzable command*. In issue #752 the pre-fix line 301 held only an operand of an `-and` chain begun three lines earlier, so the baseline XML had a `sourcefile` node for the file and no node at all for line 301 (`LINE301 NODE COUNT=0`). The fix replaced that operand with a `[System.IO.Path]::GetRelativePath(...)` call, which *is* an analyzable command: analyzed commands went 802 -> 803, a line-301 node appeared, and it was covered. Baseline `false`, post-change `COVERED` — exactly the combination the plan classified as a stop-and-report, reached by the change working as designed.

**How to apply:** When a plan's changed-line coverage branch keys off a baseline non-observation, expect the third combination and do not force the measurement into a branch. Writing `NOT REPORTED` would have required also writing the branch's mandated note asserting that no per-line counter exists — false against the post-change XML. Record the measurement truthfully, add a clearly-labelled divergence section giving the analyzed-command delta as the mechanism, leave that task's checkbox unchecked, and escalate at completion; the blocking condition (post-change >= baseline) is what actually matters and it held. Related: [[project_coverage_delta_reproduce_baseline_counting_method]], [[feedback_never_predict_an_observation_into_an_artifact]].

At authoring time, the fix is to make such a gate assert `post-change >= baseline` on the file percentage plus "the changed line is covered OR carried no counter at baseline", rather than demanding parity with the baseline's reporting state.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
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
description: Three measurement idioms that silently report the wrong number in plan gates - CSharpier's "Formatted N files" is a processed count not a rewrite count, Measure-Object -Line omits blank lines, and -clike reads a markdown checkbox as a wildcard character class
metadata:
type: project
---
Expand All @@ -25,6 +25,11 @@ Use `(Get-Content -LiteralPath $f).Count`, and cross-check with `wc -l`. The two

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.
**3. `-like`/`-clike` treats a markdown checkbox as a WILDCARD CHARACTER CLASS.**
Counting AC check-offs with `Where-Object { $_ -clike "- [x] *" }` returns **0** on a file where every line matches, because PowerShell wildcards give `[...]` character-class meaning: `- [x] *` means "dash, space, the single character `x`, space, anything". `- [ ] *` is worse — it raises `WildcardPatternException: The specified wildcard character pattern is not valid` once per pipeline item, and if the exception stream is not being read the surviving count still looks like a legitimate 0.
A zero here reads exactly like "no criteria are checked off", which is the answer a Phase 6 verification gate is looking for, so the wrong verdict is the plausible one.
Use a regex instead: `$_ -cmatch '^- \[x\] '`, or the Grep tool with `^- \[x\] ` (case-sensitive by default), which also gives line numbers to confirm the hits fall inside the AC section's line range rather than in an unrelated checkbox list elsewhere in the file (spec.md carries a severity picker at lines 24-27 that inflates a whole-file count by one).

**Why:** All three idioms look authoritative and produce a plausible number, so none of the failures 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,14 @@
---
name: csharpier-blank-line-before-comment-breaks-numstat-bounds
description: CSharpier forces a blank line between a member declaration and a following comment, so a "one comment line" insertion costs 2 lines and breaks any numstat bound written as 1 insertion
metadata:
type: project
---

CSharpier (1.2.6, this repo) **requires a blank line between a member declaration and a comment that follows it**. Inserting one standalone comment line above a field declaration therefore costs **two** inserted lines after `csharpier format .`, not one. This holds for both `//` and `///` comment styles — verified by removing the blank line and running `dotnet tool run csharpier check <file>`, which exits 1 and prints an `Expected: Around Line N` block containing the blank line, for each style.

**Why:** it broke issue #731's `[P5-T10]` scope gate. `[P1-T1]`/`[P1-T3]` inserted exactly one comment line above an `IEmailMoveMonitor` field in `QfcCollectionController.cs` and `QfcQueue.cs`, and both files measured baseline+1 immediately afterwards, satisfying those tasks. `[P5-T1]`'s mandated repository-wide format then added the blank lines, and the anchored `git diff --numstat` came out `3 1` against a bound of "at most 2 insertions, at most 1 deletion", and `2 0` against "at most 1 insertion, 0 deletions". Both breaches were exactly one formatter-inserted blank line. The *substance* of the AC ("the diff is limited to one statement and one comment line") was met; the numeric proxy encoding it was not.

**How to apply:** when a plan bounds a file with `git diff --numstat` and the change adds a standalone comment line, budget **2 insertions per comment**, not 1. Flag this at preflight: a bound of "1 insertion" for a comment-only change is unsatisfiable in any repo that runs CSharpier over the file. The two alternative placements do not help — a trailing comment on the declaration line, and deleting a nearby blank to compensate, are each 1 insertion **plus 1 deletion**, which breaks a `0 deletions` bound instead. There is no placement costing one net insertion. Also note the ordering trap: a task that verifies `line count == baseline + 1` *before* the format step passes, and the same file then fails a post-format diff bound, so the two checks disagree without either being wrong.

Related: [[csharpier-formats-xml-print-width]], [[new-cs-files-guarantee-a-format-loop-restart]].
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
---
name: dotnet-global-json-cwd-search-vs-bash-discipline
description: dotnet's global.json/tool-manifest resolution walks the process cwd, not an invoked exe's location, which breaks under a no-cd Bash-discipline delegation where the Bash tool's default cwd is a different worktree than the item worktree
metadata:
type: project
---

When a delegation prompt enforces "never use cd, address the item worktree via absolute paths"
(the standard TaskMaster item-worktree Bash-discipline block), a bare `dotnet` invocation for
SDK-pinned commands (`dotnet tool restore`, `dotnet tool run csharpier ...`) can fail with the
item worktree's own `global.json` errorMessage ("The repo-local .NET SDK is missing...") even
though the SDK was correctly installed into that worktree's `.dotnet-sdk/`.

**Why:** the .NET muxer's `global.json` search walks the **process working directory** upward,
not the location of the `dotnet.exe` you invoke. The Bash tool's default cwd is the session
worktree (e.g. `TaskMaster-wt/<session>/`), not the item worktree named in the delegation
(`.claude/worktrees/<item>/`) — confirmed via `pwd`. Since `cd` is prohibited and every other
command must take absolute-path operands, there is no literal way to point `dotnet`'s cwd-based
resolution at the item worktree.

**How to apply:**
- Invoke the item worktree's own pinned SDK executable by absolute path
(`<item-worktree>/.dotnet-sdk/dotnet.exe ...`) — this self-resolves for simple commands
(`--version`) without needing cwd-based version negotiation.
- For `dotnet tool restore`, pass `--tool-manifest <item-worktree>/dotnet-tools.json` explicitly
rather than relying on cwd-based manifest discovery.
- For `dotnet tool run <cmd>` (no `--tool-manifest` flag exists for this subcommand), the manifest
is resolved by cwd search regardless. If the session worktree is a sibling checkout of the SAME
repo and its own `dotnet-tools.json` is byte-identical to the item worktree's (verify with
`diff`/`cat` before relying on this), running from the default cwd resolves the same pinned tool
version. This is safe because CSharpier operates on the absolute file-path arguments you pass,
regardless of which cwd's manifest resolved the tool binary. Document the substitution in the
evidence artifact.
- MSBuild.exe and vstest.console.exe are standalone native executables (resolved via `vswhere`),
not the `dotnet` muxer — they are unaffected by this issue as long as the solution/project/DLL
paths passed to them are absolute.
- Record the deviation explicitly in the P0-T8/P0-T9-equivalent evidence artifact rather than
silently working around it, since it is a real mechanical consequence of the Bash-discipline
contract, not a plan defect.

See also [[project_planner_and_executor_observe_different_worktrees]] and
[[project_relative_path_in_pwsh_dotnet_io_hits_wrong_worktree]] for the same class of
cwd/worktree-mismatch issue in other tools.
Loading
Loading