23 KiB
ZB.MOM.WW.SPHistorianClient Implementation Plan
For Claude: REQUIRED SUB-SKILL: Use superpowers-extended-cc:executing-plans (or subagent-driven-development) to implement this plan task-by-task.
Goal: Repackage the proven, pure-managed .NET 10 AVEVA.Historian.Client SDK from the migration bundle as the family-branded shared library ZB.MOM.WW.SPHistorianClient, following the same conventions as the other ZB.MOM.WW.* libraries in this repo.
Architecture: This is a port + rebrand, not a rewrite. Copy the SDK src/ and tests/ into a new ZB.MOM.WW.SPHistorianClient/ directory, rewrite the C# root namespace AVEVA.Historian.Client → ZB.MOM.WW.SPHistorianClient (leaving the proto-generated ArchestrA.Grpc.Contract.* wire contracts untouched), adopt ZB conventions (Directory.Build.props / Directory.Packages.props central package management, .slnx, CLAUDE.md/README.md), drop the non-shippable reverse-engineering tooling and proprietary decompilations, add one ZB-idiomatic DI extension, then build/test/pack.
Tech Stack: .NET 10, C# (net10.0), WCF/MDAS (System.ServiceModel.*, Windows-only transports), gRPC (Grpc.Net.Client + Grpc.Tools, cross-platform 2023 R2 transport), xUnit. Central package management.
Design doc: docs/plans/2026-06-19-sphistorianclient-design.md
Branch: feat/sphistorianclient (already created; design doc already committed at bbb7942).
Source bundle location (read-only inputs)
The SDK source lives in an extracted bundle under /tmp:
- Extracted root:
/tmp/histsdk/extracted/histsdk-migration/histsdk/ - SDK source:
…/histsdk/src/AVEVA.Historian.Client/— 74.cs+ 6.proto - SDK tests:
…/histsdk/tests/AVEVA.Historian.Client.Tests/— 25.cs - Re-extract fallback (if
/tmpwas cleaned):cd /tmp/histsdk && unzip -o -q histsdk-migration.zip -d extracted
Never copy: tools/ (RE harnesses, .NET Framework + native AVEVA refs), analysis-2023r2/decompiled/ (proprietary, non-redistributable), scripts/, docs/reverse-engineering/ (identity-bearing captures), bin//obj/, the bundle's .git/, and the bundle's original .csproj files (we author fresh ZB ones).
Gotchas baked into this plan (from prior repo experience):
- Do not set
TreatWarningsAsErrors— the WCF/SSPI code carries[SupportedOSPlatform("windows")]and will emit CA platform warnings on macOS that must stay warnings. - Central package management means no inline
Version=on anyPackageReference(that isNU1008). All versions live inDirectory.Packages.props. Microsoft.Data.SqlClientmay surface anNU1903advisory on restore. WithoutTreatWarningsAsErrorsit is a warning. If a restore ever hard-fails on it, add-p:NuGetAudit=falseto the build/test command.- macOS
sed -irequires an explicit empty backup arg:sed -i '' 's/…/…/g'.
Task 1: Scaffold the library skeleton
Classification: standard Estimated implement time: ~4 min Parallelizable with: none (every later task depends on this)
Files:
- Create:
ZB.MOM.WW.SPHistorianClient/Directory.Build.props - Create:
ZB.MOM.WW.SPHistorianClient/Directory.Packages.props - Create:
ZB.MOM.WW.SPHistorianClient/.gitignore - Create:
ZB.MOM.WW.SPHistorianClient/ZB.MOM.WW.SPHistorianClient.slnx - Create:
ZB.MOM.WW.SPHistorianClient/src/ZB.MOM.WW.SPHistorianClient/ZB.MOM.WW.SPHistorianClient.csproj - Create:
ZB.MOM.WW.SPHistorianClient/tests/ZB.MOM.WW.SPHistorianClient.Tests/ZB.MOM.WW.SPHistorianClient.Tests.csproj
Step 1: Directory.Build.props (mirrors ZB.MOM.WW.Telemetry/Directory.Build.props)
<Project>
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>latest</LangVersion>
<Version>0.1.0</Version>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
</Project>
Step 2: Directory.Packages.props (versions lifted verbatim from the bundle's two .csproj files)
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<!-- Historian SDK runtime deps (WCF/MDAS transports — Windows-only at runtime) -->
<PackageVersion Include="System.Security.Cryptography.Xml" Version="10.0.7" />
<PackageVersion Include="System.ServiceModel.NetNamedPipe" Version="10.0.652802" />
<PackageVersion Include="System.ServiceModel.NetTcp" Version="10.0.652802" />
<!-- 2023 R2 gRPC transport (cross-platform) -->
<PackageVersion Include="Google.Protobuf" Version="3.24.4" />
<PackageVersion Include="Grpc.Net.Client" Version="2.58.0" />
<PackageVersion Include="Grpc.Net.Client.Web" Version="2.58.0" />
<PackageVersion Include="Grpc.Tools" Version="2.59.0" />
<!-- ZB-idiomatic DI extension (only non-BCL lib dependency) -->
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.7" />
<!-- Test -->
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageVersion Include="xunit" Version="2.9.3" />
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.4" />
<PackageVersion Include="coverlet.collector" Version="6.0.4" />
<PackageVersion Include="Microsoft.Data.SqlClient" Version="6.0.2" />
</ItemGroup>
</Project>
Step 3: .gitignore
bin/
obj/
# identity-bearing / non-redistributable — never commit
*.ndjson
current/
aveva-install-*/
Step 4: ZB.MOM.WW.SPHistorianClient.slnx
<Solution>
<Folder Name="/src/">
<Project Path="src/ZB.MOM.WW.SPHistorianClient/ZB.MOM.WW.SPHistorianClient.csproj" />
</Folder>
<Folder Name="/tests/">
<Project Path="tests/ZB.MOM.WW.SPHistorianClient.Tests/ZB.MOM.WW.SPHistorianClient.Tests.csproj" />
</Folder>
</Solution>
Step 5: src/ZB.MOM.WW.SPHistorianClient/ZB.MOM.WW.SPHistorianClient.csproj
(Derived from the bundle's AVEVA.Historian.Client.csproj: inline versions removed for central
management; ZB package metadata added; InternalsVisibleTo retargeted to the ZB test assembly and
the …ReverseEngineering one dropped; proto glob uses forward slashes for cross-platform MSBuild.)
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<IsPackable>true</IsPackable>
<PackageId>ZB.MOM.WW.SPHistorianClient</PackageId>
<Authors>ZB.MOM.WW</Authors>
<Description>Pure-managed .NET 10 client for AVEVA System Platform Historian (Wonderware) for the ZB.MOM.WW SCADA family. The wire protocol is reverse-engineered and re-implemented in C# — no native AVEVA runtime dependency. Surfaces history reads (raw / aggregate / at-time / event), tag browse + metadata, status, and tag create/delete over the WCF/MDAS transports (Windows) plus a cross-platform gRPC transport for 2023 R2.</Description>
<PackageTags>aveva;wonderware;historian;system-platform;scada;timeseries;grpc;wcf;zb-mom-ww</PackageTags>
<PackageProjectUrl>https://gitea.dohertylan.com/dohertj2/zb-mom-ww-sphistorianclient</PackageProjectUrl>
<RepositoryUrl>https://gitea.dohertylan.com/dohertj2/zb-mom-ww-sphistorianclient</RepositoryUrl>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Security.Cryptography.Xml" />
<PackageReference Include="System.ServiceModel.NetNamedPipe" />
<PackageReference Include="System.ServiceModel.NetTcp" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
</ItemGroup>
<!-- 2023 R2 gRPC transport (RemoteGrpc). Pure-managed: Grpc.Net.Client + Google.Protobuf.
Grpc.Tools is build-only (PrivateAssets=all) and generates the client stubs from the
recovered contract under Grpc/Protos at build. -->
<ItemGroup>
<PackageReference Include="Google.Protobuf" />
<PackageReference Include="Grpc.Net.Client" />
<PackageReference Include="Grpc.Net.Client.Web" />
<PackageReference Include="Grpc.Tools">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<Protobuf Include="Grpc/Protos/*.proto" GrpcServices="Client" ProtoRoot="Grpc/Protos" />
</ItemGroup>
<ItemGroup>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute">
<_Parameter1>ZB.MOM.WW.SPHistorianClient.Tests</_Parameter1>
</AssemblyAttribute>
</ItemGroup>
</Project>
Step 6: tests/ZB.MOM.WW.SPHistorianClient.Tests/ZB.MOM.WW.SPHistorianClient.Tests.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.collector" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="Microsoft.Data.SqlClient" />
<PackageReference Include="xunit" />
<PackageReference Include="xunit.runner.visualstudio" />
</ItemGroup>
<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\ZB.MOM.WW.SPHistorianClient\ZB.MOM.WW.SPHistorianClient.csproj" />
</ItemGroup>
</Project>
Step 7: Verify the skeleton is well-formed (build will fail — no sources yet — that is expected)
Run: cd ZB.MOM.WW.SPHistorianClient && dotnet restore ZB.MOM.WW.SPHistorianClient.slnx
Expected: restore succeeds (proves the props/csproj XML and central package versions resolve). A
follow-up dotnet build would fail only because no .cs exist yet — do not build here.
Step 8: Commit
cd /Users/dohertj2/Desktop/scadaproj
git add ZB.MOM.WW.SPHistorianClient/
git commit -m "feat(sphistorianclient): scaffold shared library skeleton (props, csprojs, slnx)"
Task 2: Port source + tests with namespace rewrite
Classification: standard Estimated implement time: ~4 min Parallelizable with: none Blocked by: Task 1
Files:
- Create (scripted copy):
ZB.MOM.WW.SPHistorianClient/src/ZB.MOM.WW.SPHistorianClient/**/*.{cs,proto}(74.cs+ 6.proto) - Create (scripted copy):
ZB.MOM.WW.SPHistorianClient/tests/ZB.MOM.WW.SPHistorianClient.Tests/**/*.cs(25.cs)
This task is a deterministic copy + namespace rewrite — run the script, then verify counts.
Step 1: Copy + rewrite (single script)
set -euo pipefail
BUNDLE=/tmp/histsdk/extracted/histsdk-migration/histsdk
DEST=/Users/dohertj2/Desktop/scadaproj/ZB.MOM.WW.SPHistorianClient
# Guard: re-extract if /tmp was cleaned
if [ ! -d "$BUNDLE/src/AVEVA.Historian.Client" ]; then
cd /tmp/histsdk && unzip -o -q histsdk-migration.zip -d extracted
fi
# --- src: copy .cs + .proto, preserving subdirs (NOT the old .csproj) ---
SRC="$DEST/src/ZB.MOM.WW.SPHistorianClient"
cd "$BUNDLE/src/AVEVA.Historian.Client"
find . \( -name '*.cs' -o -name '*.proto' \) | while read -r f; do
mkdir -p "$SRC/$(dirname "$f")"
cp "$f" "$SRC/$f"
done
# --- tests: copy .cs only (NOT the old .csproj) ---
TST="$DEST/tests/ZB.MOM.WW.SPHistorianClient.Tests"
cd "$BUNDLE/tests/AVEVA.Historian.Client.Tests"
find . -name '*.cs' | while read -r f; do
mkdir -p "$TST/$(dirname "$f")"
cp "$f" "$TST/$f"
done
# --- namespace rewrite in .cs ONLY (proto wire contracts stay ArchestrA.Grpc.Contract.*) ---
find "$SRC" "$TST" -name '*.cs' -print0 \
| xargs -0 sed -i '' 's/AVEVA\.Historian\.Client/ZB.MOM.WW.SPHistorianClient/g'
Step 2: Verify counts and that the rename is total
DEST=/Users/dohertj2/Desktop/scadaproj/ZB.MOM.WW.SPHistorianClient
echo "src cs: $(find "$DEST/src" -name '*.cs' | wc -l) (expect 74)"
echo "src proto: $(find "$DEST/src" -name '*.proto' | wc -l) (expect 6)"
echo "test cs: $(find "$DEST/tests" -name '*.cs' | wc -l) (expect 25)"
echo "leftover AVEVA.Historian.Client in .cs: $(grep -rl 'AVEVA\.Historian\.Client' "$DEST" --include='*.cs' | wc -l) (expect 0)"
echo "proto namespace preserved: $(grep -l 'ArchestrA.Grpc.Contract' "$DEST"/src/ZB.MOM.WW.SPHistorianClient/Grpc/Protos/*.proto | wc -l) (expect 6)"
Expected: 74, 6, 25, 0, 6. If "leftover" is non-zero, inspect those files — the only legitimate
remaining mentions would be inside comments/strings that happen to differ in casing/spacing; a clean
port should show 0.
Step 3: Commit
cd /Users/dohertj2/Desktop/scadaproj
git add ZB.MOM.WW.SPHistorianClient/src ZB.MOM.WW.SPHistorianClient/tests
git commit -m "feat(sphistorianclient): port SDK source + tests, rebrand namespace to ZB.MOM.WW.SPHistorianClient"
Task 3: Build + test green
Classification: high-risk Estimated implement time: ~5 min (plus restore/build wall-time) Parallelizable with: none Blocked by: Task 2
This is the integration gate. The port must compile and the offline test suite must pass on this macOS host.
Files:
- Modify (only if the build surfaces a defect): any ported file under
ZB.MOM.WW.SPHistorianClient/srcor…/tests, or the two.csproj.
Step 1: Build
Run: cd ZB.MOM.WW.SPHistorianClient && dotnet build ZB.MOM.WW.SPHistorianClient.slnx
Expected: Build succeeded. Platform-compatibility (CAxxxx [SupportedOSPlatform("windows")]) warnings
are acceptable and must remain warnings. If restore hard-fails on NU1903, re-run with
-p:NuGetAudit=false.
Step 2: Test
Run: dotnet test ZB.MOM.WW.SPHistorianClient.slnx
Expected: all tests pass; the live integration tests (HistorianClientIntegrationTests,
HistorianGrpcIntegrationTests, RemoteTcpIntegrationTests) skip cleanly because no HISTORIAN_*
env vars are set. The bundle's MIGRATION-README.md documents ~188 tests passing on macOS with the
live ones skipped — treat a comparable count with zero failures as success.
Step 3: Triage rules (if not green)
- Compile error referencing
AVEVA.Historian.Client→ a file was missed by the rewrite; re-run the Task 2 sed on that file. NU1008(version on PackageReference) → an inlineVersion=slipped into a.csproj; remove it (version belongs inDirectory.Packages.props).- Missing generated gRPC type (e.g.
ArchestrA.Grpc.Contract.*not found) → confirm the<Protobuf>glob in the src.csprojresolves the 6Grpc/Protos/*.protoand thatGrpc.Toolsrestored. - A genuine test failure (not a skip) → this is a real port defect; fix the ported code, do not delete/weaken the test.
Step 4: Commit (only if Step 3 required edits)
git add -A ZB.MOM.WW.SPHistorianClient/
git commit -m "fix(sphistorianclient): resolve port build/test fallout"
Task 4: Add the AddZbSpHistorianClient DI extension (TDD)
Classification: standard Estimated implement time: ~4 min Parallelizable with: Task 5 Blocked by: Task 3
HistorianClientOptions uses required + init-only properties, so the extension takes a fully-built
options instance (not an Action<T> configurator). It depends only on
Microsoft.Extensions.DependencyInjection.Abstractions.
Files:
- Create:
ZB.MOM.WW.SPHistorianClient/src/ZB.MOM.WW.SPHistorianClient/DependencyInjection/ZbSpHistorianClientServiceCollectionExtensions.cs - Test:
ZB.MOM.WW.SPHistorianClient/tests/ZB.MOM.WW.SPHistorianClient.Tests/DependencyInjectionTests.cs
Step 1: Write the failing test
using Microsoft.Extensions.DependencyInjection;
using ZB.MOM.WW.SPHistorianClient;
namespace ZB.MOM.WW.SPHistorianClient.Tests;
public class DependencyInjectionTests
{
[Fact]
public void AddZbSpHistorianClient_resolves_client_and_options()
{
var services = new ServiceCollection();
var options = new HistorianClientOptions { Host = "localhost" };
services.AddZbSpHistorianClient(options);
using var sp = services.BuildServiceProvider();
Assert.Same(options, sp.GetRequiredService<HistorianClientOptions>());
Assert.NotNull(sp.GetRequiredService<HistorianClient>());
}
[Fact]
public void AddZbSpHistorianClient_throws_when_host_missing()
{
var services = new ServiceCollection();
var options = new HistorianClientOptions { Host = "" };
Assert.Throws<ArgumentException>(() => services.AddZbSpHistorianClient(options));
}
[Fact]
public void AddZbSpHistorianClient_throws_on_null_options()
{
var services = new ServiceCollection();
Assert.Throws<ArgumentNullException>(() => services.AddZbSpHistorianClient(null!));
}
}
Step 2: Run — verify it fails to compile (AddZbSpHistorianClient not defined)
Run: dotnet test ZB.MOM.WW.SPHistorianClient.slnx --filter "FullyQualifiedName~DependencyInjectionTests"
Expected: FAIL (does not compile / method missing).
Step 3: Implement
using Microsoft.Extensions.DependencyInjection;
namespace ZB.MOM.WW.SPHistorianClient;
/// <summary>
/// ZB.MOM.WW DI registration for <see cref="HistorianClient"/>. Mirrors the family's
/// <c>AddZb*</c> convention. Because <see cref="HistorianClientOptions"/> is <c>required</c>/
/// <c>init</c>-only, callers pass a fully-built options instance (bind it from configuration in the
/// consuming app, e.g. <c>config.GetSection("Historian").Get<HistorianClientOptions>()</c>).
/// </summary>
public static class ZbSpHistorianClientServiceCollectionExtensions
{
public static IServiceCollection AddZbSpHistorianClient(
this IServiceCollection services,
HistorianClientOptions options)
{
ArgumentNullException.ThrowIfNull(services);
ArgumentNullException.ThrowIfNull(options);
if (string.IsNullOrWhiteSpace(options.Host))
{
throw new ArgumentException(
"HistorianClientOptions.Host must be set.", nameof(options));
}
services.AddSingleton(options);
// HistorianClient opens a fresh channel per operation and has a no-op DisposeAsync,
// so transient is safe and avoids assuming the shared dialect is concurrency-safe.
services.AddTransient<HistorianClient>();
return services;
}
}
Step 4: Run — verify pass
Run: dotnet test ZB.MOM.WW.SPHistorianClient.slnx --filter "FullyQualifiedName~DependencyInjectionTests"
Expected: PASS (3/3).
Step 5: Commit
git add ZB.MOM.WW.SPHistorianClient/src/ZB.MOM.WW.SPHistorianClient/DependencyInjection \
ZB.MOM.WW.SPHistorianClient/tests/ZB.MOM.WW.SPHistorianClient.Tests/DependencyInjectionTests.cs
git commit -m "feat(sphistorianclient): add AddZbSpHistorianClient DI extension"
Task 5: Author CLAUDE.md + README.md
Classification: small Estimated implement time: ~4 min Parallelizable with: Task 4 Blocked by: Task 3
Sanitized docs only — no hostnames, credentials, customer tag names, or capture data. Model the
structure on ZB.MOM.WW.Telemetry/CLAUDE.md (overview, package table, build/test/pack commands,
status) but adapt to a single-package library.
Files:
- Create:
ZB.MOM.WW.SPHistorianClient/CLAUDE.md - Create:
ZB.MOM.WW.SPHistorianClient/README.md
CLAUDE.md must cover:
- One-paragraph overview: pure-managed .NET 10 AVEVA System Platform Historian client, no native AVEVA
dependency, reverse-engineered wire protocol. Ported from the
histsdkmigration bundle. - The supported operation surface table (copy the README table from the bundle:
ProbeAsync,ReadRawAsync,ReadAggregateAsync(16 modes),ReadAtTimeAsync,ReadEventsAsync,BrowseTagNamesAsync,GetTagMetadataAsync,GetConnectionStatusAsync,GetStoreForwardStatusAsync,GetSystemParameterAsync,EnsureTagAsync,DeleteTagAsync). - Transport matrix:
LocalPipe/RemoteTcpIntegrated/RemoteTcpCertificate(WCF, Windows-only, live-verified) vsRemoteGrpc(2023 R2, cross-platform, not yet live-verified). - Out of scope: writing samples (
AddS2architecturally blocked), discrete/string tag creation. - DI: the
AddZbSpHistorianClient(options)extension + the bind-from-config note. - Build/test/pack commands (from this dir):
dotnet build ZB.MOM.WW.SPHistorianClient.slnx/dotnet test …/dotnet pack ZB.MOM.WW.SPHistorianClient.slnx -c Release -o ./artifacts. - Live integration tests gated by
HISTORIAN_*env vars (skip cleanly when unset). List the env vars.
README.md: a trimmed public-facing version — overview, quick-start snippet (the bundle's
HistorianClient usage example, namespace updated to ZB.MOM.WW.SPHistorianClient), supported surface
table, build/test commands.
Commit:
git add ZB.MOM.WW.SPHistorianClient/CLAUDE.md ZB.MOM.WW.SPHistorianClient/README.md
git commit -m "docs(sphistorianclient): add CLAUDE.md + README.md"
Task 6: Pack verification
Classification: small Estimated implement time: ~3 min Parallelizable with: none Blocked by: Task 4, Task 5
Files:
- Create (build output):
ZB.MOM.WW.SPHistorianClient/artifacts/ZB.MOM.WW.SPHistorianClient.0.1.0.nupkg
Step 1: Full green build + test once more, then pack
cd /Users/dohertj2/Desktop/scadaproj/ZB.MOM.WW.SPHistorianClient
dotnet test ZB.MOM.WW.SPHistorianClient.slnx
dotnet pack ZB.MOM.WW.SPHistorianClient.slnx -c Release -o ./artifacts
Expected: tests pass (live ones skip); pack produces artifacts/ZB.MOM.WW.SPHistorianClient.0.1.0.nupkg.
Step 2: Sanity-check the package contents
unzip -l artifacts/ZB.MOM.WW.SPHistorianClient.0.1.0.nupkg | grep -E 'ZB.MOM.WW.SPHistorianClient.dll|.nuspec'
Expected: the lib DLL and nuspec are present.
Step 3: Commit the nupkg (matches the family convention — ZB.MOM.WW.Telemetry commits its artifacts/*.nupkg)
cd /Users/dohertj2/Desktop/scadaproj
git add -f ZB.MOM.WW.SPHistorianClient/artifacts/ZB.MOM.WW.SPHistorianClient.0.1.0.nupkg
git commit -m "build(sphistorianclient): pack 0.1.0 nupkg"
Do NOT push or publish to the Gitea feed. Per repo experience, "published/adopted" claims must not be made without explicit user direction + feed verification.
Task 7: Index the new library in the umbrella CLAUDE.md (optional)
Classification: trivial Estimated implement time: ~2 min Parallelizable with: none Blocked by: Task 6
Files:
- Modify:
CLAUDE.md(repo root umbrella index)
Add a short reference so the umbrella index reflects the newly-hosted library (the intro paragraph
that enumerates the hosted ZB.MOM.WW.* sources, and/or a one-line pointer near the component table
noting ZB.MOM.WW.SPHistorianClient is a net-new shared library — not a component normalization).
Caveat: repo-root
CLAUDE.mdalready has pre-existing uncommitted edits (unrelated to this work). Before editing, rungit diff CLAUDE.mdand make sure your commit message reflects that it may bundle those edits — or stage only the hunks you add. If this risks entangling unrelated changes, skip this task and leave it for the user.
Commit:
git add CLAUDE.md
git commit -m "docs: index ZB.MOM.WW.SPHistorianClient in umbrella CLAUDE.md"
Done criteria
ZB.MOM.WW.SPHistorianClient/exists withsrc/,tests/, props,.slnx,CLAUDE.md,README.md.dotnet build+dotnet testare green on macOS (live integration tests skip cleanly).AddZbSpHistorianClientDI extension present + tested.artifacts/ZB.MOM.WW.SPHistorianClient.0.1.0.nupkgproduced.- All work committed on
feat/sphistorianclient. Not pushed/published.