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 @@ -14,6 +14,7 @@
- [Swordfish F5 test misclassification](project_swordfish_f5_test_misclassification.md) — verify using/namespace before treating a removal as Swordfish-only

## Build / toolchain environment
- [pwsh/git/gh CLI gotchas](project_pwsh_git_gh_cli_gotchas.md) — jq NOT installed (only `gh --jq`); pwsh won't concatenate `$(git merge-base ...)..HEAD`; bare `packages.config` pathspec matches 0 files
- [Project Build/Test Env](project_build_test_env.md) — git-bash quirks (MSBuild switches, MSYS_NO_PATHCONV), csharpier v1 syntax, legacy csproj Compile includes, IVT for Moq
- [VS18 build/test toolchain paths](project_vs18_build_toolchain_paths.md) — use VS **18** full-framework msbuild.exe (not .dotnet-sdk, dies on binary resx MSB3822); nuget.exe restore; dotnet-coverage needs `--` separator
- [Repo-local SDK install + nullable Rebuild](project_repo_sdk_and_nullable_rebuild.md) — .dotnet-sdk install needs pwsh7; csharpier check/format subcommands; nullable debt scope NOT stable across sessions — re-verify which csproj errors come from
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
---
name: pwsh-git-gh-cli-gotchas
description: Three verified environment facts that silently break plan verification commands — jq is not installed, PowerShell will not concatenate $(...)..HEAD into one git argument, and an unanchored git pathspec like 'packages.config' matches nothing
metadata:
type: project
---

Three verified facts about this Windows box that make plausible-looking verification
commands fail or, worse, pass vacuously. Verified 2026-08-14 during #553 preflight.

**1. `jq` is NOT installed.** `command -v jq` (git-bash) and `Get-Command jq` (pwsh 7.6.3)
both return nothing. `gh api ... --jq '<filter>'` DOES work because that filter is
compiled into `gh` — so a plan can use `--jq` freely but any standalone
`jq '<filter>' file.json` against a LOCAL file is unrunnable. Replace with
`Get-Content -Raw x.json | ConvertFrom-Json` / `ConvertTo-Json -Depth 20`
(the default `-Depth 2` silently truncates nested API objects such as a GitHub ruleset).

**2. PowerShell does not build `<sha>..HEAD` from `$(git merge-base ...)..HEAD`.**
Verified: `git diff --name-only $(git merge-base origin/main HEAD)..HEAD -- '*.cs'`
run under `pwsh -Command` makes git print its usage block and exit non-zero. The
subexpression and the trailing `..HEAD` are not concatenated into one argument. Use two
statements: `$base = git merge-base origin/main HEAD` then
`git diff --name-only "$base..HEAD" -- '*.cs'`. The bash form works; the pwsh form does not.

**3. A git pathspec with no wildcard is anchored to the repo root.** `git ls-files --
'packages.config'` returns 0 files and `'app.config'` returns 0, while
`'**/packages.config'` returns 18. `'*.cs'` DOES match at any depth (pathspec globbing
does not set FNM_PATHNAME), so `*.ext` forms are fine and bare-filename forms are not.
A "no C#/project-file changes" gate written with bare `packages.config` is vacuous.

**4. actionlint's `-color` is a BOOLEAN flag; `-color never` fails with exit 3.**
`actionlint -color never` makes Go's flag parser read `-color` as the boolean and
`never` as a positional FILE, producing `could not read "never": open never: The system
cannot find the file specified.` and exit 3 — which reads like a lint failure but is an
argument error. The suppression form is the separate boolean `-no-color`. Verifying that
a tool's download URL returns HTTP 200 is NOT verifying that its command line parses;
run `<tool> -h` during preflight when a plan hard-codes flags. Also note `-verbose`
prints `Collected N YAML files` / `Found 0 errors in N files`, which is how you prove a
lint run actually covered the file set instead of silently skipping it.

