DiGi.Maintenance is a C# engineering and architectural software library suite designed for BIM and CAD integrations (such as Revit, RhinoCommon, Grasshopper, and Dynamo BIM).
The repository contains the following core components and assemblies:
- DiGi.Maintenance.UI.Application (Path:
DiGi.Maintenance\DiGi.Maintenance.UI.Application) - DiGi.Maintenance.UI (Path:
DiGi.Maintenance\DiGi.Maintenance.UI) - DiGi.Maintenance (Path:
DiGi.Maintenance\DiGi.Maintenance)
This project strictly separates Data Models (anemic schemas) from Business/Calculation Logic (static extension methods). All new features must strictly follow this pattern.
graph TD
subgraph "Data Models (Anemic)"
C[Classes]
I[Interfaces]
E[Enums]
end
subgraph "Business Logic (Static Extensions)"
Q[Query Class]
M[Modify Class]
Cr[Create Class]
end
C -->|Query / Read| Q
C -->|Modify / Mutate| M
C -->|Create / Instantiate| Cr
- Classes: Place in the
/Classesdirectory (Namespace:[Project].Classes). Keep them simple and lightweight (properties and basic constructors only). Do NOT put complex logic inside these classes. - Interfaces: Place in the
/Interfacesdirectory (Namespace:[Project].Interfaces). - Enums: Place in the
/Enumsdirectory (Namespace:[Project].Enums).
ALL complex functionalities, including operations on classes, interfaces, and enums, MUST be implemented as Extension Methods inside static partial classes in /Query, /Modify, /Create, or /Convert directories:
- Query (Read/Extract): Static partial class
Queryreturning results based on a query without modifying the source object. - Modify (Update/Mutate): Static partial class
Modifymodifying the state or properties of the existing object in place. - Create (Instantiate): Static partial class
Createinstantiating and returning a new object. - Convert (Parse/Format/Transform): Static partial class
Convertconverting, formatting, or transforming an object into another representation.
All developers and AI agents follow these always-applicable rules (Visual Studio 2026, C#). Task-specific detail lives in the guidelines listed in Β§9 β read the one matching the task.
- English only β identifiers and comments.
- No
varunless the compiler forces it; target-typednew()(PointNode pointNode = new();, avoids IDE0090) and collection expressions (List<int> numbers = [];, avoids IDE0028). - Naming: camelCase starting with the type name,
_-suffixed qualifier when needed (PointNode pointNode_Base); an acronym type lowercases only its first letter (gISPostgreSQLConverterManager). Collections: pluralized element type (filterConditions,point3Dsβ notlistConditions). A property named after its unique, descriptive type (public AggregateFunction AggregateFunction { get; set; }). Primitives: plain camelCase. - Zero warnings/analyzer messages.
- Block-scoped namespaces (
namespace DiGi.Domain { ... }); file-scoped are prohibited (.editorconfig). Every repository builds atLangVersion latestwithAnalysisLevel latest-recommended(template props); core libraries targetnetstandard2.0β useSystem.Threading.Lockonly in net9+ projects. - Parameter line breaks (
<= 7rule,DIGI0001): β€ 7 parameters on one line however long; β₯ 8 one per line. Line length is never the trigger. Call sites prefer one line; multi-line for complex expressions, lambdas, initializers.public void Calculate(double centerX, double centerY, double radius, double? storeyHeight = null)
- Async method names end with
Async. CancellationTokenis always the LAST parameter (CA1068) β every method and overload; add new optional parameters before it and reorder the<param>tags. At call sites name every argument after a skipped default (commandTimeout: 30,cancellationToken: cancellationToken) so an inserted parameter fails to compile instead of rebinding. Detect:grep -rnE "CancellationToken [a-zA-Z_]+( = default)?, " --include=*.cs .public static async Task<bool> ClearAsync(NpgsqlConnection? npgsqlConnection, string tableName, int commandTimeout = 30, CancellationToken cancellationToken = default)
- Simplify member access (IDE0002) β then rebuild. C# binds the first segment innermost-outwards with no fallback:
from
DiGi.GIS.PostgreSQL.UI.Classes,WebAPIisDiGi.GIS.WebAPI, soDiGi.WebAPI.Query.GetAsynckeeps its prefix. When same-named extension methods can both apply, call the intended one fully qualified β theusingdecides silently. - Separate projects, not one monolithic solution.
- Output optimization: no filler; only the necessary code, logic or explanation.
- Temporary code carries
TODO [MarkerName]:β a PascalCase name of the migration, reused at every site so onegrepis the deletion checklist, stating the observable removal condition and the tracking issue. A workaround for another DiGi repository's defect is temporary too (removal condition = the upstream fix). Never tag permanent code. ToString()is not a key β identity strings render throughCultureInfo.InvariantCulture("R"with-0.0folded,G29fordecimal);pl-PLfiles[0,5, 12,25]and nothing throws. Reference:DiGi.Typology.Visual.Query.Key.- A member resolved by name through reflection is a contract on the whole hierarchy β document it at both ends,
probe
GetMethodrather than reason about it. - No caching in an optimization unless the request explicitly allows it β remove allocations and repeated enumeration first; propose a cache with its measurement and ask.
- Log through
Serilog.Modify.Logwith structured templates ("{Type}: β¦ {CountyId}", never interpolation);Log(exception, β¦)always logs at Error. - Rethrow only a requested cancellation:
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) { throw; }before any general catch.
Data models are anemic; behavior is static extension methods. Never a service/manager class for behavior (process-wide
registries are *Managers on Settings or DI singletons).
/Classes([Project].Classes),/Interfaces,/Enums. Category folders (Classes/Options,/BackgroundTask,/Result,Convert/ToX) add no namespace segment β it would change the_typediscriminator.Query(read-only, property-like names:BoundingBox(), onlyIs/Has/Tryprefixes),Modify(mutates),Create(builds),Convert(/Convert/To[TargetArea]/[TargetType].cs,ToSystem_String,ToEPW_DateTime) βConvertis the first choice for any conversion, never an instance method on the model.- Interface-contract members are instance methods on the class β geometry primitives (
Polygon2D,Circle2D, β¦ implementingIBoundable2D,ITransformable2D,IClosedCurve2D) are deliberately not anemic; never move them. - Constructors stay cheap (assign and clone only); validation and cleanup go in a
Createfactory named after the type, returningnullfor invalid input β materialise β clean up β then validate. - One public method per file in
/Query,/Modify,/Create(overloads together); nested types in[Outer].[Inner].cs; no private methods in these classes (public helper or local function). IResulttypes end inResult(TerrainPointDensityResult); the name is wire contract, so existing violations are not renamed opportunistically.- Enums start with
Undefined(-1only where0is real); tolerances come fromDiGi.Core.Constants.Tolerance(Distance1e-6,MicroDistance,MacroDistance,Angle).
Committed, non-sensitive deployment assets go in files/; secrets, credentials, machine-specific settings and
generated reports go in the git-ignored user files/ ([Uu]ser [Ff]iles/ in .gitignore; verify with
git check-ignore -v "user files/x.conf"). Both are copied flat into the output (CopyUserFiles runs after CopyFiles,
so user files/ wins) β and user files/ ships with every deploy, so training data and model variants live outside
the workspace. A file a build copies into a project (gltf-viewer-core.js, gis-theme.css) is edited only at its source.
DiGi projects reference each other by HintPath, which NuGet and MSBuild cannot follow: a host must re-declare every
package its libraries need, at the same version (unless its framework provides it), or it fails at run time with a
FileNotFoundException per item β a partial result reported as success. dotnet test never rebuilds the library
under test: build it first. Verify with CheckHostDependencies.ps1.
Never compare two interface-typed references with == (a silent reference comparison; accessors clone per call).
Use Core.Query.Equals(reference_1, reference_2).
A serializable class implements I<Project>SerializableObject, keeps private readonly fields tagged
[JsonInclude, JsonPropertyName(nameof(Prop))], exposes them as [JsonIgnore] get-only properties, uses
DateTimeOffset for dates, and has exactly three constructors:
public Holiday(string? name, string? date)
{
this.name = name;
this.date = date;
}
public Holiday(Holiday? holiday) : base(holiday)
{
if (holiday != null)
{
name = holiday.name;
date = holiday.date;
}
}
public Holiday(JsonObject? jsonObject) : base(jsonObject) { }Copy constructor: primitive lists new List<T>(source), nested objects Core.Query.Clone(item) element by element.
- XML docs: every public member, params in signature order, one
<summary>, no blank lines (<para>), no docs onpartialdeclarations. Readdocumentation/API/<Assembly>/before opening.cssource. - xUnit:
[ProjectName].xUnit,public partial class Factsin/Facts, a<summary>on every fact, shared fixtures inDiGi.Test/files/, written reports inDiGi.Test/user files/reports/. Every serializable class gets a fact ending inCore.xUnit.Query.SerializationCheck(instance)plus a direct copy-constructor assertion. Tolerance facts test just inside and just outside; benchmarks are measured in isolation with a warm-up. Slow facts are[MediumFact]/[LongFact]. - Interactive front ends are verified in a real browser (Playwright), asserting rendered output, not state flags.
Work is committed on the active version branch (the highest bare SemVer branch, e.g. 0.8.5) β no feature branch,
worktree or pull request unless asked; push only when asked. A version bump / release runs only on an explicit request:
merge the version branch into main, bump the patch (0.8.2 β 0.8.3), branch it off main, update
Directory.Build.props <Major>/<Minor>/<Build>, push both (git push -u origin <version_branch>).
Canonical, task-specific guidelines live in DiGi.Maintenance/documentation/AI Guidelines/ (relative to the workspace
root) and are mirrored per repository as skills under .agents/skills/<skill-name>/SKILL.md.
| Guideline | Read it when |
|---|---|
Coding - General.md |
Writing or editing any C# code. |
Coding - Serialization.md |
Creating or changing a serializable type or options class; debugging a JSON round trip. |
Coding - Host Dependencies.md |
Adding a NuGet package; a host failing with FileNotFoundException or a partial result; CS0012/CS0246 build walls. |
Coding - Project Setup.md |
New project or repository, .editorconfig, Directory.Build.props, analyzers. |
Coding - Automatic Tests.md |
Writing xUnit facts. |
Coding - Performance.md |
Optimizing, benchmarks, parity tests, the wiki Benchmark.md page. |
Coding - References.md |
Comparing or keying an IReference / IUniqueReference. |
Coding - Geometry.md |
Orienting, closing, triangulating, hashing or sweeping geometry. |
Coding - ComputeSharp.md |
Writing or changing a ComputeSharp IComputeShader. |
Coding - Grasshopper.md |
Grasshopper/Rhino plug-in components. |
Coding - Background Tasks.md |
Tray-application background tasks and their dialogs. |
Coding - WPF Deferred Loading.md |
A WPF window needing slow data; WPF layout checks. |
Coding - YOLO.md |
The YOLO Year Built detector. |
Coding - Laya.md |
The Laya decision model. |
Coding - WebAPI GLTF.md |
Web APIs on the DiGi.GLTF 3D framework. |
Coding - WebAPI Contracts.md |
Changing a WebAPI route/parameter/controller or writing an HTTP client. |
Coding - WebAPI Simple Authorization.md |
API-key tiered authorization. |
Coding - Deployed WebAPI.md |
Testing against api.digiproject.uk; which server runs what. |
Coding - Browser Testing.md |
Verifying interactive front-end behaviour in a real browser. |
Coding - GIS Administrative Data.md |
Anything keyed by a county code or id; the Subdivision layer. |
Coding - PostgreSQL.md |
PostgreSQL/Npgsql code, production vs development databases. |
Coding - PostgreSQL Distributed Queue Processing.md |
Distributed claim/acknowledge update queues. |
XML Documentation.md |
Reading generated API docs; adding or auditing XML docs. |
GitHub - Branches and Commits.md |
Where to commit, the pre-commit sweep, plan files, pulling all repos, releases. |
GitHub - Issues.md |
Creating, editing, verifying and closing issues; dependencies. |
GitHub - Labels.md |
Labels and the mandatory ai: * tier. |
GitHub - Sub-Issues.md |
Sub-issues for a multi-repository feature. |
GitHub - Workspace Setup.md |
Folder layout and new-machine setup. |
GitHub Wiki.md |
Editing wiki pages. |
Maintenance - Scripts.md |
DiGi.Maintenance/Scripts; scripted edits and line endings. |
Maintenance - AI Guidelines Propagation.md |
Adding, splitting or removing a guideline. |
One data rule worth knowing before it bites you: a GIS county
codedoes not identify one row β 406 county rows (one per polygon part) cover 380 codes. Key every write and lookup by countyid, give everyLIMIT/FirstOrDefaultanORDER BY, never deduplicate those rows, resolve every item's part againstbuilding_2dwhatever number of ids arrived, and read building-keyed tables withfallbackByReference: true(Coding - GIS Administrative Data.md).