Skip to content

feat: add ModelFamilyTests to CI, fix 8 neural network code bugs - #1044

Merged
ooples merged 22 commits into
masterfrom
feat/ci-test-pipeline-improvements
Mar 28, 2026
Merged

ooples merged 22 commits into
masterfrom
feat/ci-test-pipeline-improvements

Conversation

@ooples

@ooples ooples commented Mar 28, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • Add ModelFamilyTests shards to CI pipeline (9 new test shards covering Classification, Clustering, Regression, NeuralNetworks, TimeSeries, Diffusion, and Generated Layers)
  • Simplify CI test shard filters from verbose negative exclusions to clean positive namespace filters
  • Fix 8 source code bugs discovered by ModelFamilyTests invariant tests
  • Fix 7 test configuration issues (InputShape/OutputShape)

Code Bugs Fixed

  1. GraphNeuralNetwork (3 bugs → 20/20 tests pass):

    • Persist Adam optimizer state across Train calls (was creating new Adam each call, resetting momentum)
    • Normalize adjacency matrix per Kipf & Welling 2017 (unnormalized caused 1000x value amplification)
    • Restore softmax output layer (needed for CategoricalCrossEntropy loss)
  2. RecurrentLayer: Replace TensorAllocator.Rent with new Tensor to prevent stale buffer reuse after reshape

  3. TemporalMemoryLayer: Use TensorBroadcastDivide for [2048,32] / [2048,1] normalization (was TensorDivide which requires exact shape match)

  4. AudioVisualCorrespondenceNetwork: Replace ContrastiveLoss with MSE as default (ContrastiveLoss requires paired inputs incompatible with standard Train signature)

  5. TransformerEncoderLayer: Handle 1D input [features] by reshaping to [1,1,features] (was IndexOutOfRange for rank < 2)

  6. LayerBase.ApplyActivationDerivative: Reshape outputGradient to match input rank when element counts are equal (fixes backward pass for layers with internal reshaping)

Test Configuration Fixes

Test File Fix
BGETests InputShape/OutputShape → [768]
ColBERTTests OutputShape → [128] (projection dim)
TransformerEmbeddingNetworkTests InputShape/OutputShape → [768]
DenseNetNetworkTests InputShape → [1,3,32,32]
MobileNetV3NetworkTests InputShape → [1,3,32,32]
CapsuleNetworkTests OutputShape → [784]
VoxelCNNTests OutputShape → [128]
RecurrentNeuralNetworkTests InputShape → [4,128] (sequence format)

Test plan

  • dotnet build src/AiDotNet.csproj --framework net10.0 -c Release — 0 errors
  • GraphNeuralNetwork: 20/20 pass
  • GRU, LSTM, FeedForward, Autoencoder: all pass
  • BGE, AudioVisualCorrespondence: all pass
  • Full NN test suite rerun (estimated ~50% reduction in failures)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Improved default loss choice, tensor shape/gradient handling, normalization broadcasting, recurrent output allocation, and 1D transformer input/reshape logic
    • Stabilized training by reusing optimizer state and adjusting adjacency/weight initialization
  • Tests

    • Added a comprehensive associative‑memory test base and updated many model tests with explicit input/output shapes
    • Test generator now supports architecture‑only constructors
  • Chores

    • Reorganized CI test shards into focused topic and model‑family sections and renamed playground/other shards

ooples and others added 10 commits March 27, 2026 17:20
- Add 8 new ModelFamilyTests shards: Classification, Clustering/GP,
  Regression, NeuralNetworks, TimeSeries/Activation/Loss, and
  3 Diffusion sub-shards (A-I, J-R, S-Z split for the 244 test files)
- Add Generated Layers shard for auto-generated layer tests (~1670 methods)
- Simplify Unit Test shard filters from verbose negative exclusions to
  clean positive namespace filters using parenthesized OR syntax
- Keep one "remaining" catch-all shard with negative filters for any
  UnitTests namespaces not explicitly listed (e.g., ActiveLearning,
  Agents, PhysicsInformed, etc.)
- Remove redundant Category!=Integration from filters (only needed for
  the few tests that have that trait, not all shards)
- Consolidate from 22 to 30 shards (16 unit + 4 other + 9 model family + 1 serving)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…tLayer tensor

- GraphNeuralNetwork: reuse Adam optimizer across Train calls instead of
  creating new instance each time (resets momentum/velocity, causes
  divergence from 232 to 1.7M loss). Reduced from 8 to 2 test failures.
- RecurrentLayer: replace TensorAllocator.Rent with new Tensor for output
  buffer to prevent stale data from unreturned rented tensors after reshape

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three root causes fixed per Kipf & Welling 2017 GCN paper:

1. Persist Adam optimizer across Train calls — creating new Adam each
   call resets momentum/velocity, causing loss explosion (232 → 1.7M)
2. Normalize auto-generated adjacency matrix — fully connected graph
   with weight=1 amplifies values 10x per layer (10^3 after 3 layers).
   Now uses symmetric normalization: Â[i,j] = 1/numNodes
3. Restore softmax output layer — was filtered out, but
   CategoricalCrossEntropy requires probability inputs (sum to 1).
   Raw logits caused log(0) → -Inf in loss computation

Also lowered default learning rate to 0.0001 for GNNs since graph
convolution aggregates neighbor features which amplifies gradients.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
RNN test was using 1D [128] input which treated the entire input as
a single timestep. Changed to [4, 128] (4 timesteps, 128 features)
to properly test recurrent behavior. With a single timestep,
hidden state recurrence has no effect and the RNN behaves as
a feedforward network.

