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,5 +1,7 @@
# Atomic Executor Memory Index

- [#376 capstone scope-expansion layers](project_376_capstone_scope_expansion_layers.md) — 5 escalated layers past P2-T17 (ToDoModel CS0618 -> TaskVisualization CS4014/ToDoModel.Test CS0169 -> QuickFiler CS0108/CS0618/CS8600 -> TaskMaster CS8632/CS8767/CS0618 + QuickFiler.Test MSTEST0032 -> TaskMaster.Test/UtilitiesCS.Test CS8632/CS8625/CS0067); all resolved via the 3 authorized patterns, stop-condition never triggered
- [BOM breaks grep ^ anchor](project_bom_grep_anchor_false_negative.md) — bash/GNU grep's `^#nullable` silently misses BOM-prefixed UtilitiesCS files (400 opted-in via ripgrep, not 156 via bash grep); always use the Grep tool for opt-in/candidate-file classification, never bash grep
- [#372 email-classifier nullable patterns](project_372_email_classifier_nullable_patterns.md) — engine props set post-ctor=`null!`; factory returns `T?`; Prediction.Class T? cascade=`.Class!`; net481 IsNullOrEmpty no-narrow; `(await Deserialize())!` not `await Deserialize()!`; DTO `= null!` adds a coverage line; measured 30 emitting (not epic ~18)
- [Timed-out MSTest leaves detached runner](project_timedout_mstest_leaves_detached_runner.md) — a timed-out Invoke-MSTestWithCoverage bash call leaves a detached pwsh runner respawning testhosts; second run contends over user.config (ConfigurationErrorsException in unrelated TaskTree/ToDoModel) and hangs — kill the pwsh runner too, verify 0, then rerun with >=8min timeout
- [#371 OutlookObjects nullable lessons](project_371_outlookobjects_nullable_lessons.md) — public-signature nullable change regresses OTHER nullable-enabled files in same assembly (DfDeedle) — check TOTAL UtilitiesCS CS86xx per batch, keep public tuples non-null w/ ! at null sites; lazy-field CS8618→Lazy<T>?; ToLazy has class constraint; _item=null! for GetOrLoad ref-match; ForEach commented-but-resolves (grep gate=flag not block)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
---
name: 376-capstone-scope-expansion-layers
description: utilitiescs-nullable-ci-capstone (#376) P2-T17 blocking-finding escalation and its resolution across 5 layers of newly-discovered pre-existing warnings-as-errors debt
metadata:
type: project
---

The #376 capstone's Phase 2 solution-wide rebuild gate (`msbuild TaskMaster.sln /t:Rebuild
/p:TreatWarningsAsErrors=true`, no `/p:Nullable=enable`) had never been run to completion on the
real branch tip before this feature, because commit `20d163ac` (PR #361's `/t:Rebuild` fix) is not
yet an ancestor of `origin/main`. Once run for the first time, it surfaced pre-existing
warnings-as-errors debt in 5 successive layers beyond the two originally-declared Phase 2 scope
trees (`SVGControl/**`; `UtilitiesCS/EmailIntelligence/**` + `UtilitiesCS/OutlookObjects/Folder/**`):

1. `ToDoModel.csproj` CS0618
2. `TaskVisualization.csproj` CS4014, `ToDoModel.Test.csproj` CS0169 (dead-code deletion)
3. `QuickFiler.csproj`: CS0108 (interface member hiding, pragma-suppressed — adding `new` isn't one
of the three authorized patterns), CS0618, CS8600
4. `TaskMaster.csproj`: CS8632 (annotation-context scoping via `#nullable enable
annotations`/`restore annotations`, NOT full `#nullable enable`), CS8767 (interface nullable
parameter mismatch, same annotations-context bracket), CS0618; `QuickFiler.Test.csproj`:
MSTEST0032 (tautological placeholder assert, pragma-suppressed rather than fixed — fixing would
be a test-behavior change)
5. `TaskMaster.Test.csproj` + `UtilitiesCS.Test.csproj`: 29 more CS8632, 3 CS8625 (null-forgiving
`!` at deliberate-null guard-clause test call sites), 3 CS0067 (unused `PropertyChanged` events
required by `INotifyPropertyChanged`-derived interfaces — cannot be deleted, pragma-suppressed)

Orchestrator decision: expand scope to remediate ALL layers using only the three
already-established patterns (nullable annotation/null-forgiving/guard-clause; narrow pragma
bracket with rationale; dead-code deletion after grep-confirmed zero live references), looping
P2-T21/T22/T23 until `EXIT_CODE: 0`, with an explicit stop-and-escalate condition if any diagnostic
required a real behavior change. The stop condition was never triggered across all 5 layers/3 loop
iterations — every diagnostic was resolvable via the three patterns.

**Reusable lesson:** CS0108 (member hiding) and MSTEST0032 (tautological assert) are new diagnostic
CLASSES not previously seen in prior nullable-remediation children this epic; both were resolved
via narrow pragma-suppress-with-rationale rather than the "obvious" fix (adding `new`, or fixing the
placeholder assert), because those "obvious" fixes are NOT among the three authorized patterns and
would each constitute a real, if small, behavior/design change. When a plan's authorized-pattern
list is explicit and narrow, prefer the conservative pragma-suppress-with-rationale interpretation
over an unlisted-but-tempting alternative fix.
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
name: bom-grep-anchor-false-negative
description: GNU grep's ^ anchor fails to match immediately after a UTF-8 BOM, silently under-counting/misclassifying files; ripgrep (Grep tool) handles BOM correctly
metadata:
type: project
---

Most `UtilitiesCS/**/*.cs` files in TaskMaster carry a UTF-8 BOM before their first line. Bash/GNU
`grep -rl "^#nullable"` (or any `^`-anchored pattern) does NOT match immediately after a BOM, so it
silently undercounts opted-in files and can misclassify a BOM-prefixed opted-in file as
non-opted-in. Concretely: `grep -l "^#nullable" ActionButton.cs` returned nothing even though line
11 is `#nullable enable`, because the file starts with an `efbb bf` BOM. Ripgrep (the `Grep` tool)
correctly skips the BOM and gave the accurate answer (400 opted-in files across
UtilitiesCS+SVGControl, not 156 as bash grep reported).

**Why this matters:** during #376 (utilitiescs-nullable-ci-capstone) Phase 4's genuine-enforcement
verification (AC2), the plan's illustrative non-opted-in candidate (`UtilitiesCS/Dialogs/
ActionButton.cs`) was actually opted-in; using bash grep to "confirm" it as non-opted-in would
have produced a false verification result (the deliberate defect would have failed the gate,
contradicting the P4-T5 acceptance criterion). Caught before the defect was introduced by
re-checking with the Grep tool.

**How to apply:** always use the `Grep` tool (ripgrep-based), never bash/GNU `grep`, for any
`^`-anchored line-start pattern search across this repo's `.cs` files — especially when the result
selects a candidate file for a verification step whose correctness depends on the classification
being accurate. If bash grep must be used for some reason, strip the BOM first or accept that
counts/candidate selections from it are unreliable and re-verify with ripgrep before acting.
13 changes: 7 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -104,14 +104,15 @@ jobs:
shell: pwsh
run: |
# Use /t:Rebuild (not /t:Build) so this step always performs a genuine full
# recompile under /p:Nullable=enable. The preceding "Build with analyzers"
# step already compiled everything under the projects' own Nullable settings;
# MSBuild's incremental up-to-date check does not invalidate on a changed
# -p:Nullable command-line property alone, so a plain /t:Build here would
# silently skip recompilation and never actually enforce this gate.
# recompile. Enforcement now relies entirely on each file's own #nullable
# enable pragma (the repo's per-file opt-in convention; UtilitiesCS.csproj and
# SVGControl.csproj carry no project-level <Nullable> element) plus
# /p:TreatWarningsAsErrors=true. MSBuild's incremental up-to-date check does
# not invalidate on this command-line property change alone, so a plain
# /t:Build would silently skip recompilation and never enforce this gate.
& msbuild $env:SOLUTION_PATH /t:Rebuild /m /p:Configuration=Debug `
"/p:Platform=Any CPU" `
/p:Nullable=enable /p:TreatWarningsAsErrors=true
/p:TreatWarningsAsErrors=true
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }

- name: Run MSTest suite with coverage
Expand Down
6 changes: 6 additions & 0 deletions QuickFiler.Test/Controllers/QfcFormControllerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -691,7 +691,13 @@ public async Task UndoConsumer_ShouldConsumeUndoQueue()
await Task.CompletedTask;

// Assert
// Placeholder assertion (pre-existing, tautological by construction). MSTEST0032
// flags the always-true condition; replacing it with a genuine assertion is a test
// behavior change out of scope for this narrow build-debt remediation (no behavior
// change per AC7). Suppressed narrowly rather than altered.
#pragma warning disable MSTEST0032
Assert.IsTrue(true);
#pragma warning restore MSTEST0032
}

[TestMethod]
Expand Down
4 changes: 2 additions & 2 deletions QuickFiler/Controllers/BreadcrumbBridgeRouter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ private async Task ExpandLeafAsync(BreadcrumbRow row)
string requestId = "req-" + (++_requestSequence);
try
{
FolderTreeNodeKey key = await _provider.ResolveLeafKeyAsync(
FolderTreeNodeKey? key = await _provider.ResolveLeafKeyAsync(
leaf.FullPath,
CancellationToken.None
);
Expand Down Expand Up @@ -338,7 +338,7 @@ CancellationToken cancellationToken
{
try
{
FolderTreeNodeKey key = await _provider.ResolveLeafKeyAsync(
FolderTreeNodeKey? key = await _provider.ResolveLeafKeyAsync(
folderPath,
cancellationToken
);
Expand Down
21 changes: 21 additions & 0 deletions QuickFiler/Controllers/QfcCollectionController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -760,10 +760,17 @@ public void LoadConversationsAndFolders_04()

public async Task LoadConversationsAndFoldersAsync()
{
// ForEachAsync (System.Linq.Async) is obsolete (CS0618) per the framework's migration
// guidance ("Use the language support for async foreach instead"), but replacing it
// with `await foreach` here is a control-flow change to a production async method,
// not an annotation-only edit. Suppressing narrowly preserves the exact pre-existing
// behavior (no behavior change per AC7).
#pragma warning disable CS0618
await AsyncEnumerable
.Range(0, _itemGroups.Count)
.Select(i => (i, grp: _itemGroups[i]))
.ForEachAsync(async x => await LoadItemGroup(x.i, x.grp));
#pragma warning restore CS0618
}

internal async Task LoadItemGroup(int i, QfcItemGroup group)
Expand Down Expand Up @@ -819,10 +826,17 @@ public void LoadSequential_5()

public async Task LoadSequentialAsync()
{
// ForEachAsync (System.Linq.Async) is obsolete (CS0618) per the framework's migration
// guidance ("Use the language support for async foreach instead"), but replacing it
// with `await foreach` here is a control-flow change to a production async method,
// not an annotation-only edit. Suppressing narrowly preserves the exact pre-existing
// behavior (no behavior change per AC7).
#pragma warning disable CS0618
await AsyncEnumerable
.Range(0, _itemGroups.Count)
.Select(i => (i, grp: _itemGroups[i]))
.ForEachAsync(async x => await LoadGroupSequential(x.i, x.grp));
#pragma warning restore CS0618
}

public async Task LoadGroupSequential(int i, QfcItemGroup grp)
Expand Down Expand Up @@ -2197,10 +2211,17 @@ public async Task MoveEmailsAsync(SloStack<IMovedMailInfo> stackMovedItems)
{
return;
}
// ForEachAwaitAsync (System.Linq.Async) is obsolete (CS0618) per the framework's
// migration guidance ("Use the language support for async foreach instead"), but
// replacing it with `await foreach` here is a control-flow change to a production
// async method, not an annotation-only edit. Suppressing narrowly preserves the exact
// pre-existing behavior (no behavior change per AC7).
#pragma warning disable CS0618
await Enumerable
.Range(0, count)
.ToAsyncEnumerable()
.ForEachAwaitAsync(TryMoveEmailByGroupIndexAsync);
#pragma warning restore CS0618

//await _itemGroupsToMove.ToAsyncEnumerable().ForEachAwaitAsync(
// async grp => await grp.ItemController.MoveMailAsync());
Expand Down
7 changes: 7 additions & 0 deletions QuickFiler/Controllers/QfcDatamodel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,12 @@ CancellationToken token

try
{
// ForEachAwaitWithCancellationAsync (System.Linq.Async) is obsolete (CS0618) per
// the framework's migration guidance ("Use the language support for async foreach
// instead"), but replacing it with `await foreach` here is a control-flow change
// to a production async method, not an annotation-only edit. Suppressing narrowly
// preserves the exact pre-existing behavior (no behavior change per AC7).
#pragma warning disable CS0618
await _frame
.GetRowsAs<IEmailSortInfo>()
.Values.ToAsyncEnumerable()
Expand All @@ -432,6 +438,7 @@ await Task.Run(
),
token
);
#pragma warning restore CS0618
return true;
}
catch (TaskCanceledException)
Expand Down
7 changes: 7 additions & 0 deletions QuickFiler/Controllers/QfcItemController.ViewerSetup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,12 @@ internal async Task ResolveControlGroupsAsync(ItemViewer itemViewer)
await itemViewer.UiSyncContext;
var controls = itemViewer.GetAllChildren();

// SelectAwait (System.Linq.Async) is obsolete (CS0618) per the framework's migration
// guidance ("Use Select... the SelectAwait functionality now exists as overloads of
// Select"), but migrating to the new overload signature is a call-shape change to
// production code, not an annotation-only edit. Suppressing narrowly preserves the
// exact pre-existing behavior (no behavior change per AC7).
#pragma warning disable CS0618
_listTipsDetails = await _itemViewer
.TipsLabels.ToAsyncEnumerable()
.SelectAwait(x => QfcTipsDetails.CreateAsync(x, _itemViewer.UiSyncContext, Token))
Expand All @@ -221,6 +227,7 @@ internal async Task ResolveControlGroupsAsync(ItemViewer itemViewer)
.ExpandedTipsLabels.ToAsyncEnumerable()
.SelectAwait(x => QfcTipsDetails.CreateAsync(x, _itemViewer.UiSyncContext, Token))
.ToListAsync();
#pragma warning restore CS0618

_listTipsDetails.ForEach(x =>
{
Expand Down
7 changes: 7 additions & 0 deletions QuickFiler/Controllers/QfcQueue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,12 @@ int start

var digits = start + items.Count >= 10 ? 2 : 1;

// SelectAwait (System.Linq.Async) is obsolete (CS0618) per the framework's migration
// guidance ("Use Select... the SelectAwait functionality now exists as overloads of
// Select"), but migrating to the new overload signature is a call-shape change to
// production code, not an annotation-only edit. Suppressing narrowly preserves the
// exact pre-existing behavior (no behavior change per AC7).
#pragma warning disable CS0618
var itemTasks = Enumerable
.Range(start, items.Count)
.ToAsyncEnumerable()
Expand All @@ -410,6 +416,7 @@ int start
return x.grp;
})
.ToListAsync();
#pragma warning restore CS0618
return itemTasks;
}

Expand Down
7 changes: 7 additions & 0 deletions QuickFiler/Helper Classes/ConversationResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,12 @@ public static async Task<ConversationResolver> LoadAsync(
if (updateUI is not null)
resolver.UpdateUI = updateUI;

// SelectAwaitWithCancellation (System.Linq.Async) is obsolete (CS0618) per the
// framework's migration guidance ("Use Select... the functionality now exists as
// overloads of Select"), but migrating to the new overload signature is a call-shape
// change to production code, not an annotation-only edit. Suppressing narrowly
// preserves the exact pre-existing behavior (no behavior change per AC7).
#pragma warning disable CS0618
var helpers = await mailItems
.ToAsyncEnumerable()
.SelectAwaitWithCancellation(
Expand All @@ -194,6 +200,7 @@ await Task.Run(async () =>
})
)
.ToListAsync();
#pragma warning restore CS0618

resolver.MailHelper = helpers.First();
resolver.Mail = resolver.MailHelper.Item;
Expand Down
7 changes: 7 additions & 0 deletions QuickFiler/Viewers/IItemViewer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -116,10 +116,17 @@ public interface IItemViewer : IUserControl, IContainerControlLocal
// through the field. The concrete ItemViewer satisfies all four via its UserControl base.
// They are declared on the interface (not resolved by a concrete cast) so InvokeRequired-guarded
// routing stays mockable for the dispatch-routing unit tests.
// CS0108 (member hides an inherited ISynchronizeInvoke/IControl member) is suppressed
// narrowly here: these four members are a deliberate, pre-existing re-declaration for
// mockability, and adding the `new` keyword or otherwise restructuring the interface
// hierarchy is an actual API-shape change, not an annotation-only fix (out of scope per
// AC7 no-behavior-change).
#pragma warning disable CS0108
bool InvokeRequired { get; }
object Invoke(System.Delegate method);
System.IAsyncResult BeginInvoke(System.Delegate method);
int Height { get; }
#pragma warning restore CS0108

void RemoveControlsColsRightOf(Control furthestRight);
}
Expand Down
8 changes: 8 additions & 0 deletions SVGControl/SvgImageSelector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,16 @@ public SvgImageSelector(Size outer, Padding margin, AutoSize autoSize, bool useD
}

//private SvgDocument _doc;
// The `ImagePath` property's `set` accessor body below is entirely commented out (a
// pre-existing, already-documented dead no-op judgment call — see
// docs/features/active/utilitiescs-nullable-svgcontrol/evidence/other/imagepath-judgment-call-decision.md),
// so neither field below is ever assigned on any live path. CS0649 is suppressed here
// rather than resurrecting the dead setter logic, which is out of scope (no behavior
// change per AC7).
#pragma warning disable CS0649
private string? _relativeImagePath;
private string? _absoluteImagePath;
#pragma warning restore CS0649
private bool _saveRendering = false;
private bool _useDefaultImage = false;
private SvgRenderer _renderer;
Expand Down
6 changes: 6 additions & 0 deletions TaskMaster.Test/AppGlobals/AppToDoObjectsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,18 @@ public void TestInitialize()
private Mock<IntelligenceConfig> mockIntelligenceConfig = null!;
private Mock<ISmartSerializableNonTyped> mockSmartSerializable = null!;

// This file has no project-level <Nullable> and no whole-file #nullable pragma; these
// pre-existing `?` annotations need an explicit annotations context to avoid CS8632.
// Scoping narrowly to annotations-only avoids introducing new CS86xx diagnostics
// elsewhere in this file (no behavior change per AC7).
#nullable enable annotations
private static void ConfigureIdListLoader(
AppToDoObjects appToDoObjects,
string fileName,
Func<string, bool>? fileExists = null,
Func<string, string>? readAllText = null
)
#nullable restore annotations
{
var settings = new TaskMaster.Properties.Settings();
var propertyValue = settings.PropertyValues["FileName_IDList"];
Expand Down
Loading
Loading