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

## Plan validation & gates
- [Self-derived gate thresholds are blind](project_preflight_selfderived_gate_thresholds_are_blind.md) — a "count >= floor" gate whose floor comes from the runs it validates is deflation-blind + scope-incommensurable; use git-enumeration + `/ListTests` existence proofs
- [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
- [#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
Expand All @@ -22,11 +23,16 @@
- [Legacy csproj: no transitive compile refs](project_legacy_csproj_no_transitive_compile_refs.md) — non-SDK ProjectReference doesn't flow package types to csc (CS0012 despite copy-local DLL); tests need their own `<Reference>` + packages.config entry
- [sln/csproj edits: preserve CRLF](project_sln_csproj_edit_crlf_preserve.md) — git-bash `sed -i` strips CRLF from TaskMaster.sln (churn + BOM loss); use Edit or `perl -0777` w/ explicit `\r\n`
- [Incremental build makes a vacuous baseline](project_incremental_build_vacuous_baseline.md) — Invoke-VSBuild's /t:Build up-to-date check ignores /p: changes → EXIT 0 with 0 CoreCompile; add /t:Rebuild to enumerate diagnostics
- [Nullable /t:Build gate is vacuous](project_nullable_build_gate_is_vacuous_incremental.md) — the standard nullable gate passes without type-checking; isolated `/t:Rebuild ... /p:BuildProjectReferences=false` exposed 223 errors (never add /p:OutputPath — it breaks ProjectReference resolution)
- [CSharpier 1.3.0 formats XML at 100 cols](project_csharpier_formats_xml_print_width.md) — a "reformatting churn" finding on an XML resource can be formatter-mandated; measure line length + run repo-wide `check` before accepting it
- [Evidence <TS> collision clobbers committed artifacts](project_evidence_timestamp_collision_clobbers_artifacts.md) — same-day remediation can silently overwrite implementation-cycle evidence; a ` M` under `evidence/` means clobber
- [csharpier pipe-files is a non-enforcing gate](project_csharpier_pipefiles_nonenforcing_gate.md) — use `csharpier check`/`format`; tests balloon past 500 lines under genuine format (size new files AFTER format)
- [PowerShell new files need UTF-8 BOM](powershell-bom-required.md) — PSScriptAnalyzer enforces PSUseBOMForUnicodeEncodedFile; prepend BOM after Write or restart the format loop
- [poshqc Pester MCP exits -1](project_poshqc_pester_mcp_exit_minus1.md) — run_poshqc_test exits -1 (no detail) here; run it for the record, pair with direct Invoke-Pester (pwsh7) for the numeric proof
- [BOM breaks grep ^ anchor](project_bom_grep_anchor_false_negative.md) — bash grep `^#nullable` misses BOM-prefixed files; use the Grep tool for opt-in classification, never bash grep

- [Compile-time red needs body-level refs](project_compile_red_needs_body_level_references.md) — a missing type in a method SIGNATURE suppresses body binding, so an `[expect-fail]` task requiring N named CS0246s reports only 1; construct the types inline in test bodies

## Test execution & isolation
- [vstest /InIsolation + FilePathHelper serialization](project_vstest_isolation_and_filepathhelper_serialization.md) — Moq assemblies need /InIsolation (else STTE Setup FileNotFound); FilePathHelper.FilePath is "" default but null after JSON deserialize
- [Invoke-MSTest.ps1 dies on a single test assembly](project_418_invoke_mstest_single_assembly_bug.md) — StrictMode + `.Count` on a scalar String throws before vstest runs; call vstest.console.exe directly with the script's arg list
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
name: compile-red-needs-body-level-references
description: A compile-time [expect-fail] task that must name N missing types will only report the ones bound in the same phase; a missing type in a method SIGNATURE suppresses all body-level diagnostics
metadata:
type: project
---

When an `[expect-fail]` task's acceptance criterion requires the fail-before diagnostics to name several not-yet-existing types, put every reference in a **method body**, never in a signature.

Roslyn binds declarations first. If a private helper is declared `private static EngineReadinessGate CreateGateOver(...)` and `EngineReadinessGate` does not exist, the compiler emits one `CS0246` for that signature and then **does not bind any method body**, so a second missing type referenced only inside bodies never surfaces. Measured on #503 (2026-08-08): first run produced 1 diagnostic naming only `EngineReadinessGate`; after changing the helper's return type to a resolvable `Func<IAppItemEngines>` and constructing both types inline in the test bodies, the same command produced 4 diagnostics naming both `EngineReadinessGate` and `EngineGatedCommandRunner`.

**Why:** the plan's binary outcome was "diagnostics must include CS0246 naming X and Y". Recording a partial diagnostic set as satisfying it would be a false PASS, and re-running the build does not help — the shape of the test file is what determines which errors are reachable.

**How to apply:** before running the `[expect-fail]` build, check that no not-yet-existing type appears in any `class`/method/field declaration in the new test file. Keep helper signatures built only from types that already compile. Record the restructure in the fail-before artifact so the edit between the two runs is auditable.
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
name: csharpier-formats-xml-print-width
description: CSharpier 1.3.0 formats XML (not just .cs) and enforces its 100-column print width, so an "avoidable reformatting churn" finding on an XML resource can be formatter-mandated and unsatisfiable
metadata:
type: project
---

CSharpier **1.3.0 in this repo formats `*.xml`, not only `*.cs`**, and enforces its default **100-column print width** on them. `.csharpierignore` excludes `**/evidence/**`, `*.cobertura.xml`, `*.coverage`, `*.coveragexml`, `*.trx`, `*.csproj`, `*.props`, `*.targets` — but **not** `*.xml` generally, so `TaskMaster/Ribbon/RibbonExplorer.xml` is formatter-governed.

**Why:** Issue #503 remediation cycle 1 pinned a finding (F2) asserting that expanding three `<button>` elements from one line to six was "incidental churn with no functional purpose", and required collapsing them back to single-line while keeping a newly added attribute. The collapse passed every scoped gate at 524 lines, then the repo-wide `csharpier check .` failed with CSharpier's *Expected* output showing the six-line form. Arithmetic: the merge-base single-line `<button id="TriageSetA" onAction="TriageSetA_Click" label="Set A" />` is **78 chars**; adding `getEnabled="EngineCommand_GetEnabled"` makes it **116 chars**, over the 100 limit. The multi-line expansion was formatter-mandated, not gratuitous. The plan's own section 3 rule 6 ("CSharpier does not format XML") was false, and the F2 acceptance gate (<= 527 lines) was unsatisfiable while the mandatory format gate must pass.

**How to apply:**
- Before accepting any plan/review finding that an XML (or other non-`.cs`) reformatting is "avoidable churn", measure the resulting line length against 100 columns and run `csharpier check .` on the candidate form. A scoped gate passing proves nothing; only the repo-wide check does.
- When a pinned edit turns out to conflict with a mandatory gate mid-execution: **revert the edit**, restore the gate to green, restart the phase per its own loop semantics, and escalate the finding as *not remediable as specified* with the measured arithmetic. Do **not** add the file to `.csharpierignore`, raise `printWidth`, or accept a red format gate — all three are gate-weakening or scope-widening.
- The only route to shrinking such a file is splitting the resource, which is its own issue.

See also [[project_sln_csproj_edit_crlf_preserve]] and [[project_csharpier_pipefiles_nonenforcing_gate]].
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
name: evidence-timestamp-collision-clobbers-artifacts
description: A remediation cycle's <TS> can collide with committed implementation-cycle evidence filenames and silently overwrite them; check git ls-files before writing
metadata:
type: project
---

Evidence filenames are `<kind>/<name>.<TS>.md`. A **remediation cycle run on the same day** as the implementation cycle can resolve `<TS>` to a value that collides exactly with an already-committed artifact, and the Write tool will **silently overwrite** it — the loss is invisible until `git status --porcelain` shows the path as ` M` (tracked, modified) rather than `??` (untracked).

**Why:** In issue #503 remediation cycle 1, `<TS>` resolved to `2026-08-08T14-52`, which is exactly the timestamp of the committed implementation-cycle artifact `evidence/qa-gates/tests-with-coverage.2026-08-08T14-52.md` (its P6-T6 record). The remediation P3-T6 write destroyed it. It was only caught at the P3-T11 scope-lock audit, which classifies porcelain entries and noticed a ` M` under `evidence/` where every other cycle artifact was `??`.

**How to apply:**
- Before writing the first evidence artifact of a cycle, run `git ls-files '<FEATURE>/evidence'` and compare the planned filenames against the committed set. Same-day cycles are the high-risk case.
- On collision: restore the original with `git checkout -- <path>` (verify the content came back), then write the new record to a disambiguated name such as `<name>.remediation.<TS>.md`, and record the disambiguation in the artifact body — the plan's stated filename is not worth destroying prior evidence for.
- Treat any ` M` entry under `evidence/` in a scope-lock audit as a clobber until proven otherwise; cycle artifacts should be `??`.

See also [[project_preflight_mergebase_diff_gates_need_commit_cadence]].
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
name: nullable-build-gate-is-vacuous-incremental
description: The plan-standard `msbuild /t:Build /p:Nullable=enable /p:TreatWarningsAsErrors=true` gate returns EXIT 0 without type-checking, because MSBuild's up-to-date check ignores /p: changes; verify with an isolated /t:Rebuild
metadata:
type: project
---

The repo-standard type-check gate `msbuild TaskMaster.sln /t:Build /p:Configuration=Debug /p:Nullable=enable /p:TreatWarningsAsErrors=true` reports `Build succeeded. 0 Error(s)` in ~1.6 s whenever a prior build (e.g. the analyzer gate) already produced current outputs. MSBuild's up-to-date check compares timestamps only and **ignores `/p:` property changes**, so `CoreCompile` never runs and no nullable analysis happens. The gate is symmetric between baseline and post-change, so it always "passes" — vacuously.

Measured on #503 (2026-08-08): the `/t:Build` form gave 0 errors. A forced
`msbuild TaskMaster\TaskMaster.csproj /t:Rebuild /p:Configuration=Debug /p:Platform='AnyCPU' /p:Nullable=enable /p:TreatWarningsAsErrors=true /p:BuildProjectReferences=false`
gave **223 errors** (`CS8600/8601/8602/8603/8604/8618/8619/8625`), concentrated in `TaskMaster\AppGlobals\*` (AppOlObjects 58, AppAutoFileObjects 52, AppToDoObjects 48, AppOlObjects.FolderTreeService 48, AppStagingFilenames 40, ApplicationGlobals 40, AppItemEngines 18). Exactly 3 of the 223 were in newly-authored code.

Do NOT add `/p:OutputPath=<scratch>` to isolate the probe: it also redirects project-reference resolution and produces bogus `CS0006 Metadata file '...QuickFiler.dll' could not be found`. Use `/p:BuildProjectReferences=false` against the normal output path instead, and re-run the full solution build afterwards because `Rebuild` cleans the output.

**Why:** a plan can require "new files must be nullable-clean" and the plan's own command will confirm it without ever checking. Reporting PASS on that basis is an unmeasured claim.

**How to apply:** when a plan's type-check task uses `/t:Build`, execute it verbatim and record its result as the task's stated gate, then run the isolated `/t:Rebuild` as *supplementary verification* scoped to the projects the change touched. Attribute the resulting errors to files (`grep -oE "[A-Za-z0-9_.\\\\]+\.cs\([0-9]+,[0-9]+\): error CS[0-9]+" | sed 's/(.*//' | sort | uniq -c | sort -rn`) and fix only those in authored code; record the rest as pre-existing debt. Related: [[project_incremental_build_vacuous_baseline]].
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
name: preflight-mergebase-diff-gates-need-commit-cadence
description: A plan whose gates read `git diff <MERGE_BASE>..HEAD` is vacuous while HEAD == merge-base and unsatisfiable once HEAD is ahead; preflight must require commit tasks, allow docs/agent-memory paths, and scope later-cycle gates to the commit's own diff
metadata:
type: project
---

When preflighting a plan whose verification gates are expressed as `git diff --numstat|--name-only <MERGE_BASE>..HEAD` (zero-line-diff / scope-lock / file-size audits), check two things mechanically before signing off:

1. **Does HEAD actually differ from the merge-base by the time the gate runs?** On a freshly branched worktree `git rev-parse HEAD` equals `<MERGE_BASE>`, so every such gate returns an empty diff and passes for the wrong reason. The plan needs explicit commit tasks in the cadence — one after Phase 0 (so planning/baseline artifacts land and HEAD advances), one after the last source-editing phase (so the diff gates observe the real change set), and one at the end (so evidence is committed and the final clean-worktree gate is satisfiable).
2. **Does the gate's binary outcome tolerate the non-source paths that the commit cadence necessarily introduces?** Committing planning artifacts puts `docs/features/**` and `.claude/agent-memory/**` into the diff. A gate worded "no path outside the scope lock appears" becomes unsatisfiable unless it is narrowed to `.cs`/`.csproj`/`.xml`/`.sln` or explicitly exempts the documentation/evidence trees. Same for a Phase 0 gate demanding a clean `git status --porcelain` when planning artifacts are legitimately uncommitted at that point.

3. **The mirror-image defect on a later cycle: HEAD is now far ahead of the merge-base.** In a remediation or follow-up cycle the branch already carries the implementation commit, so an *unscoped* `git diff --numstat <MERGE_BASE>..HEAD` scope-lock gate enumerates the entire branch (for #503: 18 source paths against a 2-path scope lock) and can never pass. Two distinct fixes, and a plan needs both: audit *scope* against the new commit's own diff (`git show --numstat --format= HEAD`), and audit *protected paths* with a path-scoped `<MERGE_BASE>..HEAD -- <paths>` form so the enclosing branch diff cannot mask a violation. Pair the post-commit check with a pre-commit `git diff --numstat <MERGE_BASE> -- <paths>` (no `..HEAD`) so an uncommitted edit to a protected file is caught before it is committed.

4. **A scope gate must have a bucket for every path `git add -A` will sweep in.** Untracked artifacts carried in from a prior review cycle (audit `.md` files at the feature root, `docs/features/potential/promoted/**`) belong to no phase of the current plan, so a bucketed classification needs an explicit "pre-existing, verified present in the Phase 0 porcelain" bucket. Likewise, if the plan itself emits `.xml` under `<FEATURE>/evidence/`, an extension-based gate ("no `.xml` outside the scope lock") collides with the plan's own output unless it is qualified with "outside `<FEATURE>/evidence/`".

Related consequence: a gate that runs on the *committed* diff after a later mutating step (for example a CSharpier format pass in the final QC phase) measures pre-mutation content. That is acceptable when the mutating step's own argument list is scope-locked, but the plan should say so rather than claim the gate is "post-format".

**Why:** #503 preflight pass 1 (implementation plan) rejected the plan for the vacuous form — empty diff gates plus unsatisfiable clean-worktree gates — and cleared on pass 2 after commit tasks `P0-T13`, `P4-T7`, `P7-T32` were added and the diff-gate wording was widened. The #503 *remediation* plan then hit points 3 and 4 on its own first pass, and cleared once `P4-T4` was split into a path-scoped protected-path check plus a `git show --numstat --format= HEAD` scope check, and `P3-T11` gained the pre-existing-path bucket and the `<FEATURE>/evidence/` qualifier.

**How to apply:** During preflight of any plan with merge-base diff gates, run `git rev-parse HEAD` and `git status --porcelain` in the target worktree, compare against the plan's stated preconditions, and require the commit cadence as a plan delta rather than assuming the executor will commit opportunistically. See [[project_preflight_selfderived_gate_thresholds_are_blind]] for the sibling failure mode (gates that validate against numbers derived from the run being validated).
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 @@ -10,8 +10,12 @@

- [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
- [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
- [Embedded-resource fail-proof needs a rebuild gate](embedded-resource-failproof-rebuild-gate.md) — edit → rebuild → assert embedded bytes → `[expect-fail]` run; skipping the assert makes the fail-proof itself vacuous
- [#503 ribbon readiness plan seams](project_503_ribbon_readiness_plan_seams.md) — RibbonViewer 487/500 forces a 26-member region move; 6+4 Compile entries; compile-time red + dossier; #504-#508 already promoted
- [CSharpier gate: format not pipe-files](csharpier-format-not-pipe-files-gate.md) — formatting tasks must use `csharpier format` + scoped `csharpier check` exit 0; `pipe-files` is stdout-only/non-enforcing and masked a 500-line overflow in #400
- [#400 partial-class headroom placement](project_400_partial_class_headroom_placement.md) — put new coverage cases in existing `.Part2.cs` `[TestClass] partial` files to keep the 17-class filter/count assertions stable
- [Manager AsyncLazy shared seam](project_manager_asynclazy_shared_seam.md) — Globals.AF.Manager is shared across all classifier subsystems; use a key-specific accessor, never retype the dictionary value for one key
Expand Down
Loading
Loading