32f26272ae
Five tools under one repo, all docs organized per DOCS-GUIDE.md: - aalogcli: .NET 4.8 / x86 CliFx CLI for reading System Platform binary logs (*.aaLGX) for LLM debugging, built on aaOpenSource/aaLog. Commands: last, tail, range, unread, fields. Stable JSON envelope under --llm-json. Build template under lib/build/ for rebuilding aaLogReader.dll. - aot: ArchestrA Object Toolkit 2014 v4.0 reference material. Dev guide (Markdown converted from CHM), API reference for the ArchestrA.Toolkit namespace, and the Monitor / Watchdog VS sample solutions. - graccesscli: .NET 4.8 / x86 CliFx CLI that automates Galaxy configuration via the ArchestrA GRAccess COM interop. Includes session daemon, IPC protocol, and llm-json envelope contract. - grdb: SQL/DDL exploration of the Galaxy Repository database. DDL captures, reusable queries, hierarchy / contained-name <-> tag-name translation notes. - histdb: LLM-oriented reference for AVEVA Historian retrieval. INSQL linked-server, extension tables, every wwXxx time-domain extension, every retrieval mode, alarm/event SQL recipes, REST API. Distilled from the 243-page Historian Retrieval Guide. Root contains: - CLAUDE.md: thin index pointing into each tool's README. - DOCS-GUIDE.md: doctrine for organizing docs for LLM consumption. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
179 lines
6.9 KiB
C#
179 lines
6.9 KiB
C#
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.IO.Compression;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using Newtonsoft.Json.Linq;
|
|
using Shouldly;
|
|
using Xunit;
|
|
using ZB.MOM.WW.GRAccess.Cli.GRAccess;
|
|
using ZB.MOM.WW.GRAccess.Cli.Infrastructure;
|
|
|
|
namespace ZB.MOM.WW.GRAccess.Cli.Tests.Commands
|
|
{
|
|
public class LlmIntegrationCommandTests
|
|
{
|
|
[Theory]
|
|
[InlineData("capabilities", false, "")]
|
|
[InlineData("object snapshot", false, "")]
|
|
[InlineData("object lineage", false, "")]
|
|
[InlineData("object children", false, "")]
|
|
[InlineData("object attribute value get", false, "")]
|
|
[InlineData("object attribute value set", true, "name")]
|
|
[InlineData("object scripts list", false, "")]
|
|
[InlineData("object scripts set", true, "name")]
|
|
[InlineData("object scripts create", true, "name")]
|
|
[InlineData("object scripts settings set", true, "name")]
|
|
[InlineData("area create", true, "template")]
|
|
[InlineData("engine create", true, "template")]
|
|
[InlineData("instance assign-area", true, "name")]
|
|
[InlineData("io assign", true, "name")]
|
|
[InlineData("batch", false, "")]
|
|
[InlineData("validate", false, "")]
|
|
public void CapabilityRegistry_IncludesLlmCommands(string commandName, bool mutates, string confirmTarget)
|
|
{
|
|
var command = CommandCapabilityRegistry.Find(commandName);
|
|
|
|
command.ShouldNotBeNull();
|
|
command.Mutates.ShouldBe(mutates);
|
|
command.ConfirmTarget.ShouldBe(confirmTarget);
|
|
command.SupportsLlmJson.ShouldBeTrue();
|
|
}
|
|
|
|
[Fact]
|
|
public void CapabilityValidation_RejectsMissingMutationConfirmation()
|
|
{
|
|
var result = CommandCapabilityRegistry.Validate(
|
|
"object attribute value set",
|
|
new Dictionary<string, object>
|
|
{
|
|
["galaxy"] = "ZB",
|
|
["name"] = "TestMachine",
|
|
["attribute"] = "Description",
|
|
["value"] = "Updated"
|
|
},
|
|
requireMutationConfirmation: true);
|
|
|
|
result.Valid.ShouldBeFalse();
|
|
result.Errors.ShouldContain("Missing required confirm=true.");
|
|
result.Errors.ShouldContain("confirm-target must be 'TestMachine'.");
|
|
}
|
|
|
|
[Fact]
|
|
public void CapabilityValidation_AcceptsConfirmedMutation()
|
|
{
|
|
var result = CommandCapabilityRegistry.Validate(
|
|
"object attribute value set",
|
|
new Dictionary<string, object>
|
|
{
|
|
["galaxy"] = "ZB",
|
|
["name"] = "TestMachine",
|
|
["attribute"] = "Description",
|
|
["value"] = "Updated",
|
|
["confirm"] = true,
|
|
["confirm-target"] = "TestMachine"
|
|
},
|
|
requireMutationConfirmation: true);
|
|
|
|
result.Valid.ShouldBeTrue();
|
|
}
|
|
|
|
[Fact]
|
|
public void LlmResponse_EnvelopeSerializesSuccessAndUnavailable()
|
|
{
|
|
var json = LlmResponse.Ok(
|
|
"object snapshot",
|
|
"ZB",
|
|
"TestMachine",
|
|
new { name = "TestMachine" },
|
|
unavailable: new[] { new LlmUnavailableField { Field = "scripts", Reason = "not exposed" } });
|
|
|
|
var token = JObject.Parse(json);
|
|
|
|
token["success"]?.Value<bool>().ShouldBeTrue();
|
|
token["command"]?.Value<string>().ShouldBe("object snapshot");
|
|
token["data"]?["name"]?.Value<string>().ShouldBe("TestMachine");
|
|
token["unavailable"]?.Count().ShouldBe(1);
|
|
}
|
|
|
|
[Fact]
|
|
public void SplitCommandName_UsesRegistrySubcommand()
|
|
{
|
|
var split = CommandCapabilityRegistry.SplitCommandName("object attribute value get");
|
|
|
|
split.Command.ShouldBe("object");
|
|
split.Subcommand.ShouldBe("attribute-value-get");
|
|
}
|
|
|
|
[Fact]
|
|
public void PackageSnapshotParser_ExtractsLineageAttributesAndScripts()
|
|
{
|
|
var path = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName() + ".aaPKG");
|
|
try
|
|
{
|
|
File.WriteAllText(path, @"
|
|
Lineage: $gMachine -> $TestMachine
|
|
AttributeValue Name=""Description"" DataType=""MxString"" Value=""Test machine template""
|
|
ScriptBody Name=""UpdateTestChangingInt.ExecuteText"">
|
|
me.TestChangingInt = me.TestChangingInt + 1;
|
|
</ScriptBody>
|
|
");
|
|
|
|
var snapshot = PackageSnapshotParser.Parse(path);
|
|
|
|
snapshot.PackageFallbackUsed.ShouldBeTrue();
|
|
snapshot.Lineage.ShouldBe(new[] { "$gMachine", "$TestMachine" });
|
|
snapshot.AttributeValues.Single().Name.ShouldBe("Description");
|
|
snapshot.AttributeValues.Single().Value.ShouldBe("Test machine template");
|
|
snapshot.ScriptBodies.Single().Name.ShouldBe("UpdateTestChangingInt.ExecuteText");
|
|
snapshot.ScriptBodies.Single().Body.ShouldContain("TestChangingInt");
|
|
}
|
|
finally
|
|
{
|
|
if (File.Exists(path))
|
|
File.Delete(path);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void PackageSnapshotParser_ExtractsNestedBinaryScriptExtensionBody()
|
|
{
|
|
var path = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName() + ".aaPKG");
|
|
try
|
|
{
|
|
var innerBytes = Encoding.Unicode.GetBytes(string.Join("\n", new[]
|
|
{
|
|
"UpdateTestChangingInt",
|
|
"UpdateTestChangingInt_ScriptExtension",
|
|
"Me.TestChangingInt = System.Random().Next(1,1000);",
|
|
"Periodic"
|
|
}));
|
|
|
|
using (var outer = ZipFile.Open(path, ZipArchiveMode.Create))
|
|
{
|
|
var entry = outer.CreateEntry("File1.cab");
|
|
using (var entryStream = entry.Open())
|
|
using (var inner = new ZipArchive(entryStream, ZipArchiveMode.Create))
|
|
{
|
|
var txt = inner.CreateEntry("1055.txt");
|
|
using (var txtStream = txt.Open())
|
|
txtStream.Write(innerBytes, 0, innerBytes.Length);
|
|
}
|
|
}
|
|
|
|
var snapshot = PackageSnapshotParser.Parse(path);
|
|
var script = snapshot.ScriptBodies.Single(s => s.Name == "UpdateTestChangingInt.ExecuteText");
|
|
|
|
script.Body.ShouldBe("Me.TestChangingInt = System.Random().Next(1,1000);");
|
|
script.Source.ShouldBe("export-package:binary");
|
|
snapshot.AttributeValues.Single(a => a.Name == "UpdateTestChangingInt.ExecuteText").Value.ShouldBe(script.Body);
|
|
}
|
|
finally
|
|
{
|
|
if (File.Exists(path))
|
|
File.Delete(path);
|
|
}
|
|
}
|
|
}
|
|
}
|