Skip to content

Commit b9ccaa9

Browse files
committed
fix: resolve warnings
1 parent 19e41f7 commit b9ccaa9

12 files changed

Lines changed: 210 additions & 116 deletions

File tree

.github/hooks/hooks.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"hooks": {
3+
"SessionStart": [
4+
{
5+
"type": "command",
6+
"command": "node .github/hooks/scripts/session-start.js",
7+
"timeout": 10
8+
}
9+
],
10+
"Stop": [
11+
{
12+
"type": "command",
13+
"command": "node .github/hooks/scripts/stop-build.js",
14+
"timeout": 180
15+
}
16+
]
17+
}
18+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#!/usr/bin/env node
2+
3+
/**
4+
* SessionStart hook: injects CONTRIBUTING.md into agent context at the start
5+
* of each new session so the agent has Flowthru's architecture and conventions
6+
* loaded before any work begins.
7+
*/
8+
9+
const fs = require('fs');
10+
const path = require('path');
11+
12+
const repoRoot = path.resolve(__dirname, '../../..');
13+
const contributingPath = path.join(repoRoot, 'CONTRIBUTING.md');
14+
15+
let contributing;
16+
try {
17+
contributing = fs.readFileSync(contributingPath, 'utf-8');
18+
} catch (err) {
19+
process.stderr.write(`session-start: could not read CONTRIBUTING.md: ${err.message}\n`);
20+
process.exit(0);
21+
}
22+
23+
const output = {
24+
systemMessage: [
25+
'The following is CONTRIBUTING.md for this repository.',
26+
'Read and internalize it before proceeding — pay particular attention to',
27+
'the three error phases and where validations belong.',
28+
'',
29+
contributing,
30+
].join('\n'),
31+
};
32+
33+
process.stdout.write(JSON.stringify(output));
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
#!/usr/bin/env node
2+
3+
/**
4+
* Stop hook: runs `dotnet build` before the agent yields back to the user and
5+
* injects the result as a system message. Build errors surface immediately so
6+
* the agent can address them before the next prompt rather than discovering
7+
* them later.
8+
*/
9+
10+
const { spawnSync } = require('child_process');
11+
const path = require('path');
12+
13+
const repoRoot = path.resolve(__dirname, '../../..');
14+
15+
const result = spawnSync('dotnet', ['build', 'Flowthru.slnx'], {
16+
cwd: repoRoot,
17+
encoding: 'utf-8',
18+
timeout: 180000,
19+
});
20+
21+
const stdout = (result.stdout || '').trim();
22+
const stderr = (result.stderr || '').trim();
23+
const combined = [stdout, stderr].filter(Boolean).join('\n');
24+
25+
const succeeded = result.status === 0;
26+
const headline = succeeded
27+
? 'dotnet build succeeded.'
28+
: 'dotnet build FAILED — address these errors before concluding.';
29+
30+
const output = {
31+
systemMessage: [
32+
`## Build Check (dotnet build)`,
33+
'',
34+
headline,
35+
'',
36+
'```',
37+
combined,
38+
'```',
39+
].join('\n'),
40+
};
41+
42+
process.stdout.write(JSON.stringify(output));

src/extensions/Flowthru.Extensions.EFCore/Data/Storage/EFCoreSingleStorageAdapter.cs

Lines changed: 21 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -326,59 +326,33 @@ private DbContext GetContext()
326326
/// </exception>
327327
private static void ValidateEntityConfiguration(DbContext context)
328328
{
329-
try
329+
var entityType = context.Model.FindEntityType(typeof(T));
330+
if (entityType == null)
330331
{
331-
var entityType = context.Model.FindEntityType(typeof(T));
332-
if (entityType == null)
333-
{
334-
throw new InvalidOperationException(
335-
$"Entity type '{typeof(T).Name}' is not configured in DbContext '{context.GetType().Name}'. "
336-
+ $"Ensure the entity is added to the DbContext model."
337-
);
338-
}
339-
340-
var primaryKey = entityType.FindPrimaryKey();
341-
if (primaryKey == null)
342-
{
343-
throw new InvalidOperationException(
344-
$"Entity type '{typeof(T).Name}' in DbContext '{context.GetType().Name}' does not have a primary key configured. "
345-
+ $"Configure a primary key using HasKey() in OnModelCreating."
346-
);
347-
}
348-
349-
// Force identity map factory creation to validate key comparers
350-
if (primaryKey is Microsoft.EntityFrameworkCore.Metadata.Internal.IRuntimeKey runtimeKey)
351-
{
352-
_ = runtimeKey.GetIdentityMapFactory();
353-
}
332+
throw new InvalidOperationException(
333+
$"Entity type '{typeof(T).Name}' is not configured in DbContext '{context.GetType().Name}'. "
334+
+ $"Ensure the entity is added to the DbContext model."
335+
);
354336
}
355-
catch (System.Reflection.TargetInvocationException ex)
356-
{
357-
// Unwrap nested TargetInvocationExceptions from reflection chain
358-
var innerMost = ex;
359-
while (innerMost.InnerException is System.Reflection.TargetInvocationException nested)
360-
{
361-
innerMost = nested;
362-
}
363-
364-
if (innerMost.InnerException is InvalidCastException castEx)
365-
{
366-
throw new InvalidOperationException(
367-
$"Invalid key configuration for '{typeof(T).Name}' in DbContext '{context.GetType().Name}': "
368-
+ $"{castEx.Message}. Arrays and certain collection types cannot be used as entity keys. "
369-
+ $"Consider using a primitive type (int, Guid, string) or composite key of primitives.",
370-
castEx
371-
);
372-
}
373337

374-
throw;
338+
var primaryKey = entityType.FindPrimaryKey();
339+
if (primaryKey == null)
340+
{
341+
throw new InvalidOperationException(
342+
$"Entity type '{typeof(T).Name}' in DbContext '{context.GetType().Name}' does not have a primary key configured. "
343+
+ $"Configure a primary key using HasKey() in OnModelCreating."
344+
);
375345
}
376-
catch (InvalidCastException castEx)
346+
347+
// Array types use reference equality and cannot serve as EF Core identity map keys.
348+
var arrayProperty = primaryKey.Properties.FirstOrDefault(p => p.ClrType.IsArray);
349+
if (arrayProperty != null)
377350
{
378351
throw new InvalidOperationException(
379-
$"Invalid key configuration for '{typeof(T).Name}' in DbContext '{context.GetType().Name}': "
380-
+ $"{castEx.Message}. This typically indicates an unsupported key type configuration.",
381-
castEx
352+
$"Property '{arrayProperty.Name}' on entity '{typeof(T).Name}' uses array type '{arrayProperty.ClrType.Name}', "
353+
+ $"which cannot be used as an entity key. "
354+
+ $"Array types do not support value equality for EF Core change tracking. "
355+
+ $"Consider using a primitive type (int, Guid, string) or a composite key of primitives."
382356
);
383357
}
384358
}

src/extensions/Flowthru.Extensions.EFCore/Data/Storage/EFCoreStorageAdapter.cs

Lines changed: 21 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -176,59 +176,33 @@ public EFCoreStorageAdapter(
176176
/// </exception>
177177
private static void ValidateEntityConfiguration(DbContext context)
178178
{
179-
try
179+
var entityType = context.Model.FindEntityType(typeof(T));
180+
if (entityType == null)
180181
{
181-
var entityType = context.Model.FindEntityType(typeof(T));
182-
if (entityType == null)
183-
{
184-
throw new InvalidOperationException(
185-
$"Entity type '{typeof(T).Name}' is not configured in DbContext '{context.GetType().Name}'. "
186-
+ $"Ensure the entity is added to the DbContext model."
187-
);
188-
}
189-
190-
var primaryKey = entityType.FindPrimaryKey();
191-
if (primaryKey == null)
192-
{
193-
throw new InvalidOperationException(
194-
$"Entity type '{typeof(T).Name}' in DbContext '{context.GetType().Name}' does not have a primary key configured. "
195-
+ $"Configure a primary key using HasKey() in OnModelCreating."
196-
);
197-
}
198-
199-
// Force identity map factory creation to validate key comparers
200-
if (primaryKey is Microsoft.EntityFrameworkCore.Metadata.Internal.IRuntimeKey runtimeKey)
201-
{
202-
_ = runtimeKey.GetIdentityMapFactory();
203-
}
182+
throw new InvalidOperationException(
183+
$"Entity type '{typeof(T).Name}' is not configured in DbContext '{context.GetType().Name}'. "
184+
+ $"Ensure the entity is added to the DbContext model."
185+
);
204186
}
205-
catch (System.Reflection.TargetInvocationException ex)
206-
{
207-
// Unwrap nested TargetInvocationExceptions from reflection chain
208-
var innerMost = ex;
209-
while (innerMost.InnerException is System.Reflection.TargetInvocationException nested)
210-
{
211-
innerMost = nested;
212-
}
213-
214-
if (innerMost.InnerException is InvalidCastException castEx)
215-
{
216-
throw new InvalidOperationException(
217-
$"Invalid key configuration for '{typeof(T).Name}' in DbContext '{context.GetType().Name}': "
218-
+ $"{castEx.Message}. Arrays and certain collection types cannot be used as entity keys. "
219-
+ $"Consider using a primitive type (int, Guid, string) or composite key of primitives.",
220-
castEx
221-
);
222-
}
223187

224-
throw;
188+
var primaryKey = entityType.FindPrimaryKey();
189+
if (primaryKey == null)
190+
{
191+
throw new InvalidOperationException(
192+
$"Entity type '{typeof(T).Name}' in DbContext '{context.GetType().Name}' does not have a primary key configured. "
193+
+ $"Configure a primary key using HasKey() in OnModelCreating."
194+
);
225195
}
226-
catch (InvalidCastException castEx)
196+
197+
// Array types use reference equality and cannot serve as EF Core identity map keys.
198+
var arrayProperty = primaryKey.Properties.FirstOrDefault(p => p.ClrType.IsArray);
199+
if (arrayProperty != null)
227200
{
228201
throw new InvalidOperationException(
229-
$"Invalid key configuration for '{typeof(T).Name}' in DbContext '{context.GetType().Name}': "
230-
+ $"{castEx.Message}. This typically indicates an unsupported key type configuration.",
231-
castEx
202+
$"Property '{arrayProperty.Name}' on entity '{typeof(T).Name}' uses array type '{arrayProperty.ClrType.Name}', "
203+
+ $"which cannot be used as an entity key. "
204+
+ $"Array types do not support value equality for EF Core change tracking. "
205+
+ $"Consider using a primitive type (int, Guid, string) or a composite key of primitives."
232206
);
233207
}
234208
}

src/extensions/Flowthru.Extensions.Python.SourceGenerators/Flowthru.Extensions.Python.SourceGenerators.csproj

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,31 +7,11 @@
77
<IsRoslynComponent>true</IsRoslynComponent>
88
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
99
<NoWarn>RS2008;RS2007</NoWarn>
10-
<DevelopmentDependency>true</DevelopmentDependency>
1110
<IncludeBuildOutput>false</IncludeBuildOutput>
12-
<IsPackable>true</IsPackable>
11+
<!-- Not published standalone; DLL is bundled into Flowthru.Extensions.Python analyzers/dotnet/cs -->
12+
<IsPackable>false</IsPackable>
1313
<RootNamespace>Flowthru.Extensions.Python.SourceGenerators</RootNamespace>
14-
<Description>Source generators for Flowthru.Extensions.Python</Description>
15-
16-
<!-- Pack in parent package's analyzers folder -->
17-
<PackageId>Flowthru.Extensions.Python.SourceGenerators</PackageId>
18-
<IncludeInSingleFile>true</IncludeInSingleFile>
1914
</PropertyGroup>
20-
21-
<!-- Package the generator DLL in analyzers/dotnet/cs for parent package -->
22-
<PropertyGroup>
23-
<GetTargetPathDependsOn>$(GetTargetPathDependsOn);GetDependencyTargetPaths</GetTargetPathDependsOn>
24-
</PropertyGroup>
25-
26-
<Target Name="GetDependencyTargetPaths">
27-
<ItemGroup>
28-
<TargetPathWithTargetPlatformMoniker Include="$(PKGMicrosoft_CodeAnalysis_CSharp)\lib\netstandard2.0\*.dll" IncludeRuntimeDependency="false" />
29-
</ItemGroup>
30-
</Target>
31-
32-
<ItemGroup>
33-
<None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
34-
</ItemGroup>
3515

3616
<ItemGroup>
3717
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" PrivateAssets="all" />

src/extensions/Flowthru.Extensions.Python/Flowthru.Extensions.Python.csproj

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,4 +84,19 @@
8484
Visible="false" />
8585
</ItemGroup>
8686

87+
<!-- Package source generators in standard analyzer location -->
88+
<ItemGroup>
89+
<None Include="$(RepoRoot)dist/src/extensions/Flowthru.Extensions.Python.SourceGenerators/$(Configuration)/netstandard2.0/Flowthru.Extensions.Python.SourceGenerators.dll"
90+
Pack="true"
91+
PackagePath="analyzers/dotnet/cs"
92+
Visible="false" />
93+
</ItemGroup>
94+
95+
<!-- Ensure source generators are built before packing -->
96+
<Target Name="EnsureAnalyzerDllsBuilt" BeforeTargets="GenerateNuspec" Condition="'$(NoBuild)' != 'true'">
97+
<MSBuild Projects="../Flowthru.Extensions.Python.SourceGenerators/Flowthru.Extensions.Python.SourceGenerators.csproj"
98+
Targets="Build"
99+
Properties="Configuration=$(Configuration)" />
100+
</Target>
101+
87102
</Project>

tests/Flowthru.Extensions.EFCore.Tests/EFCoreSingleStorageAdapterTests.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,4 +71,20 @@ public async Task AllowEmptyData_True_PassesInspectionOnEmptyTable()
7171

7272
Assert.That(result.IsValid, Is.True);
7373
}
74+
75+
[Test]
76+
public void ArrayKeyEntity_ThrowsInvalidOperationException_OnConstruction()
77+
{
78+
var options = new DbContextOptionsBuilder<ArrayKeyDbContext>()
79+
.UseSqlite("Data Source=:memory:")
80+
.Options;
81+
82+
Assert.Throws<InvalidOperationException>(
83+
() =>
84+
EFCoreItemFactory.Single.EFCore<ArrayKeyEntity>(
85+
"test",
86+
() => new ArrayKeyDbContext(options)
87+
)
88+
);
89+
}
7490
}

tests/Flowthru.Extensions.EFCore.Tests/EFCoreStorageAdapterTests.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,4 +160,20 @@ public async Task IDbContextFactory_RoundTrip()
160160
Assert.That(loaded, Has.Count.EqualTo(1));
161161
Assert.That(loaded[0].Name, Is.EqualTo("Alice"));
162162
}
163+
164+
[Test]
165+
public void ArrayKeyEntity_ThrowsInvalidOperationException_OnConstruction()
166+
{
167+
var options = new DbContextOptionsBuilder<ArrayKeyDbContext>()
168+
.UseSqlite("Data Source=:memory:")
169+
.Options;
170+
171+
Assert.Throws<InvalidOperationException>(
172+
() =>
173+
EFCoreItemFactory.Enumerable.EFCore<ArrayKeyEntity>(
174+
"test",
175+
() => new ArrayKeyDbContext(options)
176+
)
177+
);
178+
}
163179
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using Microsoft.EntityFrameworkCore;
2+
3+
namespace Flowthru.Extensions.EFCore.Tests;
4+
5+
public class ArrayKeyDbContext : DbContext
6+
{
7+
public ArrayKeyDbContext(DbContextOptions<ArrayKeyDbContext> options)
8+
: base(options) { }
9+
10+
public DbSet<ArrayKeyEntity> ArrayKeyEntities => Set<ArrayKeyEntity>();
11+
12+
protected override void OnModelCreating(ModelBuilder modelBuilder)
13+
{
14+
modelBuilder.Entity<ArrayKeyEntity>().HasKey(e => e.Id);
15+
}
16+
}

0 commit comments

Comments
 (0)