**5. `gh workflow run --ref <branch>` races `git push` and silently runs the OLD sha.**
Verified 2026-08-14 on #553: `git push && gh workflow run ci.yml --ref <branch>` produced
a run whose `head_sha` was the PREVIOUS commit, because GitHub resolved the ref before the
push replicated. For a fault-isolation probe this is the worst possible failure — the run
goes GREEN and looks like the probe proved the gate does not fire. Always verify
`gh run list --json headSha` (or `gh api .../runs/<id> --jq .head_sha`) equals the intended
sha BEFORE watching, cancel and re-dispatch if it does not, and put a few seconds plus a
`git ls-remote --heads origin <branch>` tip check between push and dispatch.

**Why:** all five produce a wrong result rather than an obvious error — #2 emits a usage
dump that a wrapper can read as "no output, therefore clean", and #3 reports an empty
diff for files that genuinely changed.

**How to apply:** when preflighting a plan, actually execute each verification command
shape (not just read it) before signing off, especially anything using `jq` on a file,
`$(...)` inside a git rev range, or a bare-filename pathspec. Related:
[[project-build-test-env]], [[verify-line-citations-with-numbered-output]].
2 changes: 2 additions & 0 deletions .claude/agent-memory/atomic-planner/MEMORY.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Atomic Planner Memory Index