Also kept RecurrentLayer tensor allocation fix (new instead of Rent)
to prevent stale buffer reuse after reshape.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
TemporalMemoryLayer.NormalizeCellStates was using TensorDivide which
requires exact shape match. CellStates [2048, 32] and column sums
[2048, 1] need broadcast division. Changed to TensorBroadcastDivide.

Resolves 4 of 9 HTMNetwork test failures (shape mismatch errors).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…veLoss

- BGETests: set InputShape/OutputShape to [768] (embedding model)
- ColBERTTests: set InputShape/OutputShape to [768]
- TransformerEmbeddingNetworkTests: set InputShape/OutputShape to [768]
- DenseNetNetworkTests: set InputShape to [1,3,32,32] for conv layers
- MobileNetV3NetworkTests: set InputShape to [1,3,32,32] for conv layers
- AudioVisualCorrespondenceNetwork: replace ContrastiveLoss with
  MeanSquaredErrorLoss as default — ContrastiveLoss requires paired
  inputs which is incompatible with standard Train(input, target)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- CapsuleNetwork: output is 784 (flattened feature map), not 10
- VoxelCNN: output is 128 (conv feature dim), not 1

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
TransformerEncoderLayer.Forward crashed with IndexOutOfRangeException
for 1D input [features] because it only handled rank >= 2. Added rank 1
handling: reshape [features] → [1, 1, features] (single batch, single
token) and restore to [features] on output.

Fixes ColBERT, BGE, and other embedding models that pass 1D vectors
through transformer encoder layers.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ColBERT projects 768-dim BERT embeddings down to 128-dim via its
final DenseLayer(768, 128). Test was expecting 768 output.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When a layer internally reshapes tensors (e.g., 1D→2D in DenseLayer,
RecurrentLayer), the pre-activation input and output gradient can have
different ranks during backprop. Instead of throwing, reshape the
gradient to match when element counts are equal.

Fixes backward pass crashes in SparseNeuralNetwork (6 of 8 failures),
GraphAttentionNetwork, and other models with internal reshaping.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Mar 28, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
aidotnet_website Ready Ready Preview, Comment Mar 28, 2026 2:35am
aidotnet-playground-api Ready Ready Preview, Comment Mar 28, 2026 2:35am

@coderabbitai

coderabbitai Bot commented Mar 28, 2026 •

Copy link
Copy Markdown
Contributor

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 9c9a76f6-446f-4142-b64c-b7890d04907e

📥 Commits

Reviewing files that changed from the base of the PR and between 86c39ce and a61547d.

📒 Files selected for processing (2)
  • src/AiDotNet.Generators/TestScaffoldGenerator.cs
  • tests/AiDotNet.Tests/ModelFamilyTests/Base/AssociativeMemoryTestBase.cs

Walkthrough

Restructures CI test sharding; changes defaults and optimizer reuse in GNNs; adjusts layer tensor/rank handling and allocation semantics; adds architecture-only constructor detection in test generator (may emit runtime placeholders); introduces a large AssociativeMemory test base and updates many model-family test shapes.

Changes

