From 2c55d080a8849e955252e2aa87f4022ffc2fd395 Mon Sep 17 00:00:00 2001 From: Jason Naylor Date: Fri, 28 Aug 2026 15:27:26 -0700 Subject: [PATCH] Rewrite AGENTS.md as an architecture guide and add CLAUDE.md Replaces the onboarding log with guidance an agent can act on. The old file recorded that dotnet build and dotnet test had FAILED and attributed both to GitVersion, which sent readers down a dead end; it also inventoried the repo root and top-level directories, which the repository already states. What the file now carries that could not be discovered cheaply: - Editing a Generated*.cs file does not fail. Regeneration is skipped while the file is newer than MasterLCModel.xml, so the edit compiles locally and the tests pass while CI builds different code from the XML. The files are gitignored, so it can never be committed. A fresh worktree has none of them until the first build, and a stale one left by a branch switch is compiled as it stands. - Per-rule consequences instead of one blanket claim, since rule 1 neither breaks the build nor corrupts data. - The FLEx Bridge metadata cache obligation from WARNING 4. - Worktrees need a named branch, because GitVersion cannot version a detached HEAD, and removal needs the directory left first. - ParallelizeAssembly=false is required because ICU and the writing system subsystems hold shared state. - Windows builds need the C++ tools whether or not the IDE is used, because code generation preprocesses the IDL with cl.exe. Corrects the IOC description: the container is Microsoft.Extensions.DependencyInjection since #393, not StructureMap. Drops the transcribed SDK version, target frameworks and CI command list in favour of pointing at global.json, the csproj files and ci-cd.yml, which is where they are actually defined. The -m:1 rule and the instruction to report failing instructions are carried forward unchanged. CLAUDE.md imports AGENTS.md and points Claude-only procedures at .claude/skills/, matching FieldWorks and interlinearizer-extension. Co-Authored-By: Claude Opus 5 (1M context) --- AGENTS.md | 330 +++++++++++++++++++++++++++++++++++++++--------------- CLAUDE.md | 6 + 2 files changed, 243 insertions(+), 93 deletions(-) create mode 100644 CLAUDE.md diff --git a/AGENTS.md b/AGENTS.md index 73dbdb3b..a0330861 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,103 +1,247 @@ # AGENTS: liblcm (LCM) ## Summary -liblcm (LCM) is the core FieldWorks Language & Culture Model library for linguistic analyses. It provides the data model, serialization, utilities, and tooling for linguistic, anthropological, and text corpus data. It is a multi-project .NET solution with code generation steps and multi-targeting for legacy .NET Framework and modern .NET. - -## High-level repo facts -- Type: .NET solution (multi-project class libraries + build tasks + tools + tests). -- Languages: C# (.cs), MSBuild (.proj/.csproj/.props/.targets), XML, shell/batch scripts. -- Target frameworks: net462, netstandard2.0, net8.0 (see .csproj files in src/ and tests/). -- Build tools: MSBuild, dotnet SDK, GitVersion.MsBuild, NUnit. -- Output: artifacts/ (NuGet packages and binaries by configuration/TFM). - -## Build and validation (validated commands and observations) - -### What CI runs (GitHub Actions) -CI runs on Windows and Ubuntu. See .github/workflows/ci-cd.yml: -1) Install .NET SDK 8.x. -2) Ubuntu: install mono-devel and icu-fw packages. -3) Windows: remove c:\tools\php\icuuc*.dll; install .NET Framework 4.6.1 targeting pack. -4) Build: dotnet build --configuration Release -5) Test: - - Linux: . environ && dotnet test --no-restore --no-build -p:ParallelizeAssembly=false --configuration Release - - Windows: dotnet test --no-restore --no-build -p:ParallelizeAssembly=false --configuration Release -6) Pack: dotnet pack --include-symbols --no-restore --no-build -p:SymbolPackageFormat=snupkg --configuration Release - -Always mirror this sequence when validating a change locally. + +liblcm is the core FieldWorks Language and Culture Model library. It provides the +object-oriented data model, serialization, persistence, and domain services for linguistic, +anthropological, and text corpus data used by +[FieldWorks](https://github.com/sillsdev/FieldWorks). + +The codebase is heavily code-generated from `MasterLCModel.xml`. Understand the generation +pipeline and the rules below before changing anything. + +## Critical rules + +Rules 2, 3 and 5 corrupt data when broken. Rule 4 breaks the build. Rule 1 does neither, +which is what makes it the most dangerous of the five. + +1. **Never edit `Generated*.cs`.** Nine files are produced from `MasterLCModel.xml` via the + NVelocity templates in `LcmGenerate/*.vm.cs`: + + - `GeneratedConstants.cs`, `GeneratedInterfaces.cs`, `GeneratedFactoryInterfaces.cs`, + `GeneratedRepositoryInterfaces.cs` + - `DomainImpl/GeneratedClasses.cs`, `DomainImpl/GeneratedFactoryImplementations.cs` + - `Infrastructure/Impl/GeneratedRepositoryImplementations.cs`, + `Infrastructure/Impl/GeneratedBackendProvider.cs` + - `IOC/GeneratedServiceLocatorBootstrapper.cs` + + An edit there does not fail loudly. Regeneration is skipped while the file is newer than + `MasterLCModel.xml`, so the edit compiles into your local build and your tests pass, + while CI generates from the XML into a fresh tree and builds different code. The files + are gitignored, so the edit can never be committed, and any `MasterLCModel.xml` change + or `dotnet clean` destroys it. Edit the XML or the templates instead. + + A fresh clone or worktree has none of these files until the first build. After switching + branches, delete them: a generated file left over from another branch is newer than the + XML, so it will be compiled as it stands. + +2. **Model changes require a version bump and migration.** Almost every change to + `MasterLCModel.xml` requires incrementing the `version` attribute and writing a data + migration class. The only exceptions are editing `` or `` elements, + editing XML comments, and adding attributes that only affect the code generator. Read the + `WARNING` block at the top of `MasterLCModel.xml`; it is authoritative, and it also + requires a matching update to the FLEx Bridge metadata cache. + +3. **All data changes must occur within a UnitOfWork.** Use `UndoableUnitOfWorkHelper` for + user actions or `NonUndoableUnitOfWorkHelper` for system operations. Changes outside a + UOW throw or silently fail. + +4. **No references to `System.Windows.Forms`.** Enforced by the `CheckWinForms` target in + `SIL.LCModel`, `SIL.LCModel.Core` and `SIL.LCModel.Utils`. + +5. **Model version bumps require a matching migration registration.** New migrations must be + registered in the `LcmDataMigrationManager` constructor dictionary. Even a no-op version + bump needs an entry, for which `m_bumpNumberOnlyMigration` exists. + +## Proving a change Use `dotnet build -m:1` for a cold-start build (a tree with no generated sources yet). Parallel builds race on the generated sources and fail with `LcmGenerate` or `IdlImp` errors. Plain `dotnet build` is fine once those sources exist. -### Tests per README (not validated here) -- Windows, ReSharper: open LCM.sln and “Run Unit Tests”. -- Windows, no ReSharper: use MSBuild, then run nunit3-console.exe from artifacts/Debug/net462. -- Linux terminal: source environ, then run mono with nunit3-console.exe on *Tests.dll in artifacts/Debug/net462. - -### Commands actually run during onboarding -- dotnet test .\LCM.sln → FAILED -- dotnet build --configuration Release → FAILED -Failure signature (both commands): GitVersion.MsBuild (netcoreapp3.1 gitversion.dll) exited with code 1. This blocks build/test in this environment. CI uses fetch-depth 0, so ensure a full git history is available. If GitVersion still fails, check GitVersion prerequisites and local .NET runtime compatibility. - -### Known prerequisites and gotchas -- GitVersion.MsBuild is used across projects; it requires git metadata. CI checks out with fetch-depth 0. -- net462 builds on Windows require the .NET Framework 4.6.1 targeting pack (CI installs it). -- ICU data generation requires ICU binaries (CI installs icu-fw on Ubuntu). -- Some projects warn on NU1701; treat as warnings unless build breaks. -- The build prohibits references to System.Windows.Forms (CheckWinForms target). - -## Project layout and architecture - -### Key solution and build files -- LCM.sln: solution entry point. -- Directory.Build.props / Directory.Build.targets: repo-wide build settings and packaging. -- Directory.Solution.props / Directory.Solution.targets: solution-level defaults. -- GitVersion.yml: GitVersion configuration. -- global.json: SDK roll-forward config. -- .editorconfig: formatting rules. - -### Major source projects (src/) -- src/SIL.LCModel: main LCM library (net462; netstandard2.0). -- src/SIL.LCModel.Core: core utilities and ICU data generation (netstandard2.0; net462; net8.0). -- src/SIL.LCModel.Utils: shared utilities (net462; netstandard2.0). -- src/SIL.LCModel.Build.Tasks: MSBuild tasks used for code generation. -- src/SIL.LCModel.FixData: data-fix utilities. -- src/CSTools: auxiliary tools (pg/lg/Tools). - -Code generation targets to know about: -- SIL.LCModel: GenerateModel (MasterLCModel.xml → Generated*.cs). -- SIL.LCModel.Core: GenerateKernelCs, GenerateIcuData. - -### Tests (tests/) -- SIL.LCModel.Tests -- SIL.LCModel.Core.Tests -- SIL.LCModel.Utils.Tests -- SIL.LCModel.FixData.Tests -- TestHelper (support project) - -### CI/validation checks -- GitHub Actions: .github/workflows/ci-cd.yml (build, test, pack, publish). -- Tests run with dotnet test and ParallelizeAssembly=false. -- Packaging uses dotnet pack with symbol packages. - -### Dependencies not obvious from layout -- ICU data and binaries (icu-fw) for Core ICU generation. -- Mono on Linux for some runtime/test workflows. -- GitVersion.MsBuild for versioning (requires git metadata). - -## Repo top-level directories -- .github/ (GitHub Actions workflow) -- .vscode/ (VS settings) -- artifacts/ (build outputs) -- src/ (production code) -- tests/ (unit tests) - -## README highlights (summary) -- Describes liblcm as FieldWorks model library for linguistic analyses. -- Build: use `dotnet build`. Default Debug, optional Release. -- Debugging: use LOCAL_NUGET_REPO to publish local packages; see NuGet local feeds. -- Tests: Windows via ReSharper or NUnit console; Linux via mono + NUnit console (requires environ). +``` +dotnet build -m:1 --configuration Release +dotnet test tests//.csproj --configuration Release --no-restore --no-build -p:ParallelizeAssembly=false +``` + +`--no-restore --no-build` require a completed build in the same configuration. Scope tests to +one project while iterating. `-p:ParallelizeAssembly=false` is not optional: ICU and the +writing system subsystems hold shared state. + +Windows builds need the Visual Studio C++ tools whether or not you use the IDE, because code +generation preprocesses the IDL with `cl.exe`, located via `vswhere`. + +ICU needs no manual environment setup. Test assemblies declare +`[assembly: InitializeIcu(IcuDataPath = "IcuData")]`, which resolves against the build output. + +A root build compiles every project, and any stray `.cs` file inside a project directory +joins that compilation. Run `git status --porcelain` first: files left over from another +branch produce compile errors that look like your change broke something. + +The SDK floor, the target frameworks and the exact CI sequence are defined in `global.json`, +the `.csproj` files and `.github/workflows/ci-cd.yml`. Read those rather than a transcription +here. + +### Worktrees + +``` +git worktree add -b .claude/worktrees/ origin/master +``` + +Always a named branch. GitVersion cannot version a detached HEAD, and the build then fails +with `MSB3073` errors whose real cause appears only in a `WARN` line above them. + +To remove one, leave the directory first, then force it, since build output is untracked: + +``` +dotnet build-server shutdown +git worktree remove --force .claude/worktrees/ +``` + +## Architecture + +### Code generation + +`MasterLCModel.xml` is the single source of truth. The `GenerateModel` target runs +`LcmGenerate` from `SIL.LCModel.Build.Tasks`, which parses the XML and uses the NVelocity +templates in `LcmGenerate/*.vm.cs` to produce the nine generated C# files. + +`SIL.LCModel.Core` has a second generator: `GenerateKernelCs` runs the `IdlImp` task from the +same build-tasks assembly over `KernelInterfaces/*.idh` to produce `Kernel.cs`. + +### MasterLCModel.xml schema + +The model is organized into `CellarModule` elements containing `class` elements. Read the +module ids and numbers off the file rather than memorizing them. Each class carries: + +- `id`: class name, e.g. `LexEntry` +- `num`: class number within its module +- `base`: parent class; all classes descend from `CmObject` +- `abstract`, `depth`, `abbr` +- `owner`: `required` (default), `optional`, or `none` +- `singleton`: whether only one instance exists, e.g. `LangProject` + +Properties come in three kinds: + +- ``: value types. `sig` is `Integer`, `Boolean`, `String`, `Unicode`, `MultiString`, + `MultiUnicode`, `Time`, `GenDate`, `Binary`, `Guid` or `TextPropBinary` +- ``: ownership. `card` is `atomic`, `seq` or `col`; `sig` is the target class +- ``: non-owning references, same attributes as `` + +Field ids (flids) are the module number, then the class number to three digits, then the +field number to three digits. `LexSenseTags.kflidDefinition` is `5016005`: Ling module 5, +`LexSense` class 16, `Definition` field 5. + +`Unicode` and `MultiUnicode` are plain character sequences with no formatting. `String` and +`MultiString` carry embedded runs with writing systems, styles and other properties. + +### Partial class pattern + +Generated classes are `partial`. Hand-written code extends them in `DomainImpl/Overrides*.cs`, +split by domain: `OverridesLing_Lex.cs`, `OverridesCellar.cs`, `OverridesLing_Wfi.cs`, +`OverridesLing_MoClasses.cs`, `OverridesLangProj.cs`, `OverridesLing_Disc.cs`, +`OverridesNotebk.cs`. + +These add virtual properties (`[VirtualProperty]`), convenience methods, business logic and +side-effect handlers. Virtual properties are discovered by reflection, so they need no XML and +no registration. Partial interface extensions live in `InterfaceAdditions.cs`. + +### Persistence and infrastructure + +**LcmCache** (`LcmCache.cs`) is the entry point for all data access. Despite the name it is a +service locator facade, not a cache. Key accessors: `ServiceLocator`, `LanguageProject`, +`DomainDataByFlid`, `ActionHandlerAccessor`. + +**Backend providers**, all in `Infrastructure/Impl/`: + +- `XMLBackendProvider` -- file-based XML storage, the `.fwdata` format +- `MemoryOnlyBackendProvider` -- in-memory, used by tests +- `SharedXMLBackendProvider` -- multi-process shared access via memory-mapped files + +**Surrogate and IdentityMap.** Objects load lazily. The backend reads XML into +`CmObjectSurrogate` placeholders; on first access to `.Object` the surrogate parses the XML and +creates the real `CmObject`. `IdentityMap` guarantees one instance per Guid and Hvo. Bulk +loading by domain is available through `BackendProvider.LoadDomain()`. + +**Dependency injection.** `LcmServiceLocatorFactory` builds a +`Microsoft.Extensions.DependencyInjection` container and wraps it in `MicrosoftServiceLocator`, +which derives from `ServiceLocatorImplBase` so `GetInstance()` keeps working. Each type is registered as a singleton by its concrete +type, with the interface registered as an alias resolving to the same instance. Generated code +supplies the factory and repository registrations in `GeneratedServiceLocatorBootstrapper.cs`. + +### Data migration + +Migrations live in `DomainServices/DataMigration/` and are registered in +`LcmDataMigrationManager`. They operate on raw XML through `DomainObjectDTO`; no live +`ICmObject` is available. See the `writing-a-data-migration` skill for structure and repository +behaviour. + +### Key domain classes + +Simplified ownership hierarchy: + +``` +LangProject (singleton, owner=none) + +-- LexDb (atomic) + | +-- [Entries reached through a virtual property; LexEntry has owner=none] + | +-- LexSense (seq) + | | +-- LexExampleSentence (seq) + | +-- MoForm / MoStemAllomorph / MoAffixAllomorph + | +-- MoMorphSynAnalysis (col: MorphoSyntaxAnalyses) + +-- PartsOfSpeech (CmPossibilityList, atomic) + +-- SemanticDomainList (CmPossibilityList, atomic) + +-- ResearchNotebook (RnResearchNbk, atomic) + +-- TranslatedScripture (Scripture, atomic) + +-- Styles (StStyle, col) +``` + +`CmPossibility` and `CmPossibilityList` are the list and list-item pattern used throughout for +categories, types, domains and other enumerated values. + +Projects have vernacular writing systems (the language being studied) and analysis writing +systems (languages used for descriptions). `MultiUnicode` and `MultiString` properties store +alternatives keyed by writing system. + +## Project layout + +``` +src/ + SIL.LCModel/ Main library + MasterLCModel.xml Model source of truth + MasterLCModel.xsd Schema for the model + LcmGenerate/ NVelocity templates + HandGenerated.xml + DomainImpl/ Generated and hand-written class implementations + DomainServices/ Business logic and domain services + DataMigration/ Migration classes and the migration manager + Infrastructure/Impl/ Backend providers, UnitOfWork, IdentityMap + IOC/ Dependency injection setup + SIL.LCModel.Core/ Cellar types, ICU, writing systems, Kernel interfaces + SIL.LCModel.Utils/ Shared utilities + SIL.LCModel.Build.Tasks/ MSBuild tasks: LcmGenerate and IdlImp + SIL.LCModel.FixData/ Data-fix utilities + CSTools/ Auxiliary tools (pg/lg) +tests/ + SIL.LCModel.Tests/ Main library tests + SIL.LCModel.Core.Tests/ Core tests + SIL.LCModel.Utils.Tests/ Utility tests + SIL.LCModel.FixData.Tests/ FixData tests + TestHelper/ Test support project +``` + +## Common tasks + +Step-by-step guides live in `.claude/skills/`. Read the SKILL.md directly if your agent does +not load them automatically. + +- Adding a property to an existing class -- `.claude/skills/adding-a-property/SKILL.md` +- Adding a new class to the model -- `.claude/skills/adding-a-new-class/SKILL.md` +- Adding a virtual property, computed and not persisted -- + `.claude/skills/adding-a-virtual-property/SKILL.md` +- Writing a data migration -- `.claude/skills/writing-a-data-migration/SKILL.md` +- Writing tests -- `.claude/skills/writing-tests/SKILL.md` ## Trust these instructions -Follow this file first. Only search the repo if these instructions are incomplete or prove incorrect for your task. -If these instructions fail notify the author of the task that they should verify and update the instructions if necessary. + +Follow this file first. Only search the repo if these instructions are incomplete or prove +incorrect for your task. If these instructions fail notify the author of the task that they +should verify and update the instructions if necessary. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..f14594ab --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,6 @@ +@AGENTS.md + +## Claude Code + +- Keep repo-wide standing guidance in `AGENTS.md` and import it here. +- Put Claude-only workflows and task procedures under `.claude/skills/`.