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

- [#364 nullable-gate pre-existing blockers](project_364_nullable_gate_preexisting_blockers.md) — full-solution pragma-only TWAE gate fails at baseline (vendored SVGControl CS0649 + non-HelperClasses CS0618/CS0168); verify CS86xx via isolated UtilitiesCS build w/ BuildProjectReferences=false; analyzer-version drift needs nuget-install into packages/; coverage script single-assembly StrictMode bug

- [Nullable per-file pragma gate mechanics](project_nullable_pragma_gate_mechanics.md) — solution-wide TWAE aborts on vendored SVGControl CS0649; verify via isolated `UtilitiesCS.csproj -t:Rebuild -p:Platform=AnyCPU -p:BuildProjectReferences=false` + grep CS86xx=0
- [Analyzer version skew on fresh worktree](project_analyzer_version_skew_fresh_worktree.md) — first analyzer build fails CS0006 (Meziantou 3.0.101/Sonar 10.27/BannedApi 3.3.4 missing); nuget install old versions into gitignored packages/, don't edit 16 csproj
- [Nullable remediation annotation patterns](project_nullable_remediation_annotation_patterns.md) — net481 no post-condition attrs; EmailRecord struct `= default!`; `.ToString()!` for string cells; IsNullOrEmpty overload gotcha; `x!.M()` for defensive-flow-state
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
name: 364-nullable-gate-preexisting-blockers
description: The full-solution pragma-only nullable gate fails at baseline on the epic-integration branch (vendored SVGControl CS0649 + non-HelperClasses UtilitiesCS CS0618/CS0168); verify CS86xx via isolated UtilitiesCS build. Plus analyzer-version drift and coverage-script single-assembly bug.
metadata:
type: project
---

Executing the `utilitiescs-nullable-remediation` epic children (e.g. #364 HelperClasses) on branches off `epic/utilitiescs-nullable-remediation-integration`.

**Why:** The recent HEAD commit changed the CI nullable gate from `/t:Build` (a silent no-op) to `/t:Rebuild`, so a genuine recompile now surfaces pre-existing warnings that `/p:TreatWarningsAsErrors=true` promotes to errors — across projects that are OUT of a given child's scope.

**How to apply:**

1. The plan-literal gate `msbuild TaskMaster.sln /t:Rebuild /p:TreatWarningsAsErrors=true` (no `/p:Nullable=enable`) FAILS at baseline, before any edit: vendored `SVGControl/SvgImageSelector.cs` has 2 pre-existing CS0649 (fields never assigned, dating to 2023) that halt the solution build early; and non-HelperClasses `UtilitiesCS` files (EmailIntelligence/, Extensions/) have ~28 CS0618 + 2 CS0168. None are fixable within a HelperClasses-scoped child. Flag them; do not treat as regressions.
2. Authoritative CS86xx verification for a UtilitiesCS child: first build the solution once WITHOUT TWAE so SVGControl.dll exists, then `msbuild UtilitiesCS/UtilitiesCS.csproj /t:Rebuild /p:Configuration=Debug /p:Platform=AnyCPU /p:BuildProjectReferences=false` (no TWAE) and grep the output for `warning CS86`. CS86xx are severity-independent and arise only from `#nullable enable` files (project default is oblivious — `UtilitiesCS.csproj` has no `<Nullable>`), so zero CS86xx warnings == zero CS86xx errors under TWAE. `BuildProjectReferences=false` avoids re-triggering the SVGControl halt (UtilitiesCS has a project ref to SVGControl).
3. Analyzer version drift: `UtilitiesCS.csproj`/`VBFunctions.csproj` `<Analyzer Include>` paths pin OLDER analyzer versions (Meziantou 3.0.101, SonarAnalyzer 10.27.0.140913, BannedApiAnalyzers 3.3.4) than `packages.config` (3.0.123 / 10.29.0.143774 / 5.6.0). `main` has the reconciling commit (097f0ba2) but the epic-integration base does not. `Sync-PackageReferences.ps1` only fixes `<HintPath>`, NOT `<Analyzer Include>`, so the analyzer build gets CS0006. Fix without touching tracked files: `nuget.exe install <id> -Version <old> -OutputDirectory packages` for the three csproj-referenced versions (packages/ is gitignored).
4. `scripts/vscode/Invoke-MSTestWithCoverage.ps1` throws under StrictMode when discovery returns a SINGLE test assembly (`$testAssemblies.Count` on a scalar). To scope coverage to one assembly (e.g. UtilitiesCS.Test), invoke the underlying command directly: `dotnet-coverage collect --output <cobertura> --output-format cobertura --settings coverage.config -- <vstest> UtilitiesCS.Test\bin\Debug\UtilitiesCS.Test.dll /Settings:scripts/vscode/TaskMaster.cli.runsettings /InIsolation /TestCaseFilter:TestCategory!=LiveOutlook`.
5. Env bootstrap on a fresh worktree: `Invoke-Restore.ps1` (nuget restore, 169 pkgs) then the analyzer install in (3). Global `csharpier` is v1.3.0 (subcommand syntax `csharpier check .` / `csharpier format .`, NOT `--check`); `dotnet tool run csharpier` fails because the repo-local .NET SDK is not installed.
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 @@ -37,4 +37,5 @@
- [SVGControl stale binding redirect out of scope](project_svgcontrol-stale-binding-redirect-out-of-scope.md) — #354: `SVGControl/app.config`'s `System.Runtime.CompilerServices.Unsafe` redirect (6.0.2.0 vs csproj 6.0.3.0) is still stale as of 2026-07-18; check it in any future app.config binding-redirect audit
- [pr-context stale after remediation commit](project_pr-context-stale-after-remediation-commit.md) — #354 R4: pr_context artifacts don't auto-update after a remediation commit lands; compare `git rev-parse HEAD` against the summary's recorded Head ref every re-audit cycle and refresh if stale
- [partial remediation still fails new-code floor](project_partial-remediation-new-code-floor-still-fails-209.md) — #209 R4: extracting one testable seam from a native-engine adapter raised coverage 0%->7.7%, real progress but still far below 85%/90%; recompute against the actual floor, don't trust a remediation-plan's weak ">0%" acceptance bar; recommend a maintainer exemption decision when the residual is architecturally irreducible
- [nullable-epic full-solution TWAE is pre-existing](project_nullable-epic-fullsolution-twae-preexisting-blocker.md) — #364: utilitiescs-nullable-remediation children's plan-literal full-solution pragma-only TWAE build exits 1 on pre-existing out-of-scope SVGControl CS0649 + EmailIntelligence/Extensions CS0618; adjudicate DoD "toolchain passes" via the isolated UtilitiesCS.csproj build (EXIT 0, 0 CS86xx), not blocking
- [coverage hook skips when no pr_context.summary.txt](coverage-hook-skips-when-no-pr-context-summary.md) — #367: absent pr_context summary makes changedLanguages empty, so the hook runs only the 3 artifact-path/location checks and skips all per-language coverage-row enforcement; still write clean PASS/FAIL rows; evidence/qa-gates/ is an allowed audit-artifact path
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
name: nullable-epic-fullsolution-twae-preexisting-blocker
description: For utilitiescs-nullable-remediation epic children, the plan-literal full-solution pragma-only TWAE build exits 1 on pre-existing out-of-scope warnings, not on the child's work
metadata:
type: project
---

Every child of the `utilitiescs-nullable-remediation` epic runs a per-file `#nullable enable`
opt-in with a pragma-only verification build (`msbuild TaskMaster.sln /t:Rebuild ... /p:TreatWarningsAsErrors=true`,
deliberately WITHOUT `/p:Nullable=enable`). That full-solution command exits 1 for a pre-existing,
out-of-scope reason, not the child's annotations:

- `SVGControl/SvgImageSelector.cs` 4x `CS0649` (vendored, 2023 WIP fields never assigned). SVGControl
builds early (project ref of UtilitiesCS) so the solution halts there.
- Non-HelperClasses `UtilitiesCS/EmailIntelligence/` + `Extensions/` `CS0618` (obsolete AsyncEnumerable
APIs) + `CS0168`, promoted to errors by TWAE.

This is the P0-T4 baseline (present before any child edit). It surfaced only after commit `20d163ac`
changed the nullable gate from `/t:Build` (silent no-op) to `/t:Rebuild` (genuine recompile).

**Why:** These files are outside the maintainer scope lock (`UtilitiesCS/HelperClasses/` only), so a
child cannot fix them.

**How to apply:** Adjudicate the DoD/AC "full toolchain passes" item as PASS for the in-scope
obligation on the evidence that the ISOLATED build
`msbuild UtilitiesCS/UtilitiesCS.csproj /t:Rebuild ... /p:BuildProjectReferences=false` exits 0 with
zero CS86xx. Classify the full-solution TWAE exit 1 as pre-existing/out-of-scope, NOT a blocking
in-scope defect. #364 (utilitiescs-nullable-helperclasses) confirmed all 42 opted-in files clean;
`DvgForm.Designer.cs` stays oblivious (43rd file, not opted-in). Related: [[csharp-repowide-coverage-below-80]].
11 changes: 6 additions & 5 deletions UtilitiesCS/HelperClasses/BinaryFlags/GenericBitwise.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
Expand All @@ -10,10 +11,10 @@ namespace UtilitiesCS.HelperClasses
public class GenericBitwise<TFlagEnum>
where TFlagEnum : Enum
{
private readonly Func<TFlagEnum, TFlagEnum, TFlagEnum> _and = null;
private readonly Func<TFlagEnum, TFlagEnum> _not = null;
private readonly Func<TFlagEnum, TFlagEnum, TFlagEnum> _or = null;
private readonly Func<TFlagEnum, TFlagEnum, TFlagEnum> _xor = null;
private readonly Func<TFlagEnum, TFlagEnum, TFlagEnum> _and;
private readonly Func<TFlagEnum, TFlagEnum> _not;
private readonly Func<TFlagEnum, TFlagEnum, TFlagEnum> _or;
private readonly Func<TFlagEnum, TFlagEnum, TFlagEnum> _xor;

public GenericBitwise()
{
Expand Down
13 changes: 8 additions & 5 deletions UtilitiesCS/HelperClasses/CloningFunctions/DeepCompare.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
Expand All @@ -9,12 +10,14 @@ namespace UtilitiesCS.HelperClasses
{
public static class Deep
{
public static List<(string, object, object)> DeepDifferences<T>(T obj1, T obj2)
public static List<(string, object?, object?)> DeepDifferences<T>(T obj1, T obj2)
{
List<(string, object, object)> differences = new List<(string, object, object)>();
List<(string, object?, object?)> differences = new List<(string, object?, object?)>();

// Behavior-preserving `!`: throwIfNotFound: true guarantees a non-null Type (it
// throws otherwise), so dereferencing the result matches the prior behavior.
var properties = DispatchUtility
.GetType(obj1, true)
.GetType(obj1!, true)!
.GetProperties(BindingFlags.Public | BindingFlags.Instance);
//var properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var property in properties)
Expand All @@ -23,7 +26,7 @@ public static class Deep
var value2 = property.GetValue(obj2);
if (value1 != value2)
{
differences.Add(($"{property.Name}", (object)value1, (object)value2));
differences.Add(($"{property.Name}", value1, value2));
}
}
return differences;
Expand Down
22 changes: 12 additions & 10 deletions UtilitiesCS/HelperClasses/CloningFunctions/DispatchUtility.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
Expand Down Expand Up @@ -44,10 +45,10 @@ public static bool ImplementsIDispatch(object obj)
SecurityAction.LinkDemand,
Flags = SecurityPermissionFlag.UnmanagedCode
)]
public static Type GetType(object obj, bool throwIfNotFound)
public static Type? GetType(object obj, bool throwIfNotFound)
{
RequireReference(obj, "obj");
Type result = GetType((IDispatchInfo)obj, throwIfNotFound);
Type? result = GetType((IDispatchInfo)obj, throwIfNotFound);
return result;
}

Expand Down Expand Up @@ -82,10 +83,10 @@ public static bool TryGetDispId(object obj, string name, out int dispId)
/// <remarks>
/// This can invoke a method or a property get accessor.
/// </remarks>
public static object Invoke(object obj, int dispId, object[] args)
public static object? Invoke(object obj, int dispId, object[] args)
{
string memberName = "[DispId=" + dispId + "]";
object result = Invoke(obj, memberName, args);
object? result = Invoke(obj, memberName, args);
return result;
}

Expand All @@ -99,11 +100,11 @@ public static object Invoke(object obj, int dispId, object[] args)
/// <remarks>
/// This can invoke a method or a property get accessor.
/// </remarks>
public static object Invoke(object obj, string memberName, object[] args)
public static object? Invoke(object obj, string memberName, object[] args)
{
RequireReference(obj, "obj");
Type type = obj.GetType();
object result = type.InvokeMember(
object? result = type.InvokeMember(
memberName,
BindingFlags.InvokeMethod | BindingFlags.GetProperty,
null,
Expand Down Expand Up @@ -139,11 +140,11 @@ private static void RequireReference<T>(T value, string name)
/// <param name="dispatch">An object that implements IDispatch.</param>
/// <param name="throwIfNotFound">Whether an exception should be thrown if a Type can't be obtained.</param>
/// <returns>A .NET Type that can be used with reflection.</returns>
private static Type GetType(IDispatchInfo dispatch, bool throwIfNotFound)
private static Type? GetType(IDispatchInfo dispatch, bool throwIfNotFound)
{
RequireReference(dispatch, "dispatch");

Type result = null;
Type? result = null;
int typeInfoCount;
int hr = dispatch.GetTypeInfoCount(out typeInfoCount);
if (hr == S_OK && typeInfoCount > 0)
Expand Down Expand Up @@ -261,7 +262,8 @@ void GetTypeInfo(
UnmanagedType.CustomMarshaler,
MarshalTypeRef = typeof(System.Runtime.InteropServices.CustomMarshalers.TypeToTypeInfoMarshaler)
)]
out Type typeInfo
// Nullable: the marshaled Type is only set when GetTypeInfoCount returned 1.
out Type? typeInfo
);

/// <summary>
Expand Down
11 changes: 7 additions & 4 deletions UtilitiesCS/HelperClasses/CloningFunctions/ObjectCopier.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
#nullable enable
using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
Expand All @@ -17,8 +18,10 @@ public static class ObjectCopier
/// </summary>
/// <typeparam name="T">The type of object being copied.</typeparam>
/// <param name="source">The object instance to copy.</param>
/// <returns>A deep copy of the object.</returns>
public static T Clone<T>(T source)
/// <returns>A deep copy of the object, or <c>default</c> when <paramref name="source"/> is null.</returns>
// Deliberate downstream contract: the return is annotated `T?` because the null-source
// path returns default(T) (null for reference types). Callers must handle a possible null.
public static T? Clone<T>(T source)
{
if (!typeof(T).IsSerializable)
{
Expand All @@ -33,7 +36,7 @@ public static T Clone<T>(T source)
IFormatter formatter = new BinaryFormatter();
formatter.Serialize(stream, source);
stream.Seek(0, SeekOrigin.Begin);
return (T)formatter.Deserialize(stream);
return (T?)formatter.Deserialize(stream);
}
}
}
5 changes: 3 additions & 2 deletions UtilitiesCS/HelperClasses/DvgForm.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
#nullable enable
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
Expand All @@ -18,7 +19,7 @@ public DgvForm()
InitializeComponent();
}

private void DgvForm_ResizeEnd(object sender, EventArgs e)
private void DgvForm_ResizeEnd(object? sender, EventArgs e)
{
Debug.WriteLine($"Size is {this.Size.ToString()}");
}
Expand Down
3 changes: 2 additions & 1 deletion UtilitiesCS/HelperClasses/FileSystem/DirectoryInfoWrapper.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
#nullable enable
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
Expand Down
1 change: 1 addition & 0 deletions UtilitiesCS/HelperClasses/FileSystem/FileInfoWrapper.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#nullable enable
using System;
using System.IO;
using System.Linq;
Expand Down
Loading