Cohort / File(s) Summary
CI test sharding
\.github/workflows/sonarcloud.yml
Replaces broad unit filters with many topic-focused unit shards, renames non-unit shards, introduces Model Family shards, and standardizes filters to Category!=GPU&Category!=Stress.
Neural network defaults & GNN
src/NeuralNetworks/AudioVisualCorrespondenceNetwork.cs, src/NeuralNetworks/GraphNeuralNetwork.cs
AudioVisualCorrespondenceNetwork now defaults null loss to MeanSquaredErrorLoss<T>; GraphNeuralNetwork adds reusable _trainOptimizer (lazy AdamOptimizer with DefaultTrainLearningRate) and normalizes auto-generated adjacency to 1/numNodes.
Layer behaviors & allocation
src/NeuralNetworks/Layers/LayerBase.cs, src/NeuralNetworks/Layers/RecurrentLayer.cs, src/NeuralNetworks/Layers/TemporalMemoryLayer.cs, src/NeuralNetworks/Layers/TransformerEncoderLayer.cs
LayerBase now reshapes outputGradient when ranks differ but element counts match; RecurrentLayer uses direct new Tensor<T>(...) allocation instead of rented buffers; TemporalMemoryLayer uses TensorBroadcastDivide for per-column normalization; TransformerEncoderLayer adds explicit 1D input path and adjusts output restoration.
SSM minor formatting
src/NeuralNetworks/Layers/SSM/RWKV7Block.cs
Whitespace/formatting-only blank-line insertion (non-functional).
Deserialization: ContinuumMemorySystemLayer
src/Helpers/DeserializationHelper.cs
Adds constructor-resolution branch for ContinuumMemorySystemLayer (prefers ctor with (int[] inputShape, int hiddenDim)), computes hiddenDim from inputShape or uses 256 default, and throws a specific NotSupportedException if no matching ctor found.
Test scaffold generator (BLOCKING)
src/AiDotNet.Generators/TestScaffoldGenerator.cs
Detects HasArchitectureOnlyConstructor and emits instantiation using a generated NeuralNetworkArchitecture when parameterless ctor absent. Note: generator still emits runtime NotImplementedException placeholders for truly non-constructible models — this is a blocking test-generation/runtime placeholder to address.
Model-family test shapes
tests/AiDotNet.Tests/ModelFamilyTests/NeuralNetworks/*
Adds/updates InputShape/OutputShape overrides across many tests (BGE/ColBERT/TransformerEmbedding → [768]; DenseNet/MobileNetV3 → input [1,3,32,32], output [1000]; Capsule → output [784]; Recurrent → input [4,128]; VoxelCNN → output [128], etc.).
Associative memory test base & updates (High-impact)
tests/AiDotNet.Tests/ModelFamilyTests/Base/AssociativeMemoryTestBase.cs, tests/.../HopeNetworkTests.cs, tests/.../HopfieldNetworkTests.cs
Adds comprehensive AssociativeMemoryTestBase with many deterministic tensor utilities and a large suite of behavioral and training tests; migrates Hope/Hopfield tests to this base and adds model-specific overrides. Review for test runtime cost, determinism, and flakiness.
Neural-net test adjustments
tests/AiDotNet.Tests/ModelFamilyTests/NeuralNetworks/*.cs
Multiple test files updated to declare shapes explicitly (BGETests, CapsuleNetworkTests, ColBERTTests, DenseNetNetworkTests, MobileNetV3NetworkTests, RecurrentNeuralNetworkTests, TransformerEmbeddingNetworkTests, VoxelCNNTests).

Sequence Diagram(s)

(Skipped — changes are broad, not a single new multi-component control flow that needs sequence visualization.)

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Possibly related PRs

Suggested labels

feature

Poem

🧪 CI shards split into tidy lanes,

gradients reshaped to ease their strains,
optimizers wake and keep their tune,
tests grow broader under the moon,
patchwork fixes stitched into the rune.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 56.36% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the two main objectives: adding ModelFamilyTests to CI and fixing 8 neural network code bugs, which aligns with the core changes across the PR.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/ci-test-pipeline-improvements

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/NeuralNetworks/Layers/TransformerEncoderLayer.cs (2)

570-573: ⚠️ Potential issue | 🟠 Major

ForwardGpu rejects 1D input while CPU Forward now accepts it — inconsistency.

The CPU Forward method (lines 453-457) now handles rank-1 inputs by reshaping to [1, 1, features], but ForwardGpu explicitly throws at line 572 for rank < 2. This creates a behavioral gap where CPU inference works with 1D inputs but GPU inference fails.

Proposed fix to handle 1D in ForwardGpu
         if (was2D)
         {
             // 2D: [seqLen, embedDim] -> add batch dim
             input3D = gpuEngine.ReshapeGpu(input, [1, inputShape[0], inputShape[1]]);
         }
+        else if (rank == 1)
+        {
+            // 1D: [features] -> [1, 1, features]
+            input3D = gpuEngine.ReshapeGpu(input, [1, 1, inputShape[0]]);
+        }
         else if (rank == 3)
         {
             // Standard 3D: [batch, seqLen, embedDim]
             input3D = input;
         }
         else if (rank > 3)
         {
             // Higher-rank: collapse leading dims into batch
             int flatBatch = 1;
             for (int d = 0; d < rank - 2; d++)
                 flatBatch *= inputShape[d];
             input3D = gpuEngine.ReshapeGpu(input, [flatBatch, inputShape[rank - 2], inputShape[rank - 1]]);
         }
-        else
-        {
-            throw new ArgumentException($"TransformerEncoderLayer requires at least 2D input, got {rank}D");
-        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/NeuralNetworks/Layers/TransformerEncoderLayer.cs` around lines 570 - 573,
ForwardGpu in TransformerEncoderLayer rejects rank-1 input while the CPU Forward
already accepts rank-1 by reshaping to [1,1,features]; update ForwardGpu to
mirror CPU behavior: detect rank == 1, reshape the input tensor to shape [1, 1,
features] (or equivalent GPU tensor view) before processing and remember to
reshape the output back if necessary, then proceed with the existing GPU logic;
modify the rank check in ForwardGpu to only throw for rank < 1 or rank >
expected upper bound and handle rank == 1 path similarly to Forward (refer to
the ForwardGpu method and the CPU Forward method in TransformerEncoderLayer for
where to add the reshape/unreshape steps).

742-757: ⚠️ Potential issue | 🟠 Major

BackwardManual doesn't handle 1D gradient case.

The backward pass only checks for gradWas2D (line 747) but doesn't handle the case where outputGradient.Shape.Length == 1. Since Forward now accepts 1D inputs and produces 1D outputs, the backward pass should reshape 1D gradients to 3D for internal processing and restore them afterward.

Proposed fix to handle 1D gradients
     private Tensor<T> BackwardManual(Tensor<T> outputGradient)
     {
         // If forward received 2D input, the output gradient will also be 2D
         // We need to reshape it to 3D to match internal processing shapes
         Tensor<T> grad3D;
+        bool gradWas1D = outputGradient.Shape.Length == 1;
         bool gradWas2D = outputGradient.Shape.Length == 2;

-        if (gradWas2D)
+        if (gradWas1D)
+        {
+            // 1D gradient: [features] -> [1, 1, features]
+            grad3D = outputGradient.Reshape(1, 1, outputGradient.Shape[0]);
+        }
+        else if (gradWas2D)
         {
             // 2D gradient: [seq, embed] -> [1, seq, embed]
             grad3D = outputGradient.Reshape(1, outputGradient.Shape[0], outputGradient.Shape[1]);
         }
         // ... rest of backward pass ...
         
         // If input was originally 2D, reshape gradient back to 2D
-        if (gradWas2D)
+        if (gradWas1D)
+        {
+            dInput = dInput.Reshape(dInput.Shape[2]);
+        }
+        else if (gradWas2D)
         {
             dInput = dInput.Reshape(dInput.Shape[1], dInput.Shape[2]);
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/NeuralNetworks/Layers/TransformerEncoderLayer.cs` around lines 742 - 757,
BackwardManual only handles 2D gradients; add logic to detect a 1D gradient
(outputGradient.Shape.Length == 1), reshape it to a 3D tensor for internal
processing (e.g., [1,1,embed] where embed = outputGradient.Shape[0]), and record
a flag (e.g., gradWas1D) so after the internal backward computations you can
reshape the resulting gradient back to 1D. Update the initial branching that
currently sets gradWas2D and builds grad3D to also set gradWas1D and perform the
appropriate reshape, and update the final gradient restore logic to handle both
gradWas2D and gradWas1D cases; reference the BackwardManual method and the
existing grad3D/gradWas2D variables when making these changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/sonarcloud.yml:
- Around line 198-213: Add a catch-all NN shard to the test matrix before the
explicit NN exclusions so new NeuralNetworks tests still run by default;
specifically insert a new matrix entry (e.g., name: "Unit - 08x NN-Fallback
(NeuralNetworks)") that targets tests in the UnitTests.NeuralNetworks namespace
(use a filter like
'Category!=GPU&Category!=Stress&FullyQualifiedName~UnitTests.NeuralNetworks')
placed before the existing explicit shards (the ones named "Unit - 08a
NN-Classic...", "Unit - 08b NN-Efficient...", "Unit - 08c NN-VLM...", "Unit -
08d NN-Adapters/Other") and keep the later explicit shard exclusions intact so
the specialized shards still run while the fallback catches any
new/uncategorized NN test classes.

In `@src/NeuralNetworks/GraphNeuralNetwork.cs`:
- Around line 932-934: The comment above the _trainOptimizer initialization in
TrainGraph is inconsistent with Train(): update the comment to mention the
explicit learning rate value (0.0001) so it matches the Train() comment;
specifically edit the comment near _trainOptimizer ??= new AdamOptimizer<T,
Tensor<T>, Tensor<T>>(...) / new AdamOptimizerOptions<T, Tensor<T>, Tensor<T>> {
InitialLearningRate = 0.0001 } to state that the optimizer is reused to preserve
Adam momentum state and that the initial learning rate is 0.0001 (matching
Train()).
- Around line 857-861: The inline comment above the _trainOptimizer
initialization in GraphNeuralNetwork.cs is inconsistent (mentions "Use lower
learning rate (0.0005)") while the code sets InitialLearningRate = 0.0001;
update the comment to state the actual learning rate (0.0001) so it matches the
AdamOptimizer<T, Tensor<T>, Tensor<T>> initialization and the
AdamOptimizerOptions InitialLearningRate value.

In `@src/NeuralNetworks/Layers/LayerBase.cs`:
- Around line 2057-2068: Update the XML documentation for the method in class
LayerBase that contains the rank-handling logic (the method using input and
outputGradient) to reflect the new behavior: when input.Rank !=
outputGradient.Rank but input.Length == outputGradient.Length the implementation
will reshape outputGradient to input.Shape instead of throwing; only when ranks
differ and element counts differ will it throw ArgumentException. Mention the
reshaping behavior, the conditions checked (rank vs length), and the exact
exception contract (thrown only on differing element counts) so the doc matches
the code.

In `@tests/AiDotNet.Tests/ModelFamilyTests/NeuralNetworks/BGETests.cs`:
- Around line 9-11: The InputShape and OutputShape declarations in BGETests.cs
use invalid array syntax and should be set to a single-element int array of 768;
update the properties InputShape and OutputShape to return new int[] { 768 } (or
use the C# array initializer {768}) and add a brief comment above them matching
other tests (e.g., "// BGE uses 768-dimensional embeddings") to document the
embedding size; adjust the properties named InputShape and OutputShape in the
BGETests class accordingly.

In
`@tests/AiDotNet.Tests/ModelFamilyTests/NeuralNetworks/DenseNetNetworkTests.cs`:
- Around line 9-12: The comment above the InputShape property in
DenseNetNetworkTests is incorrect: InputShape => [1, 3, 32, 32] is 4D (includes
batch), not 3D; update the comment near the InputShape/OutputShape properties
(in class DenseNetNetworkTests) to state "4D input [batch, channels, height,
width] for convolutional layers" (or similar wording) so it accurately describes
the shape.

In
`@tests/AiDotNet.Tests/ModelFamilyTests/NeuralNetworks/MobileNetV3NetworkTests.cs`:
- Around line 9-12: Update the misleading comment in MobileNetV3NetworkTests:
change the comment above the InputShape property to state that MobileNetV3
expects a 4D input [batch, channels, height, width] (e.g., [1, 3, 32, 32])
rather than "3D input [channels, height, width]"; leave the InputShape and
OutputShape properties (InputShape and OutputShape) unchanged.

---

Outside diff comments:
In `@src/NeuralNetworks/Layers/TransformerEncoderLayer.cs`:
- Around line 570-573: ForwardGpu in TransformerEncoderLayer rejects rank-1
input while the CPU Forward already accepts rank-1 by reshaping to
[1,1,features]; update ForwardGpu to mirror CPU behavior: detect rank == 1,
reshape the input tensor to shape [1, 1, features] (or equivalent GPU tensor
view) before processing and remember to reshape the output back if necessary,
then proceed with the existing GPU logic; modify the rank check in ForwardGpu to
only throw for rank < 1 or rank > expected upper bound and handle rank == 1 path
similarly to Forward (refer to the ForwardGpu method and the CPU Forward method
in TransformerEncoderLayer for where to add the reshape/unreshape steps).
- Around line 742-757: BackwardManual only handles 2D gradients; add logic to
detect a 1D gradient (outputGradient.Shape.Length == 1), reshape it to a 3D
tensor for internal processing (e.g., [1,1,embed] where embed =
outputGradient.Shape[0]), and record a flag (e.g., gradWas1D) so after the
internal backward computations you can reshape the resulting gradient back to
1D. Update the initial branching that currently sets gradWas2D and builds grad3D
to also set gradWas1D and perform the appropriate reshape, and update the final
gradient restore logic to handle both gradWas2D and gradWas1D cases; reference
the BackwardManual method and the existing grad3D/gradWas2D variables when
making these changes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: c3a15aea-5033-461a-95d2-d1bec53d4ce7

📥 Commits

Reviewing files that changed from the base of the PR and between 20b72a5 and 4fa1c10.

📒 Files selected for processing (15)
  • .github/workflows/sonarcloud.yml
  • src/NeuralNetworks/AudioVisualCorrespondenceNetwork.cs
  • src/NeuralNetworks/GraphNeuralNetwork.cs
  • src/NeuralNetworks/Layers/LayerBase.cs
  • src/NeuralNetworks/Layers/RecurrentLayer.cs
  • src/NeuralNetworks/Layers/TemporalMemoryLayer.cs
  • src/NeuralNetworks/Layers/TransformerEncoderLayer.cs
  • tests/AiDotNet.Tests/ModelFamilyTests/NeuralNetworks/BGETests.cs
  • tests/AiDotNet.Tests/ModelFamilyTests/NeuralNetworks/CapsuleNetworkTests.cs
  • tests/AiDotNet.Tests/ModelFamilyTests/NeuralNetworks/ColBERTTests.cs
  • tests/AiDotNet.Tests/ModelFamilyTests/NeuralNetworks/DenseNetNetworkTests.cs
  • tests/AiDotNet.Tests/ModelFamilyTests/NeuralNetworks/MobileNetV3NetworkTests.cs
  • tests/AiDotNet.Tests/ModelFamilyTests/NeuralNetworks/RecurrentNeuralNetworkTests.cs
  • tests/AiDotNet.Tests/ModelFamilyTests/NeuralNetworks/TransformerEmbeddingNetworkTests.cs
  • tests/AiDotNet.Tests/ModelFamilyTests/NeuralNetworks/VoxelCNNTests.cs

Comment thread .github/workflows/sonarcloud.yml
Comment thread src/NeuralNetworks/GraphNeuralNetwork.cs Outdated
Comment thread src/NeuralNetworks/Layers/LayerBase.cs
Comment thread tests/AiDotNet.Tests/ModelFamilyTests/NeuralNetworks/BGETests.cs
Comment thread tests/AiDotNet.Tests/ModelFamilyTests/NeuralNetworks/DenseNetNetworkTests.cs Outdated
Comment thread tests/AiDotNet.Tests/ModelFamilyTests/NeuralNetworks/MobileNetV3NetworkTests.cs Outdated
ooples and others added 9 commits March 27, 2026 21:56
Hopfield and HopeNetwork use Hebbian learning and self-modifying
mechanisms rather than standard gradient-based training. The existing
NeuralNetworkModelTestBase tests gradient flow invariants that don't
apply. AssociativeMemoryTestBase tests pattern storage/recall, finite
outputs, and parameter changes appropriate for associative memory.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Hopfield networks are autoassociative (input=output) and use Hebbian
learning that ignores expectedOutput. The training invariant must test
that training changes output behavior, not MSE against a target.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New tests added to AssociativeMemoryTestBase:
1. PatternAutoAssociation - trained pattern should be recalled
2. NoiseRobustness - noisy input should be error-corrected
3. Capacity - multiple stored patterns should all be recallable
4. EnergyMonotonicity - stored patterns should have lower energy
5. OrthogonalPatterns - low-correlation patterns recalled distinctly
6. SerializationRoundTrip - serialize/deserialize preserves recall
7. MultiplePatternStability - older patterns not completely forgotten

HopfieldNetworkTests: all 22 tests pass, including energy hook
HopeNetworkTests: 18/22 pass, 4 failures are real code bugs
(non-deterministic predict, CMS deserialization, NaN instability)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Recovers critical fixes from feat/nuget-updates-and-model-tests branch
that were lost during squash merge:

MambaBlock: explicit per-element Conv1D backward (fixes Engine 3D
contiguity bugs), ReduceSumAxes01 workaround, GetParameterGradients,
ClearGradients, initialization strategy support

S6Scan: recomputed states during backward for numerical consistency
(per Mamba paper), explicit per-element gradient computation, Clone()
on GetSliceAlongDimension to ensure contiguous memory

RWKV7Block: SafeSetSliceDim for FFN-dimension slices, additional
forward caches for proper backward pass, initialization strategy

Mamba2Block: resolved debug logging conflict (removed)

Relates to #1045

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Models whose only required constructor parameter is
NeuralNetworkArchitecture<T> (with all other params optional) can now
be auto-constructed with a default architecture instead of emitting
throw NotImplementedException. This unlocks ~1000 models that
previously had no real test coverage from the generator.

Default architecture: OneDimensional, Regression, inputSize=16,
outputSize=4. Models with domain-specific initialization requirements
may still fail at construction time, but this exposes real constructor
bugs rather than silently skipping all invariant tests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Addresses PR #1044 review comment: NN tests not covered by explicit
shards 08a-08d (like HopfieldNetworkTests, HopeNetworkTests, etc.)
now have a catch-all shard that excludes already-sharded tests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Comment said 0.0005 but actual InitialLearningRate was 0.0001.
Fixed comment to match the code.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The method now auto-reshapes output gradients when ranks differ but
element counts match. Updated the exception doc and remarks to
document this rank-mismatch handling for layers that reshape internally.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- BGETests: add BERT-base 768-dim embedding comment for consistency
- DenseNetNetworkTests: shape is 4D [batch, channels, h, w] not 3D
- MobileNetV3NetworkTests: same 4D correction

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Mar 28, 2026

Copy link
Copy Markdown

Deployment failed with the following error:

Resource is limited - try again in 1 day (more than 100, code: "api-deployments-free-per-day").

Learn More: https://vercel.com/franklins-projects-02a0b5a0?upgradeToPro=build-rate-limit

@vercel

vercel Bot commented Mar 28, 2026

Copy link
Copy Markdown

Deployment failed with the following error:

Resource is limited - try again in 24 hours (more than 100, code: "api-deployments-free-per-day").

Learn More: https://vercel.com/franklins-projects-02a0b5a0?upgradeToPro=build-rate-limit

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

♻️ Duplicate comments (1)
.github/workflows/sonarcloud.yml (1)

214-232: ⚠️ Potential issue | 🟠 Major

The NN fallback shard is still pointed at the wrong namespace.

Unit - 08e NN-Remaining filters ModelFamilyTests.NeuralNetworks, but shard 13 still excludes UnitTests.NeuralNetworks. That means new neural-network unit tests can still be skipped entirely, while the model-family neural-network tests now run twice here and again in ModelFamily - NeuralNetworks.

Also applies to: 279-279, 325-328

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/sonarcloud.yml around lines 214 - 232, The NN fallback
shard "Unit - 08e NN-Remaining" currently includes
FullyQualifiedName~ModelFamilyTests.NeuralNetworks but incorrectly excludes
FullyQualifiedName!~UnitTests.NeuralNetworks, which causes new neural-network
unit tests to be skipped and model-family tests to run twice; update the
exclusion to FullyQualifiedName!~ModelFamilyTests.NeuralNetworks (i.e., replace
any FullyQualifiedName!~UnitTests.NeuralNetworks with
FullyQualifiedName!~ModelFamilyTests.NeuralNetworks) in the shard's filter and
apply the same replacement to the other occurrences noted (the other two
identical filter blocks referenced).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/NeuralNetworks/GraphNeuralNetwork.cs`:
- Around line 857-861: The code hardcodes the Adam initial learning rate
(0.0001) in two places; introduce a single shared constant or option and reuse
it to avoid magic numbers and drift. Add a private constant (e.g.,
GnnDefaultInitialLearningRate) or a configurable property on GraphNeuralNetwork,
then replace the literal 0.0001 in the AdamOptimizer<T, Tensor<T>, Tensor<T>>
initialization(s) (the lazy _trainOptimizer assignment and the second
initialization around the other Train path) to reference that constant/property
so both training paths use the same centralized value.
- Around line 761-766: Add a guard that rejects empty graphs before computing
the normalized weight to avoid divide-by-zero: check numNodes <= 0 at the start
of the method (in the GraphNeuralNetwork class where adj and normalizedWeight
are created), and throw a clear ArgumentException/ArgumentOutOfRangeException
indicating that numNodes must be positive; do this before the line that computes
normalizedWeight = NumOps.FromDouble(1.0 / numNodes) and before creating the adj
Tensor to ensure invalid inputs are rejected early.

In `@src/NeuralNetworks/Layers/SSM/RWKV7Block.cs`:
- Around line 1101-1107: SafeSetSliceDim is a duplicate of SafeSetSlice; remove
the SafeSetSliceDim method and update all call sites that currently call
SafeSetSliceDim(...) to call SafeSetSlice(dest, t, slice, batch, dim) instead,
ensuring the correct dimension argument (e.g., use _ffnDimension where the FFN
slice was intended instead of _modelDimension as fixed in the original bug fix).
Locate calls by the SafeSetSliceDim symbol and replace them with equivalent
SafeSetSlice calls; then delete the SafeSetSliceDim method implementation.

In `@tests/AiDotNet.Tests/ModelFamilyTests/Base/AssociativeMemoryTestBase.cs`:
- Around line 294-305: The test BatchConsistency_SingleMatchesBatch currently
calls network.Predict(input) twice and never exercises a real batching path; fix
by constructing a batched tensor (prepend a batch dimension, e.g., batchSize =
2) using CreateRandomTensor with shape derived from InputShape, call
network.Predict on the batched tensor (or the framework's batch predict method
if different), and compare the first slice of the batched result to the
single-sample result from network.Predict(input) to ensure consistency;
alternatively remove the duplicate test if batching cannot be exercised.
- Around line 527-541: The test in AssociativeMemoryTestBase.cs computes
noisyToRefDistance but never asserts that recall improves on the noisy input;
update the assertion logic after computing recalledDistance, noisyToRefDistance
and randomDistance to first ensure distances are finite (use the existing
double.IsNaN checks) and then assert that recalledDistance is no greater than
noisyToRefDistance (e.g., Assert.True(recalledDistance <= noisyToRefDistance,
...)); keep the existing random-tensor check (Assert.True(recalledDistance <
randomDistance + 0.1, ...)) but make the primary assertion that recalledDistance
<= noisyToRefDistance to prevent models that worsen the corrupted pattern from
passing. Ensure references to ComputeMSE, CreateRandomTensor, OutputShape and
ModelTestHelpers.CreateSeededRandom remain unchanged.
- Around line 575-596: The test currently only counts outputs as valid if all
entries are finite; change the check in the loop that iterates MultiPatternCount
to verify that network.Predict(patterns[p]) actually matches the stored target
(targets[p]) within a small numeric tolerance instead of merely being finite.
Replace the allFinite branch with a comparison (e.g., compute max absolute
difference or RMSE between recalled and targets[p]) and increment
finitePatternsRecalled only when that difference <= epsilon (choose a reasonable
epsilon like 1e-3 or make it a named constant); alternatively assert per-pattern
that the difference is within tolerance to give clearer failures. Ensure you
reference network.Predict, patterns, targets, MultiPatternCount (or the test
name Capacity_AllStoredPatternsShouldBeRecallable) so the assertion verifies
actual recall fidelity rather than just finiteness.

In `@tests/AiDotNet.Tests/ModelFamilyTests/NeuralNetworks/HopeNetworkTests.cs`:
- Around line 15-16: The test is hiding a missing deserializer by overriding
SupportsSerializationRoundTrip to false; either implement proper deserialization
for ContinuumMemorySystemLayer so HopeNetwork<double> supports a
serialization/clone round-trip, or make the lack of support explicit in the
public API and update tests to expect that behavior: remove or stop overriding
SupportsSerializationRoundTrip in HopeNetworkTests, then (a) implement a
Deserialize/FromSerialized form for ContinuumMemorySystemLayer and ensure
HopeNetwork<T>.Serialize/Deserialize (or Clone) exercise it, or (b) if
serialization is intentionally unsupported, add an explicit contract (e.g.,
throw NotSupportedException from HopeNetwork.Serialize/Deserialize or annotate
the public API) and change HopeNetworkTests to assert that serialization throws
the expected NotSupportedException rather than silently marking the invariant
unsupported.

---

Duplicate comments:
In @.github/workflows/sonarcloud.yml:
- Around line 214-232: The NN fallback shard "Unit - 08e NN-Remaining" currently
includes FullyQualifiedName~ModelFamilyTests.NeuralNetworks but incorrectly
excludes FullyQualifiedName!~UnitTests.NeuralNetworks, which causes new
neural-network unit tests to be skipped and model-family tests to run twice;
update the exclusion to FullyQualifiedName!~ModelFamilyTests.NeuralNetworks
(i.e., replace any FullyQualifiedName!~UnitTests.NeuralNetworks with
FullyQualifiedName!~ModelFamilyTests.NeuralNetworks) in the shard's filter and
apply the same replacement to the other occurrences noted (the other two
identical filter blocks referenced).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 07f37c58-a7e0-4fb9-bc18-8c7f40616bc3

📥 Commits

Reviewing files that changed from the base of the PR and between 4fa1c10 and a9f1cf4.

📒 Files selected for processing (11)
  • .github/workflows/sonarcloud.yml
  • src/AiDotNet.Generators/TestScaffoldGenerator.cs
  • src/NeuralNetworks/GraphNeuralNetwork.cs
  • src/NeuralNetworks/Layers/LayerBase.cs
  • src/NeuralNetworks/Layers/SSM/RWKV7Block.cs
  • tests/AiDotNet.Tests/ModelFamilyTests/Base/AssociativeMemoryTestBase.cs
  • tests/AiDotNet.Tests/ModelFamilyTests/NeuralNetworks/BGETests.cs
  • tests/AiDotNet.Tests/ModelFamilyTests/NeuralNetworks/DenseNetNetworkTests.cs
  • tests/AiDotNet.Tests/ModelFamilyTests/NeuralNetworks/HopeNetworkTests.cs
  • tests/AiDotNet.Tests/ModelFamilyTests/NeuralNetworks/HopfieldNetworkTests.cs
  • tests/AiDotNet.Tests/ModelFamilyTests/NeuralNetworks/MobileNetV3NetworkTests.cs

Comment thread src/AiDotNet.Generators/TestScaffoldGenerator.cs Outdated
Comment thread src/NeuralNetworks/GraphNeuralNetwork.cs
Comment thread src/NeuralNetworks/GraphNeuralNetwork.cs Outdated
Comment thread src/NeuralNetworks/Layers/SSM/RWKV7Block.cs Outdated
Comment thread tests/AiDotNet.Tests/ModelFamilyTests/Base/AssociativeMemoryTestBase.cs Outdated
Comment thread tests/AiDotNet.Tests/ModelFamilyTests/Base/AssociativeMemoryTestBase.cs Outdated
Comment thread tests/AiDotNet.Tests/ModelFamilyTests/NeuralNetworks/HopeNetworkTests.cs Outdated
1. TestScaffoldGenerator: handle arity-2 models and matrix output types
2. GraphNeuralNetwork: guard against empty graphs (div-by-zero)
3. GraphNeuralNetwork: centralize adam lr as DefaultTrainLearningRate
4. RWKV7Block: remove duplicate SafeSetSliceDim (identical to SafeSetSlice)
5. AssociativeMemoryTestBase: remove duplicate BatchConsistency test
6. AssociativeMemoryTestBase: noise test now checks recall beats noisy input
7. AssociativeMemoryTestBase: capacity test checks recall quality not just finite
8. HopeNetwork: add CMS layer deserialization support, re-enable serialization test

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/AiDotNet.Generators/TestScaffoldGenerator.cs`:
- Around line 914-922: The nullable-type check in TestScaffoldGenerator (inside
the loop over ctor.Parameters) incorrectly treats parameters like "string? name"
as optional; remove the Type.ToDisplayString().EndsWith("?") check and rely
solely on ctor.Parameters[pi].HasExplicitDefaultValue to determine optionality,
so update the loop that sets restOptional to only flip to false when a parameter
does not have HasExplicitDefaultValue; keep using the existing restOptional
variable and ctor/Parameters identifiers to locate and modify the logic.
- Around line 1442-1447: Remove the redundant mutation: delete the if
(needsArchitectureUsing) { model.HasArchitectureOnlyConstructor = true; } block
and the now-unused local needsArchitectureUsing, relying on the flag already set
in ProcessModelSymbol; ensure downstream checks still read
model.HasArchitectureOnlyConstructor as before and that no other code depends on
needsArchitectureUsing.

In `@src/NeuralNetworks/GraphNeuralNetwork.cs`:
- Around line 767-782: Change the thrown exception for the numNodes <= 0 check
from ArgumentException to ArgumentOutOfRangeException to more accurately reflect
an out-of-range parameter; locate the guard that references numNodes near the
adjacency matrix creation (the block that initializes adj and normalizedWeight)
and replace the throw so it uses ArgumentOutOfRangeException with the parameter
name (nameof(numNodes)) and an appropriate message indicating the graph must
have at least 1 node (optionally include the invalid value).

In `@tests/AiDotNet.Tests/ModelFamilyTests/Base/AssociativeMemoryTestBase.cs`:
- Around line 704-742: Add a clarification comment to the
SupportsSerializationRoundTrip property (referenced by
SerializationRoundTrip_ShouldPreserveRecall test) that instructs subclasses to
override it to false only when serialization is intentionally unsupported by
design and to document the reason in the subclass (e.g., mention deserializer
intentionally absent), and update HopeNetworkTests if needed to include such
documentation when overriding the property.
- Around line 828-839: Update the ComputeMSE helper to validate that output and
target have the same length instead of silently using Math.Min; inside
ComputeMSE(Tensor<double> output, Tensor<double> target) check if output.Length
!= target.Length and either throw an ArgumentException (with a clear message
including both lengths) or call Debug.Assert to surface shape mismatches in
tests, then proceed to compute the MSE using the full length; this ensures tests
fail fast on shape mismatches and points to the offending tensors.
- Around line 532-586: The test Capacity_AllStoredPatternsShouldBeRecallable
currently asserts patternsRecalled > 0 which is too permissive; update the
assertion to require a stronger recall ratio (e.g., require patternsRecalled >=
Math.Max(1, MultiPatternCount / 2)) or introduce a configurable
expectedRecallRatio constant and assert patternsRecalled >=
(int)Math.Ceiling(expectedRecallRatio * MultiPatternCount) so the threshold is
at least half (or configurable) instead of just one; adjust the assert message
to include the required ratio and actual patternsRecalled for diagnostics.
- Around line 604-640: The test
EnergyMonotonicity_TrainedPatternsShouldHaveLowerEnergy currently returns early
when ComputeEnergy(network, pattern) returns null, which can silently pass for
subclasses that forgot to implement energy support; instead throw an explicit
test-skip so test reports show it as skipped. Change the early-return branch in
EnergyMonotonicity_TrainedPatternsShouldHaveLowerEnergy to throw new
Xunit.Sdk.SkipException("Model does not support energy computation") (or use the
project’s preferred xUnit skip mechanism) when preEnergy == null; this keeps the
ComputeEnergy check but makes missing implementations visible in test results.
- Around line 247-263: The tests Metadata_ShouldExist and
Architecture_ShouldBeNonNull use only Assert.NotNull which is too weak; update
them to verify actual expected structure and values by calling
network.GetModelMetadata() and network.GetArchitecture() and asserting on
concrete properties (e.g., expected keys, types, sizes or values) rather than
nullity: for Metadata_ShouldExist, create the seeded network, train it, get var
metadata = network.GetModelMetadata() and assert metadata contains expected
entries/values (schema version, layer count, input/output shapes matching
InputShape/OutputShape, or specific metadata keys); for
Architecture_ShouldBeNonNull, get var arch = network.GetArchitecture() and
assert arch has expected structure (non-empty layers list, correct layer
types/count, expected input/output dimensions) so the tests validate meaningful
behavior instead of only non-null.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 783589d3-f140-4d40-961d-c6de563f1101

📥 Commits

Reviewing files that changed from the base of the PR and between a9f1cf4 and 86c39ce.

📒 Files selected for processing (6)
  • src/AiDotNet.Generators/TestScaffoldGenerator.cs
  • src/Helpers/DeserializationHelper.cs
  • src/NeuralNetworks/GraphNeuralNetwork.cs
  • src/NeuralNetworks/Layers/SSM/RWKV7Block.cs
  • tests/AiDotNet.Tests/ModelFamilyTests/Base/AssociativeMemoryTestBase.cs
  • tests/AiDotNet.Tests/ModelFamilyTests/NeuralNetworks/HopeNetworkTests.cs

Comment thread src/AiDotNet.Generators/TestScaffoldGenerator.cs
Comment thread src/AiDotNet.Generators/TestScaffoldGenerator.cs Outdated
Comment thread src/NeuralNetworks/GraphNeuralNetwork.cs
Comment thread tests/AiDotNet.Tests/ModelFamilyTests/Base/AssociativeMemoryTestBase.cs Outdated
1. TestScaffoldGenerator: remove nullable type suffix check — only
   HasExplicitDefaultValue determines optionality
2. TestScaffoldGenerator: remove redundant HasArchitectureOnlyConstructor
   mutation in EmitGeneratedTestClass
3. GraphNeuralNetwork: acknowledged (no change needed)
4. AssociativeMemoryTestBase: Metadata test checks Complexity > 0,
   Architecture test checks InputSize > 0
5-7. Capacity/Energy/Serialization: acknowledged nitpicks (by design)
8. ComputeMSE: throw on length mismatch instead of silent truncation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ooples added a commit that referenced this pull request Mar 28, 2026
Take master's version for all conflicting files — master already has
the reviewed fixes from PR #1044 that were duplicated in this branch.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@ooples
ooples merged commit debf4f6 into master Mar 28, 2026
7 of 11 checks passed
@ooples
ooples deleted the feat/ci-test-pipeline-improvements branch March 28, 2026 03:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant