Set up AI dev environment for recordingtest (#2)
- CLAUDE.md with collaboration rules and Planner/Generator/Evaluator cycle - .claude/ agents, commands, skills, hooks per Claude Code conventions - Sprint Contracts for sut-prober, normalizer, recorder, player, diff-reporter - SUT catalog (EG-BIM Modeler, 187 plugins) and .gitignore excluding SUT tree - PROGRESS.md / PLAN.md as shared agent handoff state - Solution scaffold targeting sut-prober PoC Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
25
src/Recordingtest.SutProber/AssemblyScanner.cs
Normal file
25
src/Recordingtest.SutProber/AssemblyScanner.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
namespace Recordingtest.SutProber;
|
||||
|
||||
public sealed record AssemblyEntry(string Name, long SizeBytes, bool HasPdb);
|
||||
|
||||
public static class AssemblyScanner
|
||||
{
|
||||
private static readonly string[] Prefixes = { "HmEG", "HmGeometry", "HmTriangle", "HmPG", "HmCommon", "Editor", "EditorCore" };
|
||||
|
||||
public static List<AssemblyEntry> Scan(string sutRoot)
|
||||
{
|
||||
var entries = new List<AssemblyEntry>();
|
||||
if (!Directory.Exists(sutRoot)) return entries;
|
||||
|
||||
foreach (var file in Directory.EnumerateFiles(sutRoot, "*.dll", SearchOption.TopDirectoryOnly)
|
||||
.OrderBy(f => f, StringComparer.Ordinal))
|
||||
{
|
||||
var name = Path.GetFileName(file);
|
||||
if (!Prefixes.Any(p => name.StartsWith(p, StringComparison.Ordinal))) continue;
|
||||
|
||||
var pdb = Path.ChangeExtension(file, ".pdb");
|
||||
entries.Add(new AssemblyEntry(name, new FileInfo(file).Length, File.Exists(pdb)));
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
}
|
||||
51
src/Recordingtest.SutProber/JsonConfigScanner.cs
Normal file
51
src/Recordingtest.SutProber/JsonConfigScanner.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Recordingtest.SutProber;
|
||||
|
||||
public sealed record JsonConfigEntry(
|
||||
string Name,
|
||||
IReadOnlyList<string> TopLevelKeys,
|
||||
IReadOnlyList<string> SuspectedNondeterministicFields);
|
||||
|
||||
public static class JsonConfigScanner
|
||||
{
|
||||
private static readonly string[] SuspectPatterns =
|
||||
{ "time", "date", "path", "recent", "guid", "id", "machine", "user" };
|
||||
|
||||
public static List<JsonConfigEntry> Scan(string sutRoot)
|
||||
{
|
||||
var jsonRoot = Path.Combine(sutRoot, "Json");
|
||||
if (!Directory.Exists(jsonRoot)) return new();
|
||||
|
||||
var entries = new List<JsonConfigEntry>();
|
||||
foreach (var file in Directory.EnumerateFiles(jsonRoot, "*.json").OrderBy(f => f, StringComparer.Ordinal))
|
||||
{
|
||||
var name = Path.GetFileName(file);
|
||||
var keys = new List<string>();
|
||||
var suspects = new List<string>();
|
||||
try
|
||||
{
|
||||
using var doc = JsonDocument.Parse(File.ReadAllText(file));
|
||||
if (doc.RootElement.ValueKind == JsonValueKind.Object)
|
||||
{
|
||||
foreach (var p in doc.RootElement.EnumerateObject())
|
||||
{
|
||||
keys.Add(p.Name);
|
||||
var lower = p.Name.ToLowerInvariant();
|
||||
if (SuspectPatterns.Any(sp => lower.Contains(sp)))
|
||||
suspects.Add(p.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
keys.Add("<invalid-json>");
|
||||
}
|
||||
|
||||
keys.Sort(StringComparer.Ordinal);
|
||||
suspects.Sort(StringComparer.Ordinal);
|
||||
entries.Add(new JsonConfigEntry(name, keys, suspects));
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
}
|
||||
32
src/Recordingtest.SutProber/PluginScanner.cs
Normal file
32
src/Recordingtest.SutProber/PluginScanner.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
namespace Recordingtest.SutProber;
|
||||
|
||||
public sealed record PluginEntry(string Name, string Path, IReadOnlyList<string> Dlls, long SizeBytes);
|
||||
|
||||
public static class PluginScanner
|
||||
{
|
||||
public static List<PluginEntry> Scan(string sutRoot)
|
||||
{
|
||||
var pluginRoot = System.IO.Path.Combine(sutRoot, "Plugins");
|
||||
if (!Directory.Exists(pluginRoot)) return new();
|
||||
|
||||
var entries = new List<PluginEntry>();
|
||||
foreach (var dir in Directory.EnumerateDirectories(pluginRoot).OrderBy(d => d, StringComparer.Ordinal))
|
||||
{
|
||||
var dlls = Directory.EnumerateFiles(dir, "*.dll", SearchOption.TopDirectoryOnly)
|
||||
.Select(System.IO.Path.GetFileName)
|
||||
.Where(n => n is not null)
|
||||
.Select(n => n!)
|
||||
.OrderBy(n => n, StringComparer.Ordinal)
|
||||
.ToList();
|
||||
|
||||
long size = 0;
|
||||
foreach (var f in Directory.EnumerateFiles(dir, "*", SearchOption.AllDirectories))
|
||||
size += new FileInfo(f).Length;
|
||||
|
||||
var name = System.IO.Path.GetFileName(dir);
|
||||
var relPath = System.IO.Path.GetRelativePath(".", dir).Replace('\\', '/');
|
||||
entries.Add(new PluginEntry(name, relPath, dlls, size));
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
}
|
||||
42
src/Recordingtest.SutProber/Program.cs
Normal file
42
src/Recordingtest.SutProber/Program.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System.Text.Json;
|
||||
using Recordingtest.SutProber;
|
||||
|
||||
// Static-only probe of the EG-BIM Modeler SUT folder. NEVER launches the SUT.
|
||||
// See docs/contracts/sut-prober.md for the sprint contract.
|
||||
|
||||
string sutPath = "EG-BIM Modeler";
|
||||
string outDir = Path.Combine("docs", "sut-catalog");
|
||||
|
||||
for (int i = 0; i < args.Length; i++)
|
||||
{
|
||||
if (args[i] == "--sut" && i + 1 < args.Length) sutPath = args[++i];
|
||||
else if (args[i] == "--out" && i + 1 < args.Length) outDir = args[++i];
|
||||
}
|
||||
|
||||
if (!Directory.Exists(sutPath))
|
||||
{
|
||||
Console.Error.WriteLine($"SUT path not found: {sutPath}");
|
||||
return 2;
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(outDir);
|
||||
|
||||
var plugins = PluginScanner.Scan(sutPath);
|
||||
var jsonConfigs = JsonConfigScanner.Scan(sutPath);
|
||||
var assemblies = AssemblyScanner.Scan(sutPath);
|
||||
|
||||
var opts = new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true,
|
||||
// deterministic property order follows POCO definition
|
||||
};
|
||||
|
||||
File.WriteAllText(Path.Combine(outDir, "plugins.json"),
|
||||
JsonSerializer.Serialize(plugins, opts));
|
||||
File.WriteAllText(Path.Combine(outDir, "json-configs.json"),
|
||||
JsonSerializer.Serialize(jsonConfigs, opts));
|
||||
File.WriteAllText(Path.Combine(outDir, "assemblies.json"),
|
||||
JsonSerializer.Serialize(assemblies, opts));
|
||||
|
||||
Console.WriteLine($"Wrote catalog to {outDir}/ — plugins: {plugins.Count}, json: {jsonConfigs.Count}, assemblies: {assemblies.Count}");
|
||||
return 0;
|
||||
10
src/Recordingtest.SutProber/Recordingtest.SutProber.csproj
Normal file
10
src/Recordingtest.SutProber/Recordingtest.SutProber.csproj
Normal file
@@ -0,0 +1,10 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<AssemblyName>Recordingtest.SutProber</AssemblyName>
|
||||
<RootNamespace>Recordingtest.SutProber</RootNamespace>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Text.Json" Version="8.0.5" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
Reference in New Issue
Block a user