fix(deps): consolidate Dependabot and add a repair pass that keeps packages.config projects internally consistent - #920
Merged
drmoisan merged 34 commits intoSep 26, 2026
Conversation
Records the diagnosis that Dependabot cannot maintain the .csproj state coupled to packages.config in non-SDK projects, and the settled design in which Dependabot detects while a NuGet-driven workflow performs the upgrade. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018zmuPjoz21q75F4iMZ4BQ1
Execution falsified the original premise. Dependabot does maintain .csproj and app.config state; its packages.config updater invokes the NuGet CLI. The verified cause is that while updating one group it also rewrites the Import and Error guards of out-of-scope packages to a version no manifest declares, so restore and build disagree and the package-imports target fails closed. Adds spec.md with 26 acceptance criteria, the research artifact, and a runbook for the one-time GitHub App token setup. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018zmuPjoz21q75F4iMZ4BQ1
Adds a ten-phase plan covering the prerequisite corrections, the Sync-PackageReferences ranking fix, the Dependabot consolidation and the repair-pass components. Records the pre-fix red run proving origin/main does not build from a cold restore: CS0006 on Meziantou.Analyzer.3.0.203, a version no manifest declares. Also records that the pester job is scoped to tests/scripts/vscode and would not execute this change's tests until its paths are widened. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018zmuPjoz21q75F4iMZ4BQ1
Replaces a vacuous fixture-availability query that evaluated to zero for every possible repository state and would have selected the deferred verification branch even with open Dependabot pull requests. Removes unreachable ok:true acceptances against sixteen pre-existing analyzer findings, replaces the unreachable eighty-five percent coverage floor at batch A close-out with a no-regression assertion against the measured baseline of 83.93 percent, and replaces an actionlint file count the command never prints with an independent enumeration. Splits ProjectConsistency into reconciliation and verification modules to stay under the five hundred line ceiling and to separate the mutator from the inspector concern. 128 tasks across ten phases. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…byte-exact path rewrites Revision 4 replaces the analyzer folder SELECTION rule with a PRESERVE rule across five places, after measurement showed neither analyzer family names the highest folder its package ships: Meziantou items sit at roslyn5.0 with 5.6 and 5.9 available, Roslynator items at roslyn4.7 with 5.0 available. A selection rule would have rewritten all eighty analyzer items rather than the fifteen that are stale. Revision 5 adds gate rule 12 requiring byte-exact path substitution and a positive residual assertion, after a sed pattern with doubled backslashes silently matched nothing while sed -i still rewrote fifteen files' line endings, producing a fifteen-file porcelain against an empty diff. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Criterion authorship belongs to the planning agents, not the executor: the acceptance-criteria-tracking skill limits executors to checking boxes, and P9-T11 asserts no criterion text was reworded during execution. Amending here, before Phase 0 begins, removes that contradiction. AC12 previously required that a listing offering two Roslyn-qualified folders select the higher. Measured, neither analyzer family in this repository names the highest folder its package ships, so a selection rule would rewrite all eighty analyzer items rather than the fifteen that are stale. AC12 now requires the existing folder segment be preserved and only the version segment moved, with a reported non-fatal class when the preserved segment is absent from the new version. Also corrects the Risks bullet that named the superseded rule. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…fier surface Adds a MEZIANTOU-898-STATE declaration so the plan survives either merge order relative to the split branch bug/meziantou-analyzer-hintpath-skew-898. Without it three tasks fail rather than trivially pass when that branch lands first, two of them in phase zero. The already-landed branch requires a positive base-commit count of sixteen files at 3.0.235, because a tree from which the fifteen items had simply been deleted would otherwise satisfy every zero-valued clause. Counts the fifth verifier surface everywhere it is cited, gives the missing-segment class one owner, and removes a test-name ban that forbade the clearest name for the case it required. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…e inert footprint floor The clause added to stop the already-landed branch being selected vacuously was itself vacuous. Measured against both refs: the unanchored literal returns sixteen files in BOTH states because it also matches the Import and Error guards, which already name 3.0.235 everywhere - that asymmetry is defect 898 itself. Read as a sum it returns thirty-three and forty-eight, neither of which is the asserted sixteen. Anchoring to Analyzer Include= returns one at an unfixed base and sixteen at a fixed one, and returns one again for a tree whose analyzer items were merely deleted. Replaces the aggregate footprint floor with five per-class counts. Neither seventy nor fifty-five could fail for any reason connected to this change: the deterministic classes total sixty paths before any evidence artifact and a sibling task asserts at least eighty-five artifacts. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The per-batch budget assertions read hook state that can never populate: the hook roots itself at the session worktree and discards out-of-root candidates with allow and no state write, so every PowerShell file this plan writes in the execution worktree is silently ignored. Replaced with a direct count over each batch commit, which measures the property instead of a proxy that cannot observe this tree. Coverage floors corrected from 85 to 80 at five sites. The authoritative CLAUDE.md is the execution worktree's copy, which records the maintainer decision of 2026-09-11 under issue 563; the session copy is 243 commits stale. The discrepancy against .claude/rules is tracked as issue 668. This is material: the measured Pester baseline of 83.93 percent sits between the two figures. Twenty-two coverage collector documents moved out of the evidence tree to gitignored coverage/, because the authoritative CLAUDE.md prohibits adding a raw collector or test-platform document to git in any form, including under a feature folder's evidence tree. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ct batch counts Revision 8 stopped committing raw coverage documents but committed nothing in their place. The policy section requires the substitution, not the prohibition alone: its rationale is that the projection and summary carry every figure a reviewer needs. The coverage runner already emits all three permitted forms into gitignored coverage, where the next run overwrites them, so two tasks now copy the JaCoCo projection and the trx summary into the evidence tree and commit them. States plainly that the PowerShell route has no enumerated permitted form, because all three are defined against the C# Cobertura path and Pester emits JaCoCo directly, so its markdown figures stand in for a form the policy does not define rather than satisfying one it does. Replaces two at-most-three batch assertions with exact counts. An at-most bound is satisfied by zero and zero, so it caught an overrun and missed an omission - the seventh absence-shaped defect in this plan, introduced while repairing the sixth. Restores the P0-T1 through P0-T10 tick set, destroyed by the previous sync. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ining SHA, tick assertions The trx-derived summary is emitted only when the document parses and carries a result-summary node; the projection is unconditional. Revision 9 treated both as unconditional, and the warning branch is live in this repository. Both copy tasks now branch, recording the warning verbatim and copying the projection alone when the summary is absent. Stops pinning the plan file's own commit hash, which the act of revising the plan invalidates. Round 2 fixed this by substituting a fresh literal and revisions 8 and 9 re-broke it. The assertion is now a property: the recorded commit is the tip commit touching the plan file and is an ancestor of HEAD. A sweep found and fixed one further literal of the same class. Adds exact ticked-task assertions at P0-T24 and at all five commit tasks - 23, 46, 64, 93, 105 and 111 - so a destroyed tick set surfaces at the next batch boundary rather than at task 127 of 128. Three of those five figures were wrong on first derivation and were corrected from the phase totals. Two sites still credited the batch-budget hook with denying a fourth write; both now name the three measurements that actually enforce it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The round-7 replacement for the self-invalidating plan hash asserted that the last-touching commit is an ancestor of HEAD. git log restricted to a path walks HEAD's own history, so every value it returns is reachable from HEAD by construction and the check cannot fail. A fix for the literal-invalidation class reintroduced the constant-valued class the same reviewer had removed at P0-T3. Now asserts a non-empty forty-character value, whose reachable failure is the empty return for an untracked path. The Pester artifact count of twenty was written without being counted and matches none of the three real populations: twenty-three tasks invoke a Pester command, twenty-two documents are produced, six record a coverage figure. Left alone it would have driven the work rather than described it. The obligation now falls on the six, enumerated by task id. Records the rev-list figure as measured rather than asserting a literal; it has already drifted from four to eight to eleven. Clarifies that a phase restart re-runs tasks without reverting their check-offs. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QaUVgY37zfbsTvSTPd7wsr
…manifest entries, NuGet pin Batch A of issue #911. - .csharpierignore: append **/packages.config and **/app.config so the formatter no longer owns the manifests the NuGet CLI writes. Appended rather than inserted, so line 4 (**/evidence/**) keeps the line number three later sites cite. - scripts/dependencies/PackageGraph.psm1 and its Pester suite: a pure-over-text parser and renderer for packages.config, project files and app.config, with discovery behind an injected delegate. 164 of 164 instrumented lines covered. - One-time normalisation of all 18 packages.config and 17 app.config files to canonical inline form. 17 and 17 changed; SVGControl/packages.config was already canonical. - Issue #898: realign the stranded Meziantou.Analyzer.3.0.203 analyzer item to 3.0.235 in 15 project files, leaving the analyzers\dotnet\roslyn5.0\cs segment byte-identical. A cold restore plus solution rebuild is now green where it previously failed CS0006. - Issue #903: add the Deedle 3.0.0 and FSharp.Core 11.0.100 entries that ToDoModel.Test.csproj already referenced by HintPath. - Pin nuget-version to 7.9.0 in the three workflows that used a floating selector, and widen the Pester workflow to the two-member test and coverage paths. Gates: csharpier check 0; msbuild analyzers 0 with 18 assemblies compiled; msbuild nullable 0; Pester 206 passed 0 failed; PSScriptAnalyzer 16, the pre-existing baseline, with 0 in files this change owns; MSTest 7343 passed, line 85.93 percent and branch 80.09 percent. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QaUVgY37zfbsTvSTPd7wsr
… dependabot consolidation Adds scripts/dependencies/PackageCompatibility.psm1, an asset-level compatibility gate for net481 that excludes netstandard2.1 outright by non-membership rather than ranking it last, which is the #902 correction: a ranking still selects the framework when nothing else is offered. Rewrites scripts/vscode/Sync-PackageReferences.ps1 from 159 to 423 lines. The $tfmPreference array is deleted and all framework selection is delegated to the shared module; the script is restructured into eight advanced functions behind a seven-member injectable filesystem seam, so the repair logic is exercisable in memory with no temporary file. The three PSAvoidUsingWriteHost findings the file carried are removed. Consolidates .github/dependabot.yml to one catch-all group with open-pull-requests-limit 1, removes the four inert partition keys, and adds an unqualified Deedle ignore entry. The eight semver-major ignore entries are retained unchanged and in order. Delivers AC1, AC4, AC7 and AC9. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QaUVgY37zfbsTvSTPd7wsr
…e verifier Adds scripts/dependencies/AnalyzerItemRepair.psm1, the restore-path layer and the #898 repair. The repair is governed by the preserve rule: it changes only the <Id>.<Version> segment of an <Analyzer Include> path and leaves every following segment byte-identical, including the Roslyn-qualified folder. The restored package is enumerated through an injected listing delegate solely to confirm the preserved segment still exists, never to select a folder. That rule is measured rather than stylistic. Neither analyzer family in this repository names the highest folder its package ships: Meziantou items sit at roslyn5.0 while the package ships roslyn4.14, roslyn4.8, roslyn5.0, roslyn5.6 and roslyn5.9, and Roslynator items sit at roslyn4.7 while the package ships roslyn3.8, roslyn4.7 and roslyn5.0. A selection rule would rewrite all 80 analyzer items in those two families rather than the 15 that were stale. Both preservation fixtures are constructed so the highest offered folder is not the one the item names, so a selection implementation fails them. When the preserved segment is absent from the new version's listing the repair emits no guessed path: it leaves the item unmodified and returns a record. When the restored directory does not exist at all it throws. Adds scripts/dependencies/ProjectConsistency.psm1 carrying version reconciliation across <Import>, <Error>, <Reference> and <HintPath>, and binding-redirect reconciliation into both the oldVersion upper bound and newVersion. A <Reference> takes a resolved assembly version rather than the package version, because the two are not required to track each other. Adds scripts/dependencies/ConsistencyVerifier.psm1 carrying every finding class, every examined count, the repairs report, the failure-result type and the repair entry point. Two classes are non-fatal and are counted and named rather than failing the run: a dependent element whose package is absent from the sibling manifest, and an analyzer item whose preserved Roslyn segment is absent from the listing. The live altcover instance in QuickFiler.Test is reported by the general rule; no package identifier is special-cased. A failure result is reserved for a divergence no repair can resolve. The module is split from the reconciliation half unconditionally. The combined file measured 644 lines against the repository's 500-line ceiling; the restore-path vocabulary moved to the module whose subject is restore paths and the verifier now stands at 493. Adds the three matching Pester suites. The AC21 case reproducing the #908 three-way divergence was captured failing against the declared pass-throughs and passing on the delivered tree, with no edit to the test between the two runs. Delivers AC8, AC11, AC12, AC13, AC14, AC16, AC21, AC22 and AC23. Gates: PoshQC format 0 rewrites on the closing pass; PSScriptAnalyzer 13, the pre-existing baseline, with 0 in files this change owns; Pester 268 passed 0 failed, aggregate line 93.85 percent, with AnalyzerItemRepair and ProjectConsistency at 100 and ConsistencyVerifier at 98.74; csharpier check 0 over 1623 files. The batch changed no C# compilation input. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QaUVgY37zfbsTvSTPd7wsr
Adds scripts/dependencies/Repair-PackageManifestConsistency.ps1, the composition root and command-line entry point. It owns every filesystem dependency: the five modules it wires are pure over text and reach the disk only through the delegates it supplies, which is what lets the whole pipeline be driven over an in-memory fixture with no temporary file. The passes run in the order each consumes the previous one's output: asset-level compatibility gate, version reconciliation, analyzer-item regeneration, binding-redirect reconciliation, normalisation, verification. An incompatible package is skipped with a recorded reason and the remaining upgrades proceed. Two resolution rules confirm rather than select, because the script can observe what a restored package ships but cannot reproduce the assembly MSBuild binds. A Reference assembly version some assembly in the restored package already carries is preserved; only a version the package carries nowhere is rewritten. Measured over this tree, 796 of 796 declared Reference versions are confirmed, so the rule is a no-op here and stays falsifiable. The alternative - resolving the compatible folder's assembly outright - disagreed with 9 declared versions and would have rewritten them. An analyzer item's folder segment is likewise preserved and the restored listing is enumerated solely to confirm the item still resolves. The missing-segment class is measured over every analyzer item rather than only over items a repair touched: each of the 162 items is compared against the path set the preserve rule derives for its own package, and all 162 are confirmed, so the class reports 0 over an examined population of 162 rather than 0 over 0. The absent-from-manifest class excludes the HintPath kind, an absent HintPath being necessarily orphaned as well, so the two altcover imports in QuickFiler.Test are counted once and the seven orphaned hint paths are reported in their own class. Adds .github/workflows/dependabot-repair.yml. It triggers on workflow_run at CI completion, restricted to head branches under the dependabot prefix, mints a GitHub App installation token from DEPENDABOT_REPAIR_APP_ID and DEPENDABOT_REPAIR_APP_PRIVATE_KEY, restores with NuGet pinned to 7.9.0, runs the repair, pushes onto Dependabot's branch with the same token, appends the repairs report to the pull-request body and applies deps:autofixed only when a repair outside the analyzer-item and binding-redirect classes was applied. Documents the workflow, its trigger, both secret names, the installation runbook and the degraded mode in .github/workflows/README.md, with the NuGet pin recorded once and asserted equal to every workflow literal. Adds tests/scripts/dependencies/Repair-PackageManifestConsistency.Tests.ps1 with 30 cases, three of them the AC10 skip-and-proceed assertions, and extends tests/scripts/dependencies/DependabotConfig.Tests.ps1 with the AC17 and AC26 assertions. The suite caught two defects in the entry point: -WhatIf did not reach Invoke-ManifestNormalization, a preference variable set on a script not reaching a module's own session state; and a disagreement on an item the repair deliberately left alone produced a failure result despite the class being non-fatal. Delivers AC5, AC10, AC15, AC17 and AC26. Gates: the repair run over the working tree applies 0 repairs over 2559 examined elements and writes nothing, twice, with the porcelain captures byte-identical and the csproj-scoped porcelain empty after both runs; the merge-base csproj diff lists exactly the 15 Write Set paths and no sixteenth; csharpier check 0 over 1623 files; actionlint 0 with empty stdout over 9 workflow files; Pester 302 passed 0 failed, with the entry point at 91.12 percent line coverage. Reported, not repaired: ten binding redirects across six app.config files name an older assembly version than the restored package and the project reference declare. The drift predates this branch, is outside the upgrade this pass repairs, and is recorded in the P7-T5 evidence for a follow-up issue. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QaUVgY37zfbsTvSTPd7wsr
…riteria Records the P8-T1 measurement that selected the deferred branch for AC18, AC19 and AC20: a repository secrets query exiting 0 with an empty name list, so CREDENTIAL-PRESENT is false from a successful query rather than a forbidden one, and an open-pull-request query exiting 0 with a Dependabot count of 0. Both conditions for the live branch fail independently, so the three criteria are unverifiable rather than failing and are left unchecked. Records the follow-up issue 914, which carries the three deferred criteria with their exact verification commands and the runbook path, the two scripts/vscode files Scope Decision 8 leaves unformatted, the ten stale binding redirects verified pre-existing at the merge base, and the latent no-AssemblyVersion fallback in Invoke-ProjectConsistencyRepair that ships unreached. The issue was created through the repository promotion lifecycle rather than the gh issue-creation command, which a repository hook refused because the orchestrator checkpoint's steps 6 to 8 are legitimately pending; the refusal output is recorded verbatim in the mirror and no gate was suppressed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QaUVgY37zfbsTvSTPd7wsr
Corrects five PSScriptAnalyzer findings the Phase 9 analyzer step surfaced in tests/scripts/dependencies/Repair-PackageManifestConsistency.Tests.ps1, the only file this change owns that carried any. No analyzer step ran between that file being authored in Batch D and this gate, which is why they surface here. PSUseShouldProcessForStateChangingFunctions fired on the two fixture builders. The New verb is in the analyzer state-changing set and neither function changes state: each returns an in-memory hashtable. Declaring SupportsShouldProcess would assert a capability they do not have, so both were renamed to Get-RepairFixture and Get-StandardFixture. Both are defined and called only inside this file. PSReviewUnusedParameter fired on three parameters referenced only inside nested scriptblocks closed over by GetNewClosure, which the analyzer data-flow does not follow. Each is now read into a local at body level and the closures capture the local, which captures the same values. No assertion, expectation or fixture datum was altered. Records the full seven-step final QA loop. PowerShell: format rewrote 0 of 46 files with an empty revert set; analyze reports exactly 13 findings, every one a member of the 16-tuple merge-base baseline and 0 in the fifteen owned files; Pester 302 passed 0 failed with aggregate line coverage 93.89 percent, every new module at or above 94.12 and Sync-PackageReferences moved from 0 covered lines to 95. C#: csharpier check 0 over 1623 files; both solution-wide rebuilds exit 0 with 0 CS0006 and 18 compiled projects each; MSTest 7343 passed 0 failed with first-party coverage 85.91 percent line and 80.07 percent branch, each 0.02 points below the P2-T7 baseline and well inside the half-point noise band. Delivers AC24 and AC25, which brings the criteria to 23 of 26. AC18, AC19 and AC20 remain unchecked as unverifiable rather than failing and are carried by issue 914. Commits the two permitted coverage-evidence forms the run produced, the JaCoCo package projection and the test-result summary, rather than the raw collector document that gate rule 12 prohibits under the evidence tree. Reported, not repaired: the change-footprint audit finds one path outside the four classes its acceptance enumerates, the feature-promotion lifecycle artifact under docs/features/potential/promoted. It was introduced by the promotion commit d46ae2d before Phase 0 and no plan task can avoid producing it; the clause omits that inherited class. Recorded in the P9-T12 artifact. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QaUVgY37zfbsTvSTPd7wsr
Records the review-handoff index over all 133 evidence artifacts, each with its discharging task, its recorded exit code and, for the criteria-bearing ones, the criterion it discharges. Every listed path was confirmed present on disk. The index also pins the merge base, the four batch commits, the Phase 8 commit and the Phase 9 commit. Asserts the gate rule 12 honesty obligation where it can fail: exactly six artifacts record a JaCoCo line figure and each was searched for the standing-in statement. Expected six, found six. The other seventeen Pester tasks record no coverage figure and carry no such obligation. Records the plan check-off re-sync. The execution copy and the session copy are byte-identical once every task line is normalised to its unchecked form, which is the fixpoint exclusion that lets this comparison terminate at all. The execution copy carried 126 ticks at that measurement and carries 128 here, the last two being the re-sync task and this one, ticked before the commit so the terminal tree is clean. Closes the plan at 128 of 128 tasks and 23 of 26 acceptance criteria. AC18, AC19 and AC20 remain unverifiable for want of a credential and a fixture and are carried by issue 914. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QaUVgY37zfbsTvSTPd7wsr
Carries the artifact recording the plan close-out: 128 of 128 task lines ticked, 0 unticked, an empty porcelain capture after the close-out commit, and that commit's SHA. The artifact is written after the commit it records because the SHA it records does not exist until that commit is made, so this one further commit exists only to carry it and leave the terminal tree clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QaUVgY37zfbsTvSTPd7wsr
…-and-ci-failing-nuget-upgrades-911
Review verdict PARTIAL, no-go until the four blocking findings are discharged. 23 of 26 acceptance criteria pass. The review found three defects in the repair workflow that no gate had exercised, because the workflow has never run. The most serious is that the push gate counts only project-file repairs, so a run whose writes are normalisation or binding-redirect goes green, skips the push, discards the repair and reports that none were applied. It also overturned the orchestrator's judgment that the reference-sync coverage shortfall was acceptable: nine of the uncovered lines are pure logic and every one is a negative or error path, including the rejection handler for the very defect this change fixes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QaUVgY37zfbsTvSTPd7wsr
Seven phases, eighty tasks, discharging four blocking and five major review findings plus four minor. Amends the AC14 note to record that the binding-redirect class is exercised by unit assertion only and is not reachable from the configured trigger, because the workflow invokes the repair entry point with no candidate upgrade set. Criterion authorship belongs to the planning agents, so the amendment is applied here rather than by the executor; the criterion stays ticked and the count stays at twenty-six. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QaUVgY37zfbsTvSTPd7wsr
Appends the gate rule 20 clause pair to P3-T6, the one task preflight found omitting it. That task removes a filter clause on the argument that the binding-redirect class is unreachable, and the unreachability rests entirely on a trigger path that has never executed - so a reader saw a deletion with no statement that its premise is unverified. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QaUVgY37zfbsTvSTPd7wsr
…ageReferences Discharges remediation finding R2 for issue 911. Adds eight Pester tests driving the existing injected seam, covering lines 151, 180, 248, 290, 293, 330, 336, 337 and 345. Per-file line coverage moves from 95 of 127 to 104 of 127. Line 248 is the issue 902 rejection handler. Also records the Phase 0 remediation baseline for this cycle. Claude-Session: https://claude.ai/code/session_01QaUVgY37zfbsTvSTPd7wsr
… incorrectly Discharges remediation findings R5, R9b and R9c for issue 911. R5: Resolve-ReferenceAssemblyVersion moves from the composition root into ProjectConsistency.psm1 and Invoke-ProjectConsistencyRepair now resolves the assembly version per package and passes it, so the entry point preserves a declared Reference assembly version instead of rewriting it to the package version for every caller. R9b: the unfiltered Get-AnalyzerAssemblyPath call site is commented as a verification membership set that must never be written to a project file. R9c: the default manifest lister emits the enumerated directory and returned file counts, making a one-level-deep discovery shortfall observable in the run log. Claude-Session: https://claude.ai/code/session_01QaUVgY37zfbsTvSTPd7wsr
… identity Discharges remediation findings R3, R6, R7 and R8 for issue 911, all four in .github/workflows/dependabot-repair.yml. R3 blocking: the repair step publishes written-count from the WrittenPath set and the commit step gates on it. RepairCount counts per-project repair records only, so a run whose only writes were a manifest normalisation or a binding-redirect reconciliation went green, skipped the push and discarded the repair. R6: the disclosure step is guarded on written-count or skip-count, and the body edit strips any prior delimited block before appending a fresh one, so repeated runs leave one block rather than accumulating. R7: the binding-redirect clause is removed from the beyond-known-weak filter and the reachability decision is recorded at the line it explains. R8: the commit identity is derived from the token step slug and the resolved bot user id, with a named guard on each, replacing a hand-written address that matched no account. Claude-Session: https://claude.ai/code/session_01QaUVgY37zfbsTvSTPd7wsr
…aceholders Discharges remediation finding R4 for issue 911, and R9a in the untracked pr_context file. R4 blocking: 103 occurrences of an absolute host path disclosing the account name were committed across 33 markdown documents under the feature folder. Every occurrence is replaced with a placeholder token, byte-exactly, through a substitution map built at run time from the home directory and applied longest-first under IgnoreCase. Seven distinct spellings were present, including a dash-mangled scratchpad form the review did not enumerate. Residual is zero occurrences across zero files, and the placeholder count rose from 121 to 224, which is 121 plus the 103 replaced. The working tree is sanitised but the pre-sanitisation blobs remain reachable in this branch history. The pull request must be squash-merged. That instruction and its reason are recorded for whoever merges. Claude-Session: https://claude.ai/code/session_01QaUVgY37zfbsTvSTPd7wsr
…tprint Phase 5 of remediation cycle 1. The full toolchain ran in order in a single pass with no restart: PoshQC format 0 rewrites of 46, PoshQC analyze 13 findings equal to baseline with 0 in the seven owned files, Pester 318 passed at 94.43 percent line coverage, CSharpier 1623 files clean, MSBuild analyzer and nullable rebuilds each 0 warnings and 0 errors across 18 compiled assemblies, and MSTest 7343 passed at 0.8593 line and 0.8010 branch. Both C# deltas against the post-merge baseline are positive on identical denominators. Sync-PackageReferences.ps1 moves from 74.80 to 81.89 percent with all nine target logic lines covered. All 17 footprint files are at most 500 lines. The change footprint is exactly the 8 enumerated non-documentation paths, every one already a spec Write Set member. Claude-Session: https://claude.ai/code/session_01QaUVgY37zfbsTvSTPd7wsr
…an close-out Phase 6 of remediation cycle 1, and the terminal commit of the cycle. The branch is pushed and the CI workflow was dispatched against it. Run 35513025198 at head de9a001 concluded success with all six jobs green: actionlint, format-check, build-analyzers, build-nullable, mstest-coverage and pester. That run carries event workflow_dispatch, so the authoritative discharge of R1 remains the pull-request context run at whatever commit is merged. Three merge-time instructions are recorded with their reasons: squash-merge, because the pre-sanitisation blobs remain reachable in branch history; re-strip the two issue-number detector false positives if pr_context is regenerated; and do not represent issue 911 as closed until 914 discharges AC18, AC19 and AC20 against a live fixture. All 80 plan tasks are ticked. Claude-Session: https://claude.ai/code/session_01QaUVgY37zfbsTvSTPd7wsr
Each of these three records a fact that does not exist until after the commit it describes, so none could be inside that commit. Documentation only, under the feature folder, and no input to any CI job, so the green run at head remains evidence about the code at head. Claude-Session: https://claude.ai/code/session_01QaUVgY37zfbsTvSTPd7wsr
Applies the one Major and four Minor items the 2026-09-20T09-42 re-audit recommended before the pull request. Not a remediation cycle: the re-audit returned zero remediable blocking findings and a Go decision, neither completed plan was reopened, and no acceptance criterion was amended. R-C2-1, Major. The workflows README advertised an app.config binding-redirect repair the workflow_run trigger cannot reach. The clause is replaced with a named limit that states the pass exists in the repair script, that the step supplies no -CandidateUpgrade so the applied-upgrade set is always empty, and the condition under which the class becomes reachable again. R-C2-2, Minor and latent. Resolve-ReferenceAssemblyVersion matched case-sensitively while Get-RewrittenReferenceVersionLine compares with -ne, which does not. The resolver now uses RegexOptions.IgnoreCase and tolerates whitespace on both sides of the Include name, matching the rewriter's Trim. One test added, observed failing before the fix on the predicted mechanism - resolver returns the empty string - and passing after. Exposure was and remains zero of 912 Reference elements. R-C2-3, Minor. The R9c discovery record used Write-Verbose and its comment claimed the shortfall was observable in the run log. The workflow step passes no -Verbose and sets no preference, so it was not. Now Write-Information with -InformationAction Continue stated on the call, so visibility does not depend on the caller, and the comment says what the code does. The test that pinned the old literal is updated. R-C2-4, Minor. Line 410 of Sync-PackageReferences.ps1 is now covered, taking the file from 104 of 127 to 105 of 127, 82.68 percent, the ceiling under the existing seam. The finding described 410 as the non-zero-fix branch; it is the zero-fix branch, and the non-zero branch at 407 was already covered. The test drives the branch that is actually uncovered and the discrepancy is recorded. R-C2-5, Minor. The P3-T10 deletion table attributed 2 deletions to P3-T4 where the diff shows 1, making the column sum to 6 against a measured 5. Re-measured and corrected to 1 with a dated corrigendum; the conclusion and the numstat are untouched. PowerShell toolchain loop, restarted once after format normalised line endings in four files. Final pass: format 0 rewrites, verified by a byte-identical aggregate hash over all 48 PowerShell files; analyze 13 findings, element for element the P0-T17 baseline, 0 in owned files; Pester 320 passed, 0 failed, 0 skipped, exit 0, aggregate line coverage 94.49 against a previous 94.43 and Sync-PackageReferences.ps1 at 82.68 against a previous 81.89. The C# loop was not re-run: this cycle changed no .cs, .csproj or config file. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QaUVgY37zfbsTvSTPd7wsr
Run 36199942860, event pull_request, head f56dabc, all six checks pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tphos3rkKSiEVLUCuSu8UK
This was referenced Sep 26, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Suggested title
fix(deps): consolidate Dependabot and add a repair pass that keeps packages.config projects internally consistent
Summary
.github/workflows/dependabot-repair.yml, a repair pass that runs on the Dependabot pull request and reconciles every project against itspackages.config(<Import>,<Error>,<Reference>,<HintPath>,<Analyzer Include>), then verifies the result..github/dependabot.ymlto a single catch-all group withopen-pull-requests-limit: 1, so one upgrade cycle produces one pull request. All existingignoreentries and semver-major exclusions are preserved and pinned by a test.packages.configandapp.configout of the CSharpier gate via.csharpierignoreand normalises all 18 manifests once. Dependabot writes these files inline, and the format check previously rejected all 20 files it touched on Bump the test-frameworks group with 11 updates #908.nuget-versionto an exact version in_build-analyzers.yml,_build-nullable.ymland_mstest-coverage.yml, replacing the floatinglatest.Sync-PackageReferences.ps1rankednetstandard2.1abovenetstandard2.0; incompatible frameworks are now excluded rather than ranked) and Bug: Bug: ToDoModel.Test/packages.config omits FSharp.Core and Deedle despite carrying HintPaths for both #903 (ToDoModel.Test/packages.configomitted FSharp.Core and Deedle).scripts/dependencies/with Pester suites; PowerShell line coverage moves from 83.93% onorigin/mainto 94.49% at head.Why
The CI failure on Dependabot pull request #908 had a verified three-version divergence inside single projects:
packages.config.csproj<Import>/<Error>.csproj<Analyzer Include>nuget restorehonourspackages.config, the build honours the.csproj, andEnsureNuGetPackageBuildImportsfails closed. Dependabot already invokesnuget update, so replacing it would inherit the same defect. The design decision recorded in the feature folder is to keep Dependabot as the upgrade engine and add a repair pass over its own pull request.The previously assumed "one pull request per group x directory" fan-out was falsified: #908 was one pull request spanning ten directories. The fan-out was one pull request per group, and four groups produced four pull requests, including #907, #908 and #909.
What Changed
Core feature: repair pass
scripts/dependencies/Repair-PackageManifestConsistency.ps1: composition root invoked by the workflow.scripts/dependencies/ProjectConsistency.psm1: version reconciliation across the four dependent element kinds, including Reference assembly versions resolved from assembly evidence.scripts/dependencies/AnalyzerItemRepair.psm1: rewrites<Analyzer Include>paths while preserving the existing analyzer folder segment and sibling items.scripts/dependencies/ConsistencyVerifier.psm1: repairs freely and fails only on residual inconsistency; asserts reference completeness and detects orphaned hint paths.scripts/dependencies/PackageCompatibility.psm1: asset-level compatibility gate; an incompatible package is skipped and the remaining upgrades proceed.scripts/dependencies/PackageGraph.psm1: manifest discovery and the idempotent normaliser.scripts/vscode/Sync-PackageReferences.ps1: rewritten framework selection (Bug: Bug: Sync-PackageReferences ranks netstandard2.1 above netstandard2.0 and would reintroduce #895 #902) behind an injectable seam.CI / tooling
.github/workflows/dependabot-repair.yml(new): gated on the repair write set; commits under a GitHub App identity when the credential exists, otherwise documented fallback..github/dependabot.yml: single group, limit 1..github/workflows/_build-analyzers.yml,_build-nullable.yml,_mstest-coverage.yml: exactnuget-versionpin..github/workflows/_pester.yml: scope extended to the new test directory..csharpierignore:packages.configandapp.config.Mechanical
packages.configand 17app.configfiles normalised (whitespace/layout only; the diff is large and mechanical).ToDoModel.Test/packages.config: FSharp.Core and Deedle entries added (Bug: Bug: ToDoModel.Test/packages.config omits FSharp.Core and Deedle despite carrying HintPaths for both #903).Tests
tests/scripts/dependencies/andtests/scripts/vscode/Sync-PackageReferences.Tests.ps1(previously the only script inscripts/vscode/with no tests). 320 tests pass.Docs
.github/workflows/README.md: operator documentation for the repair workflow, including the stated limit that theapp.configbinding-redirect pass exists in the script but is not reachable from theworkflow_runinvocation.Architecture / How It Fits Together
nuget update, which rewritespackages.config,<Import>,<Error>and<Reference>but not<Analyzer Include>, and may write out-of-scope versions.dependabot-repair.ymlchecks out the pull request branch and runsRepair-PackageManifestConsistency.ps1.PackageGraph), gates compatibility (PackageCompatibility), reconciles project elements (ProjectConsistency,AnalyzerItemRepair), then runsConsistencyVerifier, which fails the step on any residual inconsistency.pull_requestsynchronizeevent, so the required checks re-run.Verification
Completed (recorded under
evidence/qa-gates/).cs,.csprojor config file.CIrun (35513025198) passed all six jobs atde9a00106; the intervening commits are documentation and PowerShell-only. The PR-triggered run at the merge head is the authoritative gate.Not verified in this PR
Recommended
pwsh -NoProfile -File scripts/dependencies/Repair-PackageManifestConsistency.ps1against a clean checkout should produce an emptygit diff.dependabot-repairrun it triggers.Backward Compatibility / Migration Notes
.csor.csprojchanges remain in the diff. The Bug: Bug: Meziantou.Analyzer HintPath skew masked by CI cache restore-keys fallback #898 analyzer realignment landed separately via fix(898): point Analyzer Include at the Meziantou version the manifests declare #913 and was absorbed by mergingorigin/main.packages.configorapp.config.runbooks/github-app-installation-token.runbook.md), the repair push still triggers a run, but it waits for a manual approval click once per upgrade cycle.Risks and Mitigations
app-slugassumption into a named step failure, and Bug: dependabot-repair-deferred-credential-criteria-and-residuals (follow-up to #911) #914 tracks the live verification. Rollback: deletedependabot-repair.yml; the remaining changes stand on their own.workflow_runtrigger. Documented in the README and in the workflow; ten stale redirects in sixapp.configfiles were verified as pre-existing.Sync-PackageReferences.ps1is at 82.68%, which is the ceiling under its seam; it passes theCLAUDE.md80% floor but not the 85% floor in.claude/rules/. The conflict is tracked in Coverage threshold contradiction remains: CLAUDE.md/csharp.md say 80%, general-unit-test.md/quality-tiers.md say 85%/75%, and two live gates disagree #563 / Refactor: coverage-threshold-discrepancy-claude-md-vs-rules #668.Review Guide
spec.mdAcceptance Criteria, thenfeature-audit.2026-09-20T09-42.mdfor the latest review outcome..github/dependabot.yml,.github/workflows/dependabot-repair.yml,.github/workflows/README.md.scripts/dependencies/Repair-PackageManifestConsistency.ps1, then the five modules, thenscripts/vscode/Sync-PackageReferences.ps1.packages.config/app.confignormalisations and the evidence tree.Merge instructions
597bb2fcb), so earlier commits in the branch history still contain them. A squash merge keeps them offmain.Follow-ups
app-slugoutput,[bot]author login, required-check re-run, single disclosure block).CLAUDE.mdand.claude/rules/.GitHub Auto-close
Referenced, not closed: #911 (stays open until #914), #914, #898 (already closed by #913), #563, #668 (coverage-floor conflict, unresolved), #181 and #895 (already closed). #907, #908 and #909 are Dependabot pull requests, not issues. Issue states were checked with
gh issue viewin this session because the PR-context collector reported the GitHub CLI as unavailable.🤖 Generated with Claude Code
https://claude.ai/code/session_01Tphos3rkKSiEVLUCuSu8UK