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:
minsung
2026-04-07 13:57:20 +09:00
parent a48a8a2d1d
commit 7ffbb1f757
47 changed files with 1886 additions and 11 deletions

View 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;
}
}

View 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;
}
}

View 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;
}
}

View 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;

View 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>