- [#553 CI parallel-split plan seams](project_553_ci_parallel_split_plan_seams.md) — workflow-only scope: no C# toolchain; Phase 0 snapshot for byte-identity; ruleset PUT + gh pr create orchestrator-gated; no jq (ConvertTo-Json -Depth 20); pathspec anchoring; BRANCH/SCRATCH conventions

- [Dead-code removal vs coverage exclusion](project_deadcode_removal_vs_coverage_exclusion.md) — coverage gate blocked by unreachable dead prod code → plan removal (shrink denominator), never exclusion/carve-out/forced-rethrow
- [Coverage gate on CLR-invoked private members](coverage-gate-clr-invoked-private-members.md) — never gate AssemblyResolve-style private members at >=90%; split newly-added vs changed per the AC's own wording
- [Nullable context mismatch: prod vs test](project_nullable_context_mismatch_prod_vs_test.md) — check `#nullable enable` in the prod file AND missing `<LangVersion>` (C# 7.3) in the test csproj; adding `<LangVersion>latest</LangVersion>` is never "one property"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
name: project-553-ci-parallel-split-plan-seams
description: "#553 CI job-split plan seams: workflow-YAML-only scope (no C# toolchain), byte-identity via line-ending-normalized containment vs Phase 0 snapshot, ruleset PUT orchestrator-gated, QA loop before post-merge phases"
metadata:
type: project
---

Plan seams for issue #553 (split `.github/workflows/ci.yml` monolith into 5 reusable callee workflows), plan at `docs/features/active/2026-08-14-ci-parallel-job-split-553/plan.2026-08-14T09-05.md`.

**Why:** Workflow-only features break several default planning habits: there is no local test harness, the authoritative gate is a live CI run, and the merge-policy PUT is outward-facing.

**How to apply:**
- **No C# toolchain for workflow-only diffs.** Put a binding "No-C#-Toolchain Statement" in the plan preamble plus a final `git diff --name-only <merge-base>..HEAD -- '*.cs' '*.csproj' ...` empty-check task; otherwise the executor attempts an unjustifiable csharpier/msbuild/vstest pass. Seeded probe commits that touch .cs are fine if each is reverted — the merge-base content diff nets to zero.
- **Byte-identity gates need a Phase 0 snapshot.** The source file is destroyed by the rewrite, so extract reference blocks (by verified line ranges with first-line sanity asserts) into `evidence/other/pre-split/` BEFORE editing, then verify containment with CRLF→LF normalization (`Get-Content -Raw` + `.Contains`). Works because callee `jobs.<id>.steps` sits at the same YAML depth as the monolith's.
- **Ruleset PUT task**: mark `ORCHESTRATOR CONFIRMATION REQUIRED — do not execute autonomously`; BLOCKED (not skipped) without recorded confirmation. Payload verification = 5 jq checks (read-only keys stripped, exactly N contexts, strict retained, diff-only-in-contexts via del()+`git diff --no-index`, every context verbatim in the captured-names artifact).
- **Phase ordering vs the final-QA-loop contract:** QA loop (actionlint = lint; formatting/type-check N/A for YAML; live green run = test) is the last code-verification phase; post-merge phases (ruleset migration, dispatch smoke) follow it with an explicit note that they modify no source files and an authorized `DEFERRED — awaiting merge` branch. Post-merge evidence lands via an orchestrator-owned follow-up commit since the PR is already merged.
- **Probe tasks** are `[expect-fail]` with dossiers in `evidence/regression-testing/`; wait for the probe run to finish before pushing the revert (`cancel-in-progress: true` would cancel it). Nullable probe must target a project without `TreatWarningsAsErrors` in its csproj so only the nullable gate reddens.
- **Check-run names captured, never assumed** (`gh api .../commits/<head>/check-runs`); names are SHA-independent so the capture artifact can be committed after the PUT without invalidating it.

**Preflight rev-1 findings (environment facts, reusable):**
- `jq` is NOT installed (git-bash or pwsh). Only `gh api --jq` works (filter compiled into gh). Plan JSON manipulation with `ConvertFrom-Json` / `ConvertTo-Json -Depth 20` — the default depth of 2 silently truncates nested objects (would corrupt a ruleset PUT payload). Same-serializer rule: both sides of a `git diff --no-index` JSON comparison must come from the identical `ConvertTo-Json -Depth 20` call form.
- `git diff --name-only $(git merge-base ...)..HEAD` is INVALID under pwsh — PowerShell does not concatenate the subexpression with the trailing `..HEAD` into one argument. Use two statements: `$base = git merge-base ...` then `"$base..HEAD"`.
- Git pathspec with no wildcard is anchored to repo root: `'packages.config'` matches 0 files; `'**/packages.config'` matches 18. Extension globs (`'*.cs'`) match at any depth and need no prefix.
- `gh pr create` is executor-BLOCKED by `enforce-pr-author-skill.ps1` unless (a) `artifacts/pr_context.summary.txt` from `collect_pr_context` (orchestrator-only MCP tool), (b) orchestrator-state passes `--require-pr-creation-ready` (orchestrator writes it), (c) `artifacts/pr_body_<N>.md` + `artifacts/pr_body_<N>.receipt.json` with fresh SHA-256. Therefore PR-creation tasks in executor plans need the ORCHESTRATOR CONFIRMATION REQUIRED marker.
- Every outward-facing task (dispatching workflows on main, preparing commits to main) needs the literal marker sentence, not gating prose; fold observable preconditions (e.g., `gh pr view --json state` == MERGED) into an existing task's command list to avoid task renumbering.
- `$env:TEMP` is shared with concurrent sibling-worktree agents — mandate the session scratchpad for tool downloads and temp files.
- Shell state does not persist between executor tool invocations: helper functions defined in plan prose must be written once to a `SCRATCH\helpers-<issue>.ps1` and dot-sourced in every invocation that calls them.
- Never hard-code the working branch (session worktree branch != feature branch); define `BRANCH` = `git rev-parse --abbrev-ref HEAD` captured in Phase 0.

**Execution rev-3 finding (tool flag, generalizable):**
- actionlint's `-color` is a BOOLEAN flag (force color on); `-no-color` suppresses color. `-color never` makes Go's flag parser treat `never` as a positional FILE argument → `could not read "never"`, exit 3. Never write `-color <value>` for actionlint; verified against 1.7.7's own `-h` output.

**Preflight rev-2 findings (introduced by rev-1, both generalizable):**
- When a convention enumerates the tasks that dot-source a helper file, the list must include the EARLIEST invocation — Phase 0 acceptance checks often call helpers before the implementation phases do. State that the helper file is created at first use.
- Identical `ConvertTo-Json -Depth 20` parameters do NOT imply identical key order. Any git-diff comparison of two serialized objects requires BOTH sides built from the same `[ordered]@{...}` literal (same keys, same order); a plain `@{}` hashtable emits keys in unspecified order and produces a spurious diff between semantically identical documents.

Related: [[plan-validator-phase-heading-constraint]], [[feedback-ac-checkoff-one-per-task]], [[evidence-path-normalization]].
1 change: 1 addition & 0 deletions .claude/agent-memory/feature-review/MEMORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,4 @@
- [null-conditional fix relocates NRE, check callers](project_null-conditional-fix-relocates-nre-check-callers.md) — #507: `Globals.Engines`->`Globals?.Engines` matched sibling `SB` precedent and passed full evidence, but all 11 real `RibbonViewer.cs` callers are unguarded, so the NRE just moves one frame later; grep every call site before crediting a throw->null fix with resolving the reachable crash
- [coverage hook needs label+coverage+PASS/FAIL on one line](project_coverage-hook-label-plus-verdict-same-line-507.md) — #507 R1: `Test-LanguageCoverageRow` requires the language label, a coverage keyword, and PASS/FAIL all on the SAME line, and rejects any label+coverage line carrying a banned narrowing word anywhere; dot-source and simulate before finalizing, don't trust a wrapped narrative paragraph
- [505 coordinator prime/toggle race (CR-1)](project_505-coordinator-prime-toggle-race.md) — EngineToggleStateCoordinator lazy prime can overwrite a fresher toggle write and stick stale (no re-prime); Major non-blocking, TryAdd fix + promotion recommended — check status in later ribbon reviews
- [553 CI split closed, cycle 2](project_553_ci_split_review_pattern.md) — 0 blocking, 18/18 AC; reviewer self-dispatched ci.yml to cure green-run head drift; branch rebase made ALL caller SHAs stale; residuals: open PR promptly (ruleset over-blocks main), Phase 6 bookkeeping
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
name: 553-ci-split-review-pattern
description: 'Cycle-2 closure of the #553 CI parallel job split: 0 blocking, all 18 ACs PASS; reviewer resolved the green-run head mismatch by dispatching ci.yml itself; branch was rebased so all caller SHAs were stale; residuals = open PR promptly (ruleset over-blocks main) + Phase 6 plan bookkeeping'
metadata:
type: project
---

Cycle 2 (2026-08-14T17-12, head 9c00e37a, TRUE merge base 35e02895 after a rebase onto main/PR #552) closed the #553 review with 0 blocking findings and 18/18 ACs PASS. Key events worth reusing:

1. **Stale caller SHAs, again, worse:** the coordinator supplied the pre-rebase merge base AND pre-rebase green-run/probe SHAs (`d83bf377`, `5a606895`...). `git merge-base --is-ancestor` exposed the rebase; current-lineage probe pairs were different SHAs (`26b9f7b5`/`6f73cf43` etc.). Always recompute base AND re-map every cited SHA onto the actual lineage.
2. **Green-run head mismatch resolved by acting, not adjudicating:** the cited green run's head was a non-ancestor, so `modified-workflow-needs-green-run` was literally unmet. Since gh was available, the reviewer ran `gh workflow run ci.yml --ref <branch>` + `gh run watch` (~5 min, run 31840944277, 5/5 green at the exact head) instead of writing a disposition or bouncing another remediation cycle. Precondition check first: `git diff <old-run-head> HEAD -- .github/` empty proved the workflow bytes were identical, making the dispatch a formality rather than a gamble. This is the cheapest possible closure when the only gap is head drift on unchanged workflows.
3. **Probe verification pattern:** for reverted fault-isolation probes, verify (a) `git diff <probe>~1 <revert>` is byte-empty per pair, (b) net branch diff has zero files in the probed language, and (c) per-job conclusions from `gh api runs/<id>/jobs` show exactly one red gate per probe run.
4. **Ruleset PUT audit pattern:** check payload = writable-six-fields projection (name/target/enforcement/bypass_actors/conditions/rules; the 8 read-only GET fields absent), contexts-array-only delta, strict retained, then corroborate the committed post-PUT GET with your OWN live `gh api rulesets/<id>` GET. In #553 all matched (updated_at 2026-08-14T17:00 ET, five `<caller job> / <callee job>` contexts).
5. **Residuals for any later touchpoint:** PR still not open at review end → the migrated ruleset over-blocks every other PR to main until #553 merges; plan Phase 6 checkboxes lag the executed migration and evidence filenames deviate (`evidence/other/ruleset-migration/ruleset-{pre,new,post}.json` vs planned `ruleset-*-put.<TS>.json`); actionlint tarball still unpinned (accepted Info).
6. Cycle-2 remediation-inputs was written as a zero-finding CLOSURE record so the orchestrator's highest-timestamp lookup doesn't re-count cycle-1's blocking line — phrase former severities in lowercase ("former severity: blocking — resolved") to keep `Select-String -CaseSensitive "BLOCKING","Severity: Blocking"` at 0 hits.

Docs/YAML-only diff still means the coverage hook enumerates zero languages; see [[remediation-handoff-skill-conflicts-with-hook]] for artifact-layout conventions. Timing evidence: 4 samples now (245/259/296/433s vs 444s baseline); 433s outlier classification is sound (uniform compute-step scaling, flat fixed costs, no queueing).
3 changes: 2 additions & 1 deletion .claude/agent-memory/task-researcher/MEMORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
- [dependabot-net481-340](project_dependabot_net481_340.md) — #340: no packages.config package currently dropped net481; transitive-bump restraint is already Dependabot's NuGet default (cite security-updates docs, not a new config primitive); use semver-major ignore not fabricated version ceilings (2026-07-16)
- [folder-hierarchy-provider-350](project_folder_hierarchy_provider_350.md) — #350/epic 9101: reuse existing snapshot infra (IOutlookFolderTreeService.GetChildren + ParentKey walk), add IFolderHierarchyProvider facade + pure GetAncestorChain, no new COM seam; defer deleting BuildFromRows/Build to 9102/9103 (2026-07-16)
- [efcviewer-breadcrumb-webview2-349](project_efcviewer_breadcrumb_webview2_349.md) — #349 (epic child 9102): EfcViewer3 dead; no JS<->.NET bridge precedent in repo; percent defect = unscaled ColumnHeader widths at high-DPI design scale (2026-07-16)
- [svgcontrol-test-unwired-418](project_svgcontrol_test_unwired_418.md) — #418: SVGControl.Test absent from the .sln and its pinned test packages missing, so it cannot build; ExCSS 4.2.3-vs-4.3.1 redirect topology; Fizzler redirects inert (2026-08-04)
- [svgcontrol-test-unwired-418](project_svgcontrol_test_unwired_418.md) — #418 (STALE: SVGControl.Test now IN .sln as of 2026-08-14); historical package-pin + ExCSS/Fizzler redirect topology
- [ci-parallel-split-553](project_ci_parallel_split_553.md) — #553: 4 independent jobs w/ tailored setup beat build-once artifact sharing; single-PUT ruleset 18572843 swap fail-closed; called-workflow check names are "caller / callee" (2026-08-14)
- [winforms-pump-seam-230](project_winforms_pump_seam_230.md) — #230: WinFormsPumpHost design decided; CreateAsync factory-seam gap; InitializeWebViewAsync stays exempt; 19 -> 11 max (2026-08-07)
- [qfc438-search-focus-steal](project_qfc438_search_focus_steal.md) — #438: TWO focus-steal mechanisms (open _focusPending + close _focusAnchor via per-keystroke Clear); CancelSelector emits no SelectionChanged -> stale _selectedFolder (2026-08-08)
- [ribbon-engine-readiness-503](project_ribbon_engine_readiness_503.md) — #503: whole TaskMaster Ribbon layer is coverage-excluded; net481 blocks default interface members; 5 orphan onAction callbacks in RibbonExplorer.xml (2026-08-08)
Expand Down
Loading
Loading