diff --git a/.agents/skills/agenza-api-contract-review/SKILL.md b/.agents/skills/agenza-api-contract-review/SKILL.md index 2c058a5..7add959 100644 --- a/.agents/skills/agenza-api-contract-review/SKILL.md +++ b/.agents/skills/agenza-api-contract-review/SKILL.md @@ -20,10 +20,10 @@ description: > generated by `npm run generate:api-types` from services-service's live OpenAPI document, checked for staleness by `npm run generate:api-types:check` (also gated in CI — `api-contract-check` in `.github/workflows/frontend-ci.yml`). -- Frontend hand-written types: per-feature DTO interfaces in - `src/infrastructure/mappers/*.ts` (see `agent-skills/agenza-frontend-feature` - step 5) — these exist for features not yet covered by the generated - client, or that intentionally narrow it. +- Frontend decoders/mappers: feature-local infrastructure files under + `src/features/*/infrastructure/`. A hand-written wire type is justified only + when no generated contract exists or when it intentionally narrows unknown + input without shadowing the generated shape. ## Checks @@ -31,13 +31,14 @@ description: > --workspace=apps/admin-frontend` (requires services-service running and reachable, matching the CI job) — a failure here means a controller/DTO changed without regenerating. -2. **DTO duplication.** A hand-written DTO interface in - `src/infrastructure/mappers/` that shadows a type already in +2. **DTO duplication.** A hand-written DTO interface in a feature's + `infrastructure/` that shadows a type already in `services-api.d.ts` for the same resource — flag for consolidation. 3. **Field limit drift.** A `MaximumLength`/`.PrecisionScale(...)` on the backend validator or EF column vs. a `maxLength`/`max()` in the - matching Zod schema (`agent-skills/agenza-frontend-feature`'s form - section) — these must match exactly (see docs/adr/0012's + matching Zod schema (see + `.agents/skills/agenza-frontend-feature/references/page-ui-conventions.md`) + — these must match exactly (see docs/adr/0012's `Category.NameMaxLength`/`Service.NameMaxLength` alignment for the kind of drift this catches). A limit encoded only in a comment, never a type/schema/const, is itself a finding — flag it for a real check. @@ -50,8 +51,8 @@ description: > 6. **Unhandled API error shape.** A backend error `code` introduced (`Error.Conflict("Entity.SomeCode", ...)`) with no corresponding entry in the matching frontend `codeFieldMap` (see - `agent-skills/agenza-frontend-feature`'s "Structured API errors" - section) — it will still work (falls back to a global message) but + `.agents/skills/agenza-frontend-feature/references/api-integration.md`) — + it will still work (falls back to a global message) but loses field-level precision; flag it, don't treat it as broken. 7. **Structured vs. free-text errors.** Confirm a new validation failure path returns through `Error.FieldErrors` (structured, docs/adr/0012), diff --git a/.agents/skills/agenza-architecture-review/SKILL.md b/.agents/skills/agenza-architecture-review/SKILL.md index 0df6c0d..ce732b2 100644 --- a/.agents/skills/agenza-architecture-review/SKILL.md +++ b/.agents/skills/agenza-architecture-review/SKILL.md @@ -26,38 +26,38 @@ Check, across whichever of these areas are in scope for the request: shape followed? Frontend feature folders self-contained, no cross-feature imports? - **Multi-tenancy**: delegate the deep pass to - `agent-skills/agenza-tenant-isolation-review` rather than duplicating it + `.agents/skills/agenza-tenant-isolation-review` rather than duplicating it here — this review only checks that tenant scoping is *present* where expected, not the full mechanism. - **Exceptions / Result pattern**: delegate the deep pass to - `agent-skills/agenza-exception-flow-audit`. + `.agents/skills/agenza-exception-flow-audit`. - **Domain model**: anemic entities (public setters, no invariant enforcement), missing `DomainResult` usage, entities bypassing `BaseEntity`/`TenantOwnedEntity` without a documented reason. - **Persistence**: query filters applied by hand instead of via `ApplyAuditableConventions`, missing indexes for a new uniqueness rule, a migration issue — delegate depth to - `agent-skills/agenza-migration-safety`. -- **Contracts**: delegate to `agent-skills/agenza-api-contract-review`. + `.agents/skills/agenza-migration-safety`. +- **Contracts**: delegate to `.agents/skills/agenza-api-contract-review`. - **Frontend**: layering (see above), `any` usage, design-system drift (raw palette classes instead of semantic tokens), reusable-component - discipline (`agent-skills/agenza-frontend-feature`). + discipline (`.agents/skills/agenza-frontend-feature`). - **Accessibility**: keyboard operability, accessible names, contrast — sample a few recently-changed pages rather than the whole app unless asked for a full sweep. - **Tests**: coverage gate status, mock-strategy-per-layer discipline - (frontend), no integration-test reintroduction without an ADR reverting - docs/adr/0015 (backend). -- **Migrations**: `agent-skills/agenza-migration-safety`. -- **Documentation**: `AGENTS.md`/`CLAUDE.md` files still accurate and in - sync (`scripts/check_agent_governance.py` covers the mechanical half of + (frontend), narrow persistence/runtime boundaries matching the current ADR + index instead of treating historical ADR 0015 as the final state. +- **Migrations**: `.agents/skills/agenza-migration-safety`. +- **Documentation**: `AGENTS.md`, import-only `CLAUDE.md`, and the Copilot + bridge still accurate and in sync (`scripts/check_agent_governance.py` covers the mechanical half of this), STATUS.md rows matching what's actually built, ADRs referenced by number actually existing. - **CI**: workflows still matching the commands documented in `docs/QUALITY.md`, coverage gates not silently loosened. -- **Dependencies**: any package pinned for a documented reason - (`docs/QUALITY.md`, `README.md`'s Versions table) that a routine bump - would silently violate. +- **Dependencies**: compare executable pins (`packageManager`, lockfiles, + `backend/global.json`, `backend/Directory.Packages.props`, `.python-version`, + CI actions) with `docs/adr/0032` before recommending a routine bump. ## Mode: review-only (default) @@ -68,7 +68,7 @@ Produce a diagnosis, not a diff. For each finding: - **Why it matters** (tie back to a rule in `AGENTS.md`, an ADR, or a skill — don't invent a new rule mid-review; if there's genuinely no existing rule this violates, that's a finding for - `agent-skills/agenza-rule-persistence` to formalize, not a silent + `.agents/skills/agenza-rule-persistence` to formalize, not a silent judgment call) - **Severity**: blocks tenant isolation / security > breaks a build gate > architectural drift > style nit diff --git a/.agents/skills/agenza-backend-new-service/SKILL.md b/.agents/skills/agenza-backend-new-service/SKILL.md new file mode 100644 index 0000000..f4878cc --- /dev/null +++ b/.agents/skills/agenza-backend-new-service/SKILL.md @@ -0,0 +1,81 @@ +--- +name: agenza-backend-new-service +description: > + Use when creating a brand-new .NET service under backend/services or when + deciding whether a capability needs its own service. Covers this repository's + context-aggregated service boundary, project layout, central package + management, tenant-safe persistence, authentication, Aspire wiring, tests, + and documentation. Do not copy an old service template without reading it. +--- + +# Backend new service + +Create a service only for a genuinely new business context. If an existing +service owns the capability, use `agenza-backend-use-case` there instead. ADR +0001 records the context-aggregated service decision. + +## Use live references + +Inspect the current `services-service`, `identity-service`, AppHost, solution, +`backend/Directory.Packages.props`, and CI workflow before writing files. They +are the executable templates; this skill intentionally contains no copied +`Program.cs`, `.csproj`, or package-version blocks that can drift. + +## Required shape + +1. Create Domain, Application, Infrastructure, Api, and Tests projects and add + them to `backend/AdminBackend.slnx`. +2. Add a separate PersistenceTests project when the service owns tenant-scoped + EF entities/query filters or another persistence mechanism whose security + behavior cannot be proven by Domain/Application unit tests. +3. Preserve inward references: Domain has no project dependency; Application + references Domain and the framework-agnostic shared kernel; Infrastructure + implements Application ports; Api composes Application/Infrastructure and + may reference the ASP.NET Core shared package; Tests reference only the + layers their boundary needs. +4. Use central package management. Add a version once to + `backend/Directory.Packages.props`; project files contain versionless + `PackageReference` entries. Never run an unreviewed latest-version upgrade as + part of scaffolding. + +## Application and domain + +- Follow `agenza-backend-use-case` for the first vertical slice. +- Use rich entities with `DomainResult`, handlers returning `Result`, and + `PersistenceResult` at technical persistence boundaries. Expected business + outcomes do not throw. +- Define a service-local UnitOfWork shape that matches its real transaction + boundary; do not copy another service's interface blindly. +- Register handlers and validators through the service's assembly-scanning + application extension rather than one registration per slice. + +## Tenant safety and persistence + +- Resource services use `Admin.Identity.Client`, an authorization filter, and + `TenantHeaderFilter` by default. `[IgnoreTenant]` is only for a reviewed, + genuinely tenant-free action. +- Tenant-owned aggregates inherit the service-local `TenantOwnedEntity` shape. + The save interceptor assigns the current tenant; handlers do not set or accept + arbitrary tenant ids. +- Apply shared auditable/tenant conventions from `DbContext.OnModelCreating`. + Do not add hand-written query filters or capture a tenant constant during + model construction. +- Use one schema and migrations-history table owned by the service. Any schema + change also uses `agenza-migration-safety`. +- Add persistence tests proving automatic tenant assignment and cross-tenant + query isolation. Manual smoke testing complements these tests; it does not + replace them. + +## API, runtime, and delivery + +- Add API versioning to business routes; do not version fixed OIDC protocol + endpoints. +- Register the service's audience/scope in identity-service and exercise both + allowed and denied access where the runtime smoke boundary applies. +- Add the project and database/resource dependencies to + `backend/AppHost/AppHost.cs`. Aspire remains the only local orchestrator; do + not add Docker Compose or application Dockerfiles. +- Add the service to `docs/MONOREPO.md` and its context to `docs/VISION.md`. +- Run the backend, governance, and any affected API-contract gates before + completion. + diff --git a/.agents/skills/agenza-backend-use-case/SKILL.md b/.agents/skills/agenza-backend-use-case/SKILL.md index 0a102ea..78ca096 100644 --- a/.agents/skills/agenza-backend-use-case/SKILL.md +++ b/.agents/skills/agenza-backend-use-case/SKILL.md @@ -1,823 +1,92 @@ --- name: agenza-backend-use-case description: > - Use whenever adding or changing business logic in any .NET backend service - under backend/ — a new command, query, entity, value object, repository - method, or endpoint, or any change to an existing one. Trigger on "add - endpoint", "implement [operation]", "create [entity]", "command", "query", - "handler", "validator", "vertical slice". Encodes this repo's CQRS/ - vertical-slice/Result-pattern conventions (docs/adr/0005, docs/adr/0012, - docs/adr/0014), layering, rich-domain, tenant-scoping, and testing rules. - Do NOT write backend business logic without reading it first — it also - documents patterns this codebase already tried and reverted, so an agent - that skips it is likely to reintroduce a fixed bug. + Use whenever adding or changing business logic in a .NET service under + backend/, including commands, queries, entities, value objects, repository + methods, endpoints, validators, or vertical slices. Trigger on "add + endpoint", "implement operation", "create entity", "command", "query", + "handler", or "validator". Enforces this repository's CQRS, Result flow, + rich-domain, tenant-safety, persistence, and test conventions and prevents + reintroducing the exception- and validator-based patterns reverted by ADRs + 0012 and 0014. --- -# Backend Use Case - -The reference implementation is `services-service`'s Tags vertical — open -these files and mirror their shape exactly (the templates below are a -direct copy of this feature's current, ADR-0014-compliant code): - -- `ServicesService.Domain/Entities/Tag.cs`, `ValueObjects/TagColor.cs` — entity/VO with invariants -- `ServicesService.Application/Tags/CreateTag/` — full command slice -- `ServicesService.Application/Tags/UpdateTag/` — same, plus `UpdateTagCommandExtensions.ApplyTo` -- `ServicesService.Application/Tags/TagPersistenceErrorMapper.cs` — persistence-conflict mapping -- `ServicesService.Application/Tags/TagResponse.cs` — DTO shared across the feature's operations -- `ServicesService.Application/Abstractions/` — ports (`ITagRepository`, `IUnitOfWork`) -- `ServicesService.Api/Controllers/TagsController.cs` — direct command binding + Result → HTTP mapping (docs/adr/0007) -- `ServicesService.Tests/Tags/CreateTag/` — handler + validator unit tests - -identity-service's `Tenants/ProvisionTenant/` slice is the second -reference — read it when the operation needs a database transaction -across more than one abstraction (see the UnitOfWork note below). - -## Decision tree — where does a given rule live? - -| The rule is about... | It lives in... | -| ---------------------------------------------------------- | ------------------------------------------ | -| Shape of the command's own data (required, length, format, numeric range, cross-field comparison within the same command) | **FluentValidation** validator, sync rules only | -| Current state of the application (existence, uniqueness, in-use, another aggregate) | The **handler** — a plain `if (...) return Result.Failure(...)` before persisting | -| A permanent invariant of the entity itself (a `Tag` can never have an empty name, a `Service`'s min duration can never exceed its max) | **`DomainResult`** from the entity's `Create`/`Update` | -| Data integrity / concurrency at the database boundary (a unique index catching a race the pre-check missed) | The database + **`PersistenceResult`**, mapped by a per-entity `*PersistenceErrorMapper` | -| A genuinely unexpected, unrecoverable technical failure (missing config, an unrecognized DB error, a framework guarantee) | **Exception** — the one case where throwing is still correct | - -## Hard prohibitions (these are reverted patterns — see docs/adr/0012, docs/adr/0014) - -Do **not** write any of the following. `scripts/architecture_guard.py` -fails the build on several of these; the rest are still real regressions -even where the guard can't catch them syntactically. - -- A repository (or any port) injected into a validator's constructor. -- `MustAsync`/`CustomAsync` on a FluentValidation rule that queries a - repository or the database. Validators in this repo are pure, synchronous - shape checks — nothing in them ever awaits. -- Throwing for an expected business outcome (validation failure, not-found, - conflict/duplicate, in-use, forbidden). Everything expected returns a - `Result`/`DomainResult`/`PersistenceResult`. -- Conventional `try/catch` in a handler to convert a business outcome. The - only handler-level `try/catch` in this codebase is - `IUnitOfWork.ExecuteInTransactionAsync`'s rollback-on-unexpected-failure - wrapper (identity-service) — never a catch that maps to a `Result`. -- `DuplicateEntityException` (deleted, docs/adr/0014 — a unique-constraint - race returns `PersistenceResult.Failure` instead). -- `BusinessExceptionHandler` (deleted — `Admin.SharedKernel.GenericExceptionHandler` - is the only exception handler; it exists purely for unexpected 500s). -- A null-forgiving `!` on a repository lookup that assumes some earlier - validator step already guaranteed existence. Validators here never do - existence checks (they take no repository dependency at all) — the - handler that needs the entity fetches it itself and returns - `Error.NotFound(...)` on a null, in the same method, before doing - anything else with it. -- A brand-new project/folder split for a feature that fits inside an - existing service's `Application///` shape. Only - create a new microservice for a genuinely new bounded context — see - `.skills/backend-new-microservice/SKILL.md`. -- Wiring MediatR, or any DI registration for a handler/validator by hand — - `AddXApplication()` assembly-scans for both; a new slice needs no - registration at all. - -## Build order (TDD — test first at each step) - -### 1. Domain entity or value object - -- If the entity does NOT belong to a tenant (rare — e.g. `Tenant` itself - in identity-service), inherit `{Service}.Domain.Common.BaseEntity` - directly — gives `Id`, `CreatedAt`/`CreatedBy`, `UpdatedAt`/ - `UpdatedBy`, `DeletedAt`/`DeletedBy`, `IsDeleted` for free - (docs/adr/0006). Call `base(id)` from your constructor; never set the - audit fields yourself, the EF interceptor does that. -- If the entity belongs to a tenant (the common case), inherit - `{Service}.Domain.Common.TenantOwnedEntity` instead — it already - inherits `BaseEntity` and implements `ITenantOwned` (`Guid TenantId - { get; }` + `void AssignTenant(Guid tenantId)`) for you, so don't - implement `ITenantOwned` or add an `AssignTenant` override on the - entity itself. The constructor never takes a `tenantId` parameter at - all — `TenantId` starts `Guid.Empty` and only `AssignTenant` (inherited) - can set it, throwing a plain `InvalidOperationException` on empty - (docs/adr/0009, docs/adr/0014) — the one entity-level path allowed to - throw instead of returning `DomainResult`, since it's only reachable via - an internal bug (`TenantHeaderFilter` already rejects a request with no/ - mismatched tenant before any handler runs). -- Public constructor becomes `private`; add a `public static - DomainResult Create(...)` factory that validates every - invariant and returns `DomainResult.Failure(new - DomainError("Widget.Invalid", message))` on the first invalid field - instead of throwing — never a raw `Exception`/`ArgumentException` - (docs/adr/0014). `DomainResult`/`DomainResult`/`DomainError` - (`{Service}.Domain/Common/`) already exist per service — copy them - once, not per entity. -- State-changing methods (`Update`, `Cancel`, `Reschedule`) return - `DomainResult` (not `void`) for the same reason — validate every new - value into a local before assigning any field, so a failure never - leaves the entity partially mutated. -- No public setters. Add a `private` parameterless constructor ONLY if EF - needs it, and keep it private. -- Tests: plain xUnit + AwesomeAssertions, no mocks needed — Domain has - zero dependencies. Cover `MarkCreated`/`MarkUpdated`/`MarkDeleted` - (inherited from `BaseEntity`) too — they count toward the coverage - gate. `AssignTenant` (if `ITenantOwned`) is the one exception to the - `DomainResult` rule — assert it throws `InvalidOperationException` on - an empty guid. - -### 2. Port (interface) in `Application/Abstractions/` - -- Narrow, intention-revealing methods (`Add`, `GetByIdAsync`, - `NameExistsAsync`) — not a generic interface. `Add`/`Remove` are - synchronous and only stage the change (no internal commit). -- If the entity is `ITenantOwned`, its methods do NOT take a tenant id - parameter — the DbContext scopes the query automatically (step 5, - docs/adr/0006). -- The **implementation** (step 5) extends - `Admin.SharedKernel.EntityFrameworkCore.RepositoryBase` for - the Add/Remove/Find/List boilerplate underneath this interface — the - port itself stays a plain, narrow interface. - -### 3. Command or query slice in `Application///` - -``` -Application/Tags/ -├── TagResponse.cs shared DTO (feature root) -├── TagPersistenceErrorMapper.cs shared persistence-conflict mapper (feature root) -└── CreateTag/ - ├── CreateTagCommand.cs : ICommand - ├── CreateTagCommandValidator.cs AbstractValidator - shape only, parameterless - └── CreateTagCommandHandler.cs : ICommandHandler -``` - -- A **command** mutates (`ICommand` if nothing to return, - `ICommand` otherwise); a **query** reads - (`IQuery`). Handler returns `Result` / `Result` - — never throws for an expected business outcome. Use - `Error.Validation/.NotFound/.Conflict/.Forbidden(code, message)`. -- Validator: **shape rules only**, parameterless constructor, no - repository, no `MustAsync`/`CustomAsync` — see the prohibitions above. -- Cross-aggregate rules needing a repository round-trip (existence, - uniqueness, in-use) live in the **handler**, checked in this order - before any mutation: not-found → duplicate/conflict → build/apply the - domain change → persist → map a persistence conflict. See - `CreateTagCommandHandler`/`UpdateTagCommandHandler`/`DeleteTagCommandHandler` - below for the exact shape, and `Application/Services/ServiceRelationshipLoader.cs` - for a multi-dependency version that loads Category/Tags exactly once - and reuses the same instances for both construction and the response. -- Constructor-injected ports only — no EF, no HttpClient, no ASP.NET - types in Application. -- Multiple writes that must succeed together → wrap in `IUnitOfWork`, - shaped to the real need (docs/adr/0005): a single - `Task> SaveChangesAsync(...)` if everything goes - through one `DbContext` (services-service's shape — lets Infrastructure - report a recognized unique-constraint violation without throwing), or a - Result-aware `ExecuteInTransactionAsync` if the operation spans - more than one abstraction that each commit independently, e.g. an EF - repository AND `UserManager` (identity-service's shape). -- Nothing to register by hand — each service's - `Application/DependencyInjection.cs` scans the assembly for handlers - and validators. -- If the handler constructs or mutates a domain entity from the - command's fields, put that mapping in a `{Operation}CommandExtensions.cs` - extension method beside the command (`ToModel(...)` for construction, - `ApplyTo(entity)` for mutation) instead of inlining it in `Handle(...)` - (docs/adr/0007). Both return `DomainResult`/`DomainResult` - respectively, so the handler checks `IsFailure` and maps via - `.Error.ToApplicationError()` before proceeding. - -### 4. Unit tests with NSubstitute - -- `Substitute.For()` per port used by the handler — no hand-written - fake classes (docs/adr/0006). Configure return values with - `.Returns(...)`; assert interaction with `.Received(1).Method(...)` / - `.DidNotReceive().Method(...)`. -- AwesomeAssertions, asserting on the `Result`: `result.IsSuccess`, - `result.Value.Xyz`, `result.Error.Type.Should().Be(ErrorType.Conflict)`. -- Test the happy path, the not-found path, the duplicate/conflict path, - and any `DomainResult.Failure` path the handler can still hit — a - handler unit test calls `Handle(...)` directly, bypassing the - validator, so it exercises paths production traffic never reaches. -- Validator tests use the synchronous `Validate(...)` (no `MustAsync` - rules exist to require `ValidateAsync`) and need no repository fakes at - all, since the validator takes none. - -### 5. Infrastructure adapter - -- Repository extends `Admin.SharedKernel.EntityFrameworkCore.RepositoryBase` - and implements the port (docs/adr/0006). `Add`/`Remove` only stage the - change — no `SaveChangesAsync` inside the repository (the handler - commits via `IUnitOfWork`). -- EF configuration lives in `Infrastructure/Persistence/Configurations/`. - The soft-delete query filter and `DeletedAt` index apply automatically - to every `BaseEntity`, and (if `ITenantOwned`) the tenant filter + - `TenantId` index too — the `DbContext` calls - `ApplyAuditableConventions(this, typeof(BaseEntity), typeof(ITenantOwned))` - once. Never add `HasQueryFilter` by hand. If the entity has a - uniqueness rule, add a unique index on a normalized column (see - `IX_Tags_TenantId_NameNormalized`) filtered with - `.HasFilter("\"DeletedAt\" IS NULL")` so a soft-deleted row doesn't - block reusing its unique value — this index, not the handler's - pre-check, is what actually guarantees uniqueness under concurrency - (see `agent-skills/agenza-migration-safety` for the migration itself). -- If the entity is tenant-owned, also pass `ICurrentTenantProvider` into - `AuditableEntitySaveChangesInterceptor`'s constructor so it can call - `AssignTenant` on a newly added entity automatically (docs/adr/0008). -- New tables → `dotnet ef migrations add ` from the Api project - directory. - -### 6. Controller (thin) - -- Constructor-inject `IDispatcher` (never a concrete handler type) — - nothing else. The global `TenantHeaderFilter` already rejected the - request with 403 before this action runs unless `X-Tenant-Id` matched - the token's `tenant_id` claim — mark the controller/action - `[IgnoreTenant]` instead if it genuinely isn't tenant-scoped. -- `[ApiVersion("1.0")]` + `[Route("api/v{version:apiVersion}/...")]` (or - `internal/v{version:apiVersion}/...` for M2M-only routes). -- **Bind the command/query directly as the action parameter — no local - `...Body` record** (docs/adr/0007). A route id binds into its own - `Guid id` parameter and gets merged into the command right before - dispatching: `command with { WidgetId = id }`. -- `await _dispatcher.Send(...)` / `.Query(...)` → - `result.ToActionResult(this, value => Ok(value))` (or `Created`/ - `NoContent`). No try/catch per exception type. -- `[Authorize]` by default; scope checks (`User.HasScope(...)`) for - M2M-only endpoints. - -### 7. Manual verification of the new endpoint - -There are no integration tests (docs/adr/0015) — CI runs unit tests only. -Before merging, run the service (`dotnet run --project services//{Service}.Api`) -and manually exercise the new endpoint: unauthenticated → 401, wrong -scope/tenant → 403, a validation failure → 400, duplicate name → 409, -unknown id → 404, happy path → expected status + persisted effect. - -## Definition of done - -```bash -dotnet build backend/AdminBackend.slnx -dotnet test backend/AdminBackend.slnx # unit tests only; coverage gate via Directory.Build.props/.targets -python scripts/architecture_guard.py # fails on any reverted pattern above -``` - -Both green, coverage gate passing, no new NU1903 (vulnerable package) -warnings, architecture guard clean. - ---- - -## Copy-paste templates - -A fictional **Widget** entity in a fictional **Widgets** feature — this is -a direct copy of Tags' current shape (see the reference files at the top), -renamed. Assume namespace root `{Service}` = your service's actual name. - -### Command with a response (Create-shaped) - -```csharp -// Application/Widgets/CreateWidget/CreateWidgetCommand.cs -using Admin.SharedKernel; - -namespace {Service}.Application.Widgets.CreateWidget; - -public sealed record CreateWidgetCommand(string Name) : ICommand; -``` - -```csharp -// Application/Widgets/CreateWidget/CreateWidgetCommandValidator.cs -using FluentValidation; -using {Service}.Domain.Entities; - -namespace {Service}.Application.Widgets.CreateWidget; - -public sealed class CreateWidgetCommandValidator : AbstractValidator -{ - public CreateWidgetCommandValidator() - { - RuleFor(command => command.Name) - .NotEmpty() - .MaximumLength(Widget.NameMaxLength); - } -} -``` - -```csharp -// Domain/Entities/Widget.cs -using {Service}.Domain.Common; - -namespace {Service}.Domain.Entities; - -public class Widget : TenantOwnedEntity -{ - public const int NameMaxLength = 80; - - public string Name { get; private set; } - - private Widget() - { - Name = string.Empty; // EF Core materialization only. - } - - private Widget(Guid id, string name) - : base(id) - { - Name = name; - } - - public static DomainResult Create(Guid id, string name) - { - var nameResult = ValidateName(name); - if (nameResult.IsFailure) - { - return DomainResult.Failure(nameResult.Error); - } - - return DomainResult.Success(new Widget(id, nameResult.Value)); - } - - public DomainResult Update(string name) - { - var nameResult = ValidateName(name); - if (nameResult.IsFailure) - { - return DomainResult.Failure(nameResult.Error); - } - - Name = nameResult.Value; - - return DomainResult.Success(); - } - - private static DomainResult ValidateName(string name) - { - var trimmed = name?.Trim() ?? string.Empty; - - if (trimmed.Length is 0 or > NameMaxLength) - { - return DomainResult.Failure(new DomainError( - "Widget.Invalid", - $"Name is required and must be at most {NameMaxLength} characters.")); - } - - return DomainResult.Success(trimmed); - } -} -``` - -```csharp -// Application/Widgets/CreateWidget/CreateWidgetCommandExtensions.cs -using {Service}.Domain.Common; -using {Service}.Domain.Entities; - -namespace {Service}.Application.Widgets.CreateWidget; - -public static class CreateWidgetCommandExtensions -{ - public static DomainResult ToModel(this CreateWidgetCommand command) => - Widget.Create(Guid.CreateVersion7(), command.Name); -} -``` - -```csharp -// Application/Widgets/WidgetPersistenceErrorMapper.cs -using Admin.SharedKernel; -using Microsoft.Extensions.Logging; -using {Service}.Application.Abstractions; - -namespace {Service}.Application.Widgets; - -public static class WidgetPersistenceErrorMapper -{ - private const string NameConstraint = "IX_Widgets_TenantId_NameNormalized"; - - public static Error Map(PersistenceError error, string name, ILogger logger) - { - if (error.ConstraintName == NameConstraint) - { - return Error.Conflict("Widget.DuplicateName", $"A widget named '{name}' already exists."); - } - - logger.LogError( - "Unrecognized unique constraint {ConstraintName} violated while saving a Widget", - error.ConstraintName); - return Error.Conflict("Widget.DuplicateConflict", "Could not save the widget due to a data conflict."); - } -} -``` - -```csharp -// Application/Widgets/CreateWidget/CreateWidgetCommandHandler.cs -using Admin.SharedKernel; -using Microsoft.Extensions.Logging; -using {Service}.Application.Abstractions; - -namespace {Service}.Application.Widgets.CreateWidget; - -public sealed class CreateWidgetCommandHandler : ICommandHandler -{ - private readonly IWidgetRepository _repository; - private readonly IUnitOfWork _unitOfWork; - private readonly ILogger _logger; - - public CreateWidgetCommandHandler( - IWidgetRepository repository, IUnitOfWork unitOfWork, ILogger logger) - { - _repository = repository; - _unitOfWork = unitOfWork; - _logger = logger; - } - - public async Task> Handle(CreateWidgetCommand command, CancellationToken cancellationToken) - { - if (await _repository.NameExistsAsync(command.Name, excludeId: null, cancellationToken)) - { - return Result.Failure( - Error.Conflict("Widget.DuplicateName", $"A widget named '{command.Name}' already exists.")); - } - - var widgetResult = command.ToModel(); - if (widgetResult.IsFailure) - { - return Result.Failure(widgetResult.Error.ToApplicationError()); - } - - var widget = widgetResult.Value; - _repository.Add(widget); - - var saveResult = await _unitOfWork.SaveChangesAsync(cancellationToken); - if (saveResult.IsFailure) - { - return Result.Failure(WidgetPersistenceErrorMapper.Map(saveResult.Error, command.Name, _logger)); - } - - return WidgetResponse.FromWidget(widget); - } -} -``` - -No `ICurrentTenantProvider` needed in this handler at all — the tenant -is assigned automatically on save (docs/adr/0008). Only the `DbContext` -(query scoping) and `AuditableEntitySaveChangesInterceptor` (assignment) -need it; see step 5. - -### Command with a response and a route id (Update-shaped) - -```csharp -// Application/Widgets/UpdateWidget/UpdateWidgetCommand.cs -using Admin.SharedKernel; - -namespace {Service}.Application.Widgets.UpdateWidget; - -public sealed record UpdateWidgetCommand(Guid WidgetId, string Name) : ICommand; -``` - -```csharp -// Application/Widgets/UpdateWidget/UpdateWidgetCommandValidator.cs -using FluentValidation; -using {Service}.Domain.Entities; - -namespace {Service}.Application.Widgets.UpdateWidget; - -public sealed class UpdateWidgetCommandValidator : AbstractValidator -{ - public UpdateWidgetCommandValidator() - { - RuleFor(command => command.WidgetId).NotEmpty(); - - RuleFor(command => command.Name) - .NotEmpty() - .MaximumLength(Widget.NameMaxLength); - } -} -``` - -Cross-aggregate rules (existence, uniqueness) never live in the validator — -that's the handler's job below. `WidgetId` is still shape-validated even -though it's route-sourced: the controller merges the route id in via -`with` BEFORE dispatching (see the Controller template below). - -```csharp -// Application/Widgets/UpdateWidget/UpdateWidgetCommandExtensions.cs -using {Service}.Domain.Common; -using {Service}.Domain.Entities; - -namespace {Service}.Application.Widgets.UpdateWidget; - -public static class UpdateWidgetCommandExtensions -{ - public static DomainResult ApplyTo(this UpdateWidgetCommand command, Widget widget) => - widget.Update(command.Name); -} -``` - -```csharp -// Application/Widgets/UpdateWidget/UpdateWidgetCommandHandler.cs -using Admin.SharedKernel; -using Microsoft.Extensions.Logging; -using {Service}.Application.Abstractions; - -namespace {Service}.Application.Widgets.UpdateWidget; - -public sealed class UpdateWidgetCommandHandler : ICommandHandler -{ - private readonly IWidgetRepository _repository; - private readonly IUnitOfWork _unitOfWork; - private readonly ILogger _logger; - - public UpdateWidgetCommandHandler( - IWidgetRepository repository, IUnitOfWork unitOfWork, ILogger logger) - { - _repository = repository; - _unitOfWork = unitOfWork; - _logger = logger; - } - - public async Task> Handle(UpdateWidgetCommand command, CancellationToken cancellationToken) - { - var widget = await _repository.GetByIdAsync(command.WidgetId, cancellationToken); - if (widget is null) - { - return Result.Failure( - Error.NotFound("Widget.NotFound", $"Widget '{command.WidgetId}' was not found.")); - } - - if (await _repository.NameExistsAsync(command.Name, command.WidgetId, cancellationToken)) - { - return Result.Failure( - Error.Conflict("Widget.DuplicateName", $"A widget named '{command.Name}' already exists.")); - } - - var applyResult = command.ApplyTo(widget); - if (applyResult.IsFailure) - { - return Result.Failure(applyResult.Error.ToApplicationError()); - } - - var saveResult = await _unitOfWork.SaveChangesAsync(cancellationToken); - if (saveResult.IsFailure) - { - return Result.Failure(WidgetPersistenceErrorMapper.Map(saveResult.Error, command.Name, _logger)); - } - - return WidgetResponse.FromWidget(widget); - } -} -``` - -### Command with no response (Delete-shaped) - -```csharp -// Application/Widgets/DeleteWidget/DeleteWidgetCommand.cs -using Admin.SharedKernel; - -namespace {Service}.Application.Widgets.DeleteWidget; - -public sealed record DeleteWidgetCommand(Guid WidgetId) : ICommand; -``` - -```csharp -// Application/Widgets/DeleteWidget/DeleteWidgetCommandHandler.cs -using Admin.SharedKernel; -using Microsoft.Extensions.Logging; -using {Service}.Application.Abstractions; - -namespace {Service}.Application.Widgets.DeleteWidget; - -public sealed class DeleteWidgetCommandHandler : ICommandHandler -{ - private readonly IWidgetRepository _repository; - private readonly IUnitOfWork _unitOfWork; - private readonly ILogger _logger; - - public DeleteWidgetCommandHandler( - IWidgetRepository repository, IUnitOfWork unitOfWork, ILogger logger) - { - _repository = repository; - _unitOfWork = unitOfWork; - _logger = logger; - } - - public async Task Handle(DeleteWidgetCommand command, CancellationToken cancellationToken) - { - var widget = await _repository.GetByIdAsync(command.WidgetId, cancellationToken); - if (widget is null) - { - return Result.Failure(Error.NotFound("Widget.NotFound", $"Widget '{command.WidgetId}' was not found.")); - } - - _repository.Remove(widget); - - var saveResult = await _unitOfWork.SaveChangesAsync(cancellationToken); - if (saveResult.IsFailure) - { - return Result.Failure(WidgetPersistenceErrorMapper.Map(saveResult.Error, widget.Name, _logger)); - } - - return Result.Success(); - } -} -``` - -### Query (List/Get-shaped) - -```csharp -// Application/Widgets/ListWidgets/ListWidgetsQuery.cs -using Admin.SharedKernel; - -namespace {Service}.Application.Widgets.ListWidgets; - -public sealed record ListWidgetsQuery : IQuery>; -``` - -```csharp -// Application/Widgets/ListWidgets/ListWidgetsQueryHandler.cs -using Admin.SharedKernel; -using {Service}.Application.Abstractions; -using {Service}.Application.Widgets; - -namespace {Service}.Application.Widgets.ListWidgets; - -public sealed class ListWidgetsQueryHandler : IQueryHandler> -{ - private readonly IWidgetRepository _repository; - - public ListWidgetsQueryHandler(IWidgetRepository repository) - { - _repository = repository; - } - - public async Task>> Handle( - ListWidgetsQuery query, CancellationToken cancellationToken) - { - var widgets = await _repository.ListAsync(cancellationToken); - IReadOnlyList response = widgets.Select(WidgetResponse.FromWidget).ToList(); - return Result.Success(response); - } -} -``` - -No validator needed unless the query takes user input. - -### Shared feature DTO (once per feature, not per operation) - -```csharp -// Application/Widgets/WidgetResponse.cs -using {Service}.Domain.Entities; - -namespace {Service}.Application.Widgets; - -public sealed record WidgetResponse(Guid Id, string Name) -{ - public static WidgetResponse FromWidget(Widget widget) => new(widget.Id, widget.Name); -} -``` - -### Controller (dispatch + Result → HTTP) - -```csharp -using Admin.SharedKernel; -using Asp.Versioning; -using Microsoft.AspNetCore.Mvc; -using {Service}.Application.Widgets.CreateWidget; -using {Service}.Application.Widgets.DeleteWidget; -using {Service}.Application.Widgets.ListWidgets; -using {Service}.Application.Widgets.UpdateWidget; - -namespace {Service}.Api.Controllers; - -[ApiController] -[ApiVersion("1.0")] -[Route("api/v{version:apiVersion}/widgets")] -public class WidgetsController : ControllerBase -{ - private readonly IDispatcher _dispatcher; - - public WidgetsController(IDispatcher dispatcher) - { - _dispatcher = dispatcher; - } - - [HttpGet] - public async Task List(CancellationToken cancellationToken) - { - var result = await _dispatcher.Query(new ListWidgetsQuery(), cancellationToken); - return result.ToActionResult(this, widgets => Ok(widgets)); - } - - [HttpPost] - public async Task Create(CreateWidgetCommand command, CancellationToken cancellationToken) - { - var result = await _dispatcher.Send(command, cancellationToken); - return result.ToActionResult(this, widget => Created($"/api/v1/widgets/{widget.Id}", widget)); - } - - [HttpPut("{id:guid}")] - public async Task Update(Guid id, UpdateWidgetCommand command, CancellationToken cancellationToken) - { - var result = await _dispatcher.Send(command with { WidgetId = id }, cancellationToken); - return result.ToActionResult(this, widget => Ok(widget)); - } - - [HttpDelete("{id:guid}")] - public async Task Delete(Guid id, CancellationToken cancellationToken) - { - var result = await _dispatcher.Send(new DeleteWidgetCommand(id), cancellationToken); - return result.ToActionResult(this, NoContent); - } -} -``` - -### Unit tests with NSubstitute (handler + validator) - -```csharp -// Tests/Widgets/CreateWidget/CreateWidgetCommandHandlerTests.cs -using Admin.SharedKernel; -using Microsoft.Extensions.Logging; -using {Service}.Application.Abstractions; -using {Service}.Application.Widgets.CreateWidget; -using {Service}.Domain.Entities; - -namespace {Service}.Tests.Widgets.CreateWidget; - -public class CreateWidgetCommandHandlerTests -{ - private readonly IWidgetRepository _repository = Substitute.For(); - private readonly IUnitOfWork _unitOfWork = Substitute.For(); - private readonly ILogger _logger = Substitute.For>(); - private readonly CreateWidgetCommandHandler _handler; - - public CreateWidgetCommandHandlerTests() - { - _repository.NameExistsAsync(Arg.Any(), Arg.Any(), Arg.Any()).Returns(false); - _unitOfWork.SaveChangesAsync(Arg.Any()).Returns(PersistenceResult.Success(1)); - _handler = new CreateWidgetCommandHandler(_repository, _unitOfWork, _logger); - } - - [Fact] - public async Task Handle_WithValidCommand_PersistsAndReturnsTheValue() - { - var result = await _handler.Handle(new CreateWidgetCommand("Example"), CancellationToken.None); - - result.IsSuccess.Should().BeTrue(); - result.Value.Name.Should().Be("Example"); - _repository.Received(1).Add(Arg.Is(w => w.Id == result.Value.Id)); - await _unitOfWork.Received(1).SaveChangesAsync(Arg.Any()); - } - - [Fact] - public async Task Handle_WithDuplicateName_ReturnsConflictWithoutPersisting() - { - _repository.NameExistsAsync("Example", null, Arg.Any()).Returns(true); - - var result = await _handler.Handle(new CreateWidgetCommand("Example"), CancellationToken.None); - - result.IsFailure.Should().BeTrue(); - result.Error.Code.Should().Be("Widget.DuplicateName"); - _repository.DidNotReceive().Add(Arg.Any()); - await _unitOfWork.DidNotReceive().SaveChangesAsync(Arg.Any()); - } - - [Fact] - public async Task Handle_WithInvalidName_ReturnsFailure() - { - var result = await _handler.Handle(new CreateWidgetCommand(""), CancellationToken.None); - - result.IsFailure.Should().BeTrue(); - result.Error.Code.Should().Be("Widget.Invalid"); - } -} -``` - -```csharp -// Tests/Widgets/CreateWidget/CreateWidgetCommandValidatorTests.cs -using {Service}.Application.Widgets.CreateWidget; - -namespace {Service}.Tests.Widgets.CreateWidget; - -public class CreateWidgetCommandValidatorTests -{ - private readonly CreateWidgetCommandValidator _validator = new(); - - [Fact] - public void Validate_WithValidCommand_Passes() - { - _validator.Validate(new CreateWidgetCommand("Example")).IsValid.Should().BeTrue(); - } - - [Fact] - public void Validate_WithEmptyName_Fails() - { - _validator.Validate(new CreateWidgetCommand("")).IsValid.Should().BeFalse(); - } -} -``` - -No repository fake needed - the validator takes no dependencies. -Duplicate-name coverage lives in `CreateWidgetCommandHandlerTests` instead. - -### Automatic tenant assignment has no automated regression test - -`{Service}.Tests` references only Domain + Application (mocked ports, no -EF Core) — deliberately, to keep the unit-test tier free of Infrastructure/ -EF dependencies (docs/adr/0015). This means the -`AuditableEntitySaveChangesInterceptor` behavior docs/adr/0008 depends on — -a newly added entity with `TenantId == Guid.Empty` gets the current tenant -assigned on save — has no automated coverage. The first time a service -gets a tenant-owned entity, manually verify this by running the service -and creating a record through its API, confirming the persisted row's -`TenantId` matches the caller's tenant. +# Backend use case + +Before writing code, inspect the closest production slice and its tests. Use +compiled code, the current solution, and generated contracts as templates; +never copy a full implementation from prose. Read `backend/AGENTS.md` and only +the ADRs routed by `docs/adr/README.md` for the affected concern. + +## Put each rule in one layer + +| Rule | Owner | +| --- | --- | +| Request shape, required fields, format, range, cross-field input comparison | Synchronous FluentValidation validator | +| Existence, uniqueness pre-check, in-use state, another aggregate | Handler | +| Permanent entity/value-object invariant | Domain factory or mutation returning `DomainResult` | +| Race-safe uniqueness and relational integrity | Database constraint plus `PersistenceResult` mapping | +| Unexpected or unrecoverable technical failure | Exception | + +Expected validation, not-found, conflict, in-use, and authorization outcomes +never throw. Do not inject repositories into validators, use repository-backed +`MustAsync`/`CustomAsync`, add business-exception types or handlers, catch an +expected outcome in a handler, or use `!` after a lookup that can be absent. + +## Build the smallest vertical slice + +1. **Domain.** Tenant-owned entities inherit the service's + `TenantOwnedEntity`; tenant-free entities inherit `BaseEntity`. Keep setters + private. Factories and state changes validate invariants before mutation and + return `DomainResult`; audit fields and tenant assignment remain framework + responsibilities. +2. **Port.** Add a narrow intent-revealing interface under + `Application/Abstractions`. Repository methods do not accept a tenant id; + the live `DbContext` applies tenant filtering. `Add` and `Remove` stage work + and do not commit internally. +3. **Slice.** Put the command/query, synchronous validator, handler, and any + operation-specific mapping under `Application///`. + Application depends only on ports and domain types. Use an operation mapping + extension when command-to-domain construction or mutation would otherwise + obscure the handler. +4. **Handler.** Check not-found and conflicts before mutation, map domain + failures explicitly, stage persistence, commit through the service's + `IUnitOfWork`, and map recognized persistence conflicts to application + errors. Use a transaction only when multiple writes must succeed together. +5. **Infrastructure.** Implement the port with the shared repository base and + auditable conventions. Do not add tenant or soft-delete query filters by + hand. Add tenant-scoped indexes and foreign keys where the business rule + requires them. Any schema change also triggers + `.agents/skills/agenza-migration-safety`. +6. **API.** Keep controllers thin: authorize by default, bind the command or + query directly, merge route ids immediately before dispatch, and use the + shared Result-to-HTTP mapper. `[IgnoreTenant]` requires a genuinely + tenant-free operation. Do not add local body DTOs that duplicate the + command or catch business exceptions. + +Create a new service only for a justified business context and follow +`.agents/skills/agenza-backend-new-service`; a new feature normally belongs in +an existing service. + +## Test the affected boundaries + +- Domain tests cover factories, mutations, invariant failures, audit behavior, + and tenant assignment programming guards without mocks. +- Handler tests use NSubstitute ports and cover success, not-found, conflict, + reachable domain failure, persistence failure, and required interactions. +- Validator tests call synchronous `Validate` and need no repository fake. +- Persistence behavior involving tenant assignment, global filters, indexes, + or foreign keys requires the narrow persistence-test tier established by the + current solution and ADR index. +- Controller, OpenAPI, authentication, or runtime-boundary changes require the + applicable contract and smoke checks documented in `docs/QUALITY.md` and CI. + +Do not infer the available test tiers from a historical ADR. Inspect the +solution, workflows, and existing test projects before deciding what applies. + +## Complete + +Run every backend and governance command in `backend/AGENTS.md` and root +`AGENTS.md`. Report the actual build, test, coverage, migration, contract, and +smoke results that apply; do not call the task done while a required gate is +red. diff --git a/.agents/skills/agenza-exception-flow-audit/SKILL.md b/.agents/skills/agenza-exception-flow-audit/SKILL.md index 0e35db4..e6aeff1 100644 --- a/.agents/skills/agenza-exception-flow-audit/SKILL.md +++ b/.agents/skills/agenza-exception-flow-audit/SKILL.md @@ -58,7 +58,7 @@ A table, one row per occurrence: | --- | --- | --- | --- | --- | --- | --- | For every row classified **Expected outcome**, describe the fix in terms of -`agent-skills/agenza-backend-use-case`'s decision tree (which layer's +`.agents/skills/agenza-backend-use-case`'s decision tree (which layer's `Result` type should carry this instead, and where the check belongs — validator vs. handler vs. persistence). @@ -72,4 +72,4 @@ validator vs. handler vs. persistence). explicitly-allowed pattern (docs/adr/0014), not a violation. - If a finding would change how an error is reported to a caller (e.g. changing an HTTP status code), that's a contract change — flag it for - `agent-skills/agenza-api-contract-review` too, don't fix it silently. + `.agents/skills/agenza-api-contract-review` too, don't fix it silently. diff --git a/.agents/skills/agenza-frontend-exploratory-qa/SKILL.md b/.agents/skills/agenza-frontend-exploratory-qa/SKILL.md new file mode 100644 index 0000000..336ff92 --- /dev/null +++ b/.agents/skills/agenza-frontend-exploratory-qa/SKILL.md @@ -0,0 +1,65 @@ +--- +name: agenza-frontend-exploratory-qa +description: > + Use to perform a review-only exploratory test of a frontend screen in a + browser, covering functional behavior, failure paths, usability, + accessibility, responsiveness, and user-visible security risks. Trigger on + "test this screen", "exploratory QA", "review accessibility", "teste esta + tela", or "faça um QA da interface". Produces an evidence-based pt-BR report + and never edits code or performs destructive real-world actions. +--- + +# Frontend exploratory QA + +## Establish safe scope + +1. Read the root and frontend `AGENTS.md`. For the admin frontend, also read + `../agenza-frontend-feature/references/page-ui-conventions.md`. +2. Identify the screen, intended user, expected primary flow, environment, test + data, and explicit restrictions from the request and repository evidence. +3. Confirm that any state-changing test is safe for the identified environment. + Never delete real data, send messages, make payments, change production + state, or perform an irreversible action without explicit authorization. +4. If the environment or impact cannot be established, continue with read-only + checks and report the blocked scenarios instead of assuming permission. + +## Explore + +- Map the visible controls, navigation, main path, loading, empty, success, and + error states before interacting. +- Exercise the primary flow and safe alternatives: cancel, close, back, retry, + refresh, duplicate submission, invalid input, boundary lengths, whitespace, + accents, special characters, and interrupted or slow responses when the + environment supports them. +- Check that expected failures preserve user input, explain recovery, and do not + expose stack traces, secrets, tenant data, or unauthorized actions. +- Use keyboard-only navigation. Verify logical focus order, visible focus, + accessible names, field-error association, dialog focus management, and + operation without color alone. +- Inspect desktop and 375 px mobile layouts, zoom to 200% when practical, and + check overflow, truncation, touch targets, overlays, tables, and virtual + keyboard obstruction. +- Compare the refreshed state with the displayed state to catch stale, + duplicated, or lost data. + +Do not call an assumption a defect. Reproduce a suspected defect twice when it +is safe, record the exact observed result, and distinguish confirmed bugs, +risks, usability problems, and suggestions. Capture screenshots or other +objective evidence when the available browser tooling supports it. + +## Report in pt-BR + +Lead with an approval recommendation: approve, approve with reservations, or do +not approve. Then report: + +1. Tested flows and untested scenarios with reasons. +2. Confirmed findings ordered by critical, high, medium, and low severity. +3. For each finding: category, reproduction steps, actual and expected result, + user/business impact, evidence, recommended fix, and verification criterion. +4. Accessibility, responsiveness, and UX observations that are not confirmed + functional bugs. +5. The five highest-priority follow-ups, balancing impact, frequency, and fix + effort. + +This skill is diagnostic. Do not edit code during the QA pass; implementation +requires a separate explicit request and the frontend feature skill. diff --git a/.agents/skills/agenza-frontend-feature/SKILL.md b/.agents/skills/agenza-frontend-feature/SKILL.md index 8c7f3cb..51d0a1f 100644 --- a/.agents/skills/agenza-frontend-feature/SKILL.md +++ b/.agents/skills/agenza-frontend-feature/SKILL.md @@ -1,667 +1,116 @@ --- name: agenza-frontend-feature description: > - Use whenever building or changing a feature in apps/admin-frontend — - React components, pages, hooks, forms, Zod schemas, use cases, or HTTP - calls. Trigger on "let's build [feature]", "implement [feature]", "add a - page/form/hook", or when the user provides an API spec for a resource. - Covers this project's feature-based Clean Architecture layering (ADR 009: - app/, features/{auth,catalog}/, shared/), React Hook Form + Zod forms, - structured server-error-to-field mapping, out-of-order-response and - inline-creation state handling, shadcn/ui usage, accessibility, dark - mode, mobile, comment policy, and pt-BR text rules. Do NOT proceed - without reading it — several conventions here differ from generic React - tutorials and from older, now-superseded guidance for this same project. + Use whenever changing React or TypeScript under apps/admin-frontend, + including pages, hooks, forms, domain models, repositories, generated + contracts, tests, auth, or shared UI. Routes the task to the minimum + required frontend references and enforces this repository's Result-based, + feature-oriented architecture. Read it before implementation because its + conventions intentionally differ from generic React tutorials. --- -# Frontend Feature - -## Physical layout (ADR 009) - -```text -src/ - app/ bootstrap, routing, DI wiring - main.tsx composition root: the only createAppContainer() call - App.tsx - routes/ router.tsx, RouteErrorElement - providers/ AppProviders, AppContainerContext, useAppContainer - composition/ container.ts - the only place allowed to construct - concrete repository/auth implementations - layouts/ AdminLayout - pages/ stub pages not yet promoted to their own feature - - features/ - auth/ - domain/ User, Tenant, Session, their errors - application/ AuthRepository port, 4 use cases, TenantContext - infrastructure/ OidcAuthRepository, createUserManager, oidc mapper - presentation/ AuthProvider, useAuth, TenantBoundary, ProtectedRoute, - LoginPage, CallbackPage - index.ts public API - everything outside this feature imports - through here, never a deep path into the above - - catalog/ Categories, Services - one feature, they - collaborate in the same business context. Tags - was removed from the frontend (docs/adr/016 in - this app's ADRs) - the backend Tag domain/API - is intentionally retained, unrelated to this - feature's current frontend shape - domain/ Category, Service entities + their errors - application/ 3 repository ports, 12 use cases - infrastructure/ Api*Repository, mappers, generated/ (OpenAPI types) - presentation/ every entity folder (categories/, services/) - shares the same internal shape - location alone - tells you a file's role: - / - Page.tsx composition shell (stays at entity root by - default; Categories uses pages/, ADR 012) - hooks/ data hook (useCategories/useServices) - + controller hook (useXPage) + any sub-hooks - (useServiceEditor, useServiceDeletion, ...) - components/ presentational pieces: tables, dialogs, - field-groups - forms/ the entity's create/edit form + its zod - schema + its own fieldMaps.ts (never shared - across entities - see "Forms" below) - models/ services/ only - pure, non-React view-model/ - formatting logic (servicePresentationModels, - serviceFormatters); categories has no - equivalent, so no models/ for it - index.ts public API - - shared/ - domain/ DomainError - the base class every entity error extends - application/ AppError, HttpClient port, SessionEventBus port, - RequestSession (atomic per-request session snapshot) - infrastructure/ - http/ AuthenticatedHttpClient, ApiError, ProblemDetails, - mapErrorToAppError, NetworkError, TimeoutError - InMemorySessionEventBus.ts - presentation/ - components/ PageHeader, StatusMessage, ErrorBoundary, - CollectionFeedback, DeleteConfirmationDialog, etc. - hooks/ useAsync, useDebouncedValue, useCreateInline, - useDialogTarget, useDeleteConfirmation - forms/ serverFormError.ts (mapApiErrorToForm) - providers/ ThemeProvider - - components/ui/ shadcn/ui primitives - stay at this top-level path, - lib/utils.ts NOT moved into shared/ (see below) -``` - -**`src/components/ui/**` and `src/lib/utils.ts` are exceptions to the -feature layout** — shadcn's CLI generates every `components/ui/*.tsx` file -importing `@/lib/utils` by a fixed convention; moving either would mean -hand-editing generated files just to accommodate the reorganization, which -this project's own rules prohibit (see "Build from existing components" -below). They stay exactly where `npx shadcn add` puts them. - -A feature vertical is a full slice inside its feature's four layers: - -```text -features//domain/ → plain TS class, no framework deps -features//application/ → repository interface (port) + use cases -features//infrastructure/ → implements the port via HttpClient -features//presentation/ → hooks built on useAsync, forms, pages -``` - -For translating an external API spec into the DTO/mapper/MSW-handler seam, -use `apps/admin-frontend/.skills/admin-api-contract/SKILL.md` alongside -this skill. For TypeScript-strict-mode test gotchas and mock-strategy-per- -layer rules, use `apps/admin-frontend/.skills/admin-tdd-conventions/SKILL.md`. -This skill governs everything between those two: architecture, forms, -state, UI, and completion criteria. - ---- - -## Pre-conditions before writing any code - -1. **Get the API spec** from the user before touching infrastructure. - Ask for: endpoint paths, HTTP methods, request shape, response shape, - error codes/shapes. Never invent field names — this is one of the - question-policy triggers in the root `AGENTS.md` (changes a contract). -2. **Check whether `HttpClient` exists** at - `src/shared/application/HttpClient.ts` (implemented by - `AuthenticatedHttpClient` in `src/shared/infrastructure/http/`). Every - REST repository depends on it; it already exists for every current - feature. -3. **Decide whether this is a new feature or belongs in an existing - one.** A resource that collaborates closely with Categories/Services - (shares forms, cross-references, or the same backend service) belongs - in `features/catalog/`; a genuinely independent domain gets its own - `features//` following the same four-layer shape. -4. **Identify which use cases the current page actually needs.** Don't - build every possible use case upfront. - -For authentication work, preserve the repo's fail-closed flow: - -- `/login` automatically starts the OIDC redirect once authentication state - is known; it is an informative transition/recovery screen, not a second - “Entrar” confirmation. Pass the current `light` or `dark` theme through - the OIDC authorization request so the identity credential page can apply - it before rendering. -- Map provider failures inside auth infrastructure to `AuthFlowError`. - Presentation shows a stable support code, a specific curated pt-BR - explanation, the next recovery action, and tells the user what context to - send when requesting help without exposing raw technical details or asking - them to share a password. A generic “contacte o administrador” fallback is - not sufficient for an authentication failure. -- A silent renewal may update tokens and expiry only. If `user.id` or - `tenant.id` differs from the cached session, clear the OIDC user and require - a full login before any request can use the new identity. - ---- - -## Comments — minimum of the minimum, by default zero - -Default to no comment. Identifiers, types, and structure carry the -meaning — a comment restating what a well-named function/prop/hook -already says is waste. Add a one-line comment (never a paragraph, never a -JSDoc block on a clearly named interface/hook/prop/entity) only when a -careful senior reviewer would still get it wrong without it: a security/ -tenant-isolation default, a concurrency/race guard, a genuine React/ -Radix/RHF/Zod/browser quirk, or an unavoidable lint suppression. -Architectural rationale belongs in `docs/adr/` — reference it in one -short clause at most (`see docs/adr/0006`), never restate it. If a -mechanism needs a paragraph to explain, simplify the mechanism/names/ -types first rather than documenting the complexity. This is the same bar -as `apps/admin-frontend/AGENTS.md` and `backend/AGENTS.md`. - ---- - -## Step-by-step build order - -### 1. Domain entity (TDD) - -`features//domain/entities/EntityName.ts` — zero imports from -React, that feature's own `application/`, `infrastructure/`, or -`presentation/`, and zero imports from another feature. Private -constructor + static `create(input)` factory that validates invariants -and returns `Result` -(`shared/application/Result.ts`) instead of throwing (docs/adr/014, -docs/adr/015 — both Catalog's `Category.ts` and Auth's -`Session.ts`/`User.ts`/`Tenant.ts` follow this). Every caller composes -with `flatMapResult`/`combineResults`, or plain early-return `Result` -branching for a short sequential chain with heterogeneous error types -(see `mapOidcUserToSession`, `features/auth/infrastructure/`) — never -`try/catch`. A mapper that turns a domain validation failure arising from -an untrusted API response into a curated `AppError` uses -`shared/infrastructure/http/malformedResponseError.ts`, not its own -message. `useAsync` (`shared/presentation/hooks/useAsync.ts`) takes -`() => Promise>`, not a throwing `() => Promise`. - -A test fixture that needs a known-valid entity (most test files touching -auth or catalog do) imports `Tenant`/`User`/`Session`/`Category` -from `src/test/fixtures/{authEntityFixtures,unwrapResult}.ts` instead of -the real `domain/entities/` path — those re-export the same `create()` -call shape already unwrapped, so call sites read exactly like before -without every test wrapping every call in `unwrapResult(...)`. Only each -entity's own `*.test.ts` imports the real class directly, since it -specifically asserts on both the success and failure `Result` shapes. - -Every feature vertical (Catalog now, Auth now, a future one like -Services) follows this same Result convention — there is no throwing -variant left to mirror. - -No constructor parameter property shorthand (`erasableSyntaxOnly`) — -explicit field declarations + assignment in the constructor body. Optional -fields: `if (value !== undefined) { this.field = value }`, never a direct -assignment of a possibly-`undefined` value (`exactOptionalPropertyTypes`). -`strict: true` — never `any`; if a value's shape is genuinely unknown at a -boundary, type it `unknown` and narrow it, never widen with `any`. - -### 2. Repository interface (no test needed) - -`features//application/repositories/FeatureRepository.ts` — -interface only. Every method takes `tenantContext: TenantContext` -(imported from `@/features/auth`, never from its internal path) as its -first parameter. Returns domain entities, never raw DTOs. `Promise` for nullable results. - -### 3. Use cases (TDD) - -`features//application/use-cases/FeatureName/UseCaseName.ts` — -one class per use case, explicit constructor body (no shorthand): - -```typescript -export class ListServices { - private readonly serviceRepository: ServiceRepository; - - constructor(serviceRepository: ServiceRepository) { - this.serviceRepository = serviceRepository; - } -} -``` - -Test with hand-written fake repositories (`.skills/admin-tdd-conventions`). -Add a shared fake to -`features//application/test-helpers/createFakeFeatureRepository.ts` -after the second use case needs it. - -### 4. Wire into the container - -Add to `AppContainer`'s facade interface and `createAppContainer()` in -`app/composition/container.ts` — the **only** place allowed to construct -concrete repository implementations. Import the concrete classes from the -feature's `index.ts` (`@/features/`), not a deep path — see -docs/adr/009's "Execution" section for why `index.ts` re-exports -composition-only wiring alongside the genuinely public surface. - -### 5. Infrastructure mapper (TDD) - -`features//infrastructure/mappers/featureMapper.ts` — pure -function `mapApiDtoToDomainEntity(dto: FeatureDto): Feature`. Test every -field mapping and every validation failure path. - -### 6. Infrastructure repository (TDD with MSW) - -`features//infrastructure/repositories/ApiFeatureRepository.ts` -— implements the port, takes `HttpClient` in its constructor (explicit -field pattern). Tests use MSW handlers in -`src/test/mocks/handlers/featureHandlers.ts`, registered in -`src/test/mocks/handlers/index.ts`. `onUnhandledRequest: 'error'` is -global — any call without a registered handler fails loudly. A test mock -handler typing a fixture against a feature's internal DTO type -(`import type { CategoryDto } from '@/features/catalog/infrastructure/ -mappers/categoryMapper'`) is the one place allowed to import a feature's -internals directly from outside it — `src/test/**` is exempt from the -public-API-only rule (ESLint + `architecture_guard.py` both carve this -out explicitly). - -### 7. Presentation hook (TDD) — build on `useAsync`, not a new pattern - -`shared/presentation/hooks/useAsync.ts` is the one shared "call an async -function, track loading/data/error" primitive — every feature hook -(`useCategories`, `useServices` — and `AuthProvider` for the -shared session) builds on it instead of a bespoke `useState`/`useEffect` -pair or a server-state library (see "Prohibited" below). It already -handles the two things that are easy to get wrong by hand: - -- **Out-of-order responses**: if a second `execute()` fires before the - first resolves (a fast filter change, page change, or tenant switch), - only the most recently started call's result is ever applied — pass - `resetKey` (e.g. the tenant id) so a genuine context switch clears - `data`/`error` synchronously instead of flashing stale data. -- **Unmounted-component writes**: guarded internally; you don't need your - own `isMounted` ref. - -For a mutation (create/update/delete on a feature's data hook), -**a create's success must not depend on the follow-up refetch succeeding**: -call `mutate(current => [...(current ?? []), created])` to insert the new -item into the hook's state immediately after the write succeeds, then -`void execute()` in the background to reconcile with the server. If that -background refetch fails, the optimistically-inserted item is still on -screen; surface the refetch's own `status`/`error` separately rather than -rolling back a successful create because of it. `update`/`delete` can -simply `await execute()` since there's no optimistic value to insert. - -Get `tenantContext` from `useAuth()` (`@/features/auth`) inside a -`ProtectedRoute` — treat it as possibly `null` in a hook (the page can -mount while `useAuth()` is still resolving), guard each method, and pass -the tenant id as `useAsync`'s `resetKey` so a tenant switch clears data -instead of leaking the previous tenant's rows onto screen even for one -frame (multi-tenancy — see root `AGENTS.md`). - -### 8. Page component - -Replace the stub. **`CategoriesListPage`/`CategoryEditorDialog` -(`features/catalog/presentation/categories/`) is the reference for -behavior and design** (search → table → dialog create/edit → -`AlertDialog` delete-confirm, loading/error/empty states) — **not for -anatomy**. Copy the _pattern_, not the file count: a feature with more -independent workflows legitimately needs more files than Categories does. -See "Componentization" below for when and how to split a page's -controller hook, form, and dialog. - -#### List = `Table`; form = `Dialog` by default - -A page listing records renders a `Table` (`src/components/ui/table.tsx`): -one row per record, actions (Edit/Delete) as buttons in the last column — -not stacked `Card`s. A create/edit form opens in a `Dialog` -(`src/components/ui/dialog.tsx`) over the list by default. One `Dialog` -instance switches between create/edit based on which record triggered it, -not a dialog per row. The form component stays dialog-agnostic. - -Categories maps `/categories/new` and `/categories/:id/edit` to the same -nested editor `Dialog` over the still-mounted `/categories` list -(docs/adr/012). `CategoryEditorDialog` renders one `CategoryForm` and -`useCategoryEditor` selects create or update from the route. In edit mode -`useCategoryEditor` fetches its own category directly via -`GET /api/v1/categories/{id}` — it does **not** read the list's data -through outlet context (docs/adr/013 superseded that shape; a -`useOutletContext()` cast has no runtime guarantee an ancestor route -actually supplied a value). `useCategoriesListPage` refetches the list -unconditionally whenever navigation returns from the editor route back to -the bare `/categories` route, whether the editor closed via cancel or a -successful save. Its smartphone table uses labelled icon actions with -larger touch targets and reveals action text from `sm` upward. - -A destructive action (delete) is confirmed with the shared -`DeleteConfirmationDialog` (`shared/presentation/components/`, built on -`AlertDialog`) — never `window.confirm`, and never a hand-rolled -`AlertDialog` per feature once `DeleteConfirmationDialog` already covers -the shape. Pair it with the shared `useDeleteConfirmation` -(`shared/presentation/hooks/`) for the target/progress/error state -machine behind it. - -#### Componentization — page shell, controller hook, promotion rule - -- A page component (`XPage.tsx`) is a **composition shell**: it renders - presentational components wired to a controller hook's view models, and - nothing else — no `useState`, no business logic, no direct repository/ - use-case calls. -- A controller hook (`useXPage`) follows the same single-responsibility - bar as any other code: when it accumulates more than one real workflow - (search/filter state, an editor with dirty-tracking, a deletion - confirmation are three _different_ concerns), split it into focused - hooks (`useXFilters`, `useXEditor`, `useXDeletion`) that the page's - composer hook assembles — see `features/catalog/presentation/services/hooks/` - for the reference (`useServicesPage` composing `useServiceFilters` + - `useServiceEditor` + `useServiceDeletion`). -- Extract a component or hook on its **first** use if it's already a - distinct concern (a field group, a delete dialog) — keep it - feature-local (e.g. `features/catalog/presentation/services/components/ -ServiceCategoryField.tsx`). Only **promote** something to `shared/` - once a **second**, genuinely-identical use appears across features — - the "second use" rule gates promotion, not the initial extraction. -- Break a type cycle between a controller and the component(s) it feeds - by putting the shared shape in a neutral, feature-local module (e.g. - `servicePresentationModels.ts`) that both sides import — the controller - must never import a component's Props type, and a component must never - import the controller's internal types. -- A dialog or form with a large, flat prop list is a signal to group - related props into a cohesive, named model (`editor`, `categoryOptions`, - `discardConfirmation`) instead of one generic catch-all object that - just hides the count. -- Decomposition triggers: multiple independent workflows, several - dialogs, distinct state clusters, an unmanageable prop list, a - controller/component type cycle, or a page test file too large to - navigate. There is no hard line-count cap. -- `GenericCrudPage` (or any config-driven, entity-agnostic CRUD - abstraction) is prohibited — share only behavior proven identical - across features (see the shared hooks/components list above), never a - generic page shape. - -#### Forms: React Hook Form + Zod - -Any form beyond a single trivial field uses `react-hook-form` + -`@hookform/resolvers/zod` — see `CategoryForm.tsx` -(`features/catalog/presentation/categories/pages/CategoryEditorDialog/forms/`) -for the exact shape: - -```typescript -const categoryFormSchema = z.object({ - name: z.string().trim().min(1, NAME_MESSAGE).max(60, NAME_MESSAGE), -}); -export type CategoryFormValues = z.infer; - -const { - register, - handleSubmit, - setError, - setFocus, - formState: { errors }, -} = useForm({ - resolver: zodResolver(categoryFormSchema), - defaultValues: initialValues, - mode: "onTouched", - reValidateMode: "onChange", -}); -``` - -(A field wired through `Controller` instead of `register` — e.g. a -`Select`, a color swatch group, a multi-value picker — also destructures -`control` from `useForm`; `CategoryForm` doesn't need one since its only -field is a plain text input.) - -- `
void handleSubmit(onSubmit)(e)} noValidate ...>` — - `noValidate` because native browser constraint validation would - intercept submit before react-hook-form/zod ever sees it. -- **A form with several field groups (name/description, duration range, - price/discount, category, tags — see `ServiceForm`) splits into one - component per group, sharing the RHF instance via `FormProvider`/ - `useFormContext`** instead of prop-drilling `register`/`control`/ - `errors` into each. The orchestrator component still owns - `useForm`/`handleSubmit`/the server-error effect; each field-group - component calls `useFormContext()` for - its own slice. -- **Structured API errors, mapped to fields — never parsed from free - text.** `shared/presentation/forms/serverFormError.ts`'s - `mapApiErrorToForm(error, fieldMap, codeFieldMap, fallbackMessage)` - differentiates a 400 validation `AppError` (has `rawFieldErrors` — map - each backend field name to the form's field via `fieldMap`) from a - 409/404/403 `AppError` (has `backendCode` — map via `codeFieldMap` when - the code names a specific field, e.g. a duplicate-name conflict - highlighting the name field, otherwise it becomes a global message). It - only ever depends on `AppError` (application-layer) — `ApiError`/ - `ProblemDetails` (infrastructure) never cross into a form. Apply the - result with `setError(field, { type: 'server', message })` in a - `useEffect` keyed on the server-error object, and - `setFocus(firstField)` so a screen-reader/keyboard user lands on the - first invalid field instead of losing their position — see - `CategoryForm`'s `serverError` effect. -- Don't reach for Formik or Yup without an explicit ADR — React Hook Form - - Zod is the established, working pattern here (`docs/DECISIONS.md`). - -#### Inline creation (a select that can create its own options) - -`shared/presentation/hooks/useCreateInline.ts` is the shared -`isCreating`/`serverError`/`create`/`reset` state machine behind any -"create a related record without leaving this form" flow -(`CreatableSingleSelect`/`CreatableMultiSelect`). It keeps the outer -form's already-typed values untouched and keeps the popover open to show -an error, instead of every entity reinventing this. Reuse it — don't -hand-roll a second inline-create state machine, and don't let an inline -create's error/loading state leak into or reset the outer form. - -#### Build from existing components — don't hand-roll markup, don't extend speculatively - -shadcn/ui primitives live in `src/components/ui/` and are already themed. -If a page needs something not there (select, badge, etc.), add it with -`npx shadcn@ add -c apps/admin-frontend` from the -repo root — use the version already pinned in -`apps/admin-frontend/package.json`'s `devDependencies.shadcn`, not -`@latest` (which would bypass that pin and could fetch an update the -repo hasn't reviewed). Then check the result compiles under -`exactOptionalPropertyTypes: true` (some generated files need fixing — -see `dropdown-menu.tsx`'s removal for when to give up and remove instead -of patch). - -Use generated files as the CLI writes them. Don't add a prop, variant, or -custom styling to a `src/components/ui/*` file unless a page genuinely -needs it right now — no speculative extensions "in case a future page -wants it." Do it at the call site instead (a conditional `` in -`children`, a `className` override on an existing `variant`). - -Shared composites live in `shared/presentation/components/` — reuse -before writing a new one: - -| Component | Use for | -| ----------------------------- | ------------------------------------------------------------------- | -| `PageHeader` | Title + primary action row at the top of every page | -| `StatusMessage` | Loading / empty / error text (`tone="error"` for errors) | -| `CollectionFeedback` | Loading/error/empty/last-known-good states for a tenant-scoped list | -| `DeleteConfirmationDialog` | Destructive-action `AlertDialog`, wired to `useDeleteConfirmation` | -| `TextField` / `TextAreaField` | Labeled form inputs (wraps shadcn `Label` + `Input`/`Textarea`) | -| `CenteredScreen` | Full-page centered content (pre-auth screens only) | -| `FullScreenSpinner` | Full-page loading state | -| `ThemeToggle` | Already in `AdminLayout` — don't add another one | - -Only promote a one-off to `shared/` once a second, genuinely identical -use appears (see "Componentization" above) — until then it stays -feature-local. - -#### Use semantic tokens — never raw palette classes - -`src/index.css` defines the whole palette as CSS variables, redefined -under `.dark` — `bg-background`/`text-foreground` etc. resolve correctly -in both themes automatically. A raw class like `bg-slate-50` does not — -it's a fixed light-mode color that breaks the moment a user switches to -dark. - -| Instead of (stale, don't use) | Use | For | -| ----------------------------------- | ----------------------------- | ----------------------------- | -| `bg-slate-50` | `bg-background` | Page background | -| `bg-white` | `bg-card` | Card/surface background | -| `border-slate-200` | `border-border` | Card and divider borders | -| `text-slate-800` | `text-foreground` | Headings, primary text | -| `text-slate-600` / `text-slate-400` | `text-muted-foreground` | Secondary/muted text | -| `text-red-600` | `text-destructive` | Error text | -| `bg-teal-600` / `text-teal-700` | `text-primary` / `bg-primary` | Brand accent, primary buttons | - -There is no brand color to special-case — the app uses the stock -shadcn/ui neutral theme. If in doubt, use a token. - -#### Icons and accessibility - -`lucide-react`, matched to the icon already used for this section in -`AdminLayout`'s nav. Always add `aria-hidden="true"` on a decorative icon. -Every interactive element needs a real accessible name (visible label, -`aria-label`, or `sr-only` text) and must be reachable and operable by -keyboard alone — tab order, `Enter`/`Space` activation, `Escape` closing a -`Dialog`/`AlertDialog`/popover (Radix primitives give you this for free; -don't fight it with a custom `onKeyDown` unless a page genuinely needs -one). Check color contrast against both themes when introducing any new -non-token color. - -#### Mobile responsiveness — every page must work at 375px wide - -- `Table` already scrolls horizontally on its own - (`data-slot="table-container"` wraps it in `overflow-x-auto`) — don't - add a second scroll wrapper. -- `Dialog` is responsive by default (`max-w-[calc(100%-2rem)]` below its - `sm:` breakpoint). -- Any `flex` row inside a form that could get tight still needs - `flex-wrap` — see `CategoryForm`'s button row. -- Never use a fixed pixel width wider than ~300px without a responsive - override. Prefer `w-full` + `max-w-*`. -- `AdminLayout` already handles the page shell (off-canvas sidebar below - `md`) — pages don't need their own mobile nav handling. - -#### States - -Handle all three `useAsync` states: loading → `StatusMessage`, error → -`StatusMessage tone="error"`, success → real UI (or `CollectionFeedback` -for a tenant-scoped list, which also covers the empty and -last-known-good-after-a-failed-refresh states). - -#### Language — all user-facing text is Brazilian Portuguese (pt-BR) - -Every string a user reads or a screen reader announces — headings, button -labels, `PageHeader`/`StatusMessage` text, form labels/hints, -`aria-label`s, confirm prompts, error-message fallbacks — is pt-BR. See -`CategoriesListPage`/`CategoryEditorDialog` for the pattern (e.g. "Nova -categoria", `aria-label={\`Excluir categoria ${category.name}\`}`). Code -stays in English: identifiers, comments, commit -messages, this skill's own prose. - -Nav labels (source of truth: `AdminLayout.tsx`'s `NAV_ITEMS`) are Painel, -Agendamentos, Serviços, Categorias, Clientes, Caixa de entrada, -Configurações — reuse the exact same word for a stub page's -`PlaceholderPage title` and for that vertical's `PageHeader title` once -built. - ---- +# Frontend feature + +Start with the code that owns the current behavior. `AGENTS.md` contains +invariants; `docs/STATUS.md` contains progress; ADRs contain rationale. Never +infer the current tree from an old example in prose. + +## Load only what the task needs + +| If the task touches... | Also read | +| --- | --- | +| Repository, mapper, decoder, OpenAPI type, MSW API handler | [references/api-integration.md](references/api-integration.md) | +| Test file, fake, wrapper, MSW setup | [references/testing.md](references/testing.md) | +| Page, form, dialog, table, shared component, visual behavior | [references/page-ui-conventions.md](references/page-ui-conventions.md) | +| Auth/session behavior | frontend ADRs 004, 006, 007 and 015 via `docs/adr/README.md` | +| Moving feature boundaries or public APIs | frontend ADR 009 and the current ESLint rules | + +Do not open all three references for a narrow change. + +## Architectural shape + +- `src/app/`: bootstrap, routing, layouts, providers, composition. +- `src/features//`: domain, application, infrastructure, and + presentation owned by one business capability. +- `src/shared/`: cross-feature primitives that already have at least two + identical consumers or are genuine application-wide boundaries. +- `src/components/ui/` and `src/lib/utils.ts`: shadcn-generated locations; + they intentionally stay outside `shared/`. +- Unimplemented routes remain small placeholders in `src/app/pages/` until a + real feature slice exists. + +## Decision rules + +### Domain and Result flow + +- Domain factories validate invariants and return `Result`. + They do not throw for expected invalid input. +- API mappers compose domain results. A malformed external response becomes a + curated `AppError` at the infrastructure boundary. +- `useAsync` consumes `() => Promise>`. Expected failures never + become rejected promises merely to fit a hook. +- Validate runtime input even when a generated TypeScript type looks narrower; + wire data is untrusted. + +### Application boundary + +- A repository port returns domain values wrapped in `Result`; it never exposes + raw DTOs. +- Repository methods do not accept tenant context. Tenant selection belongs to + the authenticated request-session boundary. +- Add a use-case class when it performs orchestration, policy, or composition. + If a facade operation is a pure repository pass-through, expose the method + shape directly instead of adding an `execute` wrapper. +- Construct concrete implementations only in `app/composition/container.ts` + and expose grouped facades, never raw repositories or `HttpClient`. + +### Feature boundaries + +- Import another feature only through its `index.ts` public API. +- Keep feature-specific DTOs, forms, hooks, view models, and tests inside that + feature. Promote a genuinely identical cross-feature primitive to `shared/` + only when the second use exists. +- Do not create `GenericCrudPage` or another config-driven entity-agnostic UI. + +### Tenant and auth safety + +- `AuthenticatedHttpClient` reads one `GetRequestSession` snapshot and attaches + the bearer token plus `X-Tenant-Id`; individual repositories do not choose a + tenant. +- Pass the authenticated tenant id as `useAsync.resetKey` so previous-tenant + data cannot paint after a switch. +- Preserve both user and tenant identity during silent renewal. A changed claim + requires a full login. +- Keep routed tenant content below `TenantBoundary`. + +## Implementation sequence + +Use only the steps relevant to the requested behavior: + +1. Confirm the business rule or wire contract from code/OpenAPI/docs; ask only + when a missing answer would change a public contract, auth, tenant isolation, + or business behavior. +2. Add or change the domain behavior with a failing test when domain logic is + involved. +3. Change the port and orchestration boundary only if the behavior requires it. +4. Change decoder/mapper/repository and MSW tests for external data. +5. Wire the facade/container without leaking concrete infrastructure. +6. Build the hook with `useAsync` and a tenant reset key when it owns server data. +7. Build the smallest accessible page/form composition needed now. +8. Update `docs/STATUS.md` only when implementation status changed; update an + ADR only when a durable decision changed. ## Prohibited -- A second, competing design system or component library alongside - shadcn/ui + Radix + Tailwind — extend the existing one (see "Build from - existing components" above). -- Formik or Yup without an explicit ADR — this project already made this - decision (React Hook Form + Zod). -- Redux, Zustand, or any global client-state store used as a server-data - cache — `useAsync` + the container's use cases are the established - pattern; a genuinely local UI-only state (a dialog's open/closed flag) - is fine as plain `useState`, but server data always flows through a - hook built on `useAsync`. -- Hand-duplicating a contract the codebase already generates — - `features/catalog/infrastructure/generated/services-api.d.ts` is - generated from the backend's OpenAPI document - (`npm run generate:api-types`); don't hand-write a parallel DTO type - for something already generated, and don't let a hand-written one - silently drift from it (see `agent-skills/agenza-api-contract-review`). -- Importing a feature's internal `domain/`, `application/`, - `infrastructure/`, or `presentation/` module from outside that feature - — share through its `index.ts` public API instead (ADR 009). This is - ESLint- and `architecture_guard.py`-enforced. -- `GenericCrudPage`, or any generic entity-agnostic CRUD abstraction. -- `any`, anywhere, including test files and fakes. - ---- - -## HttpClient (already built — read before touching infrastructure) - -```typescript -// shared/application/HttpClient.ts -export type Decoder = (payload: unknown) => T; - -export interface HttpClient { - get(path: string, decode: Decoder): Promise; - post(path: string, body: unknown, decode: Decoder): Promise; - put(path: string, body: unknown, decode: Decoder): Promise; - delete(path: string): Promise; -} -``` - -Every `get`/`post`/`put` call takes a `decode` function alongside its `T` - -a generic type parameter alone validates nothing at runtime, so the -decoder is what actually stands between an untrusted response body and a -value the rest of the app treats as `T` (docs/adr/011). A feature's mapper -owns its own decoder next to its DTO type (e.g. `categoryMapper.ts`'s -`decodeCategoryDto`/`decodeCategoryDtoArray`) - hand-rolled `typeof`/`Array.isArray` -guards matching `shared/infrastructure/http/ProblemDetails.ts`'s existing -style, not a schema library. A decoder that throws is caught by the same -place every other infrastructure failure already is (see below) - never -add a second try/catch in the repository for this. - -`AuthenticatedHttpClient` (`shared/infrastructure/http/`): constructor -takes `getRequestSession: GetRequestSession` (returns both the access -token and tenant id from one session read — `shared/application/ -RequestSession.ts`), prepends `VITE_API_BASE_URL`, attaches `Authorization: -Bearer ` and `X-Tenant-Id`, converts every failure (missing -session, 401, non-2xx `ProblemDetails`, network/timeout, or a `decode` -rejection) into an `AppError` (`shared/application/AppError.ts`) before it -leaves infrastructure — never `ApiError`/`ProblemDetails`/a raw decode -error directly (docs/adr/007, docs/adr/011). Wired into -`createAppContainer()` (`app/composition/container.ts`) using -`authRepository.getCurrentSession()` to supply both values from the same -read. - ---- +- `any`, deep cross-feature imports, raw infrastructure imports from + presentation, hand-duplicated generated contracts, or a second design system. +- A global client-state library used as a server cache without an ADR replacing + the established `useAsync` approach. +- Raw backend/exception messages rendered to users. +- Speculative components, variants, use cases, or abstractions. -## Commit checklist +## Completion -- [ ] Domain entity: explicit field declarations, named errors, no framework deps, no `any` -- [ ] Repository interface: `TenantContext` first param on all methods -- [ ] Use cases: explicit constructor body (no shorthand), tested with fakes -- [ ] Container: wired in interface and factory, imported from the feature's `index.ts` -- [ ] Mapper: tested, all fields and failure paths covered -- [ ] Infrastructure repo: tested with MSW, handler registered -- [ ] Hook: built on `useAsync`, tenant-scoped via `resetKey`, mutations - use `mutate` for optimistic success decoupled from refetch failure -- [ ] Form (if any): React Hook Form + Zod, server errors mapped to - fields via `mapApiErrorToForm`, `setFocus` on the first error -- [ ] Page: a composition shell handing view models to presentational - components; controller hook split by workflow once it has more - than one -- [ ] Page: handles loading/error/success, built from shadcn/ui primitives - and shared composites (not hand-rolled markup) -- [ ] List uses `Table`; form uses the feature's documented interaction - (`Dialog` by default, routed editor only where an ADR establishes it) -- [ ] Destructive actions confirmed with `DeleteConfirmationDialog` — not - `window.confirm` or a hand-rolled `AlertDialog` -- [ ] No prop/variant added to a `src/components/ui/*` file unless this - page genuinely needs it right now -- [ ] Page: uses semantic tokens only — no raw `slate-*`/`teal-*`/etc. -- [ ] Page: checked in dark mode and at 375px wide, no horizontal overflow -- [ ] Page: keyboard-operable, decorative icons `aria-hidden`, every - interactive element has an accessible name -- [ ] All user-facing text (labels, messages, `aria-label`s, confirm - prompts) is in pt-BR -- [ ] No import of another feature's internals bypassing its `index.ts`, - no hand-duplicated generated contract, no new global client-state store -- [ ] Comments are at the "minimum of the minimum" bar — none by default -- [ ] `npm run build` clean (catches TypeScript strict mode issues) -- [ ] `npm run lint` clean -- [ ] `npm run test` all green — behavioral assertions, not implementation details +Run the frontend and governance gates from `apps/admin-frontend/AGENTS.md`. +Report actual results and any remaining uncertainty; do not call the task done +while an applicable gate is red. diff --git a/.agents/skills/agenza-frontend-feature/references/api-integration.md b/.agents/skills/agenza-frontend-feature/references/api-integration.md new file mode 100644 index 0000000..a24a1a1 --- /dev/null +++ b/.agents/skills/agenza-frontend-feature/references/api-integration.md @@ -0,0 +1,47 @@ +# Frontend API integration + +Read this reference only when changing a repository, mapper, decoder, generated +OpenAPI type, request body, endpoint path, or MSW API handler. + +## Source of truth + +1. Inspect the backend controller/response/command and the generated OpenAPI + types already checked into the feature. +2. Run or inspect the API type generation workflow when the contract may have + changed. +3. Use `docs/API.md` for integration policy and confirmed endpoint notes, not as + a substitute for the generated contract. +4. Ask the user only if the remaining ambiguity would change a public contract + or business rule. + +Do not create a hand-written DTO that shadows an available generated type. A +feature-local decoder may narrow an `unknown` payload into that generated type. + +## Boundary flow + +- `HttpClient` receives a decoder and returns `Promise>`. +- A decoder may throw while rejecting malformed untrusted data; the global + authenticated HTTP boundary catches that technical failure and returns a + curated `Result.failure`. Repositories and presentation do not add another + try/catch for expected failures. +- A mapper converts the decoded wire shape into domain values and composes any + domain validation `Result`. +- Preserve absent/null distinctions only when the contract distinguishes them; + normalize them before they enter the domain. + +## Tenant and authentication + +The mechanism is already decided: `AuthenticatedHttpClient` obtains one atomic +request-session snapshot, attaches `Authorization: Bearer ...` and +`X-Tenant-Id`, and the backend verifies the header against the token claim. +Repository methods neither accept `TenantContext` nor set the tenant header. + +## Tests + +- Mapper/decoder tests cover every field plus malformed and domain-invalid data. +- Repository tests use MSW and the real `HttpClient` path. +- Handlers match the exact URL, method, request, response, and relevant RFC 7807 + error shape. Register every handler; unhandled requests fail globally. +- Test at least the success path and each error behavior the repository maps or + exposes differently. + diff --git a/.agents/skills/agenza-frontend-feature/references/page-ui-conventions.md b/.agents/skills/agenza-frontend-feature/references/page-ui-conventions.md new file mode 100644 index 0000000..0e75dac --- /dev/null +++ b/.agents/skills/agenza-frontend-feature/references/page-ui-conventions.md @@ -0,0 +1,73 @@ +# Page and UI conventions + +Read this reference only for page, form, dialog, table, component, or visual +behavior changes. + +## Composition + +- A routed page is a composition shell. It renders view models and callbacks + from a controller hook; it does not call repositories or infrastructure. +- Split a controller when it owns more than one independent workflow, such as + filtering, editing, deletion, or dirty-state confirmation. There is no + line-count threshold. +- Extract a distinct concern locally on first use. Promote it to `shared/` only + after a second genuinely identical use across features. +- Keep shared controller/component shapes in a neutral feature-local module; + neither side imports the other's internal type. +- Do not build a generic CRUD page. Reuse proven behaviors and primitives, not + an entity configuration object. + +Categories is the current implemented CRUD reference. Inspect its live files +under `features/catalog/presentation/categories/` rather than copying a folder +layout described in documentation. + +## Interaction patterns + +- Lists use the existing shadcn `Table`. Destructive actions use the shared + confirmation dialog rather than `window.confirm` or a feature-specific copy. +- Create/edit uses one form implementation. A dialog is the default interaction; + use routing when navigation, deep-linking, or refresh behavior justifies it + and record a reusable architectural change in an ADR. +- Preserve last-known-good data during refresh failures when the current shared + collection feedback component supports it. + +## Forms + +- Non-trivial forms use React Hook Form and Zod with `noValidate` on the form. +- The form orchestrator owns `useForm`, submit, and server-error application. + Field-group components consume the same form through `FormProvider` when + prop-drilling would otherwise repeat form internals. +- Map structured backend field/code errors through the shared form-error helper. + Focus the first invalid field. Never parse free-text backend messages. +- A component controlled through RHF `Controller` forwards its ref to a real + focusable DOM element. +- Keep an inline-create workflow's pending/error state separate from the outer + form. Do not invent a shared abstraction until a live second use proves it. + +## Existing UI + +- Prefer `src/components/ui/` primitives and then + `shared/presentation/components/`. Inspect the directories for the current + inventory; do not maintain a duplicate component list here. +- Add shadcn components with the version pinned in `package.json`, never + `@latest`. Keep generated primitives close to upstream and solve one-off + styling at the call site. +- Use semantic tokens such as `bg-background`, `bg-card`, `text-foreground`, + `text-muted-foreground`, `border-border`, and `text-destructive`. Raw palette + classes break theme portability. + +## Accessibility and responsive behavior + +- Every interactive element has a visible or programmatic accessible name and + works by keyboard. Decorative icons use `aria-hidden="true"`. +- Prefer Radix interaction behavior over custom keyboard handlers. +- Add `jest-axe` coverage to new or materially changed routed pages/forms and + verify focus movement for server validation errors. +- Verify light and dark themes and a 375 px viewport. Avoid fixed widths that + overflow; use the existing table/dialog responsiveness before adding wrappers. + +## Language and comments + +User-visible and assistive strings are pt-BR; code identifiers remain English. +Comments default to zero and explain only a non-obvious security, concurrency, +library, browser, or lint constraint. Put architectural rationale in an ADR. diff --git a/.agents/skills/agenza-frontend-feature/references/testing.md b/.agents/skills/agenza-frontend-feature/references/testing.md new file mode 100644 index 0000000..fdcf479 --- /dev/null +++ b/.agents/skills/agenza-frontend-feature/references/testing.md @@ -0,0 +1,50 @@ +# Frontend testing conventions + +Read this reference only when creating or changing tests, fakes, wrappers, MSW +handlers, or test infrastructure. + +## Strategy by boundary + +| Subject | Test boundary | +| --- | --- | +| Domain | Pure inputs and `Result` outputs; no mocks | +| Application orchestration | Hand-written repository fake | +| Infrastructure repository | MSW around the real `HttpClient` | +| Hook/component | Typed fake `AppContainer`; router/auth providers only as needed | + +Do not mix boundaries. A use-case test does not need MSW; a repository test does +not replace `HttpClient` with a repository fake. + +## Fakes + +- Start from the current `createFake*Repository` or + `createFakeAppContainer` helper. +- Default unused expected operations to resolved `Result.failure` values, not + `Promise.reject`. This application represents expected failures as values. +- Override only the operation under test and use `vi.fn` when call assertions + matter. +- Add a shared feature fake after a second test needs the same complete shape. + +## TypeScript and React + +- Constructor fields are explicit; optional properties use conditional spreads. +- Type render helpers explicitly when inference would lose the subject's public + result type. +- Give wrapper/render helpers explicit return types when ESLint requires them. +- A never-resolving promise for an in-flight state uses a non-empty executor or + the narrow documented lint suppression; do not generalize a suppression. +- Use `waitFor` for observable async state and `act` around direct state-causing + calls. Wait for the initial auth check before asserting authenticated content. + +## MSW and accessibility + +- Every request has a registered handler and `onUnhandledRequest: 'error'` + remains enabled. +- Test wire shapes, request bodies, and relevant error variants at the HTTP + boundary. +- Add `jest-axe` to new or materially changed routed pages/forms, alongside + keyboard/focus assertions where behavior depends on them. + +Run targeted Vitest files during development, then the complete format, lint, +build, and coverage gates from `apps/admin-frontend/AGENTS.md`. + diff --git a/.agents/skills/agenza-rule-persistence/SKILL.md b/.agents/skills/agenza-rule-persistence/SKILL.md index 7e08ef8..ab70e09 100644 --- a/.agents/skills/agenza-rule-persistence/SKILL.md +++ b/.agents/skills/agenza-rule-persistence/SKILL.md @@ -36,11 +36,11 @@ three months later: 2. **Update `AGENTS.md`.** Root `AGENTS.md` if it applies everywhere; `backend/AGENTS.md`/`apps/admin-frontend/AGENTS.md` if it's area-local. State the rule, not a narrative of how it was discovered. -3. **Update the skill.** If a skill in `agent-skills/` teaches the old +3. **Update the skill.** If a skill in `.agents/skills/` teaches the old pattern (in prose *or* in a copy-paste template — templates rot silently because they're copied verbatim without re-reading the prose around them), fix it there. Run `python scripts/sync_agent_skills.py` - afterward so `.claude/skills/`/`.agents/skills/` pick up the change. + afterward so `.claude/skills/` picks up the change. 4. **Add or update an ADR.** If this is a genuine architectural decision (not just a bug fix), it needs `docs/adr/NNNN-....md` explaining the context, the decision, and — if it reverses an earlier ADR — which one @@ -63,13 +63,14 @@ three months later: A rule can be technically "fixed" in the places above and still get reintroduced because something else still teaches the old pattern. Check: -- Other `CLAUDE.md`/`AGENTS.md` files that might restate the rule locally - and now disagree with the update. -- Older skills (including ones outside `agent-skills/`, like - `backend/.skills/`/`apps/admin-frontend/.skills/`) that predate the - change. +- Other `CLAUDE.md`/`AGENTS.md` files or the Copilot bridge that might restate + the rule locally and now disagree with the update. +- Any forbidden legacy instruction layer (`agent-skills/`, `prompts/`, + `.claude/agents/`, `.skills/`, `.agent.md`) or generated artifact that still + teaches the old behavior. - Comments in code that assert the old rationale. -- `prompts/` templates and worked examples in `docs/SDD-GUIDE.md`. +- Worked examples in `docs/SDD-GUIDE.md` and any task template outside the + canonical skill tree. - Test files whose names or comments describe the old behavior as correct, even if the assertions themselves were updated. @@ -81,5 +82,5 @@ genuinely doesn't apply (e.g. no ADR is warranted for a pure typo fix), say so explicitly rather than leaving it silently incomplete. Run `python scripts/check_agent_governance.py` after this cycle — it flags skills not in sync, ADR references that don't exist, and -`CLAUDE.md` files missing the `@AGENTS.md` import, three of the most -common ways a "persisted" rule quietly isn't. +`CLAUDE.md` files missing the `@AGENTS.md` import, and a missing Copilot bridge, +four of the most common ways a "persisted" rule quietly isn't. diff --git a/.agents/skills/agenza-tenant-isolation-review/SKILL.md b/.agents/skills/agenza-tenant-isolation-review/SKILL.md index 643753b..08579d7 100644 --- a/.agents/skills/agenza-tenant-isolation-review/SKILL.md +++ b/.agents/skills/agenza-tenant-isolation-review/SKILL.md @@ -39,8 +39,7 @@ is in scope. entity's repository takes an explicit `tenantId` parameter (the DbContext scopes it) — a parameter like that is a sign someone hand- rolled scoping instead of relying on the automatic mechanism, which is - itself worth flagging even if the value passed happens to be correct - today. + itself worth flagging even if the value passed happens to be correct. - **New-entity assignment**: `AuditableEntitySaveChangesInterceptor` calls `AssignTenant` on save for any newly added `ITenantOwned` entity with `TenantId == Guid.Empty`, sourcing it from `ICurrentTenantProvider` — it @@ -49,7 +48,7 @@ is in scope. - **Frontend cache/query keys**: any client-side cache (`useAsync`'s `resetKey`, a memoized list, browser storage) keyed in a way that includes the tenant id or is cleared synchronously on tenant switch — - see `agent-skills/agenza-frontend-feature`'s `useAsync` section for the + see `.agents/skills/agenza-frontend-feature`'s `useAsync` section for the `resetKey` mechanism. A cache that survives a tenant switch and can render the previous tenant's data for even one frame is a finding, not a nit. @@ -58,7 +57,7 @@ is in scope. unique index on a business field is itself a cross-tenant leak (tenant A can't reuse a name tenant B already used). A composite FK crossing tenant boundaries (referencing another tenant's row) is a finding. -- **Migrations**: hand off to `agent-skills/agenza-migration-safety` for +- **Migrations**: hand off to `.agents/skills/agenza-migration-safety` for the migration-safety half; this skill only confirms the resulting schema still enforces tenant scoping (index/FK shape above). - **Logs**: a log statement that includes another tenant's data alongside @@ -81,9 +80,7 @@ as blocking. ## Output format `surface (endpoint/query/cache/index) | mechanism relied on | verified? | -finding (if any) | severity | fix`. For anything not directly verifiable -by reading code (e.g. actual runtime behavior of a query filter), say so -explicitly and recommend the manual two-tenant verification step already -called out in `agent-skills/agenza-backend-use-case` ("Automatic tenant -assignment has no automated regression test") rather than asserting it's -safe from static reading alone. +finding (if any) | severity | fix`. For behavior not provable statically, +inspect the current `*PersistenceTests` projects and recommend a two-tenant +runtime smoke only when it adds coverage. Never infer a missing test tier from +an older ADR or instruction; inspect the solution and CI first. diff --git a/.agents/skills/evolve-modular-architecture/SKILL.md b/.agents/skills/evolve-modular-architecture/SKILL.md index 03a1e33..6e2c76d 100644 --- a/.agents/skills/evolve-modular-architecture/SKILL.md +++ b/.agents/skills/evolve-modular-architecture/SKILL.md @@ -1,6 +1,6 @@ --- name: evolve-modular-architecture -description: Assess, design, review, and incrementally evolve modular software architecture from repository and business evidence. Use when Codex needs to define or repair module boundaries, decompose a monolith, choose between a simple monolith, modular monolith, and microservices, select architecture per module, introduce tactical DDD only where justified, plan a safe extraction or migration, write ADRs, or create automated architectural fitness functions that prevent structural drift. +description: Assess, design, review, and incrementally evolve modular software architecture from repository and business evidence. Use when defining or repairing module boundaries, decomposing a monolith, choosing between a simple monolith, modular monolith, and microservices, selecting architecture per module, introducing tactical DDD only where justified, planning a safe extraction or migration, writing ADRs, or creating automated architectural fitness functions that prevent structural drift. --- # Evolve Modular Architecture diff --git a/.claude/agents/agenza-architecture-reviewer.md b/.claude/agents/agenza-architecture-reviewer.md deleted file mode 100644 index ee175b4..0000000 --- a/.claude/agents/agenza-architecture-reviewer.md +++ /dev/null @@ -1,40 +0,0 @@ ---- -name: agenza-architecture-reviewer -description: > - Use for a general architecture audit of this monorepo — layering, - vertical slices, dependency direction, Result-pattern usage, domain - model shape, testing discipline, documentation accuracy, and CI/ - dependency consistency. Trigger on "review the architecture", "audit - the codebase", or before a release. Read-only: produces a diagnosis, - never edits code. -tools: Read, Grep, Glob, Bash ---- - -You are a read-only architecture reviewer for this repository. You do not -edit files. Your job is to produce a diagnosis the user or another agent -can act on. - -Before reviewing anything, read the root `AGENTS.md`, the `AGENTS.md` for -whichever area is in scope (`backend/AGENTS.md`, -`apps/admin-frontend/AGENTS.md`), and -`agent-skills/agenza-architecture-review/SKILL.md` — that skill is the -canonical source for what to check and how to report it; follow it -exactly rather than inventing your own checklist. For the deep multi- -tenancy, exception-flow, or API-contract passes, note that a dedicated -reviewer exists for each (`agenza-tenant-reviewer`, -`agenza-exception-auditor`, `agenza-contract-reviewer`) — you may run a -shallow presence check yourself, but don't duplicate their full depth. - -Run `python scripts/architecture_guard.py --inventory` and -`python scripts/check_agent_governance.py` as part of your pass — they -catch the mechanically-detectable half of what this skill asks for; add -your own reading for everything a regex can't see (anemic domain models, -speculative abstractions, documentation drift, cross-cutting consistency). - -Report findings in the format `agenza-architecture-review` specifies: -file/location, what's wrong, why it matters (cite the `AGENTS.md` rule, -ADR, or skill it violates — never invent a new rule mid-review), severity, -and a one-sentence suggested fix. You have no Edit/Write tool — you cannot -implement fixes regardless of how the task is phrased; the skill's -"implement" mode is for whichever caller has write access to act on your -report, not for you. diff --git a/.claude/agents/agenza-contract-reviewer.md b/.claude/agents/agenza-contract-reviewer.md deleted file mode 100644 index af3ec36..0000000 --- a/.claude/agents/agenza-contract-reviewer.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -name: agenza-contract-reviewer -description: > - Use to audit consistency between the backend's OpenAPI contract and the - frontend's DTOs, generated types, and error handling. Trigger on - "review the API contract", "check for contract drift", or after a - change to a controller, DTO, ProblemDetails shape, or the generated - TypeScript client. Read-only: detects and reports drift, never changes - a public contract silently. -tools: Read, Grep, Glob, Bash ---- - -You are a read-only API-contract reviewer covering both -`backend/services/services-service` (the OpenAPI source) and -`apps/admin-frontend` (the generated + hand-written consumers). You do not -edit files, and you never change a public contract as a side effect of a -review. - -Read `agent-skills/agenza-api-contract-review/SKILL.md` first — it is the -canonical source for what "the contract" means here and the exact checks -to run (generated-types staleness, DTO duplication, field-limit drift, -enum drift, renamed properties, unhandled error codes, structured-vs- -free-text errors). Follow it exactly. - -Where possible, verify mechanically: `npm run generate:api-types:check ---workspace=apps/admin-frontend` (needs services-service reachable — note -if it isn't rather than skipping the check silently), and `grep`/`Grep` -comparisons between backend `MaximumLength`/`.PrecisionScale(...)`/enum -definitions and their frontend Zod-schema/constant counterparts. - -Report drift in the table format the skill specifies, and call out any -breaking change (renamed/removed field or endpoint, narrowed enum, -tightened validation on an existing field) prominently, separate from -non-breaking drift — a breaking change needs a decision from the user -(root `AGENTS.md`'s question policy), not a silent recommendation. diff --git a/.claude/agents/agenza-exception-auditor.md b/.claude/agents/agenza-exception-auditor.md deleted file mode 100644 index 799cbb7..0000000 --- a/.claude/agents/agenza-exception-auditor.md +++ /dev/null @@ -1,37 +0,0 @@ ---- -name: agenza-exception-auditor -description: > - Use to inventory and classify every throw/try/catch/Exception in - backend/ — on request, before a release, or when reviewing a diff that - touches error handling. Trigger on "audit exceptions", "review error - handling", "check for business exceptions creeping back in". Read-only: - classifies and recommends, never edits code. -tools: Read, Grep, Glob, Bash ---- - -You are a read-only exception-flow auditor for this repository's .NET -backend. You do not edit files. - -Read `agent-skills/agenza-exception-flow-audit/SKILL.md` first — it is -the canonical source for the classification taxonomy (expected outcome / -unexpected technical failure / programming violation / transactional -cleanup / technical-exception-to-result conversion) and the known-correct -examples to calibrate against. Follow it exactly. - -Run `python scripts/architecture_guard.py --inventory` first — it already -flags the two patterns that must never exist at all -(`DuplicateEntityException`, `BusinessExceptionHandler`) plus several -related heuristics (validator-repository dependencies, `MustAsync`/ -`CustomAsync` in validators, domain entities throwing instead of returning -`DomainResult`, null-forgiving lookups). Use `grep`/`Grep` for `throw`, -`try`, `catch`, and `Exception` across `backend/` to find what the guard's -narrower heuristics don't cover, then classify every occurrence by hand -against docs/adr/0012 and docs/adr/0014. - -Produce the table the skill specifies: file, line, type, purpose, -classification, recommended action, justification for keeping (if -applicable). Do not recommend removing every `throw` mechanically — a -correct transactional-cleanup or infrastructure-boundary conversion must -be identified and left alone, not flagged as a violation. If a fix would -change an HTTP status code or error shape, flag it for -`agenza-contract-reviewer` instead of prescribing the fix yourself. diff --git a/.claude/agents/agenza-tenant-reviewer.md b/.claude/agents/agenza-tenant-reviewer.md deleted file mode 100644 index e3dbb8d..0000000 --- a/.claude/agents/agenza-tenant-reviewer.md +++ /dev/null @@ -1,41 +0,0 @@ ---- -name: agenza-tenant-reviewer -description: > - Use for any multi-tenancy / tenant-isolation audit — on request, before - a release, or whenever a change touches auth, a repository, a query, a - cache key, or a migration. Trigger on "review tenant isolation", "check - for cross-tenant leaks", "audit multi-tenancy". Read-only: any confirmed - cross-tenant exposure is reported as a security/privacy failure, top of - the list, regardless of what else is in scope. -tools: Read, Grep, Glob, Bash ---- - -You are a read-only tenant-isolation reviewer for this repository — both -the .NET backend's tenant-scoping mechanism and the frontend's tenant- -aware caching. You do not edit files. - -Read `agent-skills/agenza-tenant-isolation-review/SKILL.md` first — it is -the canonical checklist (header/claim verification, global query filters -reading the live `DbContext` instance, repository/handler scoping, -automatic new-entity tenant assignment, frontend cache/query-key -isolation, per-tenant uniqueness indexes, cross-tenant FKs, logging, -test coverage for cross-tenant access). Follow it exactly rather than -inventing your own pass. - -Use `Grep`/`Read` to verify each mechanism against the actual code -(`TenantHeaderFilter`, `ApplyAuditableConventions`, -`AuditableEntitySaveChangesInterceptor`, `useAsync`'s `resetKey` usage, -index definitions in EF configurations/migrations) rather than trusting -that a pattern applies just because it's documented elsewhere — this -skill's whole purpose is verifying the mechanism actually holds for the -surface in scope, not restating the mechanism's description. - -Report in the table format the skill specifies. Treat ANY confirmed -cross-tenant data exposure — even read-only, even UI-only, even -transient (one frame, a stale cache entry, a log line) — as a -security/privacy failure and put it first in your findings, regardless of -what else you were asked to review. Where static reading can't confirm -runtime behavior (e.g. an EF query filter's actual effect), say so -explicitly and recommend the manual two-tenant verification step from -`agent-skills/agenza-backend-use-case` rather than asserting safety you -haven't verified. diff --git a/.claude/settings.local.json b/.claude/settings.local.json deleted file mode 100644 index 1183e8b..0000000 --- a/.claude/settings.local.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "permissions": { - "allow": [ - "Bash(cd \"C:\\\\Users\\\\evert\\\\Downloads\\\\admin-complete\\\\admin\" && mkdir -p apps/admin-frontend packages/shared-types/src backend/services \"ai-services/assistant-service/app\" \"ai-services/assistant-service/tests\" infra && ls)", - "Bash(cd \"D:/Agenza/apps/admin-frontend/src/features/catalog/presentation\" && \\\\ *)", - "Bash(git mv *)" - ] - } -} diff --git a/.claude/skills/agenza-api-contract-review/SKILL.md b/.claude/skills/agenza-api-contract-review/SKILL.md index 2c058a5..7add959 100644 --- a/.claude/skills/agenza-api-contract-review/SKILL.md +++ b/.claude/skills/agenza-api-contract-review/SKILL.md @@ -20,10 +20,10 @@ description: > generated by `npm run generate:api-types` from services-service's live OpenAPI document, checked for staleness by `npm run generate:api-types:check` (also gated in CI — `api-contract-check` in `.github/workflows/frontend-ci.yml`). -- Frontend hand-written types: per-feature DTO interfaces in - `src/infrastructure/mappers/*.ts` (see `agent-skills/agenza-frontend-feature` - step 5) — these exist for features not yet covered by the generated - client, or that intentionally narrow it. +- Frontend decoders/mappers: feature-local infrastructure files under + `src/features/*/infrastructure/`. A hand-written wire type is justified only + when no generated contract exists or when it intentionally narrows unknown + input without shadowing the generated shape. ## Checks @@ -31,13 +31,14 @@ description: > --workspace=apps/admin-frontend` (requires services-service running and reachable, matching the CI job) — a failure here means a controller/DTO changed without regenerating. -2. **DTO duplication.** A hand-written DTO interface in - `src/infrastructure/mappers/` that shadows a type already in +2. **DTO duplication.** A hand-written DTO interface in a feature's + `infrastructure/` that shadows a type already in `services-api.d.ts` for the same resource — flag for consolidation. 3. **Field limit drift.** A `MaximumLength`/`.PrecisionScale(...)` on the backend validator or EF column vs. a `maxLength`/`max()` in the - matching Zod schema (`agent-skills/agenza-frontend-feature`'s form - section) — these must match exactly (see docs/adr/0012's + matching Zod schema (see + `.agents/skills/agenza-frontend-feature/references/page-ui-conventions.md`) + — these must match exactly (see docs/adr/0012's `Category.NameMaxLength`/`Service.NameMaxLength` alignment for the kind of drift this catches). A limit encoded only in a comment, never a type/schema/const, is itself a finding — flag it for a real check. @@ -50,8 +51,8 @@ description: > 6. **Unhandled API error shape.** A backend error `code` introduced (`Error.Conflict("Entity.SomeCode", ...)`) with no corresponding entry in the matching frontend `codeFieldMap` (see - `agent-skills/agenza-frontend-feature`'s "Structured API errors" - section) — it will still work (falls back to a global message) but + `.agents/skills/agenza-frontend-feature/references/api-integration.md`) — + it will still work (falls back to a global message) but loses field-level precision; flag it, don't treat it as broken. 7. **Structured vs. free-text errors.** Confirm a new validation failure path returns through `Error.FieldErrors` (structured, docs/adr/0012), diff --git a/.claude/skills/agenza-architecture-review/SKILL.md b/.claude/skills/agenza-architecture-review/SKILL.md index 0df6c0d..ce732b2 100644 --- a/.claude/skills/agenza-architecture-review/SKILL.md +++ b/.claude/skills/agenza-architecture-review/SKILL.md @@ -26,38 +26,38 @@ Check, across whichever of these areas are in scope for the request: shape followed? Frontend feature folders self-contained, no cross-feature imports? - **Multi-tenancy**: delegate the deep pass to - `agent-skills/agenza-tenant-isolation-review` rather than duplicating it + `.agents/skills/agenza-tenant-isolation-review` rather than duplicating it here — this review only checks that tenant scoping is *present* where expected, not the full mechanism. - **Exceptions / Result pattern**: delegate the deep pass to - `agent-skills/agenza-exception-flow-audit`. + `.agents/skills/agenza-exception-flow-audit`. - **Domain model**: anemic entities (public setters, no invariant enforcement), missing `DomainResult` usage, entities bypassing `BaseEntity`/`TenantOwnedEntity` without a documented reason. - **Persistence**: query filters applied by hand instead of via `ApplyAuditableConventions`, missing indexes for a new uniqueness rule, a migration issue — delegate depth to - `agent-skills/agenza-migration-safety`. -- **Contracts**: delegate to `agent-skills/agenza-api-contract-review`. + `.agents/skills/agenza-migration-safety`. +- **Contracts**: delegate to `.agents/skills/agenza-api-contract-review`. - **Frontend**: layering (see above), `any` usage, design-system drift (raw palette classes instead of semantic tokens), reusable-component - discipline (`agent-skills/agenza-frontend-feature`). + discipline (`.agents/skills/agenza-frontend-feature`). - **Accessibility**: keyboard operability, accessible names, contrast — sample a few recently-changed pages rather than the whole app unless asked for a full sweep. - **Tests**: coverage gate status, mock-strategy-per-layer discipline - (frontend), no integration-test reintroduction without an ADR reverting - docs/adr/0015 (backend). -- **Migrations**: `agent-skills/agenza-migration-safety`. -- **Documentation**: `AGENTS.md`/`CLAUDE.md` files still accurate and in - sync (`scripts/check_agent_governance.py` covers the mechanical half of + (frontend), narrow persistence/runtime boundaries matching the current ADR + index instead of treating historical ADR 0015 as the final state. +- **Migrations**: `.agents/skills/agenza-migration-safety`. +- **Documentation**: `AGENTS.md`, import-only `CLAUDE.md`, and the Copilot + bridge still accurate and in sync (`scripts/check_agent_governance.py` covers the mechanical half of this), STATUS.md rows matching what's actually built, ADRs referenced by number actually existing. - **CI**: workflows still matching the commands documented in `docs/QUALITY.md`, coverage gates not silently loosened. -- **Dependencies**: any package pinned for a documented reason - (`docs/QUALITY.md`, `README.md`'s Versions table) that a routine bump - would silently violate. +- **Dependencies**: compare executable pins (`packageManager`, lockfiles, + `backend/global.json`, `backend/Directory.Packages.props`, `.python-version`, + CI actions) with `docs/adr/0032` before recommending a routine bump. ## Mode: review-only (default) @@ -68,7 +68,7 @@ Produce a diagnosis, not a diff. For each finding: - **Why it matters** (tie back to a rule in `AGENTS.md`, an ADR, or a skill — don't invent a new rule mid-review; if there's genuinely no existing rule this violates, that's a finding for - `agent-skills/agenza-rule-persistence` to formalize, not a silent + `.agents/skills/agenza-rule-persistence` to formalize, not a silent judgment call) - **Severity**: blocks tenant isolation / security > breaks a build gate > architectural drift > style nit diff --git a/.claude/skills/agenza-backend-new-service/SKILL.md b/.claude/skills/agenza-backend-new-service/SKILL.md new file mode 100644 index 0000000..f4878cc --- /dev/null +++ b/.claude/skills/agenza-backend-new-service/SKILL.md @@ -0,0 +1,81 @@ +--- +name: agenza-backend-new-service +description: > + Use when creating a brand-new .NET service under backend/services or when + deciding whether a capability needs its own service. Covers this repository's + context-aggregated service boundary, project layout, central package + management, tenant-safe persistence, authentication, Aspire wiring, tests, + and documentation. Do not copy an old service template without reading it. +--- + +# Backend new service + +Create a service only for a genuinely new business context. If an existing +service owns the capability, use `agenza-backend-use-case` there instead. ADR +0001 records the context-aggregated service decision. + +## Use live references + +Inspect the current `services-service`, `identity-service`, AppHost, solution, +`backend/Directory.Packages.props`, and CI workflow before writing files. They +are the executable templates; this skill intentionally contains no copied +`Program.cs`, `.csproj`, or package-version blocks that can drift. + +## Required shape + +1. Create Domain, Application, Infrastructure, Api, and Tests projects and add + them to `backend/AdminBackend.slnx`. +2. Add a separate PersistenceTests project when the service owns tenant-scoped + EF entities/query filters or another persistence mechanism whose security + behavior cannot be proven by Domain/Application unit tests. +3. Preserve inward references: Domain has no project dependency; Application + references Domain and the framework-agnostic shared kernel; Infrastructure + implements Application ports; Api composes Application/Infrastructure and + may reference the ASP.NET Core shared package; Tests reference only the + layers their boundary needs. +4. Use central package management. Add a version once to + `backend/Directory.Packages.props`; project files contain versionless + `PackageReference` entries. Never run an unreviewed latest-version upgrade as + part of scaffolding. + +## Application and domain + +- Follow `agenza-backend-use-case` for the first vertical slice. +- Use rich entities with `DomainResult`, handlers returning `Result`, and + `PersistenceResult` at technical persistence boundaries. Expected business + outcomes do not throw. +- Define a service-local UnitOfWork shape that matches its real transaction + boundary; do not copy another service's interface blindly. +- Register handlers and validators through the service's assembly-scanning + application extension rather than one registration per slice. + +## Tenant safety and persistence + +- Resource services use `Admin.Identity.Client`, an authorization filter, and + `TenantHeaderFilter` by default. `[IgnoreTenant]` is only for a reviewed, + genuinely tenant-free action. +- Tenant-owned aggregates inherit the service-local `TenantOwnedEntity` shape. + The save interceptor assigns the current tenant; handlers do not set or accept + arbitrary tenant ids. +- Apply shared auditable/tenant conventions from `DbContext.OnModelCreating`. + Do not add hand-written query filters or capture a tenant constant during + model construction. +- Use one schema and migrations-history table owned by the service. Any schema + change also uses `agenza-migration-safety`. +- Add persistence tests proving automatic tenant assignment and cross-tenant + query isolation. Manual smoke testing complements these tests; it does not + replace them. + +## API, runtime, and delivery + +- Add API versioning to business routes; do not version fixed OIDC protocol + endpoints. +- Register the service's audience/scope in identity-service and exercise both + allowed and denied access where the runtime smoke boundary applies. +- Add the project and database/resource dependencies to + `backend/AppHost/AppHost.cs`. Aspire remains the only local orchestrator; do + not add Docker Compose or application Dockerfiles. +- Add the service to `docs/MONOREPO.md` and its context to `docs/VISION.md`. +- Run the backend, governance, and any affected API-contract gates before + completion. + diff --git a/.claude/skills/agenza-backend-use-case/SKILL.md b/.claude/skills/agenza-backend-use-case/SKILL.md index 0a102ea..78ca096 100644 --- a/.claude/skills/agenza-backend-use-case/SKILL.md +++ b/.claude/skills/agenza-backend-use-case/SKILL.md @@ -1,823 +1,92 @@ --- name: agenza-backend-use-case description: > - Use whenever adding or changing business logic in any .NET backend service - under backend/ — a new command, query, entity, value object, repository - method, or endpoint, or any change to an existing one. Trigger on "add - endpoint", "implement [operation]", "create [entity]", "command", "query", - "handler", "validator", "vertical slice". Encodes this repo's CQRS/ - vertical-slice/Result-pattern conventions (docs/adr/0005, docs/adr/0012, - docs/adr/0014), layering, rich-domain, tenant-scoping, and testing rules. - Do NOT write backend business logic without reading it first — it also - documents patterns this codebase already tried and reverted, so an agent - that skips it is likely to reintroduce a fixed bug. + Use whenever adding or changing business logic in a .NET service under + backend/, including commands, queries, entities, value objects, repository + methods, endpoints, validators, or vertical slices. Trigger on "add + endpoint", "implement operation", "create entity", "command", "query", + "handler", or "validator". Enforces this repository's CQRS, Result flow, + rich-domain, tenant-safety, persistence, and test conventions and prevents + reintroducing the exception- and validator-based patterns reverted by ADRs + 0012 and 0014. --- -# Backend Use Case - -The reference implementation is `services-service`'s Tags vertical — open -these files and mirror their shape exactly (the templates below are a -direct copy of this feature's current, ADR-0014-compliant code): - -- `ServicesService.Domain/Entities/Tag.cs`, `ValueObjects/TagColor.cs` — entity/VO with invariants -- `ServicesService.Application/Tags/CreateTag/` — full command slice -- `ServicesService.Application/Tags/UpdateTag/` — same, plus `UpdateTagCommandExtensions.ApplyTo` -- `ServicesService.Application/Tags/TagPersistenceErrorMapper.cs` — persistence-conflict mapping -- `ServicesService.Application/Tags/TagResponse.cs` — DTO shared across the feature's operations -- `ServicesService.Application/Abstractions/` — ports (`ITagRepository`, `IUnitOfWork`) -- `ServicesService.Api/Controllers/TagsController.cs` — direct command binding + Result → HTTP mapping (docs/adr/0007) -- `ServicesService.Tests/Tags/CreateTag/` — handler + validator unit tests - -identity-service's `Tenants/ProvisionTenant/` slice is the second -reference — read it when the operation needs a database transaction -across more than one abstraction (see the UnitOfWork note below). - -## Decision tree — where does a given rule live? - -| The rule is about... | It lives in... | -| ---------------------------------------------------------- | ------------------------------------------ | -| Shape of the command's own data (required, length, format, numeric range, cross-field comparison within the same command) | **FluentValidation** validator, sync rules only | -| Current state of the application (existence, uniqueness, in-use, another aggregate) | The **handler** — a plain `if (...) return Result.Failure(...)` before persisting | -| A permanent invariant of the entity itself (a `Tag` can never have an empty name, a `Service`'s min duration can never exceed its max) | **`DomainResult`** from the entity's `Create`/`Update` | -| Data integrity / concurrency at the database boundary (a unique index catching a race the pre-check missed) | The database + **`PersistenceResult`**, mapped by a per-entity `*PersistenceErrorMapper` | -| A genuinely unexpected, unrecoverable technical failure (missing config, an unrecognized DB error, a framework guarantee) | **Exception** — the one case where throwing is still correct | - -## Hard prohibitions (these are reverted patterns — see docs/adr/0012, docs/adr/0014) - -Do **not** write any of the following. `scripts/architecture_guard.py` -fails the build on several of these; the rest are still real regressions -even where the guard can't catch them syntactically. - -- A repository (or any port) injected into a validator's constructor. -- `MustAsync`/`CustomAsync` on a FluentValidation rule that queries a - repository or the database. Validators in this repo are pure, synchronous - shape checks — nothing in them ever awaits. -- Throwing for an expected business outcome (validation failure, not-found, - conflict/duplicate, in-use, forbidden). Everything expected returns a - `Result`/`DomainResult`/`PersistenceResult`. -- Conventional `try/catch` in a handler to convert a business outcome. The - only handler-level `try/catch` in this codebase is - `IUnitOfWork.ExecuteInTransactionAsync`'s rollback-on-unexpected-failure - wrapper (identity-service) — never a catch that maps to a `Result`. -- `DuplicateEntityException` (deleted, docs/adr/0014 — a unique-constraint - race returns `PersistenceResult.Failure` instead). -- `BusinessExceptionHandler` (deleted — `Admin.SharedKernel.GenericExceptionHandler` - is the only exception handler; it exists purely for unexpected 500s). -- A null-forgiving `!` on a repository lookup that assumes some earlier - validator step already guaranteed existence. Validators here never do - existence checks (they take no repository dependency at all) — the - handler that needs the entity fetches it itself and returns - `Error.NotFound(...)` on a null, in the same method, before doing - anything else with it. -- A brand-new project/folder split for a feature that fits inside an - existing service's `Application///` shape. Only - create a new microservice for a genuinely new bounded context — see - `.skills/backend-new-microservice/SKILL.md`. -- Wiring MediatR, or any DI registration for a handler/validator by hand — - `AddXApplication()` assembly-scans for both; a new slice needs no - registration at all. - -## Build order (TDD — test first at each step) - -### 1. Domain entity or value object - -- If the entity does NOT belong to a tenant (rare — e.g. `Tenant` itself - in identity-service), inherit `{Service}.Domain.Common.BaseEntity` - directly — gives `Id`, `CreatedAt`/`CreatedBy`, `UpdatedAt`/ - `UpdatedBy`, `DeletedAt`/`DeletedBy`, `IsDeleted` for free - (docs/adr/0006). Call `base(id)` from your constructor; never set the - audit fields yourself, the EF interceptor does that. -- If the entity belongs to a tenant (the common case), inherit - `{Service}.Domain.Common.TenantOwnedEntity` instead — it already - inherits `BaseEntity` and implements `ITenantOwned` (`Guid TenantId - { get; }` + `void AssignTenant(Guid tenantId)`) for you, so don't - implement `ITenantOwned` or add an `AssignTenant` override on the - entity itself. The constructor never takes a `tenantId` parameter at - all — `TenantId` starts `Guid.Empty` and only `AssignTenant` (inherited) - can set it, throwing a plain `InvalidOperationException` on empty - (docs/adr/0009, docs/adr/0014) — the one entity-level path allowed to - throw instead of returning `DomainResult`, since it's only reachable via - an internal bug (`TenantHeaderFilter` already rejects a request with no/ - mismatched tenant before any handler runs). -- Public constructor becomes `private`; add a `public static - DomainResult Create(...)` factory that validates every - invariant and returns `DomainResult.Failure(new - DomainError("Widget.Invalid", message))` on the first invalid field - instead of throwing — never a raw `Exception`/`ArgumentException` - (docs/adr/0014). `DomainResult`/`DomainResult`/`DomainError` - (`{Service}.Domain/Common/`) already exist per service — copy them - once, not per entity. -- State-changing methods (`Update`, `Cancel`, `Reschedule`) return - `DomainResult` (not `void`) for the same reason — validate every new - value into a local before assigning any field, so a failure never - leaves the entity partially mutated. -- No public setters. Add a `private` parameterless constructor ONLY if EF - needs it, and keep it private. -- Tests: plain xUnit + AwesomeAssertions, no mocks needed — Domain has - zero dependencies. Cover `MarkCreated`/`MarkUpdated`/`MarkDeleted` - (inherited from `BaseEntity`) too — they count toward the coverage - gate. `AssignTenant` (if `ITenantOwned`) is the one exception to the - `DomainResult` rule — assert it throws `InvalidOperationException` on - an empty guid. - -### 2. Port (interface) in `Application/Abstractions/` - -- Narrow, intention-revealing methods (`Add`, `GetByIdAsync`, - `NameExistsAsync`) — not a generic interface. `Add`/`Remove` are - synchronous and only stage the change (no internal commit). -- If the entity is `ITenantOwned`, its methods do NOT take a tenant id - parameter — the DbContext scopes the query automatically (step 5, - docs/adr/0006). -- The **implementation** (step 5) extends - `Admin.SharedKernel.EntityFrameworkCore.RepositoryBase` for - the Add/Remove/Find/List boilerplate underneath this interface — the - port itself stays a plain, narrow interface. - -### 3. Command or query slice in `Application///` - -``` -Application/Tags/ -├── TagResponse.cs shared DTO (feature root) -├── TagPersistenceErrorMapper.cs shared persistence-conflict mapper (feature root) -└── CreateTag/ - ├── CreateTagCommand.cs : ICommand - ├── CreateTagCommandValidator.cs AbstractValidator - shape only, parameterless - └── CreateTagCommandHandler.cs : ICommandHandler -``` - -- A **command** mutates (`ICommand` if nothing to return, - `ICommand` otherwise); a **query** reads - (`IQuery`). Handler returns `Result` / `Result` - — never throws for an expected business outcome. Use - `Error.Validation/.NotFound/.Conflict/.Forbidden(code, message)`. -- Validator: **shape rules only**, parameterless constructor, no - repository, no `MustAsync`/`CustomAsync` — see the prohibitions above. -- Cross-aggregate rules needing a repository round-trip (existence, - uniqueness, in-use) live in the **handler**, checked in this order - before any mutation: not-found → duplicate/conflict → build/apply the - domain change → persist → map a persistence conflict. See - `CreateTagCommandHandler`/`UpdateTagCommandHandler`/`DeleteTagCommandHandler` - below for the exact shape, and `Application/Services/ServiceRelationshipLoader.cs` - for a multi-dependency version that loads Category/Tags exactly once - and reuses the same instances for both construction and the response. -- Constructor-injected ports only — no EF, no HttpClient, no ASP.NET - types in Application. -- Multiple writes that must succeed together → wrap in `IUnitOfWork`, - shaped to the real need (docs/adr/0005): a single - `Task> SaveChangesAsync(...)` if everything goes - through one `DbContext` (services-service's shape — lets Infrastructure - report a recognized unique-constraint violation without throwing), or a - Result-aware `ExecuteInTransactionAsync` if the operation spans - more than one abstraction that each commit independently, e.g. an EF - repository AND `UserManager` (identity-service's shape). -- Nothing to register by hand — each service's - `Application/DependencyInjection.cs` scans the assembly for handlers - and validators. -- If the handler constructs or mutates a domain entity from the - command's fields, put that mapping in a `{Operation}CommandExtensions.cs` - extension method beside the command (`ToModel(...)` for construction, - `ApplyTo(entity)` for mutation) instead of inlining it in `Handle(...)` - (docs/adr/0007). Both return `DomainResult`/`DomainResult` - respectively, so the handler checks `IsFailure` and maps via - `.Error.ToApplicationError()` before proceeding. - -### 4. Unit tests with NSubstitute - -- `Substitute.For()` per port used by the handler — no hand-written - fake classes (docs/adr/0006). Configure return values with - `.Returns(...)`; assert interaction with `.Received(1).Method(...)` / - `.DidNotReceive().Method(...)`. -- AwesomeAssertions, asserting on the `Result`: `result.IsSuccess`, - `result.Value.Xyz`, `result.Error.Type.Should().Be(ErrorType.Conflict)`. -- Test the happy path, the not-found path, the duplicate/conflict path, - and any `DomainResult.Failure` path the handler can still hit — a - handler unit test calls `Handle(...)` directly, bypassing the - validator, so it exercises paths production traffic never reaches. -- Validator tests use the synchronous `Validate(...)` (no `MustAsync` - rules exist to require `ValidateAsync`) and need no repository fakes at - all, since the validator takes none. - -### 5. Infrastructure adapter - -- Repository extends `Admin.SharedKernel.EntityFrameworkCore.RepositoryBase` - and implements the port (docs/adr/0006). `Add`/`Remove` only stage the - change — no `SaveChangesAsync` inside the repository (the handler - commits via `IUnitOfWork`). -- EF configuration lives in `Infrastructure/Persistence/Configurations/`. - The soft-delete query filter and `DeletedAt` index apply automatically - to every `BaseEntity`, and (if `ITenantOwned`) the tenant filter + - `TenantId` index too — the `DbContext` calls - `ApplyAuditableConventions(this, typeof(BaseEntity), typeof(ITenantOwned))` - once. Never add `HasQueryFilter` by hand. If the entity has a - uniqueness rule, add a unique index on a normalized column (see - `IX_Tags_TenantId_NameNormalized`) filtered with - `.HasFilter("\"DeletedAt\" IS NULL")` so a soft-deleted row doesn't - block reusing its unique value — this index, not the handler's - pre-check, is what actually guarantees uniqueness under concurrency - (see `agent-skills/agenza-migration-safety` for the migration itself). -- If the entity is tenant-owned, also pass `ICurrentTenantProvider` into - `AuditableEntitySaveChangesInterceptor`'s constructor so it can call - `AssignTenant` on a newly added entity automatically (docs/adr/0008). -- New tables → `dotnet ef migrations add ` from the Api project - directory. - -### 6. Controller (thin) - -- Constructor-inject `IDispatcher` (never a concrete handler type) — - nothing else. The global `TenantHeaderFilter` already rejected the - request with 403 before this action runs unless `X-Tenant-Id` matched - the token's `tenant_id` claim — mark the controller/action - `[IgnoreTenant]` instead if it genuinely isn't tenant-scoped. -- `[ApiVersion("1.0")]` + `[Route("api/v{version:apiVersion}/...")]` (or - `internal/v{version:apiVersion}/...` for M2M-only routes). -- **Bind the command/query directly as the action parameter — no local - `...Body` record** (docs/adr/0007). A route id binds into its own - `Guid id` parameter and gets merged into the command right before - dispatching: `command with { WidgetId = id }`. -- `await _dispatcher.Send(...)` / `.Query(...)` → - `result.ToActionResult(this, value => Ok(value))` (or `Created`/ - `NoContent`). No try/catch per exception type. -- `[Authorize]` by default; scope checks (`User.HasScope(...)`) for - M2M-only endpoints. - -### 7. Manual verification of the new endpoint - -There are no integration tests (docs/adr/0015) — CI runs unit tests only. -Before merging, run the service (`dotnet run --project services//{Service}.Api`) -and manually exercise the new endpoint: unauthenticated → 401, wrong -scope/tenant → 403, a validation failure → 400, duplicate name → 409, -unknown id → 404, happy path → expected status + persisted effect. - -## Definition of done - -```bash -dotnet build backend/AdminBackend.slnx -dotnet test backend/AdminBackend.slnx # unit tests only; coverage gate via Directory.Build.props/.targets -python scripts/architecture_guard.py # fails on any reverted pattern above -``` - -Both green, coverage gate passing, no new NU1903 (vulnerable package) -warnings, architecture guard clean. - ---- - -## Copy-paste templates - -A fictional **Widget** entity in a fictional **Widgets** feature — this is -a direct copy of Tags' current shape (see the reference files at the top), -renamed. Assume namespace root `{Service}` = your service's actual name. - -### Command with a response (Create-shaped) - -```csharp -// Application/Widgets/CreateWidget/CreateWidgetCommand.cs -using Admin.SharedKernel; - -namespace {Service}.Application.Widgets.CreateWidget; - -public sealed record CreateWidgetCommand(string Name) : ICommand; -``` - -```csharp -// Application/Widgets/CreateWidget/CreateWidgetCommandValidator.cs -using FluentValidation; -using {Service}.Domain.Entities; - -namespace {Service}.Application.Widgets.CreateWidget; - -public sealed class CreateWidgetCommandValidator : AbstractValidator -{ - public CreateWidgetCommandValidator() - { - RuleFor(command => command.Name) - .NotEmpty() - .MaximumLength(Widget.NameMaxLength); - } -} -``` - -```csharp -// Domain/Entities/Widget.cs -using {Service}.Domain.Common; - -namespace {Service}.Domain.Entities; - -public class Widget : TenantOwnedEntity -{ - public const int NameMaxLength = 80; - - public string Name { get; private set; } - - private Widget() - { - Name = string.Empty; // EF Core materialization only. - } - - private Widget(Guid id, string name) - : base(id) - { - Name = name; - } - - public static DomainResult Create(Guid id, string name) - { - var nameResult = ValidateName(name); - if (nameResult.IsFailure) - { - return DomainResult.Failure(nameResult.Error); - } - - return DomainResult.Success(new Widget(id, nameResult.Value)); - } - - public DomainResult Update(string name) - { - var nameResult = ValidateName(name); - if (nameResult.IsFailure) - { - return DomainResult.Failure(nameResult.Error); - } - - Name = nameResult.Value; - - return DomainResult.Success(); - } - - private static DomainResult ValidateName(string name) - { - var trimmed = name?.Trim() ?? string.Empty; - - if (trimmed.Length is 0 or > NameMaxLength) - { - return DomainResult.Failure(new DomainError( - "Widget.Invalid", - $"Name is required and must be at most {NameMaxLength} characters.")); - } - - return DomainResult.Success(trimmed); - } -} -``` - -```csharp -// Application/Widgets/CreateWidget/CreateWidgetCommandExtensions.cs -using {Service}.Domain.Common; -using {Service}.Domain.Entities; - -namespace {Service}.Application.Widgets.CreateWidget; - -public static class CreateWidgetCommandExtensions -{ - public static DomainResult ToModel(this CreateWidgetCommand command) => - Widget.Create(Guid.CreateVersion7(), command.Name); -} -``` - -```csharp -// Application/Widgets/WidgetPersistenceErrorMapper.cs -using Admin.SharedKernel; -using Microsoft.Extensions.Logging; -using {Service}.Application.Abstractions; - -namespace {Service}.Application.Widgets; - -public static class WidgetPersistenceErrorMapper -{ - private const string NameConstraint = "IX_Widgets_TenantId_NameNormalized"; - - public static Error Map(PersistenceError error, string name, ILogger logger) - { - if (error.ConstraintName == NameConstraint) - { - return Error.Conflict("Widget.DuplicateName", $"A widget named '{name}' already exists."); - } - - logger.LogError( - "Unrecognized unique constraint {ConstraintName} violated while saving a Widget", - error.ConstraintName); - return Error.Conflict("Widget.DuplicateConflict", "Could not save the widget due to a data conflict."); - } -} -``` - -```csharp -// Application/Widgets/CreateWidget/CreateWidgetCommandHandler.cs -using Admin.SharedKernel; -using Microsoft.Extensions.Logging; -using {Service}.Application.Abstractions; - -namespace {Service}.Application.Widgets.CreateWidget; - -public sealed class CreateWidgetCommandHandler : ICommandHandler -{ - private readonly IWidgetRepository _repository; - private readonly IUnitOfWork _unitOfWork; - private readonly ILogger _logger; - - public CreateWidgetCommandHandler( - IWidgetRepository repository, IUnitOfWork unitOfWork, ILogger logger) - { - _repository = repository; - _unitOfWork = unitOfWork; - _logger = logger; - } - - public async Task> Handle(CreateWidgetCommand command, CancellationToken cancellationToken) - { - if (await _repository.NameExistsAsync(command.Name, excludeId: null, cancellationToken)) - { - return Result.Failure( - Error.Conflict("Widget.DuplicateName", $"A widget named '{command.Name}' already exists.")); - } - - var widgetResult = command.ToModel(); - if (widgetResult.IsFailure) - { - return Result.Failure(widgetResult.Error.ToApplicationError()); - } - - var widget = widgetResult.Value; - _repository.Add(widget); - - var saveResult = await _unitOfWork.SaveChangesAsync(cancellationToken); - if (saveResult.IsFailure) - { - return Result.Failure(WidgetPersistenceErrorMapper.Map(saveResult.Error, command.Name, _logger)); - } - - return WidgetResponse.FromWidget(widget); - } -} -``` - -No `ICurrentTenantProvider` needed in this handler at all — the tenant -is assigned automatically on save (docs/adr/0008). Only the `DbContext` -(query scoping) and `AuditableEntitySaveChangesInterceptor` (assignment) -need it; see step 5. - -### Command with a response and a route id (Update-shaped) - -```csharp -// Application/Widgets/UpdateWidget/UpdateWidgetCommand.cs -using Admin.SharedKernel; - -namespace {Service}.Application.Widgets.UpdateWidget; - -public sealed record UpdateWidgetCommand(Guid WidgetId, string Name) : ICommand; -``` - -```csharp -// Application/Widgets/UpdateWidget/UpdateWidgetCommandValidator.cs -using FluentValidation; -using {Service}.Domain.Entities; - -namespace {Service}.Application.Widgets.UpdateWidget; - -public sealed class UpdateWidgetCommandValidator : AbstractValidator -{ - public UpdateWidgetCommandValidator() - { - RuleFor(command => command.WidgetId).NotEmpty(); - - RuleFor(command => command.Name) - .NotEmpty() - .MaximumLength(Widget.NameMaxLength); - } -} -``` - -Cross-aggregate rules (existence, uniqueness) never live in the validator — -that's the handler's job below. `WidgetId` is still shape-validated even -though it's route-sourced: the controller merges the route id in via -`with` BEFORE dispatching (see the Controller template below). - -```csharp -// Application/Widgets/UpdateWidget/UpdateWidgetCommandExtensions.cs -using {Service}.Domain.Common; -using {Service}.Domain.Entities; - -namespace {Service}.Application.Widgets.UpdateWidget; - -public static class UpdateWidgetCommandExtensions -{ - public static DomainResult ApplyTo(this UpdateWidgetCommand command, Widget widget) => - widget.Update(command.Name); -} -``` - -```csharp -// Application/Widgets/UpdateWidget/UpdateWidgetCommandHandler.cs -using Admin.SharedKernel; -using Microsoft.Extensions.Logging; -using {Service}.Application.Abstractions; - -namespace {Service}.Application.Widgets.UpdateWidget; - -public sealed class UpdateWidgetCommandHandler : ICommandHandler -{ - private readonly IWidgetRepository _repository; - private readonly IUnitOfWork _unitOfWork; - private readonly ILogger _logger; - - public UpdateWidgetCommandHandler( - IWidgetRepository repository, IUnitOfWork unitOfWork, ILogger logger) - { - _repository = repository; - _unitOfWork = unitOfWork; - _logger = logger; - } - - public async Task> Handle(UpdateWidgetCommand command, CancellationToken cancellationToken) - { - var widget = await _repository.GetByIdAsync(command.WidgetId, cancellationToken); - if (widget is null) - { - return Result.Failure( - Error.NotFound("Widget.NotFound", $"Widget '{command.WidgetId}' was not found.")); - } - - if (await _repository.NameExistsAsync(command.Name, command.WidgetId, cancellationToken)) - { - return Result.Failure( - Error.Conflict("Widget.DuplicateName", $"A widget named '{command.Name}' already exists.")); - } - - var applyResult = command.ApplyTo(widget); - if (applyResult.IsFailure) - { - return Result.Failure(applyResult.Error.ToApplicationError()); - } - - var saveResult = await _unitOfWork.SaveChangesAsync(cancellationToken); - if (saveResult.IsFailure) - { - return Result.Failure(WidgetPersistenceErrorMapper.Map(saveResult.Error, command.Name, _logger)); - } - - return WidgetResponse.FromWidget(widget); - } -} -``` - -### Command with no response (Delete-shaped) - -```csharp -// Application/Widgets/DeleteWidget/DeleteWidgetCommand.cs -using Admin.SharedKernel; - -namespace {Service}.Application.Widgets.DeleteWidget; - -public sealed record DeleteWidgetCommand(Guid WidgetId) : ICommand; -``` - -```csharp -// Application/Widgets/DeleteWidget/DeleteWidgetCommandHandler.cs -using Admin.SharedKernel; -using Microsoft.Extensions.Logging; -using {Service}.Application.Abstractions; - -namespace {Service}.Application.Widgets.DeleteWidget; - -public sealed class DeleteWidgetCommandHandler : ICommandHandler -{ - private readonly IWidgetRepository _repository; - private readonly IUnitOfWork _unitOfWork; - private readonly ILogger _logger; - - public DeleteWidgetCommandHandler( - IWidgetRepository repository, IUnitOfWork unitOfWork, ILogger logger) - { - _repository = repository; - _unitOfWork = unitOfWork; - _logger = logger; - } - - public async Task Handle(DeleteWidgetCommand command, CancellationToken cancellationToken) - { - var widget = await _repository.GetByIdAsync(command.WidgetId, cancellationToken); - if (widget is null) - { - return Result.Failure(Error.NotFound("Widget.NotFound", $"Widget '{command.WidgetId}' was not found.")); - } - - _repository.Remove(widget); - - var saveResult = await _unitOfWork.SaveChangesAsync(cancellationToken); - if (saveResult.IsFailure) - { - return Result.Failure(WidgetPersistenceErrorMapper.Map(saveResult.Error, widget.Name, _logger)); - } - - return Result.Success(); - } -} -``` - -### Query (List/Get-shaped) - -```csharp -// Application/Widgets/ListWidgets/ListWidgetsQuery.cs -using Admin.SharedKernel; - -namespace {Service}.Application.Widgets.ListWidgets; - -public sealed record ListWidgetsQuery : IQuery>; -``` - -```csharp -// Application/Widgets/ListWidgets/ListWidgetsQueryHandler.cs -using Admin.SharedKernel; -using {Service}.Application.Abstractions; -using {Service}.Application.Widgets; - -namespace {Service}.Application.Widgets.ListWidgets; - -public sealed class ListWidgetsQueryHandler : IQueryHandler> -{ - private readonly IWidgetRepository _repository; - - public ListWidgetsQueryHandler(IWidgetRepository repository) - { - _repository = repository; - } - - public async Task>> Handle( - ListWidgetsQuery query, CancellationToken cancellationToken) - { - var widgets = await _repository.ListAsync(cancellationToken); - IReadOnlyList response = widgets.Select(WidgetResponse.FromWidget).ToList(); - return Result.Success(response); - } -} -``` - -No validator needed unless the query takes user input. - -### Shared feature DTO (once per feature, not per operation) - -```csharp -// Application/Widgets/WidgetResponse.cs -using {Service}.Domain.Entities; - -namespace {Service}.Application.Widgets; - -public sealed record WidgetResponse(Guid Id, string Name) -{ - public static WidgetResponse FromWidget(Widget widget) => new(widget.Id, widget.Name); -} -``` - -### Controller (dispatch + Result → HTTP) - -```csharp -using Admin.SharedKernel; -using Asp.Versioning; -using Microsoft.AspNetCore.Mvc; -using {Service}.Application.Widgets.CreateWidget; -using {Service}.Application.Widgets.DeleteWidget; -using {Service}.Application.Widgets.ListWidgets; -using {Service}.Application.Widgets.UpdateWidget; - -namespace {Service}.Api.Controllers; - -[ApiController] -[ApiVersion("1.0")] -[Route("api/v{version:apiVersion}/widgets")] -public class WidgetsController : ControllerBase -{ - private readonly IDispatcher _dispatcher; - - public WidgetsController(IDispatcher dispatcher) - { - _dispatcher = dispatcher; - } - - [HttpGet] - public async Task List(CancellationToken cancellationToken) - { - var result = await _dispatcher.Query(new ListWidgetsQuery(), cancellationToken); - return result.ToActionResult(this, widgets => Ok(widgets)); - } - - [HttpPost] - public async Task Create(CreateWidgetCommand command, CancellationToken cancellationToken) - { - var result = await _dispatcher.Send(command, cancellationToken); - return result.ToActionResult(this, widget => Created($"/api/v1/widgets/{widget.Id}", widget)); - } - - [HttpPut("{id:guid}")] - public async Task Update(Guid id, UpdateWidgetCommand command, CancellationToken cancellationToken) - { - var result = await _dispatcher.Send(command with { WidgetId = id }, cancellationToken); - return result.ToActionResult(this, widget => Ok(widget)); - } - - [HttpDelete("{id:guid}")] - public async Task Delete(Guid id, CancellationToken cancellationToken) - { - var result = await _dispatcher.Send(new DeleteWidgetCommand(id), cancellationToken); - return result.ToActionResult(this, NoContent); - } -} -``` - -### Unit tests with NSubstitute (handler + validator) - -```csharp -// Tests/Widgets/CreateWidget/CreateWidgetCommandHandlerTests.cs -using Admin.SharedKernel; -using Microsoft.Extensions.Logging; -using {Service}.Application.Abstractions; -using {Service}.Application.Widgets.CreateWidget; -using {Service}.Domain.Entities; - -namespace {Service}.Tests.Widgets.CreateWidget; - -public class CreateWidgetCommandHandlerTests -{ - private readonly IWidgetRepository _repository = Substitute.For(); - private readonly IUnitOfWork _unitOfWork = Substitute.For(); - private readonly ILogger _logger = Substitute.For>(); - private readonly CreateWidgetCommandHandler _handler; - - public CreateWidgetCommandHandlerTests() - { - _repository.NameExistsAsync(Arg.Any(), Arg.Any(), Arg.Any()).Returns(false); - _unitOfWork.SaveChangesAsync(Arg.Any()).Returns(PersistenceResult.Success(1)); - _handler = new CreateWidgetCommandHandler(_repository, _unitOfWork, _logger); - } - - [Fact] - public async Task Handle_WithValidCommand_PersistsAndReturnsTheValue() - { - var result = await _handler.Handle(new CreateWidgetCommand("Example"), CancellationToken.None); - - result.IsSuccess.Should().BeTrue(); - result.Value.Name.Should().Be("Example"); - _repository.Received(1).Add(Arg.Is(w => w.Id == result.Value.Id)); - await _unitOfWork.Received(1).SaveChangesAsync(Arg.Any()); - } - - [Fact] - public async Task Handle_WithDuplicateName_ReturnsConflictWithoutPersisting() - { - _repository.NameExistsAsync("Example", null, Arg.Any()).Returns(true); - - var result = await _handler.Handle(new CreateWidgetCommand("Example"), CancellationToken.None); - - result.IsFailure.Should().BeTrue(); - result.Error.Code.Should().Be("Widget.DuplicateName"); - _repository.DidNotReceive().Add(Arg.Any()); - await _unitOfWork.DidNotReceive().SaveChangesAsync(Arg.Any()); - } - - [Fact] - public async Task Handle_WithInvalidName_ReturnsFailure() - { - var result = await _handler.Handle(new CreateWidgetCommand(""), CancellationToken.None); - - result.IsFailure.Should().BeTrue(); - result.Error.Code.Should().Be("Widget.Invalid"); - } -} -``` - -```csharp -// Tests/Widgets/CreateWidget/CreateWidgetCommandValidatorTests.cs -using {Service}.Application.Widgets.CreateWidget; - -namespace {Service}.Tests.Widgets.CreateWidget; - -public class CreateWidgetCommandValidatorTests -{ - private readonly CreateWidgetCommandValidator _validator = new(); - - [Fact] - public void Validate_WithValidCommand_Passes() - { - _validator.Validate(new CreateWidgetCommand("Example")).IsValid.Should().BeTrue(); - } - - [Fact] - public void Validate_WithEmptyName_Fails() - { - _validator.Validate(new CreateWidgetCommand("")).IsValid.Should().BeFalse(); - } -} -``` - -No repository fake needed - the validator takes no dependencies. -Duplicate-name coverage lives in `CreateWidgetCommandHandlerTests` instead. - -### Automatic tenant assignment has no automated regression test - -`{Service}.Tests` references only Domain + Application (mocked ports, no -EF Core) — deliberately, to keep the unit-test tier free of Infrastructure/ -EF dependencies (docs/adr/0015). This means the -`AuditableEntitySaveChangesInterceptor` behavior docs/adr/0008 depends on — -a newly added entity with `TenantId == Guid.Empty` gets the current tenant -assigned on save — has no automated coverage. The first time a service -gets a tenant-owned entity, manually verify this by running the service -and creating a record through its API, confirming the persisted row's -`TenantId` matches the caller's tenant. +# Backend use case + +Before writing code, inspect the closest production slice and its tests. Use +compiled code, the current solution, and generated contracts as templates; +never copy a full implementation from prose. Read `backend/AGENTS.md` and only +the ADRs routed by `docs/adr/README.md` for the affected concern. + +## Put each rule in one layer + +| Rule | Owner | +| --- | --- | +| Request shape, required fields, format, range, cross-field input comparison | Synchronous FluentValidation validator | +| Existence, uniqueness pre-check, in-use state, another aggregate | Handler | +| Permanent entity/value-object invariant | Domain factory or mutation returning `DomainResult` | +| Race-safe uniqueness and relational integrity | Database constraint plus `PersistenceResult` mapping | +| Unexpected or unrecoverable technical failure | Exception | + +Expected validation, not-found, conflict, in-use, and authorization outcomes +never throw. Do not inject repositories into validators, use repository-backed +`MustAsync`/`CustomAsync`, add business-exception types or handlers, catch an +expected outcome in a handler, or use `!` after a lookup that can be absent. + +## Build the smallest vertical slice + +1. **Domain.** Tenant-owned entities inherit the service's + `TenantOwnedEntity`; tenant-free entities inherit `BaseEntity`. Keep setters + private. Factories and state changes validate invariants before mutation and + return `DomainResult`; audit fields and tenant assignment remain framework + responsibilities. +2. **Port.** Add a narrow intent-revealing interface under + `Application/Abstractions`. Repository methods do not accept a tenant id; + the live `DbContext` applies tenant filtering. `Add` and `Remove` stage work + and do not commit internally. +3. **Slice.** Put the command/query, synchronous validator, handler, and any + operation-specific mapping under `Application///`. + Application depends only on ports and domain types. Use an operation mapping + extension when command-to-domain construction or mutation would otherwise + obscure the handler. +4. **Handler.** Check not-found and conflicts before mutation, map domain + failures explicitly, stage persistence, commit through the service's + `IUnitOfWork`, and map recognized persistence conflicts to application + errors. Use a transaction only when multiple writes must succeed together. +5. **Infrastructure.** Implement the port with the shared repository base and + auditable conventions. Do not add tenant or soft-delete query filters by + hand. Add tenant-scoped indexes and foreign keys where the business rule + requires them. Any schema change also triggers + `.agents/skills/agenza-migration-safety`. +6. **API.** Keep controllers thin: authorize by default, bind the command or + query directly, merge route ids immediately before dispatch, and use the + shared Result-to-HTTP mapper. `[IgnoreTenant]` requires a genuinely + tenant-free operation. Do not add local body DTOs that duplicate the + command or catch business exceptions. + +Create a new service only for a justified business context and follow +`.agents/skills/agenza-backend-new-service`; a new feature normally belongs in +an existing service. + +## Test the affected boundaries + +- Domain tests cover factories, mutations, invariant failures, audit behavior, + and tenant assignment programming guards without mocks. +- Handler tests use NSubstitute ports and cover success, not-found, conflict, + reachable domain failure, persistence failure, and required interactions. +- Validator tests call synchronous `Validate` and need no repository fake. +- Persistence behavior involving tenant assignment, global filters, indexes, + or foreign keys requires the narrow persistence-test tier established by the + current solution and ADR index. +- Controller, OpenAPI, authentication, or runtime-boundary changes require the + applicable contract and smoke checks documented in `docs/QUALITY.md` and CI. + +Do not infer the available test tiers from a historical ADR. Inspect the +solution, workflows, and existing test projects before deciding what applies. + +## Complete + +Run every backend and governance command in `backend/AGENTS.md` and root +`AGENTS.md`. Report the actual build, test, coverage, migration, contract, and +smoke results that apply; do not call the task done while a required gate is +red. diff --git a/.claude/skills/agenza-exception-flow-audit/SKILL.md b/.claude/skills/agenza-exception-flow-audit/SKILL.md index 0e35db4..e6aeff1 100644 --- a/.claude/skills/agenza-exception-flow-audit/SKILL.md +++ b/.claude/skills/agenza-exception-flow-audit/SKILL.md @@ -58,7 +58,7 @@ A table, one row per occurrence: | --- | --- | --- | --- | --- | --- | --- | For every row classified **Expected outcome**, describe the fix in terms of -`agent-skills/agenza-backend-use-case`'s decision tree (which layer's +`.agents/skills/agenza-backend-use-case`'s decision tree (which layer's `Result` type should carry this instead, and where the check belongs — validator vs. handler vs. persistence). @@ -72,4 +72,4 @@ validator vs. handler vs. persistence). explicitly-allowed pattern (docs/adr/0014), not a violation. - If a finding would change how an error is reported to a caller (e.g. changing an HTTP status code), that's a contract change — flag it for - `agent-skills/agenza-api-contract-review` too, don't fix it silently. + `.agents/skills/agenza-api-contract-review` too, don't fix it silently. diff --git a/.claude/skills/agenza-frontend-exploratory-qa/SKILL.md b/.claude/skills/agenza-frontend-exploratory-qa/SKILL.md new file mode 100644 index 0000000..336ff92 --- /dev/null +++ b/.claude/skills/agenza-frontend-exploratory-qa/SKILL.md @@ -0,0 +1,65 @@ +--- +name: agenza-frontend-exploratory-qa +description: > + Use to perform a review-only exploratory test of a frontend screen in a + browser, covering functional behavior, failure paths, usability, + accessibility, responsiveness, and user-visible security risks. Trigger on + "test this screen", "exploratory QA", "review accessibility", "teste esta + tela", or "faça um QA da interface". Produces an evidence-based pt-BR report + and never edits code or performs destructive real-world actions. +--- + +# Frontend exploratory QA + +## Establish safe scope + +1. Read the root and frontend `AGENTS.md`. For the admin frontend, also read + `../agenza-frontend-feature/references/page-ui-conventions.md`. +2. Identify the screen, intended user, expected primary flow, environment, test + data, and explicit restrictions from the request and repository evidence. +3. Confirm that any state-changing test is safe for the identified environment. + Never delete real data, send messages, make payments, change production + state, or perform an irreversible action without explicit authorization. +4. If the environment or impact cannot be established, continue with read-only + checks and report the blocked scenarios instead of assuming permission. + +## Explore + +- Map the visible controls, navigation, main path, loading, empty, success, and + error states before interacting. +- Exercise the primary flow and safe alternatives: cancel, close, back, retry, + refresh, duplicate submission, invalid input, boundary lengths, whitespace, + accents, special characters, and interrupted or slow responses when the + environment supports them. +- Check that expected failures preserve user input, explain recovery, and do not + expose stack traces, secrets, tenant data, or unauthorized actions. +- Use keyboard-only navigation. Verify logical focus order, visible focus, + accessible names, field-error association, dialog focus management, and + operation without color alone. +- Inspect desktop and 375 px mobile layouts, zoom to 200% when practical, and + check overflow, truncation, touch targets, overlays, tables, and virtual + keyboard obstruction. +- Compare the refreshed state with the displayed state to catch stale, + duplicated, or lost data. + +Do not call an assumption a defect. Reproduce a suspected defect twice when it +is safe, record the exact observed result, and distinguish confirmed bugs, +risks, usability problems, and suggestions. Capture screenshots or other +objective evidence when the available browser tooling supports it. + +## Report in pt-BR + +Lead with an approval recommendation: approve, approve with reservations, or do +not approve. Then report: + +1. Tested flows and untested scenarios with reasons. +2. Confirmed findings ordered by critical, high, medium, and low severity. +3. For each finding: category, reproduction steps, actual and expected result, + user/business impact, evidence, recommended fix, and verification criterion. +4. Accessibility, responsiveness, and UX observations that are not confirmed + functional bugs. +5. The five highest-priority follow-ups, balancing impact, frequency, and fix + effort. + +This skill is diagnostic. Do not edit code during the QA pass; implementation +requires a separate explicit request and the frontend feature skill. diff --git a/.claude/skills/agenza-frontend-feature/SKILL.md b/.claude/skills/agenza-frontend-feature/SKILL.md index 8c7f3cb..51d0a1f 100644 --- a/.claude/skills/agenza-frontend-feature/SKILL.md +++ b/.claude/skills/agenza-frontend-feature/SKILL.md @@ -1,667 +1,116 @@ --- name: agenza-frontend-feature description: > - Use whenever building or changing a feature in apps/admin-frontend — - React components, pages, hooks, forms, Zod schemas, use cases, or HTTP - calls. Trigger on "let's build [feature]", "implement [feature]", "add a - page/form/hook", or when the user provides an API spec for a resource. - Covers this project's feature-based Clean Architecture layering (ADR 009: - app/, features/{auth,catalog}/, shared/), React Hook Form + Zod forms, - structured server-error-to-field mapping, out-of-order-response and - inline-creation state handling, shadcn/ui usage, accessibility, dark - mode, mobile, comment policy, and pt-BR text rules. Do NOT proceed - without reading it — several conventions here differ from generic React - tutorials and from older, now-superseded guidance for this same project. + Use whenever changing React or TypeScript under apps/admin-frontend, + including pages, hooks, forms, domain models, repositories, generated + contracts, tests, auth, or shared UI. Routes the task to the minimum + required frontend references and enforces this repository's Result-based, + feature-oriented architecture. Read it before implementation because its + conventions intentionally differ from generic React tutorials. --- -# Frontend Feature - -## Physical layout (ADR 009) - -```text -src/ - app/ bootstrap, routing, DI wiring - main.tsx composition root: the only createAppContainer() call - App.tsx - routes/ router.tsx, RouteErrorElement - providers/ AppProviders, AppContainerContext, useAppContainer - composition/ container.ts - the only place allowed to construct - concrete repository/auth implementations - layouts/ AdminLayout - pages/ stub pages not yet promoted to their own feature - - features/ - auth/ - domain/ User, Tenant, Session, their errors - application/ AuthRepository port, 4 use cases, TenantContext - infrastructure/ OidcAuthRepository, createUserManager, oidc mapper - presentation/ AuthProvider, useAuth, TenantBoundary, ProtectedRoute, - LoginPage, CallbackPage - index.ts public API - everything outside this feature imports - through here, never a deep path into the above - - catalog/ Categories, Services - one feature, they - collaborate in the same business context. Tags - was removed from the frontend (docs/adr/016 in - this app's ADRs) - the backend Tag domain/API - is intentionally retained, unrelated to this - feature's current frontend shape - domain/ Category, Service entities + their errors - application/ 3 repository ports, 12 use cases - infrastructure/ Api*Repository, mappers, generated/ (OpenAPI types) - presentation/ every entity folder (categories/, services/) - shares the same internal shape - location alone - tells you a file's role: - / - Page.tsx composition shell (stays at entity root by - default; Categories uses pages/, ADR 012) - hooks/ data hook (useCategories/useServices) - + controller hook (useXPage) + any sub-hooks - (useServiceEditor, useServiceDeletion, ...) - components/ presentational pieces: tables, dialogs, - field-groups - forms/ the entity's create/edit form + its zod - schema + its own fieldMaps.ts (never shared - across entities - see "Forms" below) - models/ services/ only - pure, non-React view-model/ - formatting logic (servicePresentationModels, - serviceFormatters); categories has no - equivalent, so no models/ for it - index.ts public API - - shared/ - domain/ DomainError - the base class every entity error extends - application/ AppError, HttpClient port, SessionEventBus port, - RequestSession (atomic per-request session snapshot) - infrastructure/ - http/ AuthenticatedHttpClient, ApiError, ProblemDetails, - mapErrorToAppError, NetworkError, TimeoutError - InMemorySessionEventBus.ts - presentation/ - components/ PageHeader, StatusMessage, ErrorBoundary, - CollectionFeedback, DeleteConfirmationDialog, etc. - hooks/ useAsync, useDebouncedValue, useCreateInline, - useDialogTarget, useDeleteConfirmation - forms/ serverFormError.ts (mapApiErrorToForm) - providers/ ThemeProvider - - components/ui/ shadcn/ui primitives - stay at this top-level path, - lib/utils.ts NOT moved into shared/ (see below) -``` - -**`src/components/ui/**` and `src/lib/utils.ts` are exceptions to the -feature layout** — shadcn's CLI generates every `components/ui/*.tsx` file -importing `@/lib/utils` by a fixed convention; moving either would mean -hand-editing generated files just to accommodate the reorganization, which -this project's own rules prohibit (see "Build from existing components" -below). They stay exactly where `npx shadcn add` puts them. - -A feature vertical is a full slice inside its feature's four layers: - -```text -features//domain/ → plain TS class, no framework deps -features//application/ → repository interface (port) + use cases -features//infrastructure/ → implements the port via HttpClient -features//presentation/ → hooks built on useAsync, forms, pages -``` - -For translating an external API spec into the DTO/mapper/MSW-handler seam, -use `apps/admin-frontend/.skills/admin-api-contract/SKILL.md` alongside -this skill. For TypeScript-strict-mode test gotchas and mock-strategy-per- -layer rules, use `apps/admin-frontend/.skills/admin-tdd-conventions/SKILL.md`. -This skill governs everything between those two: architecture, forms, -state, UI, and completion criteria. - ---- - -## Pre-conditions before writing any code - -1. **Get the API spec** from the user before touching infrastructure. - Ask for: endpoint paths, HTTP methods, request shape, response shape, - error codes/shapes. Never invent field names — this is one of the - question-policy triggers in the root `AGENTS.md` (changes a contract). -2. **Check whether `HttpClient` exists** at - `src/shared/application/HttpClient.ts` (implemented by - `AuthenticatedHttpClient` in `src/shared/infrastructure/http/`). Every - REST repository depends on it; it already exists for every current - feature. -3. **Decide whether this is a new feature or belongs in an existing - one.** A resource that collaborates closely with Categories/Services - (shares forms, cross-references, or the same backend service) belongs - in `features/catalog/`; a genuinely independent domain gets its own - `features//` following the same four-layer shape. -4. **Identify which use cases the current page actually needs.** Don't - build every possible use case upfront. - -For authentication work, preserve the repo's fail-closed flow: - -- `/login` automatically starts the OIDC redirect once authentication state - is known; it is an informative transition/recovery screen, not a second - “Entrar” confirmation. Pass the current `light` or `dark` theme through - the OIDC authorization request so the identity credential page can apply - it before rendering. -- Map provider failures inside auth infrastructure to `AuthFlowError`. - Presentation shows a stable support code, a specific curated pt-BR - explanation, the next recovery action, and tells the user what context to - send when requesting help without exposing raw technical details or asking - them to share a password. A generic “contacte o administrador” fallback is - not sufficient for an authentication failure. -- A silent renewal may update tokens and expiry only. If `user.id` or - `tenant.id` differs from the cached session, clear the OIDC user and require - a full login before any request can use the new identity. - ---- - -## Comments — minimum of the minimum, by default zero - -Default to no comment. Identifiers, types, and structure carry the -meaning — a comment restating what a well-named function/prop/hook -already says is waste. Add a one-line comment (never a paragraph, never a -JSDoc block on a clearly named interface/hook/prop/entity) only when a -careful senior reviewer would still get it wrong without it: a security/ -tenant-isolation default, a concurrency/race guard, a genuine React/ -Radix/RHF/Zod/browser quirk, or an unavoidable lint suppression. -Architectural rationale belongs in `docs/adr/` — reference it in one -short clause at most (`see docs/adr/0006`), never restate it. If a -mechanism needs a paragraph to explain, simplify the mechanism/names/ -types first rather than documenting the complexity. This is the same bar -as `apps/admin-frontend/AGENTS.md` and `backend/AGENTS.md`. - ---- - -## Step-by-step build order - -### 1. Domain entity (TDD) - -`features//domain/entities/EntityName.ts` — zero imports from -React, that feature's own `application/`, `infrastructure/`, or -`presentation/`, and zero imports from another feature. Private -constructor + static `create(input)` factory that validates invariants -and returns `Result` -(`shared/application/Result.ts`) instead of throwing (docs/adr/014, -docs/adr/015 — both Catalog's `Category.ts` and Auth's -`Session.ts`/`User.ts`/`Tenant.ts` follow this). Every caller composes -with `flatMapResult`/`combineResults`, or plain early-return `Result` -branching for a short sequential chain with heterogeneous error types -(see `mapOidcUserToSession`, `features/auth/infrastructure/`) — never -`try/catch`. A mapper that turns a domain validation failure arising from -an untrusted API response into a curated `AppError` uses -`shared/infrastructure/http/malformedResponseError.ts`, not its own -message. `useAsync` (`shared/presentation/hooks/useAsync.ts`) takes -`() => Promise>`, not a throwing `() => Promise`. - -A test fixture that needs a known-valid entity (most test files touching -auth or catalog do) imports `Tenant`/`User`/`Session`/`Category` -from `src/test/fixtures/{authEntityFixtures,unwrapResult}.ts` instead of -the real `domain/entities/` path — those re-export the same `create()` -call shape already unwrapped, so call sites read exactly like before -without every test wrapping every call in `unwrapResult(...)`. Only each -entity's own `*.test.ts` imports the real class directly, since it -specifically asserts on both the success and failure `Result` shapes. - -Every feature vertical (Catalog now, Auth now, a future one like -Services) follows this same Result convention — there is no throwing -variant left to mirror. - -No constructor parameter property shorthand (`erasableSyntaxOnly`) — -explicit field declarations + assignment in the constructor body. Optional -fields: `if (value !== undefined) { this.field = value }`, never a direct -assignment of a possibly-`undefined` value (`exactOptionalPropertyTypes`). -`strict: true` — never `any`; if a value's shape is genuinely unknown at a -boundary, type it `unknown` and narrow it, never widen with `any`. - -### 2. Repository interface (no test needed) - -`features//application/repositories/FeatureRepository.ts` — -interface only. Every method takes `tenantContext: TenantContext` -(imported from `@/features/auth`, never from its internal path) as its -first parameter. Returns domain entities, never raw DTOs. `Promise` for nullable results. - -### 3. Use cases (TDD) - -`features//application/use-cases/FeatureName/UseCaseName.ts` — -one class per use case, explicit constructor body (no shorthand): - -```typescript -export class ListServices { - private readonly serviceRepository: ServiceRepository; - - constructor(serviceRepository: ServiceRepository) { - this.serviceRepository = serviceRepository; - } -} -``` - -Test with hand-written fake repositories (`.skills/admin-tdd-conventions`). -Add a shared fake to -`features//application/test-helpers/createFakeFeatureRepository.ts` -after the second use case needs it. - -### 4. Wire into the container - -Add to `AppContainer`'s facade interface and `createAppContainer()` in -`app/composition/container.ts` — the **only** place allowed to construct -concrete repository implementations. Import the concrete classes from the -feature's `index.ts` (`@/features/`), not a deep path — see -docs/adr/009's "Execution" section for why `index.ts` re-exports -composition-only wiring alongside the genuinely public surface. - -### 5. Infrastructure mapper (TDD) - -`features//infrastructure/mappers/featureMapper.ts` — pure -function `mapApiDtoToDomainEntity(dto: FeatureDto): Feature`. Test every -field mapping and every validation failure path. - -### 6. Infrastructure repository (TDD with MSW) - -`features//infrastructure/repositories/ApiFeatureRepository.ts` -— implements the port, takes `HttpClient` in its constructor (explicit -field pattern). Tests use MSW handlers in -`src/test/mocks/handlers/featureHandlers.ts`, registered in -`src/test/mocks/handlers/index.ts`. `onUnhandledRequest: 'error'` is -global — any call without a registered handler fails loudly. A test mock -handler typing a fixture against a feature's internal DTO type -(`import type { CategoryDto } from '@/features/catalog/infrastructure/ -mappers/categoryMapper'`) is the one place allowed to import a feature's -internals directly from outside it — `src/test/**` is exempt from the -public-API-only rule (ESLint + `architecture_guard.py` both carve this -out explicitly). - -### 7. Presentation hook (TDD) — build on `useAsync`, not a new pattern - -`shared/presentation/hooks/useAsync.ts` is the one shared "call an async -function, track loading/data/error" primitive — every feature hook -(`useCategories`, `useServices` — and `AuthProvider` for the -shared session) builds on it instead of a bespoke `useState`/`useEffect` -pair or a server-state library (see "Prohibited" below). It already -handles the two things that are easy to get wrong by hand: - -- **Out-of-order responses**: if a second `execute()` fires before the - first resolves (a fast filter change, page change, or tenant switch), - only the most recently started call's result is ever applied — pass - `resetKey` (e.g. the tenant id) so a genuine context switch clears - `data`/`error` synchronously instead of flashing stale data. -- **Unmounted-component writes**: guarded internally; you don't need your - own `isMounted` ref. - -For a mutation (create/update/delete on a feature's data hook), -**a create's success must not depend on the follow-up refetch succeeding**: -call `mutate(current => [...(current ?? []), created])` to insert the new -item into the hook's state immediately after the write succeeds, then -`void execute()` in the background to reconcile with the server. If that -background refetch fails, the optimistically-inserted item is still on -screen; surface the refetch's own `status`/`error` separately rather than -rolling back a successful create because of it. `update`/`delete` can -simply `await execute()` since there's no optimistic value to insert. - -Get `tenantContext` from `useAuth()` (`@/features/auth`) inside a -`ProtectedRoute` — treat it as possibly `null` in a hook (the page can -mount while `useAuth()` is still resolving), guard each method, and pass -the tenant id as `useAsync`'s `resetKey` so a tenant switch clears data -instead of leaking the previous tenant's rows onto screen even for one -frame (multi-tenancy — see root `AGENTS.md`). - -### 8. Page component - -Replace the stub. **`CategoriesListPage`/`CategoryEditorDialog` -(`features/catalog/presentation/categories/`) is the reference for -behavior and design** (search → table → dialog create/edit → -`AlertDialog` delete-confirm, loading/error/empty states) — **not for -anatomy**. Copy the _pattern_, not the file count: a feature with more -independent workflows legitimately needs more files than Categories does. -See "Componentization" below for when and how to split a page's -controller hook, form, and dialog. - -#### List = `Table`; form = `Dialog` by default - -A page listing records renders a `Table` (`src/components/ui/table.tsx`): -one row per record, actions (Edit/Delete) as buttons in the last column — -not stacked `Card`s. A create/edit form opens in a `Dialog` -(`src/components/ui/dialog.tsx`) over the list by default. One `Dialog` -instance switches between create/edit based on which record triggered it, -not a dialog per row. The form component stays dialog-agnostic. - -Categories maps `/categories/new` and `/categories/:id/edit` to the same -nested editor `Dialog` over the still-mounted `/categories` list -(docs/adr/012). `CategoryEditorDialog` renders one `CategoryForm` and -`useCategoryEditor` selects create or update from the route. In edit mode -`useCategoryEditor` fetches its own category directly via -`GET /api/v1/categories/{id}` — it does **not** read the list's data -through outlet context (docs/adr/013 superseded that shape; a -`useOutletContext()` cast has no runtime guarantee an ancestor route -actually supplied a value). `useCategoriesListPage` refetches the list -unconditionally whenever navigation returns from the editor route back to -the bare `/categories` route, whether the editor closed via cancel or a -successful save. Its smartphone table uses labelled icon actions with -larger touch targets and reveals action text from `sm` upward. - -A destructive action (delete) is confirmed with the shared -`DeleteConfirmationDialog` (`shared/presentation/components/`, built on -`AlertDialog`) — never `window.confirm`, and never a hand-rolled -`AlertDialog` per feature once `DeleteConfirmationDialog` already covers -the shape. Pair it with the shared `useDeleteConfirmation` -(`shared/presentation/hooks/`) for the target/progress/error state -machine behind it. - -#### Componentization — page shell, controller hook, promotion rule - -- A page component (`XPage.tsx`) is a **composition shell**: it renders - presentational components wired to a controller hook's view models, and - nothing else — no `useState`, no business logic, no direct repository/ - use-case calls. -- A controller hook (`useXPage`) follows the same single-responsibility - bar as any other code: when it accumulates more than one real workflow - (search/filter state, an editor with dirty-tracking, a deletion - confirmation are three _different_ concerns), split it into focused - hooks (`useXFilters`, `useXEditor`, `useXDeletion`) that the page's - composer hook assembles — see `features/catalog/presentation/services/hooks/` - for the reference (`useServicesPage` composing `useServiceFilters` + - `useServiceEditor` + `useServiceDeletion`). -- Extract a component or hook on its **first** use if it's already a - distinct concern (a field group, a delete dialog) — keep it - feature-local (e.g. `features/catalog/presentation/services/components/ -ServiceCategoryField.tsx`). Only **promote** something to `shared/` - once a **second**, genuinely-identical use appears across features — - the "second use" rule gates promotion, not the initial extraction. -- Break a type cycle between a controller and the component(s) it feeds - by putting the shared shape in a neutral, feature-local module (e.g. - `servicePresentationModels.ts`) that both sides import — the controller - must never import a component's Props type, and a component must never - import the controller's internal types. -- A dialog or form with a large, flat prop list is a signal to group - related props into a cohesive, named model (`editor`, `categoryOptions`, - `discardConfirmation`) instead of one generic catch-all object that - just hides the count. -- Decomposition triggers: multiple independent workflows, several - dialogs, distinct state clusters, an unmanageable prop list, a - controller/component type cycle, or a page test file too large to - navigate. There is no hard line-count cap. -- `GenericCrudPage` (or any config-driven, entity-agnostic CRUD - abstraction) is prohibited — share only behavior proven identical - across features (see the shared hooks/components list above), never a - generic page shape. - -#### Forms: React Hook Form + Zod - -Any form beyond a single trivial field uses `react-hook-form` + -`@hookform/resolvers/zod` — see `CategoryForm.tsx` -(`features/catalog/presentation/categories/pages/CategoryEditorDialog/forms/`) -for the exact shape: - -```typescript -const categoryFormSchema = z.object({ - name: z.string().trim().min(1, NAME_MESSAGE).max(60, NAME_MESSAGE), -}); -export type CategoryFormValues = z.infer; - -const { - register, - handleSubmit, - setError, - setFocus, - formState: { errors }, -} = useForm({ - resolver: zodResolver(categoryFormSchema), - defaultValues: initialValues, - mode: "onTouched", - reValidateMode: "onChange", -}); -``` - -(A field wired through `Controller` instead of `register` — e.g. a -`Select`, a color swatch group, a multi-value picker — also destructures -`control` from `useForm`; `CategoryForm` doesn't need one since its only -field is a plain text input.) - -- ` void handleSubmit(onSubmit)(e)} noValidate ...>` — - `noValidate` because native browser constraint validation would - intercept submit before react-hook-form/zod ever sees it. -- **A form with several field groups (name/description, duration range, - price/discount, category, tags — see `ServiceForm`) splits into one - component per group, sharing the RHF instance via `FormProvider`/ - `useFormContext`** instead of prop-drilling `register`/`control`/ - `errors` into each. The orchestrator component still owns - `useForm`/`handleSubmit`/the server-error effect; each field-group - component calls `useFormContext()` for - its own slice. -- **Structured API errors, mapped to fields — never parsed from free - text.** `shared/presentation/forms/serverFormError.ts`'s - `mapApiErrorToForm(error, fieldMap, codeFieldMap, fallbackMessage)` - differentiates a 400 validation `AppError` (has `rawFieldErrors` — map - each backend field name to the form's field via `fieldMap`) from a - 409/404/403 `AppError` (has `backendCode` — map via `codeFieldMap` when - the code names a specific field, e.g. a duplicate-name conflict - highlighting the name field, otherwise it becomes a global message). It - only ever depends on `AppError` (application-layer) — `ApiError`/ - `ProblemDetails` (infrastructure) never cross into a form. Apply the - result with `setError(field, { type: 'server', message })` in a - `useEffect` keyed on the server-error object, and - `setFocus(firstField)` so a screen-reader/keyboard user lands on the - first invalid field instead of losing their position — see - `CategoryForm`'s `serverError` effect. -- Don't reach for Formik or Yup without an explicit ADR — React Hook Form - - Zod is the established, working pattern here (`docs/DECISIONS.md`). - -#### Inline creation (a select that can create its own options) - -`shared/presentation/hooks/useCreateInline.ts` is the shared -`isCreating`/`serverError`/`create`/`reset` state machine behind any -"create a related record without leaving this form" flow -(`CreatableSingleSelect`/`CreatableMultiSelect`). It keeps the outer -form's already-typed values untouched and keeps the popover open to show -an error, instead of every entity reinventing this. Reuse it — don't -hand-roll a second inline-create state machine, and don't let an inline -create's error/loading state leak into or reset the outer form. - -#### Build from existing components — don't hand-roll markup, don't extend speculatively - -shadcn/ui primitives live in `src/components/ui/` and are already themed. -If a page needs something not there (select, badge, etc.), add it with -`npx shadcn@ add -c apps/admin-frontend` from the -repo root — use the version already pinned in -`apps/admin-frontend/package.json`'s `devDependencies.shadcn`, not -`@latest` (which would bypass that pin and could fetch an update the -repo hasn't reviewed). Then check the result compiles under -`exactOptionalPropertyTypes: true` (some generated files need fixing — -see `dropdown-menu.tsx`'s removal for when to give up and remove instead -of patch). - -Use generated files as the CLI writes them. Don't add a prop, variant, or -custom styling to a `src/components/ui/*` file unless a page genuinely -needs it right now — no speculative extensions "in case a future page -wants it." Do it at the call site instead (a conditional `` in -`children`, a `className` override on an existing `variant`). - -Shared composites live in `shared/presentation/components/` — reuse -before writing a new one: - -| Component | Use for | -| ----------------------------- | ------------------------------------------------------------------- | -| `PageHeader` | Title + primary action row at the top of every page | -| `StatusMessage` | Loading / empty / error text (`tone="error"` for errors) | -| `CollectionFeedback` | Loading/error/empty/last-known-good states for a tenant-scoped list | -| `DeleteConfirmationDialog` | Destructive-action `AlertDialog`, wired to `useDeleteConfirmation` | -| `TextField` / `TextAreaField` | Labeled form inputs (wraps shadcn `Label` + `Input`/`Textarea`) | -| `CenteredScreen` | Full-page centered content (pre-auth screens only) | -| `FullScreenSpinner` | Full-page loading state | -| `ThemeToggle` | Already in `AdminLayout` — don't add another one | - -Only promote a one-off to `shared/` once a second, genuinely identical -use appears (see "Componentization" above) — until then it stays -feature-local. - -#### Use semantic tokens — never raw palette classes - -`src/index.css` defines the whole palette as CSS variables, redefined -under `.dark` — `bg-background`/`text-foreground` etc. resolve correctly -in both themes automatically. A raw class like `bg-slate-50` does not — -it's a fixed light-mode color that breaks the moment a user switches to -dark. - -| Instead of (stale, don't use) | Use | For | -| ----------------------------------- | ----------------------------- | ----------------------------- | -| `bg-slate-50` | `bg-background` | Page background | -| `bg-white` | `bg-card` | Card/surface background | -| `border-slate-200` | `border-border` | Card and divider borders | -| `text-slate-800` | `text-foreground` | Headings, primary text | -| `text-slate-600` / `text-slate-400` | `text-muted-foreground` | Secondary/muted text | -| `text-red-600` | `text-destructive` | Error text | -| `bg-teal-600` / `text-teal-700` | `text-primary` / `bg-primary` | Brand accent, primary buttons | - -There is no brand color to special-case — the app uses the stock -shadcn/ui neutral theme. If in doubt, use a token. - -#### Icons and accessibility - -`lucide-react`, matched to the icon already used for this section in -`AdminLayout`'s nav. Always add `aria-hidden="true"` on a decorative icon. -Every interactive element needs a real accessible name (visible label, -`aria-label`, or `sr-only` text) and must be reachable and operable by -keyboard alone — tab order, `Enter`/`Space` activation, `Escape` closing a -`Dialog`/`AlertDialog`/popover (Radix primitives give you this for free; -don't fight it with a custom `onKeyDown` unless a page genuinely needs -one). Check color contrast against both themes when introducing any new -non-token color. - -#### Mobile responsiveness — every page must work at 375px wide - -- `Table` already scrolls horizontally on its own - (`data-slot="table-container"` wraps it in `overflow-x-auto`) — don't - add a second scroll wrapper. -- `Dialog` is responsive by default (`max-w-[calc(100%-2rem)]` below its - `sm:` breakpoint). -- Any `flex` row inside a form that could get tight still needs - `flex-wrap` — see `CategoryForm`'s button row. -- Never use a fixed pixel width wider than ~300px without a responsive - override. Prefer `w-full` + `max-w-*`. -- `AdminLayout` already handles the page shell (off-canvas sidebar below - `md`) — pages don't need their own mobile nav handling. - -#### States - -Handle all three `useAsync` states: loading → `StatusMessage`, error → -`StatusMessage tone="error"`, success → real UI (or `CollectionFeedback` -for a tenant-scoped list, which also covers the empty and -last-known-good-after-a-failed-refresh states). - -#### Language — all user-facing text is Brazilian Portuguese (pt-BR) - -Every string a user reads or a screen reader announces — headings, button -labels, `PageHeader`/`StatusMessage` text, form labels/hints, -`aria-label`s, confirm prompts, error-message fallbacks — is pt-BR. See -`CategoriesListPage`/`CategoryEditorDialog` for the pattern (e.g. "Nova -categoria", `aria-label={\`Excluir categoria ${category.name}\`}`). Code -stays in English: identifiers, comments, commit -messages, this skill's own prose. - -Nav labels (source of truth: `AdminLayout.tsx`'s `NAV_ITEMS`) are Painel, -Agendamentos, Serviços, Categorias, Clientes, Caixa de entrada, -Configurações — reuse the exact same word for a stub page's -`PlaceholderPage title` and for that vertical's `PageHeader title` once -built. - ---- +# Frontend feature + +Start with the code that owns the current behavior. `AGENTS.md` contains +invariants; `docs/STATUS.md` contains progress; ADRs contain rationale. Never +infer the current tree from an old example in prose. + +## Load only what the task needs + +| If the task touches... | Also read | +| --- | --- | +| Repository, mapper, decoder, OpenAPI type, MSW API handler | [references/api-integration.md](references/api-integration.md) | +| Test file, fake, wrapper, MSW setup | [references/testing.md](references/testing.md) | +| Page, form, dialog, table, shared component, visual behavior | [references/page-ui-conventions.md](references/page-ui-conventions.md) | +| Auth/session behavior | frontend ADRs 004, 006, 007 and 015 via `docs/adr/README.md` | +| Moving feature boundaries or public APIs | frontend ADR 009 and the current ESLint rules | + +Do not open all three references for a narrow change. + +## Architectural shape + +- `src/app/`: bootstrap, routing, layouts, providers, composition. +- `src/features//`: domain, application, infrastructure, and + presentation owned by one business capability. +- `src/shared/`: cross-feature primitives that already have at least two + identical consumers or are genuine application-wide boundaries. +- `src/components/ui/` and `src/lib/utils.ts`: shadcn-generated locations; + they intentionally stay outside `shared/`. +- Unimplemented routes remain small placeholders in `src/app/pages/` until a + real feature slice exists. + +## Decision rules + +### Domain and Result flow + +- Domain factories validate invariants and return `Result`. + They do not throw for expected invalid input. +- API mappers compose domain results. A malformed external response becomes a + curated `AppError` at the infrastructure boundary. +- `useAsync` consumes `() => Promise>`. Expected failures never + become rejected promises merely to fit a hook. +- Validate runtime input even when a generated TypeScript type looks narrower; + wire data is untrusted. + +### Application boundary + +- A repository port returns domain values wrapped in `Result`; it never exposes + raw DTOs. +- Repository methods do not accept tenant context. Tenant selection belongs to + the authenticated request-session boundary. +- Add a use-case class when it performs orchestration, policy, or composition. + If a facade operation is a pure repository pass-through, expose the method + shape directly instead of adding an `execute` wrapper. +- Construct concrete implementations only in `app/composition/container.ts` + and expose grouped facades, never raw repositories or `HttpClient`. + +### Feature boundaries + +- Import another feature only through its `index.ts` public API. +- Keep feature-specific DTOs, forms, hooks, view models, and tests inside that + feature. Promote a genuinely identical cross-feature primitive to `shared/` + only when the second use exists. +- Do not create `GenericCrudPage` or another config-driven entity-agnostic UI. + +### Tenant and auth safety + +- `AuthenticatedHttpClient` reads one `GetRequestSession` snapshot and attaches + the bearer token plus `X-Tenant-Id`; individual repositories do not choose a + tenant. +- Pass the authenticated tenant id as `useAsync.resetKey` so previous-tenant + data cannot paint after a switch. +- Preserve both user and tenant identity during silent renewal. A changed claim + requires a full login. +- Keep routed tenant content below `TenantBoundary`. + +## Implementation sequence + +Use only the steps relevant to the requested behavior: + +1. Confirm the business rule or wire contract from code/OpenAPI/docs; ask only + when a missing answer would change a public contract, auth, tenant isolation, + or business behavior. +2. Add or change the domain behavior with a failing test when domain logic is + involved. +3. Change the port and orchestration boundary only if the behavior requires it. +4. Change decoder/mapper/repository and MSW tests for external data. +5. Wire the facade/container without leaking concrete infrastructure. +6. Build the hook with `useAsync` and a tenant reset key when it owns server data. +7. Build the smallest accessible page/form composition needed now. +8. Update `docs/STATUS.md` only when implementation status changed; update an + ADR only when a durable decision changed. ## Prohibited -- A second, competing design system or component library alongside - shadcn/ui + Radix + Tailwind — extend the existing one (see "Build from - existing components" above). -- Formik or Yup without an explicit ADR — this project already made this - decision (React Hook Form + Zod). -- Redux, Zustand, or any global client-state store used as a server-data - cache — `useAsync` + the container's use cases are the established - pattern; a genuinely local UI-only state (a dialog's open/closed flag) - is fine as plain `useState`, but server data always flows through a - hook built on `useAsync`. -- Hand-duplicating a contract the codebase already generates — - `features/catalog/infrastructure/generated/services-api.d.ts` is - generated from the backend's OpenAPI document - (`npm run generate:api-types`); don't hand-write a parallel DTO type - for something already generated, and don't let a hand-written one - silently drift from it (see `agent-skills/agenza-api-contract-review`). -- Importing a feature's internal `domain/`, `application/`, - `infrastructure/`, or `presentation/` module from outside that feature - — share through its `index.ts` public API instead (ADR 009). This is - ESLint- and `architecture_guard.py`-enforced. -- `GenericCrudPage`, or any generic entity-agnostic CRUD abstraction. -- `any`, anywhere, including test files and fakes. - ---- - -## HttpClient (already built — read before touching infrastructure) - -```typescript -// shared/application/HttpClient.ts -export type Decoder = (payload: unknown) => T; - -export interface HttpClient { - get(path: string, decode: Decoder): Promise; - post(path: string, body: unknown, decode: Decoder): Promise; - put(path: string, body: unknown, decode: Decoder): Promise; - delete(path: string): Promise; -} -``` - -Every `get`/`post`/`put` call takes a `decode` function alongside its `T` - -a generic type parameter alone validates nothing at runtime, so the -decoder is what actually stands between an untrusted response body and a -value the rest of the app treats as `T` (docs/adr/011). A feature's mapper -owns its own decoder next to its DTO type (e.g. `categoryMapper.ts`'s -`decodeCategoryDto`/`decodeCategoryDtoArray`) - hand-rolled `typeof`/`Array.isArray` -guards matching `shared/infrastructure/http/ProblemDetails.ts`'s existing -style, not a schema library. A decoder that throws is caught by the same -place every other infrastructure failure already is (see below) - never -add a second try/catch in the repository for this. - -`AuthenticatedHttpClient` (`shared/infrastructure/http/`): constructor -takes `getRequestSession: GetRequestSession` (returns both the access -token and tenant id from one session read — `shared/application/ -RequestSession.ts`), prepends `VITE_API_BASE_URL`, attaches `Authorization: -Bearer ` and `X-Tenant-Id`, converts every failure (missing -session, 401, non-2xx `ProblemDetails`, network/timeout, or a `decode` -rejection) into an `AppError` (`shared/application/AppError.ts`) before it -leaves infrastructure — never `ApiError`/`ProblemDetails`/a raw decode -error directly (docs/adr/007, docs/adr/011). Wired into -`createAppContainer()` (`app/composition/container.ts`) using -`authRepository.getCurrentSession()` to supply both values from the same -read. - ---- +- `any`, deep cross-feature imports, raw infrastructure imports from + presentation, hand-duplicated generated contracts, or a second design system. +- A global client-state library used as a server cache without an ADR replacing + the established `useAsync` approach. +- Raw backend/exception messages rendered to users. +- Speculative components, variants, use cases, or abstractions. -## Commit checklist +## Completion -- [ ] Domain entity: explicit field declarations, named errors, no framework deps, no `any` -- [ ] Repository interface: `TenantContext` first param on all methods -- [ ] Use cases: explicit constructor body (no shorthand), tested with fakes -- [ ] Container: wired in interface and factory, imported from the feature's `index.ts` -- [ ] Mapper: tested, all fields and failure paths covered -- [ ] Infrastructure repo: tested with MSW, handler registered -- [ ] Hook: built on `useAsync`, tenant-scoped via `resetKey`, mutations - use `mutate` for optimistic success decoupled from refetch failure -- [ ] Form (if any): React Hook Form + Zod, server errors mapped to - fields via `mapApiErrorToForm`, `setFocus` on the first error -- [ ] Page: a composition shell handing view models to presentational - components; controller hook split by workflow once it has more - than one -- [ ] Page: handles loading/error/success, built from shadcn/ui primitives - and shared composites (not hand-rolled markup) -- [ ] List uses `Table`; form uses the feature's documented interaction - (`Dialog` by default, routed editor only where an ADR establishes it) -- [ ] Destructive actions confirmed with `DeleteConfirmationDialog` — not - `window.confirm` or a hand-rolled `AlertDialog` -- [ ] No prop/variant added to a `src/components/ui/*` file unless this - page genuinely needs it right now -- [ ] Page: uses semantic tokens only — no raw `slate-*`/`teal-*`/etc. -- [ ] Page: checked in dark mode and at 375px wide, no horizontal overflow -- [ ] Page: keyboard-operable, decorative icons `aria-hidden`, every - interactive element has an accessible name -- [ ] All user-facing text (labels, messages, `aria-label`s, confirm - prompts) is in pt-BR -- [ ] No import of another feature's internals bypassing its `index.ts`, - no hand-duplicated generated contract, no new global client-state store -- [ ] Comments are at the "minimum of the minimum" bar — none by default -- [ ] `npm run build` clean (catches TypeScript strict mode issues) -- [ ] `npm run lint` clean -- [ ] `npm run test` all green — behavioral assertions, not implementation details +Run the frontend and governance gates from `apps/admin-frontend/AGENTS.md`. +Report actual results and any remaining uncertainty; do not call the task done +while an applicable gate is red. diff --git a/.claude/skills/agenza-frontend-feature/references/api-integration.md b/.claude/skills/agenza-frontend-feature/references/api-integration.md new file mode 100644 index 0000000..a24a1a1 --- /dev/null +++ b/.claude/skills/agenza-frontend-feature/references/api-integration.md @@ -0,0 +1,47 @@ +# Frontend API integration + +Read this reference only when changing a repository, mapper, decoder, generated +OpenAPI type, request body, endpoint path, or MSW API handler. + +## Source of truth + +1. Inspect the backend controller/response/command and the generated OpenAPI + types already checked into the feature. +2. Run or inspect the API type generation workflow when the contract may have + changed. +3. Use `docs/API.md` for integration policy and confirmed endpoint notes, not as + a substitute for the generated contract. +4. Ask the user only if the remaining ambiguity would change a public contract + or business rule. + +Do not create a hand-written DTO that shadows an available generated type. A +feature-local decoder may narrow an `unknown` payload into that generated type. + +## Boundary flow + +- `HttpClient` receives a decoder and returns `Promise>`. +- A decoder may throw while rejecting malformed untrusted data; the global + authenticated HTTP boundary catches that technical failure and returns a + curated `Result.failure`. Repositories and presentation do not add another + try/catch for expected failures. +- A mapper converts the decoded wire shape into domain values and composes any + domain validation `Result`. +- Preserve absent/null distinctions only when the contract distinguishes them; + normalize them before they enter the domain. + +## Tenant and authentication + +The mechanism is already decided: `AuthenticatedHttpClient` obtains one atomic +request-session snapshot, attaches `Authorization: Bearer ...` and +`X-Tenant-Id`, and the backend verifies the header against the token claim. +Repository methods neither accept `TenantContext` nor set the tenant header. + +## Tests + +- Mapper/decoder tests cover every field plus malformed and domain-invalid data. +- Repository tests use MSW and the real `HttpClient` path. +- Handlers match the exact URL, method, request, response, and relevant RFC 7807 + error shape. Register every handler; unhandled requests fail globally. +- Test at least the success path and each error behavior the repository maps or + exposes differently. + diff --git a/.claude/skills/agenza-frontend-feature/references/page-ui-conventions.md b/.claude/skills/agenza-frontend-feature/references/page-ui-conventions.md new file mode 100644 index 0000000..0e75dac --- /dev/null +++ b/.claude/skills/agenza-frontend-feature/references/page-ui-conventions.md @@ -0,0 +1,73 @@ +# Page and UI conventions + +Read this reference only for page, form, dialog, table, component, or visual +behavior changes. + +## Composition + +- A routed page is a composition shell. It renders view models and callbacks + from a controller hook; it does not call repositories or infrastructure. +- Split a controller when it owns more than one independent workflow, such as + filtering, editing, deletion, or dirty-state confirmation. There is no + line-count threshold. +- Extract a distinct concern locally on first use. Promote it to `shared/` only + after a second genuinely identical use across features. +- Keep shared controller/component shapes in a neutral feature-local module; + neither side imports the other's internal type. +- Do not build a generic CRUD page. Reuse proven behaviors and primitives, not + an entity configuration object. + +Categories is the current implemented CRUD reference. Inspect its live files +under `features/catalog/presentation/categories/` rather than copying a folder +layout described in documentation. + +## Interaction patterns + +- Lists use the existing shadcn `Table`. Destructive actions use the shared + confirmation dialog rather than `window.confirm` or a feature-specific copy. +- Create/edit uses one form implementation. A dialog is the default interaction; + use routing when navigation, deep-linking, or refresh behavior justifies it + and record a reusable architectural change in an ADR. +- Preserve last-known-good data during refresh failures when the current shared + collection feedback component supports it. + +## Forms + +- Non-trivial forms use React Hook Form and Zod with `noValidate` on the form. +- The form orchestrator owns `useForm`, submit, and server-error application. + Field-group components consume the same form through `FormProvider` when + prop-drilling would otherwise repeat form internals. +- Map structured backend field/code errors through the shared form-error helper. + Focus the first invalid field. Never parse free-text backend messages. +- A component controlled through RHF `Controller` forwards its ref to a real + focusable DOM element. +- Keep an inline-create workflow's pending/error state separate from the outer + form. Do not invent a shared abstraction until a live second use proves it. + +## Existing UI + +- Prefer `src/components/ui/` primitives and then + `shared/presentation/components/`. Inspect the directories for the current + inventory; do not maintain a duplicate component list here. +- Add shadcn components with the version pinned in `package.json`, never + `@latest`. Keep generated primitives close to upstream and solve one-off + styling at the call site. +- Use semantic tokens such as `bg-background`, `bg-card`, `text-foreground`, + `text-muted-foreground`, `border-border`, and `text-destructive`. Raw palette + classes break theme portability. + +## Accessibility and responsive behavior + +- Every interactive element has a visible or programmatic accessible name and + works by keyboard. Decorative icons use `aria-hidden="true"`. +- Prefer Radix interaction behavior over custom keyboard handlers. +- Add `jest-axe` coverage to new or materially changed routed pages/forms and + verify focus movement for server validation errors. +- Verify light and dark themes and a 375 px viewport. Avoid fixed widths that + overflow; use the existing table/dialog responsiveness before adding wrappers. + +## Language and comments + +User-visible and assistive strings are pt-BR; code identifiers remain English. +Comments default to zero and explain only a non-obvious security, concurrency, +library, browser, or lint constraint. Put architectural rationale in an ADR. diff --git a/.claude/skills/agenza-frontend-feature/references/testing.md b/.claude/skills/agenza-frontend-feature/references/testing.md new file mode 100644 index 0000000..fdcf479 --- /dev/null +++ b/.claude/skills/agenza-frontend-feature/references/testing.md @@ -0,0 +1,50 @@ +# Frontend testing conventions + +Read this reference only when creating or changing tests, fakes, wrappers, MSW +handlers, or test infrastructure. + +## Strategy by boundary + +| Subject | Test boundary | +| --- | --- | +| Domain | Pure inputs and `Result` outputs; no mocks | +| Application orchestration | Hand-written repository fake | +| Infrastructure repository | MSW around the real `HttpClient` | +| Hook/component | Typed fake `AppContainer`; router/auth providers only as needed | + +Do not mix boundaries. A use-case test does not need MSW; a repository test does +not replace `HttpClient` with a repository fake. + +## Fakes + +- Start from the current `createFake*Repository` or + `createFakeAppContainer` helper. +- Default unused expected operations to resolved `Result.failure` values, not + `Promise.reject`. This application represents expected failures as values. +- Override only the operation under test and use `vi.fn` when call assertions + matter. +- Add a shared feature fake after a second test needs the same complete shape. + +## TypeScript and React + +- Constructor fields are explicit; optional properties use conditional spreads. +- Type render helpers explicitly when inference would lose the subject's public + result type. +- Give wrapper/render helpers explicit return types when ESLint requires them. +- A never-resolving promise for an in-flight state uses a non-empty executor or + the narrow documented lint suppression; do not generalize a suppression. +- Use `waitFor` for observable async state and `act` around direct state-causing + calls. Wait for the initial auth check before asserting authenticated content. + +## MSW and accessibility + +- Every request has a registered handler and `onUnhandledRequest: 'error'` + remains enabled. +- Test wire shapes, request bodies, and relevant error variants at the HTTP + boundary. +- Add `jest-axe` to new or materially changed routed pages/forms, alongside + keyboard/focus assertions where behavior depends on them. + +Run targeted Vitest files during development, then the complete format, lint, +build, and coverage gates from `apps/admin-frontend/AGENTS.md`. + diff --git a/.claude/skills/agenza-rule-persistence/SKILL.md b/.claude/skills/agenza-rule-persistence/SKILL.md index 7e08ef8..ab70e09 100644 --- a/.claude/skills/agenza-rule-persistence/SKILL.md +++ b/.claude/skills/agenza-rule-persistence/SKILL.md @@ -36,11 +36,11 @@ three months later: 2. **Update `AGENTS.md`.** Root `AGENTS.md` if it applies everywhere; `backend/AGENTS.md`/`apps/admin-frontend/AGENTS.md` if it's area-local. State the rule, not a narrative of how it was discovered. -3. **Update the skill.** If a skill in `agent-skills/` teaches the old +3. **Update the skill.** If a skill in `.agents/skills/` teaches the old pattern (in prose *or* in a copy-paste template — templates rot silently because they're copied verbatim without re-reading the prose around them), fix it there. Run `python scripts/sync_agent_skills.py` - afterward so `.claude/skills/`/`.agents/skills/` pick up the change. + afterward so `.claude/skills/` picks up the change. 4. **Add or update an ADR.** If this is a genuine architectural decision (not just a bug fix), it needs `docs/adr/NNNN-....md` explaining the context, the decision, and — if it reverses an earlier ADR — which one @@ -63,13 +63,14 @@ three months later: A rule can be technically "fixed" in the places above and still get reintroduced because something else still teaches the old pattern. Check: -- Other `CLAUDE.md`/`AGENTS.md` files that might restate the rule locally - and now disagree with the update. -- Older skills (including ones outside `agent-skills/`, like - `backend/.skills/`/`apps/admin-frontend/.skills/`) that predate the - change. +- Other `CLAUDE.md`/`AGENTS.md` files or the Copilot bridge that might restate + the rule locally and now disagree with the update. +- Any forbidden legacy instruction layer (`agent-skills/`, `prompts/`, + `.claude/agents/`, `.skills/`, `.agent.md`) or generated artifact that still + teaches the old behavior. - Comments in code that assert the old rationale. -- `prompts/` templates and worked examples in `docs/SDD-GUIDE.md`. +- Worked examples in `docs/SDD-GUIDE.md` and any task template outside the + canonical skill tree. - Test files whose names or comments describe the old behavior as correct, even if the assertions themselves were updated. @@ -81,5 +82,5 @@ genuinely doesn't apply (e.g. no ADR is warranted for a pure typo fix), say so explicitly rather than leaving it silently incomplete. Run `python scripts/check_agent_governance.py` after this cycle — it flags skills not in sync, ADR references that don't exist, and -`CLAUDE.md` files missing the `@AGENTS.md` import, three of the most -common ways a "persisted" rule quietly isn't. +`CLAUDE.md` files missing the `@AGENTS.md` import, and a missing Copilot bridge, +four of the most common ways a "persisted" rule quietly isn't. diff --git a/.claude/skills/agenza-tenant-isolation-review/SKILL.md b/.claude/skills/agenza-tenant-isolation-review/SKILL.md index 643753b..08579d7 100644 --- a/.claude/skills/agenza-tenant-isolation-review/SKILL.md +++ b/.claude/skills/agenza-tenant-isolation-review/SKILL.md @@ -39,8 +39,7 @@ is in scope. entity's repository takes an explicit `tenantId` parameter (the DbContext scopes it) — a parameter like that is a sign someone hand- rolled scoping instead of relying on the automatic mechanism, which is - itself worth flagging even if the value passed happens to be correct - today. + itself worth flagging even if the value passed happens to be correct. - **New-entity assignment**: `AuditableEntitySaveChangesInterceptor` calls `AssignTenant` on save for any newly added `ITenantOwned` entity with `TenantId == Guid.Empty`, sourcing it from `ICurrentTenantProvider` — it @@ -49,7 +48,7 @@ is in scope. - **Frontend cache/query keys**: any client-side cache (`useAsync`'s `resetKey`, a memoized list, browser storage) keyed in a way that includes the tenant id or is cleared synchronously on tenant switch — - see `agent-skills/agenza-frontend-feature`'s `useAsync` section for the + see `.agents/skills/agenza-frontend-feature`'s `useAsync` section for the `resetKey` mechanism. A cache that survives a tenant switch and can render the previous tenant's data for even one frame is a finding, not a nit. @@ -58,7 +57,7 @@ is in scope. unique index on a business field is itself a cross-tenant leak (tenant A can't reuse a name tenant B already used). A composite FK crossing tenant boundaries (referencing another tenant's row) is a finding. -- **Migrations**: hand off to `agent-skills/agenza-migration-safety` for +- **Migrations**: hand off to `.agents/skills/agenza-migration-safety` for the migration-safety half; this skill only confirms the resulting schema still enforces tenant scoping (index/FK shape above). - **Logs**: a log statement that includes another tenant's data alongside @@ -81,9 +80,7 @@ as blocking. ## Output format `surface (endpoint/query/cache/index) | mechanism relied on | verified? | -finding (if any) | severity | fix`. For anything not directly verifiable -by reading code (e.g. actual runtime behavior of a query filter), say so -explicitly and recommend the manual two-tenant verification step already -called out in `agent-skills/agenza-backend-use-case` ("Automatic tenant -assignment has no automated regression test") rather than asserting it's -safe from static reading alone. +finding (if any) | severity | fix`. For behavior not provable statically, +inspect the current `*PersistenceTests` projects and recommend a two-tenant +runtime smoke only when it adds coverage. Never infer a missing test tier from +an older ADR or instruction; inspect the solution and CI first. diff --git a/.claude/skills/evolve-modular-architecture/SKILL.md b/.claude/skills/evolve-modular-architecture/SKILL.md index 03a1e33..6e2c76d 100644 --- a/.claude/skills/evolve-modular-architecture/SKILL.md +++ b/.claude/skills/evolve-modular-architecture/SKILL.md @@ -1,6 +1,6 @@ --- name: evolve-modular-architecture -description: Assess, design, review, and incrementally evolve modular software architecture from repository and business evidence. Use when Codex needs to define or repair module boundaries, decompose a monolith, choose between a simple monolith, modular monolith, and microservices, select architecture per module, introduce tactical DDD only where justified, plan a safe extraction or migration, write ADRs, or create automated architectural fitness functions that prevent structural drift. +description: Assess, design, review, and incrementally evolve modular software architecture from repository and business evidence. Use when defining or repairing module boundaries, decomposing a monolith, choosing between a simple monolith, modular monolith, and microservices, selecting architecture per module, introducing tactical DDD only where justified, planning a safe extraction or migration, writing ADRs, or creating automated architectural fitness functions that prevent structural drift. --- # Evolve Modular Architecture diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..b431030 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,7 @@ +# GitHub Copilot integration + +Before changing or reviewing anything, read and follow the root `AGENTS.md` and +the nearest nested `AGENTS.md`; they are the canonical repository instructions. +Load a matching workflow from `.agents/skills/` only when the task triggers it. +Do not copy rules into this file or infer current feature state, versions, or +contracts from it; use the living sources routed by `AGENTS.md`. diff --git a/.github/workflows/agent-governance.yml b/.github/workflows/agent-governance.yml index eba38ce..6202317 100644 --- a/.github/workflows/agent-governance.yml +++ b/.github/workflows/agent-governance.yml @@ -30,7 +30,7 @@ jobs: with: python-version: '3.14.6' - - name: Skill sync check (agent-skills/ -> .agents/skills/, .claude/skills/) + - name: Skill sync check (.agents/skills/ -> .claude/skills/) run: python scripts/sync_agent_skills.py --check # Each remaining step uses if: always() so one failing check doesn't diff --git a/.gitignore b/.gitignore index 66c76e4..0682412 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,6 @@ build/ .env.local .env.*.local *.log + +# Tool-local agent state +**/.claude/settings.local.json diff --git a/AGENTS.md b/AGENTS.md index 56fdab0..a7d962b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,222 +1,134 @@ -# Admin Panel Monorepo — Agent Instructions - -This is the canonical, tool-independent instruction file for every AI coding -agent working in this repository (Claude Code, OpenAI Codex, or any other -agent that reads `AGENTS.md`). It holds only durable, repo-wide rules. -Area-specific rules live in the local files linked below — read this file -first, then the one for the area you're touching. - -## What this repo is - -A multi-tenant SaaS admin panel for small healthcare/wellness businesses: -React frontend, .NET microservices, Python AI services. See -[docs/VISION.md](docs/VISION.md) for where it's heading and -[docs/MONOREPO.md](docs/MONOREPO.md) for the layout that exists today. - -## Read next (in this order) - -| Area | Read | -| ------------------------------------------ | -------------------------------------------------------------- | -| `.NET backend (backend/**)` | [backend/AGENTS.md](backend/AGENTS.md) | -| `Admin frontend (apps/admin-frontend/**)` | [apps/admin-frontend/AGENTS.md](apps/admin-frontend/AGENTS.md) | -| `Python AI service (ai-services//**)` | that service's own `README.md` | -| Repo layout & workspace conventions | [docs/MONOREPO.md](docs/MONOREPO.md) | -| Target architecture | [docs/VISION.md](docs/VISION.md) | -| How humans direct agents here (SDD) | [docs/SDD-GUIDE.md](docs/SDD-GUIDE.md) | -| CI, coverage gates, review tooling | [docs/QUALITY.md](docs/QUALITY.md) | -| Cross-cutting decisions with rationale | [docs/adr/](docs/adr/) | -| How this governance framework works | [docs/AGENT-GOVERNANCE.md](docs/AGENT-GOVERNANCE.md) | - -An area's own `AGENTS.md` always wins on anything specific to that area; -this file only covers what applies everywhere. +# Agenza monorepo — agent instructions + +This is the canonical, tool-independent entry point. Keep only durable, +repo-wide rules here; area rules live in `backend/AGENTS.md` and +`apps/admin-frontend/AGENTS.md`. Current state, versions, and historical +rationale belong in code/config, STATUS docs, and indexed ADRs. + +## Route context, do not preload it + +| Scope | Read next | +| --- | --- | +| Backend | `backend/AGENTS.md` | +| Admin frontend | `apps/admin-frontend/AGENTS.md` | +| Python AI service | that service's README and config | +| Current repo layout | `docs/MONOREPO.md` | +| Target direction | `docs/VISION.md` | +| CI and coverage | `docs/QUALITY.md` | +| Decision rationale | `docs/adr/README.md`, then only the relevant ADRs | +| Human/agent workflow | `docs/SDD-GUIDE.md` | +| Governance mechanics | `docs/AGENT-GOVERNANCE.md` | + +Do not read every linked document or ADR by default. Prefer executable truth +(code, tests, generated contracts, migrations, config) over prose describing it. ## Question policy -Before asking the user anything, look for the answer yourself: code, tests, -ADRs, `AGENTS.md`/`CLAUDE.md` files, skills, scripts, workflows, -configuration, OpenAPI contracts, migrations, and repo history all count as -sources of truth before a question does. - -Only ask when the answer could plausibly: - -- change a business rule, -- change a public contract (API, DTO, event shape), -- affect authentication or authorization, -- compromise multi-tenancy or tenant data isolation, -- modify data already in a production migration, -- or choose between two architecturally incompatible strategies with no - clear winner in existing code/ADRs. - -Do not block independent, unambiguous work on one open question — finish -what you can and flag the question alongside it. Do not invent requirements -that were never stated or implied by the spec/ADRs/code. - -## Non-negotiables - -- **Tenant scoping.** Every query or command in every service (frontend use - case, .NET handler, Python endpoint) is scoped to a tenant. A tenant id - from the client is never trusted on its own — it is always verified - against the authenticated principal. See backend/AGENTS.md's "Tenant - scoping" section for the concrete mechanism (`TenantHeaderFilter`, - `ITenantOwned`, automatic assignment) and - `agent-skills/agenza-tenant-isolation-review` for how to audit it. -- **Clean Architecture per app/service.** Each app and each backend - microservice keeps its own Domain → Application → Infrastructure/ - Presentation layering, dependencies pointing inward only. Never reach - across a service's internals — cross-service contracts go through HTTP - APIs, never shared database access. (`packages/shared-types` was removed - as unused while `admin-frontend` is the only Node app — recreate it only - when a second app needs shared TS DTOs, see docs/VISION.md.) -- **No shared mutable state across stacks.** The frontend, .NET services, - and Python services talk over HTTP (and later, events) — never shared - files, multi-service writes to the same database, or in-process calls - across a service boundary. -- **Aspire is the single local application orchestrator.** Add and evolve - frontend, backend, Python, and PostgreSQL resources in - `backend/AppHost/AppHost.cs`; do not add Docker Compose or application - Dockerfiles as a parallel local runtime. Docker is used only as Aspire's - PostgreSQL container engine until a real deployment design is accepted - with its own ADR and CI proof (docs/adr/0029). -- **Exceptions are not conventional control flow in the .NET backend.** No - backend layer (Domain, Application, Infrastructure) throws for an - _expected_ outcome — validation failure, not-found, conflict/duplicate, - in-use, tenant authorization. Every layer's failure signature is explicit - in its return type (`Result`/`DomainResult`/`PersistenceResult`). - Exceptions stay reserved for genuinely unexpected/unrecoverable failures. - This is not a style preference — it reverts a pattern - (`BusinessException`/`DuplicateEntityException`/`BusinessExceptionHandler`, - `MustAsync` repository checks in validators) this codebase already tried, - hit problems with, and formally reverted (docs/adr/0012, docs/adr/0014). - Full detail in backend/AGENTS.md; audit with - `agent-skills/agenza-exception-flow-audit`. **This is a backend-specific - rule, not a repo-wide ban on `throw`/`catch`** — the frontend has its - own, separate, already-established exception-and-catch convention - (`DomainError`, `ApiError`, see `apps/admin-frontend/AGENTS.md` and - `agent-skills/agenza-frontend-feature`) that this rule does not override. - -## Testing & quality policy - -- Both `build` and `test` must pass for whichever stack you touched before - calling a change done — see the area's own `AGENTS.md` for exact - commands. Lint/format gates are equally mandatory, not optional style - nits. -- Never delete or skip a test, disable a lint rule, shrink a coverage gate, - or widen an allowlist just to make a gate pass. Fix the underlying issue. - If a gate is genuinely wrong, that's an ADR-worthy decision, not a silent - workaround. -- A migration or schema change that could destroy or silently alter - production data needs `agent-skills/agenza-migration-safety` and, when - real data loss risk exists, a direct question to the user — this is one - of the question-policy triggers above, not an exception to it. - -## Documentation policy - -- A decision another agent (or a human, in six months) might re-litigate - gets an ADR: `docs/adr/` for cross-cutting decisions, - `apps/admin-frontend/docs/adr/` for frontend-local ones. -- Docs are updated in the same change that makes them stale (STATUS.md - rows, API docs, this file, area `AGENTS.md`/`CLAUDE.md` files) — not in a - follow-up. -- A code comment explains a non-obvious _why_ (a security default, a - library quirk, a subtle ordering/transaction constraint) — never _what_ - the code does, and never rationale that belongs in an ADR instead. +Search code, tests, ADR indexes, instructions, skills, contracts, migrations, +configuration, scripts, workflows, and history before asking the user. + +Ask only when the answer could materially change: + +- a business rule or public contract; +- authentication, authorization, or tenant isolation; +- data already represented by a production migration; +- an architecturally incompatible strategy with no clear repository precedent. + +Finish independent, unambiguous work while one question remains open. Never +invent requirements absent from the spec or repository evidence. + +## Repo-wide non-negotiables + +- **Tenant isolation:** every tenant-scoped operation is tied to the + authenticated principal. Client-supplied tenant identity is never trusted + alone. Any observable cross-tenant exposure is a security failure. +- **Boundaries:** each app/service owns its Domain -> Application -> + Infrastructure/Presentation dependencies. Cross-service interaction uses + explicit HTTP/event contracts, never internal project references or shared + database writes. +- **No shared mutable state across stacks:** frontend, .NET, and Python + communicate through service boundaries, not shared files or in-process calls. +- **Aspire is the local orchestrator:** evolve the resource graph in + `backend/AppHost/AppHost.cs`. Do not add Docker Compose or application + Dockerfiles as a parallel local runtime without an accepted deployment ADR. +- **Expected backend outcomes are values:** validation, not-found, conflict, + in-use, and tenant authorization flow through `Result`/`DomainResult`/ + `PersistenceResult`. Exceptions remain for unexpected technical failures and + the narrow cases documented by backend rules. + +## Quality and documentation + +- Run build, test, lint/format, and coverage gates for every affected stack. + Fix the cause; never delete/skip tests, disable a rule, lower a threshold, or + widen an allowlist merely to pass. +- Update living documentation in the same change that makes it stale. Do not + duplicate current versions, file inventories, test counts, or feature status + in instruction files. +- A durable decision that may be re-litigated gets an ADR. Index it as accepted, + superseded, or historical so agents do not treat incompatible decisions as + simultaneously current. +- Comments explain a non-obvious why. They do not narrate code or duplicate ADR + rationale. ## Git workflow -Trunk-based, single long-lived branch (`main`) — no permanent `develop`/ -staging branch. Full rationale: docs/adr/0021, docs/adr/0030, and -docs/adr/0031. - -- **Direct local commits to `main` are allowed.** The repository installs no - local Git hooks; contributors run the applicable quality commands - explicitly, and required GitHub checks are the delivery gate for - `origin/main`. Synchronize with `origin/main` before direct work and never - rewrite published history. -- **When a task uses a branch, use one task per branch.** Cut it from an - up-to-date `main` with - `git fetch origin && git checkout -b / origin/main`, where - `` is `feat`, `fix`, `chore`, `docs`, or `refactor`. Don't stack a - branch on top of another - unmerged feature branch (merging B into unmerged A, then A into `main` - later) — that's what turned a small, current diff into a large, - stale-conflict one here before this ADR. If work genuinely depends on - another unmerged branch, rebase onto `main` once that branch lands, - don't merge into it. -- **Rebase onto `origin/main` before opening or updating a PR**, and - again if `main` moves before merge. Small, same-day conflicts beat a - multi-week reconciliation. -- **Squash merge only**, branch auto-deleted on merge (already configured - on the GitHub repo — don't change the merge-method settings without - updating docs/adr/0021). -- **Concurrent agents (or an agent running alongside a human's own - GitHub Desktop / IDE commits) use isolated working trees** — `git -worktree add ../agenza- `, or the Agent tool's - `isolation: "worktree"` — never share one working directory across - simultaneous tasks. Uncommitted changes stay in the worktree that owns - them; a concurrent task must not reuse a worktree whose branch or files - belong to another task. - -## Rule persistence policy - -A correction to an agent, a recurring bug, or a new architectural decision -is **not durable** just because it was said once in a conversation. It only -counts as persisted once it has, wherever applicable: - -1. a rule in the right `AGENTS.md` (root or local), -2. an updated skill in `agent-skills/`, -3. an ADR or other doc update, -4. a regression test, -5. an automated guard (`scripts/architecture_guard.py`), -6. a CI gate that enforces it. - -When the user corrects an agent, the agent should judge whether the -correction is a one-off or a durable rule, business constraint, or process -improvement. If durable, follow `agent-skills/agenza-rule-persistence` -through that full checklist — including checking `CLAUDE.md` files, older -skills, comments, docs, templates, and tests that might still teach the -superseded pattern. +The repository is trunk-based with `main` as its only long-lived branch. + +- Direct local commits to `main` are allowed after synchronizing with + `origin/main`; never rewrite published history. +- A task branch starts from current `origin/main`, uses `/` where + type is `feat`, `fix`, `chore`, `docs`, or `refactor`, and is rebased before a + PR or update. Do not stack it on an unmerged feature branch. +- PRs squash-merge and delete their branch. +- Concurrent agents or humans use isolated worktrees. Never share one working + directory across simultaneous tasks or overwrite unrelated user changes. + +## Rule persistence + +When a correction, recurring bug, or review finding establishes a durable rule, +use `.agents/skills/agenza-rule-persistence`. Update every applicable layer: + +1. concrete code/documentation; +2. the correct `AGENTS.md`; +3. the canonical skill and its references; +4. an ADR when architectural; +5. a regression test; +6. an automated guard when mechanically detectable; +7. the CI path that runs it. + +Check examples, comments, and historical instruction layers for the superseded +teaching. A conversation-only correction is not persisted. ## Skills -The single editable source of skills is `agent-skills/` (portable -frontmatter: `name` + `description` only, no tool-specific fields). It is -synced — never hand-copied — into `.agents/skills/` (Codex) and -`.claude/skills/` (Claude Code) by `scripts/sync_agent_skills.py`. Run -`python scripts/sync_agent_skills.py --check` after editing anything under -`agent-skills/`; run it without `--check` to actually sync. +`.agents/skills/` is the only editable repository skill source and is consumed +directly by Codex and GitHub Copilot. The sync script copies it verbatim to +`.claude/skills/` for Claude Code; never edit that distribution by hand. +Repository-local `agent-skills/`, `prompts/`, `.claude/agents/`, `.skills/`, and +standalone `.agent.md` instruction layers are prohibited because they create +parallel workflows or tool-specific teaching. -Use `agent-skills/agenza-architecture-review` to audit whether the current -monorepo follows its established rules. Use -`agent-skills/evolve-modular-architecture` when deciding how the -architecture should evolve: defining or repairing module boundaries, -choosing between a modular monolith and selective service extraction, -planning an incremental migration, writing the ADR, or defining fitness -functions for the new boundary. +Run `python scripts/sync_agent_skills.py` after changing a canonical skill and +`--check` to verify distributions. -## Mandatory commands before calling anything done +## Mandatory commands ```bash -# Governance (always, regardless of what changed) +# Governance — always python scripts/sync_agent_skills.py --check python scripts/check_agent_governance.py python scripts/architecture_guard.py -# Backend, if backend/** changed +# Backend, when backend/** changed dotnet build backend/AdminBackend.slnx dotnet test backend/AdminBackend.slnx -# Frontend, if apps/admin-frontend/** changed +# Frontend, when apps/admin-frontend/** changed npm run format:check --workspace=apps/admin-frontend npm run lint --workspace=apps/admin-frontend npm run build --workspace=apps/admin-frontend npm run test:coverage --workspace=apps/admin-frontend ``` -## Completion criteria - -A task is done only when every gate that applies to what you touched is -green: build, tests, lint/format, coverage gate, the three governance -scripts above, docs updated (STATUS/ADR/AGENTS.md as applicable), and — for -a durable rule change — the rule-persistence checklist satisfied. Do not -report a task complete while an applicable gate is still red; say what's -red and why instead. +A task is complete only when every applicable gate is green, documentation is +truthful, and no required work remains. Report any red gate and its cause. diff --git a/CLAUDE.md b/CLAUDE.md index 4360d52..43c994c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,24 +1 @@ @AGENTS.md - -# Claude Code integration - -Use the skills available under `.claude/skills/` whenever a skill's -description matches the task at hand — they are synced copies of the -canonical skills in `agent-skills/`, never edit them directly. - -For architecture, exception-flow, API-contract, or tenant-isolation -reviews, prefer the matching read-only subagent in `.claude/agents/` -(`agenza-architecture-reviewer`, `agenza-exception-auditor`, -`agenza-contract-reviewer`, `agenza-tenant-reviewer`) over redoing the -review inline. - -Run the governance checks before considering any task complete: - -```bash -python scripts/sync_agent_skills.py --check -python scripts/check_agent_governance.py -python scripts/architecture_guard.py -``` - -Do not claim a task is complete while any applicable gate — these -governance scripts, build, test, lint, or coverage — is still failing. diff --git a/README.md b/README.md index a214959..a8b9295 100644 --- a/README.md +++ b/README.md @@ -7,13 +7,13 @@ monorepo: React frontend, .NET backend microservices, Python AI services. | Path | Stack | Purpose | | --------------------- | ----------------------------- | ----------------------------------------------- | -| `apps/admin-frontend` | Vite + React 19 + TS (strict) | The admin panel UI | -| `backend` | .NET 10 (ASP.NET Core) | Business microservices, one per bounded context | -| `ai-services` | Python 3.14 (FastAPI) | AI/ML services | +| `apps/admin-frontend` | Vite + React + strict TypeScript | The admin panel UI | +| `backend` | ASP.NET Core | Context-aggregated business services | +| `ai-services` | Python + FastAPI | AI/ML services | | `infra` | PostgreSQL init scripts | Local database roles and schema grants | See [docs/MONOREPO.md](docs/MONOREPO.md) for conventions, and each stack's own -`CLAUDE.md`/`README.md` for stack-specific guidance. +`AGENTS.md`/`README.md` for stack-specific guidance. This repo is built AI-first: the docs are the spec, agents execute, CI verifies. **[docs/SDD-GUIDE.md](docs/SDD-GUIDE.md)** is the developer @@ -33,7 +33,7 @@ dotnet run --project backend/services/services-service/ServicesService.Api # AI services (Python) cd ai-services/assistant-service -pip install uv==0.11.32 +pip install uv uv sync --frozen --extra dev uv run uvicorn app.main:app --reload --port 8001 @@ -41,17 +41,10 @@ uv run uvicorn app.main:app --reload --port 8001 dotnet run --project backend/AppHost --launch-profile http ``` -## Versions +## Tool versions -| Stack | Minimum supported (CI-gated) | Recommended local/runtime | -| ------ | ------------------------------------------------------------ | -------------------------------------------- | -| Node | 26.5.1 (`.nvmrc`, `engines.node`) | Same — `nvm use` picks it up automatically | -| npm | 12.0.2 (`packageManager`) | Same | -| .NET | 10.0.302 (`backend/global.json`, `rollForward: latestPatch`) | Same | -| Python | 3.14.6 (`requires-python`, CI) | 3.14.6 (`.python-version`, `uv.lock`) | -| Docker | 29.5 | Same (container runtime for Aspire Postgres) | - -Runtime and package-manager pins are aligned across local development and CI. -TypeScript and Microsoft.OpenApi intentionally remain on their latest -compatible stable lines; [ADR 0032](docs/adr/0032-stable-runtime-and-toolchain-compatibility-pins.md) -records the upgrade conditions. +Use the repository pins instead of copying versions from documentation: +`.nvmrc`/`packageManager`, `backend/global.json`, `.python-version`/`uv.lock`, +and the CI setup actions are the executable sources. Compatibility exceptions +and upgrade conditions are recorded in +[ADR 0032](docs/adr/0032-stable-runtime-and-toolchain-compatibility-pins.md). diff --git a/agent-skills/agenza-api-contract-review/SKILL.md b/agent-skills/agenza-api-contract-review/SKILL.md deleted file mode 100644 index 2c058a5..0000000 --- a/agent-skills/agenza-api-contract-review/SKILL.md +++ /dev/null @@ -1,75 +0,0 @@ ---- -name: agenza-api-contract-review -description: > - Use to audit consistency between the backend's OpenAPI-exposed contract - and the frontend's DTOs/generated types/error handling. Trigger on - "review the API contract", "check for contract drift", "did the backend - response shape change", or before/after a change to a controller, DTO, - ProblemDetails shape, or the generated TypeScript client. Detection and - reporting only — never changes a public contract silently. ---- - -# API Contract Review - -## What "the contract" means here - -- Backend: each service's OpenAPI document (`GET /openapi/v1.json`), - generated from its controllers/DTOs (`{Service}Response` records, - `Error`/`ProblemDetails` shapes). -- Frontend: `apps/admin-frontend/src/features/catalog/infrastructure/generated/services-api.d.ts`, - generated by `npm run generate:api-types` from services-service's live - OpenAPI document, checked for staleness by `npm run generate:api-types:check` - (also gated in CI — `api-contract-check` in `.github/workflows/frontend-ci.yml`). -- Frontend hand-written types: per-feature DTO interfaces in - `src/infrastructure/mappers/*.ts` (see `agent-skills/agenza-frontend-feature` - step 5) — these exist for features not yet covered by the generated - client, or that intentionally narrow it. - -## Checks - -1. **Generated types are current.** Run `npm run generate:api-types:check - --workspace=apps/admin-frontend` (requires services-service running and - reachable, matching the CI job) — a failure here means a controller/DTO - changed without regenerating. -2. **DTO duplication.** A hand-written DTO interface in - `src/infrastructure/mappers/` that shadows a type already in - `services-api.d.ts` for the same resource — flag for consolidation. -3. **Field limit drift.** A `MaximumLength`/`.PrecisionScale(...)` on the - backend validator or EF column vs. a `maxLength`/`max()` in the - matching Zod schema (`agent-skills/agenza-frontend-feature`'s form - section) — these must match exactly (see docs/adr/0012's - `Category.NameMaxLength`/`Service.NameMaxLength` alignment for the kind - of drift this catches). A limit encoded only in a comment, never a - type/schema/const, is itself a finding — flag it for a real check. -4. **Enum drift.** A backend enum/palette (e.g. `TagColor.Palette`) vs. - its frontend mirror (e.g. `TAG_COLOR_PALETTE`) — every value present on - both sides, in the same casing/format the wire actually uses. -5. **Renamed property.** A DTO property renamed on one side without the - matching mapper (`serverFormError.ts`'s `fieldMap`, - `infrastructure/mappers/*.ts`) updated on the other. -6. **Unhandled API error shape.** A backend error `code` introduced - (`Error.Conflict("Entity.SomeCode", ...)`) with no corresponding entry - in the matching frontend `codeFieldMap` (see - `agent-skills/agenza-frontend-feature`'s "Structured API errors" - section) — it will still work (falls back to a global message) but - loses field-level precision; flag it, don't treat it as broken. -7. **Structured vs. free-text errors.** Confirm a new validation failure - path returns through `Error.FieldErrors` (structured, docs/adr/0012), - not a single joined message string a frontend would have to parse. - -## Output format - -A table: `resource | field/enum/error-code | backend value | frontend -value | drift type | suggested fix`. Call out anything that's a breaking -change for an existing consumer (renamed/removed field or endpoint, -narrowed enum, tightened validation on an existing field) separately and -prominently — that needs the root `AGENTS.md` question-policy treatment -(confirm with the user) before changing, not a silent fix. - -## Non-goals - -- Never change a public contract (rename a field, remove an endpoint, - narrow a validation rule) as a side effect of a "review" — surface it as - a finding requiring a decision. -- Don't hand-edit `services-api.d.ts` — it's generated; fix the source - (backend DTO/controller) and regenerate. diff --git a/agent-skills/agenza-architecture-review/SKILL.md b/agent-skills/agenza-architecture-review/SKILL.md deleted file mode 100644 index 0df6c0d..0000000 --- a/agent-skills/agenza-architecture-review/SKILL.md +++ /dev/null @@ -1,98 +0,0 @@ ---- -name: agenza-architecture-review -description: > - Use for a general architecture audit of this monorepo — on request, - periodically, or before a release. Trigger on "architecture review", - "audit the codebase", "is our architecture sound?", or when asked to - check consistency across the monorepo, Clean Architecture layering, - vertical slices, multi-tenancy, the Result pattern, testing, migrations, - documentation, CI, or dependencies. Review-only by default — implements - and validates fixes only when the task explicitly asks for - implementation, not just a report. ---- - -# Architecture Review - -## Scope - -Check, across whichever of these areas are in scope for the request: - -- **Monorepo structure**: does `docs/MONOREPO.md` still match reality? - New app/service not listed, a stale entry for something removed? -- **Clean Architecture / layering**: any dependency pointing outward - (Domain referencing Application/Infrastructure, `domain/`/`application/` - in the frontend importing React or `infrastructure/`/`presentation/`)? -- **Vertical slices / feature organization**: backend `Application///` - shape followed? Frontend feature folders self-contained, no cross-feature - imports? -- **Multi-tenancy**: delegate the deep pass to - `agent-skills/agenza-tenant-isolation-review` rather than duplicating it - here — this review only checks that tenant scoping is *present* where - expected, not the full mechanism. -- **Exceptions / Result pattern**: delegate the deep pass to - `agent-skills/agenza-exception-flow-audit`. -- **Domain model**: anemic entities (public setters, no invariant - enforcement), missing `DomainResult` usage, entities bypassing - `BaseEntity`/`TenantOwnedEntity` without a documented reason. -- **Persistence**: query filters applied by hand instead of via - `ApplyAuditableConventions`, missing indexes for a new uniqueness rule, - a migration issue — delegate depth to - `agent-skills/agenza-migration-safety`. -- **Contracts**: delegate to `agent-skills/agenza-api-contract-review`. -- **Frontend**: layering (see above), `any` usage, design-system drift - (raw palette classes instead of semantic tokens), reusable-component - discipline (`agent-skills/agenza-frontend-feature`). -- **Accessibility**: keyboard operability, accessible names, contrast — - sample a few recently-changed pages rather than the whole app unless - asked for a full sweep. -- **Tests**: coverage gate status, mock-strategy-per-layer discipline - (frontend), no integration-test reintroduction without an ADR reverting - docs/adr/0015 (backend). -- **Migrations**: `agent-skills/agenza-migration-safety`. -- **Documentation**: `AGENTS.md`/`CLAUDE.md` files still accurate and in - sync (`scripts/check_agent_governance.py` covers the mechanical half of - this), STATUS.md rows matching what's actually built, ADRs referenced - by number actually existing. -- **CI**: workflows still matching the commands documented in - `docs/QUALITY.md`, coverage gates not silently loosened. -- **Dependencies**: any package pinned for a documented reason - (`docs/QUALITY.md`, `README.md`'s Versions table) that a routine bump - would silently violate. - -## Mode: review-only (default) - -Produce a diagnosis, not a diff. For each finding: - -- **File/location** -- **What's wrong** (one sentence) -- **Why it matters** (tie back to a rule in `AGENTS.md`, an ADR, or a - skill — don't invent a new rule mid-review; if there's genuinely no - existing rule this violates, that's a finding for - `agent-skills/agenza-rule-persistence` to formalize, not a silent - judgment call) -- **Severity**: blocks tenant isolation / security > breaks a build gate - > architectural drift > style nit -- **Suggested fix** (one sentence — enough to hand to the relevant build - skill, not a full patch) - -Do not edit code in this mode, even for an "obvious" one-line fix. - -## Mode: implement (only when explicitly requested) - -1. **Diagnose** using the review above. -2. **Fix**, using the matching build skill for the area - (`agenza-backend-use-case`, `agenza-frontend-feature`, - `agenza-migration-safety`) rather than ad hoc edits. -3. **Validate**: run the commands in the relevant `AGENTS.md` - ("Mandatory commands") plus `scripts/architecture_guard.py`. -4. **Report evidence**: paste the actual command output (or a faithful - summary of it) showing the gate now passes — not just "should be - fixed now." - -## Non-goals - -- Don't rewrite working code to a "nicer" pattern with no rule behind it — - see the repo-wide anti-speculation rule in root `AGENTS.md`. -- Don't silently widen an allowlist, delete a test, or lower a coverage - gate to make a finding go away — that's the exact anti-pattern this - governance framework exists to prevent (see `docs/AGENT-GOVERNANCE.md`). diff --git a/agent-skills/agenza-backend-use-case/SKILL.md b/agent-skills/agenza-backend-use-case/SKILL.md deleted file mode 100644 index 0a102ea..0000000 --- a/agent-skills/agenza-backend-use-case/SKILL.md +++ /dev/null @@ -1,823 +0,0 @@ ---- -name: agenza-backend-use-case -description: > - Use whenever adding or changing business logic in any .NET backend service - under backend/ — a new command, query, entity, value object, repository - method, or endpoint, or any change to an existing one. Trigger on "add - endpoint", "implement [operation]", "create [entity]", "command", "query", - "handler", "validator", "vertical slice". Encodes this repo's CQRS/ - vertical-slice/Result-pattern conventions (docs/adr/0005, docs/adr/0012, - docs/adr/0014), layering, rich-domain, tenant-scoping, and testing rules. - Do NOT write backend business logic without reading it first — it also - documents patterns this codebase already tried and reverted, so an agent - that skips it is likely to reintroduce a fixed bug. ---- - -# Backend Use Case - -The reference implementation is `services-service`'s Tags vertical — open -these files and mirror their shape exactly (the templates below are a -direct copy of this feature's current, ADR-0014-compliant code): - -- `ServicesService.Domain/Entities/Tag.cs`, `ValueObjects/TagColor.cs` — entity/VO with invariants -- `ServicesService.Application/Tags/CreateTag/` — full command slice -- `ServicesService.Application/Tags/UpdateTag/` — same, plus `UpdateTagCommandExtensions.ApplyTo` -- `ServicesService.Application/Tags/TagPersistenceErrorMapper.cs` — persistence-conflict mapping -- `ServicesService.Application/Tags/TagResponse.cs` — DTO shared across the feature's operations -- `ServicesService.Application/Abstractions/` — ports (`ITagRepository`, `IUnitOfWork`) -- `ServicesService.Api/Controllers/TagsController.cs` — direct command binding + Result → HTTP mapping (docs/adr/0007) -- `ServicesService.Tests/Tags/CreateTag/` — handler + validator unit tests - -identity-service's `Tenants/ProvisionTenant/` slice is the second -reference — read it when the operation needs a database transaction -across more than one abstraction (see the UnitOfWork note below). - -## Decision tree — where does a given rule live? - -| The rule is about... | It lives in... | -| ---------------------------------------------------------- | ------------------------------------------ | -| Shape of the command's own data (required, length, format, numeric range, cross-field comparison within the same command) | **FluentValidation** validator, sync rules only | -| Current state of the application (existence, uniqueness, in-use, another aggregate) | The **handler** — a plain `if (...) return Result.Failure(...)` before persisting | -| A permanent invariant of the entity itself (a `Tag` can never have an empty name, a `Service`'s min duration can never exceed its max) | **`DomainResult`** from the entity's `Create`/`Update` | -| Data integrity / concurrency at the database boundary (a unique index catching a race the pre-check missed) | The database + **`PersistenceResult`**, mapped by a per-entity `*PersistenceErrorMapper` | -| A genuinely unexpected, unrecoverable technical failure (missing config, an unrecognized DB error, a framework guarantee) | **Exception** — the one case where throwing is still correct | - -## Hard prohibitions (these are reverted patterns — see docs/adr/0012, docs/adr/0014) - -Do **not** write any of the following. `scripts/architecture_guard.py` -fails the build on several of these; the rest are still real regressions -even where the guard can't catch them syntactically. - -- A repository (or any port) injected into a validator's constructor. -- `MustAsync`/`CustomAsync` on a FluentValidation rule that queries a - repository or the database. Validators in this repo are pure, synchronous - shape checks — nothing in them ever awaits. -- Throwing for an expected business outcome (validation failure, not-found, - conflict/duplicate, in-use, forbidden). Everything expected returns a - `Result`/`DomainResult`/`PersistenceResult`. -- Conventional `try/catch` in a handler to convert a business outcome. The - only handler-level `try/catch` in this codebase is - `IUnitOfWork.ExecuteInTransactionAsync`'s rollback-on-unexpected-failure - wrapper (identity-service) — never a catch that maps to a `Result`. -- `DuplicateEntityException` (deleted, docs/adr/0014 — a unique-constraint - race returns `PersistenceResult.Failure` instead). -- `BusinessExceptionHandler` (deleted — `Admin.SharedKernel.GenericExceptionHandler` - is the only exception handler; it exists purely for unexpected 500s). -- A null-forgiving `!` on a repository lookup that assumes some earlier - validator step already guaranteed existence. Validators here never do - existence checks (they take no repository dependency at all) — the - handler that needs the entity fetches it itself and returns - `Error.NotFound(...)` on a null, in the same method, before doing - anything else with it. -- A brand-new project/folder split for a feature that fits inside an - existing service's `Application///` shape. Only - create a new microservice for a genuinely new bounded context — see - `.skills/backend-new-microservice/SKILL.md`. -- Wiring MediatR, or any DI registration for a handler/validator by hand — - `AddXApplication()` assembly-scans for both; a new slice needs no - registration at all. - -## Build order (TDD — test first at each step) - -### 1. Domain entity or value object - -- If the entity does NOT belong to a tenant (rare — e.g. `Tenant` itself - in identity-service), inherit `{Service}.Domain.Common.BaseEntity` - directly — gives `Id`, `CreatedAt`/`CreatedBy`, `UpdatedAt`/ - `UpdatedBy`, `DeletedAt`/`DeletedBy`, `IsDeleted` for free - (docs/adr/0006). Call `base(id)` from your constructor; never set the - audit fields yourself, the EF interceptor does that. -- If the entity belongs to a tenant (the common case), inherit - `{Service}.Domain.Common.TenantOwnedEntity` instead — it already - inherits `BaseEntity` and implements `ITenantOwned` (`Guid TenantId - { get; }` + `void AssignTenant(Guid tenantId)`) for you, so don't - implement `ITenantOwned` or add an `AssignTenant` override on the - entity itself. The constructor never takes a `tenantId` parameter at - all — `TenantId` starts `Guid.Empty` and only `AssignTenant` (inherited) - can set it, throwing a plain `InvalidOperationException` on empty - (docs/adr/0009, docs/adr/0014) — the one entity-level path allowed to - throw instead of returning `DomainResult`, since it's only reachable via - an internal bug (`TenantHeaderFilter` already rejects a request with no/ - mismatched tenant before any handler runs). -- Public constructor becomes `private`; add a `public static - DomainResult Create(...)` factory that validates every - invariant and returns `DomainResult.Failure(new - DomainError("Widget.Invalid", message))` on the first invalid field - instead of throwing — never a raw `Exception`/`ArgumentException` - (docs/adr/0014). `DomainResult`/`DomainResult`/`DomainError` - (`{Service}.Domain/Common/`) already exist per service — copy them - once, not per entity. -- State-changing methods (`Update`, `Cancel`, `Reschedule`) return - `DomainResult` (not `void`) for the same reason — validate every new - value into a local before assigning any field, so a failure never - leaves the entity partially mutated. -- No public setters. Add a `private` parameterless constructor ONLY if EF - needs it, and keep it private. -- Tests: plain xUnit + AwesomeAssertions, no mocks needed — Domain has - zero dependencies. Cover `MarkCreated`/`MarkUpdated`/`MarkDeleted` - (inherited from `BaseEntity`) too — they count toward the coverage - gate. `AssignTenant` (if `ITenantOwned`) is the one exception to the - `DomainResult` rule — assert it throws `InvalidOperationException` on - an empty guid. - -### 2. Port (interface) in `Application/Abstractions/` - -- Narrow, intention-revealing methods (`Add`, `GetByIdAsync`, - `NameExistsAsync`) — not a generic interface. `Add`/`Remove` are - synchronous and only stage the change (no internal commit). -- If the entity is `ITenantOwned`, its methods do NOT take a tenant id - parameter — the DbContext scopes the query automatically (step 5, - docs/adr/0006). -- The **implementation** (step 5) extends - `Admin.SharedKernel.EntityFrameworkCore.RepositoryBase` for - the Add/Remove/Find/List boilerplate underneath this interface — the - port itself stays a plain, narrow interface. - -### 3. Command or query slice in `Application///` - -``` -Application/Tags/ -├── TagResponse.cs shared DTO (feature root) -├── TagPersistenceErrorMapper.cs shared persistence-conflict mapper (feature root) -└── CreateTag/ - ├── CreateTagCommand.cs : ICommand - ├── CreateTagCommandValidator.cs AbstractValidator - shape only, parameterless - └── CreateTagCommandHandler.cs : ICommandHandler -``` - -- A **command** mutates (`ICommand` if nothing to return, - `ICommand` otherwise); a **query** reads - (`IQuery`). Handler returns `Result` / `Result` - — never throws for an expected business outcome. Use - `Error.Validation/.NotFound/.Conflict/.Forbidden(code, message)`. -- Validator: **shape rules only**, parameterless constructor, no - repository, no `MustAsync`/`CustomAsync` — see the prohibitions above. -- Cross-aggregate rules needing a repository round-trip (existence, - uniqueness, in-use) live in the **handler**, checked in this order - before any mutation: not-found → duplicate/conflict → build/apply the - domain change → persist → map a persistence conflict. See - `CreateTagCommandHandler`/`UpdateTagCommandHandler`/`DeleteTagCommandHandler` - below for the exact shape, and `Application/Services/ServiceRelationshipLoader.cs` - for a multi-dependency version that loads Category/Tags exactly once - and reuses the same instances for both construction and the response. -- Constructor-injected ports only — no EF, no HttpClient, no ASP.NET - types in Application. -- Multiple writes that must succeed together → wrap in `IUnitOfWork`, - shaped to the real need (docs/adr/0005): a single - `Task> SaveChangesAsync(...)` if everything goes - through one `DbContext` (services-service's shape — lets Infrastructure - report a recognized unique-constraint violation without throwing), or a - Result-aware `ExecuteInTransactionAsync` if the operation spans - more than one abstraction that each commit independently, e.g. an EF - repository AND `UserManager` (identity-service's shape). -- Nothing to register by hand — each service's - `Application/DependencyInjection.cs` scans the assembly for handlers - and validators. -- If the handler constructs or mutates a domain entity from the - command's fields, put that mapping in a `{Operation}CommandExtensions.cs` - extension method beside the command (`ToModel(...)` for construction, - `ApplyTo(entity)` for mutation) instead of inlining it in `Handle(...)` - (docs/adr/0007). Both return `DomainResult`/`DomainResult` - respectively, so the handler checks `IsFailure` and maps via - `.Error.ToApplicationError()` before proceeding. - -### 4. Unit tests with NSubstitute - -- `Substitute.For()` per port used by the handler — no hand-written - fake classes (docs/adr/0006). Configure return values with - `.Returns(...)`; assert interaction with `.Received(1).Method(...)` / - `.DidNotReceive().Method(...)`. -- AwesomeAssertions, asserting on the `Result`: `result.IsSuccess`, - `result.Value.Xyz`, `result.Error.Type.Should().Be(ErrorType.Conflict)`. -- Test the happy path, the not-found path, the duplicate/conflict path, - and any `DomainResult.Failure` path the handler can still hit — a - handler unit test calls `Handle(...)` directly, bypassing the - validator, so it exercises paths production traffic never reaches. -- Validator tests use the synchronous `Validate(...)` (no `MustAsync` - rules exist to require `ValidateAsync`) and need no repository fakes at - all, since the validator takes none. - -### 5. Infrastructure adapter - -- Repository extends `Admin.SharedKernel.EntityFrameworkCore.RepositoryBase` - and implements the port (docs/adr/0006). `Add`/`Remove` only stage the - change — no `SaveChangesAsync` inside the repository (the handler - commits via `IUnitOfWork`). -- EF configuration lives in `Infrastructure/Persistence/Configurations/`. - The soft-delete query filter and `DeletedAt` index apply automatically - to every `BaseEntity`, and (if `ITenantOwned`) the tenant filter + - `TenantId` index too — the `DbContext` calls - `ApplyAuditableConventions(this, typeof(BaseEntity), typeof(ITenantOwned))` - once. Never add `HasQueryFilter` by hand. If the entity has a - uniqueness rule, add a unique index on a normalized column (see - `IX_Tags_TenantId_NameNormalized`) filtered with - `.HasFilter("\"DeletedAt\" IS NULL")` so a soft-deleted row doesn't - block reusing its unique value — this index, not the handler's - pre-check, is what actually guarantees uniqueness under concurrency - (see `agent-skills/agenza-migration-safety` for the migration itself). -- If the entity is tenant-owned, also pass `ICurrentTenantProvider` into - `AuditableEntitySaveChangesInterceptor`'s constructor so it can call - `AssignTenant` on a newly added entity automatically (docs/adr/0008). -- New tables → `dotnet ef migrations add ` from the Api project - directory. - -### 6. Controller (thin) - -- Constructor-inject `IDispatcher` (never a concrete handler type) — - nothing else. The global `TenantHeaderFilter` already rejected the - request with 403 before this action runs unless `X-Tenant-Id` matched - the token's `tenant_id` claim — mark the controller/action - `[IgnoreTenant]` instead if it genuinely isn't tenant-scoped. -- `[ApiVersion("1.0")]` + `[Route("api/v{version:apiVersion}/...")]` (or - `internal/v{version:apiVersion}/...` for M2M-only routes). -- **Bind the command/query directly as the action parameter — no local - `...Body` record** (docs/adr/0007). A route id binds into its own - `Guid id` parameter and gets merged into the command right before - dispatching: `command with { WidgetId = id }`. -- `await _dispatcher.Send(...)` / `.Query(...)` → - `result.ToActionResult(this, value => Ok(value))` (or `Created`/ - `NoContent`). No try/catch per exception type. -- `[Authorize]` by default; scope checks (`User.HasScope(...)`) for - M2M-only endpoints. - -### 7. Manual verification of the new endpoint - -There are no integration tests (docs/adr/0015) — CI runs unit tests only. -Before merging, run the service (`dotnet run --project services//{Service}.Api`) -and manually exercise the new endpoint: unauthenticated → 401, wrong -scope/tenant → 403, a validation failure → 400, duplicate name → 409, -unknown id → 404, happy path → expected status + persisted effect. - -## Definition of done - -```bash -dotnet build backend/AdminBackend.slnx -dotnet test backend/AdminBackend.slnx # unit tests only; coverage gate via Directory.Build.props/.targets -python scripts/architecture_guard.py # fails on any reverted pattern above -``` - -Both green, coverage gate passing, no new NU1903 (vulnerable package) -warnings, architecture guard clean. - ---- - -## Copy-paste templates - -A fictional **Widget** entity in a fictional **Widgets** feature — this is -a direct copy of Tags' current shape (see the reference files at the top), -renamed. Assume namespace root `{Service}` = your service's actual name. - -### Command with a response (Create-shaped) - -```csharp -// Application/Widgets/CreateWidget/CreateWidgetCommand.cs -using Admin.SharedKernel; - -namespace {Service}.Application.Widgets.CreateWidget; - -public sealed record CreateWidgetCommand(string Name) : ICommand; -``` - -```csharp -// Application/Widgets/CreateWidget/CreateWidgetCommandValidator.cs -using FluentValidation; -using {Service}.Domain.Entities; - -namespace {Service}.Application.Widgets.CreateWidget; - -public sealed class CreateWidgetCommandValidator : AbstractValidator -{ - public CreateWidgetCommandValidator() - { - RuleFor(command => command.Name) - .NotEmpty() - .MaximumLength(Widget.NameMaxLength); - } -} -``` - -```csharp -// Domain/Entities/Widget.cs -using {Service}.Domain.Common; - -namespace {Service}.Domain.Entities; - -public class Widget : TenantOwnedEntity -{ - public const int NameMaxLength = 80; - - public string Name { get; private set; } - - private Widget() - { - Name = string.Empty; // EF Core materialization only. - } - - private Widget(Guid id, string name) - : base(id) - { - Name = name; - } - - public static DomainResult Create(Guid id, string name) - { - var nameResult = ValidateName(name); - if (nameResult.IsFailure) - { - return DomainResult.Failure(nameResult.Error); - } - - return DomainResult.Success(new Widget(id, nameResult.Value)); - } - - public DomainResult Update(string name) - { - var nameResult = ValidateName(name); - if (nameResult.IsFailure) - { - return DomainResult.Failure(nameResult.Error); - } - - Name = nameResult.Value; - - return DomainResult.Success(); - } - - private static DomainResult ValidateName(string name) - { - var trimmed = name?.Trim() ?? string.Empty; - - if (trimmed.Length is 0 or > NameMaxLength) - { - return DomainResult.Failure(new DomainError( - "Widget.Invalid", - $"Name is required and must be at most {NameMaxLength} characters.")); - } - - return DomainResult.Success(trimmed); - } -} -``` - -```csharp -// Application/Widgets/CreateWidget/CreateWidgetCommandExtensions.cs -using {Service}.Domain.Common; -using {Service}.Domain.Entities; - -namespace {Service}.Application.Widgets.CreateWidget; - -public static class CreateWidgetCommandExtensions -{ - public static DomainResult ToModel(this CreateWidgetCommand command) => - Widget.Create(Guid.CreateVersion7(), command.Name); -} -``` - -```csharp -// Application/Widgets/WidgetPersistenceErrorMapper.cs -using Admin.SharedKernel; -using Microsoft.Extensions.Logging; -using {Service}.Application.Abstractions; - -namespace {Service}.Application.Widgets; - -public static class WidgetPersistenceErrorMapper -{ - private const string NameConstraint = "IX_Widgets_TenantId_NameNormalized"; - - public static Error Map(PersistenceError error, string name, ILogger logger) - { - if (error.ConstraintName == NameConstraint) - { - return Error.Conflict("Widget.DuplicateName", $"A widget named '{name}' already exists."); - } - - logger.LogError( - "Unrecognized unique constraint {ConstraintName} violated while saving a Widget", - error.ConstraintName); - return Error.Conflict("Widget.DuplicateConflict", "Could not save the widget due to a data conflict."); - } -} -``` - -```csharp -// Application/Widgets/CreateWidget/CreateWidgetCommandHandler.cs -using Admin.SharedKernel; -using Microsoft.Extensions.Logging; -using {Service}.Application.Abstractions; - -namespace {Service}.Application.Widgets.CreateWidget; - -public sealed class CreateWidgetCommandHandler : ICommandHandler -{ - private readonly IWidgetRepository _repository; - private readonly IUnitOfWork _unitOfWork; - private readonly ILogger _logger; - - public CreateWidgetCommandHandler( - IWidgetRepository repository, IUnitOfWork unitOfWork, ILogger logger) - { - _repository = repository; - _unitOfWork = unitOfWork; - _logger = logger; - } - - public async Task> Handle(CreateWidgetCommand command, CancellationToken cancellationToken) - { - if (await _repository.NameExistsAsync(command.Name, excludeId: null, cancellationToken)) - { - return Result.Failure( - Error.Conflict("Widget.DuplicateName", $"A widget named '{command.Name}' already exists.")); - } - - var widgetResult = command.ToModel(); - if (widgetResult.IsFailure) - { - return Result.Failure(widgetResult.Error.ToApplicationError()); - } - - var widget = widgetResult.Value; - _repository.Add(widget); - - var saveResult = await _unitOfWork.SaveChangesAsync(cancellationToken); - if (saveResult.IsFailure) - { - return Result.Failure(WidgetPersistenceErrorMapper.Map(saveResult.Error, command.Name, _logger)); - } - - return WidgetResponse.FromWidget(widget); - } -} -``` - -No `ICurrentTenantProvider` needed in this handler at all — the tenant -is assigned automatically on save (docs/adr/0008). Only the `DbContext` -(query scoping) and `AuditableEntitySaveChangesInterceptor` (assignment) -need it; see step 5. - -### Command with a response and a route id (Update-shaped) - -```csharp -// Application/Widgets/UpdateWidget/UpdateWidgetCommand.cs -using Admin.SharedKernel; - -namespace {Service}.Application.Widgets.UpdateWidget; - -public sealed record UpdateWidgetCommand(Guid WidgetId, string Name) : ICommand; -``` - -```csharp -// Application/Widgets/UpdateWidget/UpdateWidgetCommandValidator.cs -using FluentValidation; -using {Service}.Domain.Entities; - -namespace {Service}.Application.Widgets.UpdateWidget; - -public sealed class UpdateWidgetCommandValidator : AbstractValidator -{ - public UpdateWidgetCommandValidator() - { - RuleFor(command => command.WidgetId).NotEmpty(); - - RuleFor(command => command.Name) - .NotEmpty() - .MaximumLength(Widget.NameMaxLength); - } -} -``` - -Cross-aggregate rules (existence, uniqueness) never live in the validator — -that's the handler's job below. `WidgetId` is still shape-validated even -though it's route-sourced: the controller merges the route id in via -`with` BEFORE dispatching (see the Controller template below). - -```csharp -// Application/Widgets/UpdateWidget/UpdateWidgetCommandExtensions.cs -using {Service}.Domain.Common; -using {Service}.Domain.Entities; - -namespace {Service}.Application.Widgets.UpdateWidget; - -public static class UpdateWidgetCommandExtensions -{ - public static DomainResult ApplyTo(this UpdateWidgetCommand command, Widget widget) => - widget.Update(command.Name); -} -``` - -```csharp -// Application/Widgets/UpdateWidget/UpdateWidgetCommandHandler.cs -using Admin.SharedKernel; -using Microsoft.Extensions.Logging; -using {Service}.Application.Abstractions; - -namespace {Service}.Application.Widgets.UpdateWidget; - -public sealed class UpdateWidgetCommandHandler : ICommandHandler -{ - private readonly IWidgetRepository _repository; - private readonly IUnitOfWork _unitOfWork; - private readonly ILogger _logger; - - public UpdateWidgetCommandHandler( - IWidgetRepository repository, IUnitOfWork unitOfWork, ILogger logger) - { - _repository = repository; - _unitOfWork = unitOfWork; - _logger = logger; - } - - public async Task> Handle(UpdateWidgetCommand command, CancellationToken cancellationToken) - { - var widget = await _repository.GetByIdAsync(command.WidgetId, cancellationToken); - if (widget is null) - { - return Result.Failure( - Error.NotFound("Widget.NotFound", $"Widget '{command.WidgetId}' was not found.")); - } - - if (await _repository.NameExistsAsync(command.Name, command.WidgetId, cancellationToken)) - { - return Result.Failure( - Error.Conflict("Widget.DuplicateName", $"A widget named '{command.Name}' already exists.")); - } - - var applyResult = command.ApplyTo(widget); - if (applyResult.IsFailure) - { - return Result.Failure(applyResult.Error.ToApplicationError()); - } - - var saveResult = await _unitOfWork.SaveChangesAsync(cancellationToken); - if (saveResult.IsFailure) - { - return Result.Failure(WidgetPersistenceErrorMapper.Map(saveResult.Error, command.Name, _logger)); - } - - return WidgetResponse.FromWidget(widget); - } -} -``` - -### Command with no response (Delete-shaped) - -```csharp -// Application/Widgets/DeleteWidget/DeleteWidgetCommand.cs -using Admin.SharedKernel; - -namespace {Service}.Application.Widgets.DeleteWidget; - -public sealed record DeleteWidgetCommand(Guid WidgetId) : ICommand; -``` - -```csharp -// Application/Widgets/DeleteWidget/DeleteWidgetCommandHandler.cs -using Admin.SharedKernel; -using Microsoft.Extensions.Logging; -using {Service}.Application.Abstractions; - -namespace {Service}.Application.Widgets.DeleteWidget; - -public sealed class DeleteWidgetCommandHandler : ICommandHandler -{ - private readonly IWidgetRepository _repository; - private readonly IUnitOfWork _unitOfWork; - private readonly ILogger _logger; - - public DeleteWidgetCommandHandler( - IWidgetRepository repository, IUnitOfWork unitOfWork, ILogger logger) - { - _repository = repository; - _unitOfWork = unitOfWork; - _logger = logger; - } - - public async Task Handle(DeleteWidgetCommand command, CancellationToken cancellationToken) - { - var widget = await _repository.GetByIdAsync(command.WidgetId, cancellationToken); - if (widget is null) - { - return Result.Failure(Error.NotFound("Widget.NotFound", $"Widget '{command.WidgetId}' was not found.")); - } - - _repository.Remove(widget); - - var saveResult = await _unitOfWork.SaveChangesAsync(cancellationToken); - if (saveResult.IsFailure) - { - return Result.Failure(WidgetPersistenceErrorMapper.Map(saveResult.Error, widget.Name, _logger)); - } - - return Result.Success(); - } -} -``` - -### Query (List/Get-shaped) - -```csharp -// Application/Widgets/ListWidgets/ListWidgetsQuery.cs -using Admin.SharedKernel; - -namespace {Service}.Application.Widgets.ListWidgets; - -public sealed record ListWidgetsQuery : IQuery>; -``` - -```csharp -// Application/Widgets/ListWidgets/ListWidgetsQueryHandler.cs -using Admin.SharedKernel; -using {Service}.Application.Abstractions; -using {Service}.Application.Widgets; - -namespace {Service}.Application.Widgets.ListWidgets; - -public sealed class ListWidgetsQueryHandler : IQueryHandler> -{ - private readonly IWidgetRepository _repository; - - public ListWidgetsQueryHandler(IWidgetRepository repository) - { - _repository = repository; - } - - public async Task>> Handle( - ListWidgetsQuery query, CancellationToken cancellationToken) - { - var widgets = await _repository.ListAsync(cancellationToken); - IReadOnlyList response = widgets.Select(WidgetResponse.FromWidget).ToList(); - return Result.Success(response); - } -} -``` - -No validator needed unless the query takes user input. - -### Shared feature DTO (once per feature, not per operation) - -```csharp -// Application/Widgets/WidgetResponse.cs -using {Service}.Domain.Entities; - -namespace {Service}.Application.Widgets; - -public sealed record WidgetResponse(Guid Id, string Name) -{ - public static WidgetResponse FromWidget(Widget widget) => new(widget.Id, widget.Name); -} -``` - -### Controller (dispatch + Result → HTTP) - -```csharp -using Admin.SharedKernel; -using Asp.Versioning; -using Microsoft.AspNetCore.Mvc; -using {Service}.Application.Widgets.CreateWidget; -using {Service}.Application.Widgets.DeleteWidget; -using {Service}.Application.Widgets.ListWidgets; -using {Service}.Application.Widgets.UpdateWidget; - -namespace {Service}.Api.Controllers; - -[ApiController] -[ApiVersion("1.0")] -[Route("api/v{version:apiVersion}/widgets")] -public class WidgetsController : ControllerBase -{ - private readonly IDispatcher _dispatcher; - - public WidgetsController(IDispatcher dispatcher) - { - _dispatcher = dispatcher; - } - - [HttpGet] - public async Task List(CancellationToken cancellationToken) - { - var result = await _dispatcher.Query(new ListWidgetsQuery(), cancellationToken); - return result.ToActionResult(this, widgets => Ok(widgets)); - } - - [HttpPost] - public async Task Create(CreateWidgetCommand command, CancellationToken cancellationToken) - { - var result = await _dispatcher.Send(command, cancellationToken); - return result.ToActionResult(this, widget => Created($"/api/v1/widgets/{widget.Id}", widget)); - } - - [HttpPut("{id:guid}")] - public async Task Update(Guid id, UpdateWidgetCommand command, CancellationToken cancellationToken) - { - var result = await _dispatcher.Send(command with { WidgetId = id }, cancellationToken); - return result.ToActionResult(this, widget => Ok(widget)); - } - - [HttpDelete("{id:guid}")] - public async Task Delete(Guid id, CancellationToken cancellationToken) - { - var result = await _dispatcher.Send(new DeleteWidgetCommand(id), cancellationToken); - return result.ToActionResult(this, NoContent); - } -} -``` - -### Unit tests with NSubstitute (handler + validator) - -```csharp -// Tests/Widgets/CreateWidget/CreateWidgetCommandHandlerTests.cs -using Admin.SharedKernel; -using Microsoft.Extensions.Logging; -using {Service}.Application.Abstractions; -using {Service}.Application.Widgets.CreateWidget; -using {Service}.Domain.Entities; - -namespace {Service}.Tests.Widgets.CreateWidget; - -public class CreateWidgetCommandHandlerTests -{ - private readonly IWidgetRepository _repository = Substitute.For(); - private readonly IUnitOfWork _unitOfWork = Substitute.For(); - private readonly ILogger _logger = Substitute.For>(); - private readonly CreateWidgetCommandHandler _handler; - - public CreateWidgetCommandHandlerTests() - { - _repository.NameExistsAsync(Arg.Any(), Arg.Any(), Arg.Any()).Returns(false); - _unitOfWork.SaveChangesAsync(Arg.Any()).Returns(PersistenceResult.Success(1)); - _handler = new CreateWidgetCommandHandler(_repository, _unitOfWork, _logger); - } - - [Fact] - public async Task Handle_WithValidCommand_PersistsAndReturnsTheValue() - { - var result = await _handler.Handle(new CreateWidgetCommand("Example"), CancellationToken.None); - - result.IsSuccess.Should().BeTrue(); - result.Value.Name.Should().Be("Example"); - _repository.Received(1).Add(Arg.Is(w => w.Id == result.Value.Id)); - await _unitOfWork.Received(1).SaveChangesAsync(Arg.Any()); - } - - [Fact] - public async Task Handle_WithDuplicateName_ReturnsConflictWithoutPersisting() - { - _repository.NameExistsAsync("Example", null, Arg.Any()).Returns(true); - - var result = await _handler.Handle(new CreateWidgetCommand("Example"), CancellationToken.None); - - result.IsFailure.Should().BeTrue(); - result.Error.Code.Should().Be("Widget.DuplicateName"); - _repository.DidNotReceive().Add(Arg.Any()); - await _unitOfWork.DidNotReceive().SaveChangesAsync(Arg.Any()); - } - - [Fact] - public async Task Handle_WithInvalidName_ReturnsFailure() - { - var result = await _handler.Handle(new CreateWidgetCommand(""), CancellationToken.None); - - result.IsFailure.Should().BeTrue(); - result.Error.Code.Should().Be("Widget.Invalid"); - } -} -``` - -```csharp -// Tests/Widgets/CreateWidget/CreateWidgetCommandValidatorTests.cs -using {Service}.Application.Widgets.CreateWidget; - -namespace {Service}.Tests.Widgets.CreateWidget; - -public class CreateWidgetCommandValidatorTests -{ - private readonly CreateWidgetCommandValidator _validator = new(); - - [Fact] - public void Validate_WithValidCommand_Passes() - { - _validator.Validate(new CreateWidgetCommand("Example")).IsValid.Should().BeTrue(); - } - - [Fact] - public void Validate_WithEmptyName_Fails() - { - _validator.Validate(new CreateWidgetCommand("")).IsValid.Should().BeFalse(); - } -} -``` - -No repository fake needed - the validator takes no dependencies. -Duplicate-name coverage lives in `CreateWidgetCommandHandlerTests` instead. - -### Automatic tenant assignment has no automated regression test - -`{Service}.Tests` references only Domain + Application (mocked ports, no -EF Core) — deliberately, to keep the unit-test tier free of Infrastructure/ -EF dependencies (docs/adr/0015). This means the -`AuditableEntitySaveChangesInterceptor` behavior docs/adr/0008 depends on — -a newly added entity with `TenantId == Guid.Empty` gets the current tenant -assigned on save — has no automated coverage. The first time a service -gets a tenant-owned entity, manually verify this by running the service -and creating a record through its API, confirming the persisted row's -`TenantId` matches the caller's tenant. diff --git a/agent-skills/agenza-exception-flow-audit/SKILL.md b/agent-skills/agenza-exception-flow-audit/SKILL.md deleted file mode 100644 index 0e35db4..0000000 --- a/agent-skills/agenza-exception-flow-audit/SKILL.md +++ /dev/null @@ -1,75 +0,0 @@ ---- -name: agenza-exception-flow-audit -description: > - Use to audit throw/try/catch/Exception usage anywhere in backend/ — on - request, before a release, or whenever a change touches error handling. - Trigger on "audit exceptions", "review error handling", "check for - business exceptions", or when reviewing a diff that adds a throw/catch. - Classifies every occurrence against docs/adr/0012 and docs/adr/0014 - instead of recommending blanket removal — some throws are correct and - must stay. ---- - -# Exception Flow Audit - -This repo made a deliberate, documented choice (docs/adr/0014, refining -docs/adr/0012 and docs/adr/0005): exceptions are not conventional control -flow for an *expected* outcome, but they are still the right tool for a -genuinely unexpected or unrecoverable failure. This skill's job is -classification, not elimination — recommending "remove every throw" is as -wrong as leaving a business-flow exception in place. - -## What to scan - -Every `throw`, `try`, `catch`, and reference to `Exception`, -`BusinessException`, `DomainException`, `DuplicateEntityException`, -`DbUpdateException`, `InvalidOperationException`, `NotImplementedException` -under `backend/` (excluding `bin/`, `obj/`). `scripts/architecture_guard.py` -already fails the build on the two patterns that must never exist at all -(`DuplicateEntityException`, `BusinessExceptionHandler`) — this skill -covers everything else, including patterns a regex can't safely judge. - -## Classification - -For every occurrence, assign exactly one of: - -| Classification | Meaning | Action | -| ---------------------------------------------- | --------------------------------------------------------------------------- | ---------------------------------------------------------------------- | -| **Expected outcome** | Validation failure, not-found, conflict/duplicate, in-use, tenant authorization — anything a well-formed client request can legitimately trigger | Must use `Result`/`DomainResult`/`PersistenceResult` instead — flag for fix | -| **Unexpected technical failure** | Missing startup config, an unrecognized database error, a framework guarantee violated | Exception may stay | -| **Programming violation** | A guard against a state only reachable via an internal bug (e.g. `TenantOwnedEntity.AssignTenant` on an empty guid, `ModelBuilderExtensions`'s reflection guard) | Exception may stay — document why in a one-line comment if not already obvious | -| **Transactional cleanup** | A `try/finally` or `try/catch` whose only job is rollback/resource cleanup around an operation whose *business* outcomes already flow through `Result` | May stay — verify the business path itself uses no exceptions, only the cleanup wrapper does | -| **Technical-exception-to-result conversion** | A `catch` at an infrastructure boundary that recognizes a specific provider exception (e.g. Postgres `23505`) and converts it to a typed `PersistenceResult.Failure` | May stay — this is the one legitimate `catch` inside otherwise Result-based flow, and only at the infrastructure boundary, never in a handler | - -Known-correct examples already in this codebase (use these as the litmus -test for classification, don't re-flag them): identity-service's -`IUnitOfWork.ExecuteInTransactionAsync` (transactional cleanup), -`ServicesService.Infrastructure/Persistence/UnitOfWork.cs`'s `DbUpdateException` -catch (technical-exception-to-result conversion), `TenantOwnedEntity.AssignTenant` -and `AuditableEntitySaveChangesInterceptor`'s tenant guard (programming -violation), any `?? throw new InvalidOperationException(...)` on missing -startup configuration (unexpected technical failure). - -## Output format - -A table, one row per occurrence: - -| File | Line | Type (`throw`/`try`/`catch`/`Exception` reference) | Purpose (one sentence) | Classification | Recommended action | Justification for keeping (if applicable) | -| --- | --- | --- | --- | --- | --- | --- | - -For every row classified **Expected outcome**, describe the fix in terms of -`agent-skills/agenza-backend-use-case`'s decision tree (which layer's -`Result` type should carry this instead, and where the check belongs — -validator vs. handler vs. persistence). - -## Explicit non-goals - -- Do not recommend removing every `throw` mechanically — a `try/finally` - around a `SaveChangesAsync` rollback, or a fail-fast startup guard, is - correct and must stay exactly as-is. -- Do not flag a `catch` at an infrastructure boundary that converts a - provider exception to a typed result — that conversion is the - explicitly-allowed pattern (docs/adr/0014), not a violation. -- If a finding would change how an error is reported to a caller (e.g. - changing an HTTP status code), that's a contract change — flag it for - `agent-skills/agenza-api-contract-review` too, don't fix it silently. diff --git a/agent-skills/agenza-frontend-feature/SKILL.md b/agent-skills/agenza-frontend-feature/SKILL.md deleted file mode 100644 index 8c7f3cb..0000000 --- a/agent-skills/agenza-frontend-feature/SKILL.md +++ /dev/null @@ -1,667 +0,0 @@ ---- -name: agenza-frontend-feature -description: > - Use whenever building or changing a feature in apps/admin-frontend — - React components, pages, hooks, forms, Zod schemas, use cases, or HTTP - calls. Trigger on "let's build [feature]", "implement [feature]", "add a - page/form/hook", or when the user provides an API spec for a resource. - Covers this project's feature-based Clean Architecture layering (ADR 009: - app/, features/{auth,catalog}/, shared/), React Hook Form + Zod forms, - structured server-error-to-field mapping, out-of-order-response and - inline-creation state handling, shadcn/ui usage, accessibility, dark - mode, mobile, comment policy, and pt-BR text rules. Do NOT proceed - without reading it — several conventions here differ from generic React - tutorials and from older, now-superseded guidance for this same project. ---- - -# Frontend Feature - -## Physical layout (ADR 009) - -```text -src/ - app/ bootstrap, routing, DI wiring - main.tsx composition root: the only createAppContainer() call - App.tsx - routes/ router.tsx, RouteErrorElement - providers/ AppProviders, AppContainerContext, useAppContainer - composition/ container.ts - the only place allowed to construct - concrete repository/auth implementations - layouts/ AdminLayout - pages/ stub pages not yet promoted to their own feature - - features/ - auth/ - domain/ User, Tenant, Session, their errors - application/ AuthRepository port, 4 use cases, TenantContext - infrastructure/ OidcAuthRepository, createUserManager, oidc mapper - presentation/ AuthProvider, useAuth, TenantBoundary, ProtectedRoute, - LoginPage, CallbackPage - index.ts public API - everything outside this feature imports - through here, never a deep path into the above - - catalog/ Categories, Services - one feature, they - collaborate in the same business context. Tags - was removed from the frontend (docs/adr/016 in - this app's ADRs) - the backend Tag domain/API - is intentionally retained, unrelated to this - feature's current frontend shape - domain/ Category, Service entities + their errors - application/ 3 repository ports, 12 use cases - infrastructure/ Api*Repository, mappers, generated/ (OpenAPI types) - presentation/ every entity folder (categories/, services/) - shares the same internal shape - location alone - tells you a file's role: - / - Page.tsx composition shell (stays at entity root by - default; Categories uses pages/, ADR 012) - hooks/ data hook (useCategories/useServices) - + controller hook (useXPage) + any sub-hooks - (useServiceEditor, useServiceDeletion, ...) - components/ presentational pieces: tables, dialogs, - field-groups - forms/ the entity's create/edit form + its zod - schema + its own fieldMaps.ts (never shared - across entities - see "Forms" below) - models/ services/ only - pure, non-React view-model/ - formatting logic (servicePresentationModels, - serviceFormatters); categories has no - equivalent, so no models/ for it - index.ts public API - - shared/ - domain/ DomainError - the base class every entity error extends - application/ AppError, HttpClient port, SessionEventBus port, - RequestSession (atomic per-request session snapshot) - infrastructure/ - http/ AuthenticatedHttpClient, ApiError, ProblemDetails, - mapErrorToAppError, NetworkError, TimeoutError - InMemorySessionEventBus.ts - presentation/ - components/ PageHeader, StatusMessage, ErrorBoundary, - CollectionFeedback, DeleteConfirmationDialog, etc. - hooks/ useAsync, useDebouncedValue, useCreateInline, - useDialogTarget, useDeleteConfirmation - forms/ serverFormError.ts (mapApiErrorToForm) - providers/ ThemeProvider - - components/ui/ shadcn/ui primitives - stay at this top-level path, - lib/utils.ts NOT moved into shared/ (see below) -``` - -**`src/components/ui/**` and `src/lib/utils.ts` are exceptions to the -feature layout** — shadcn's CLI generates every `components/ui/*.tsx` file -importing `@/lib/utils` by a fixed convention; moving either would mean -hand-editing generated files just to accommodate the reorganization, which -this project's own rules prohibit (see "Build from existing components" -below). They stay exactly where `npx shadcn add` puts them. - -A feature vertical is a full slice inside its feature's four layers: - -```text -features//domain/ → plain TS class, no framework deps -features//application/ → repository interface (port) + use cases -features//infrastructure/ → implements the port via HttpClient -features//presentation/ → hooks built on useAsync, forms, pages -``` - -For translating an external API spec into the DTO/mapper/MSW-handler seam, -use `apps/admin-frontend/.skills/admin-api-contract/SKILL.md` alongside -this skill. For TypeScript-strict-mode test gotchas and mock-strategy-per- -layer rules, use `apps/admin-frontend/.skills/admin-tdd-conventions/SKILL.md`. -This skill governs everything between those two: architecture, forms, -state, UI, and completion criteria. - ---- - -## Pre-conditions before writing any code - -1. **Get the API spec** from the user before touching infrastructure. - Ask for: endpoint paths, HTTP methods, request shape, response shape, - error codes/shapes. Never invent field names — this is one of the - question-policy triggers in the root `AGENTS.md` (changes a contract). -2. **Check whether `HttpClient` exists** at - `src/shared/application/HttpClient.ts` (implemented by - `AuthenticatedHttpClient` in `src/shared/infrastructure/http/`). Every - REST repository depends on it; it already exists for every current - feature. -3. **Decide whether this is a new feature or belongs in an existing - one.** A resource that collaborates closely with Categories/Services - (shares forms, cross-references, or the same backend service) belongs - in `features/catalog/`; a genuinely independent domain gets its own - `features//` following the same four-layer shape. -4. **Identify which use cases the current page actually needs.** Don't - build every possible use case upfront. - -For authentication work, preserve the repo's fail-closed flow: - -- `/login` automatically starts the OIDC redirect once authentication state - is known; it is an informative transition/recovery screen, not a second - “Entrar” confirmation. Pass the current `light` or `dark` theme through - the OIDC authorization request so the identity credential page can apply - it before rendering. -- Map provider failures inside auth infrastructure to `AuthFlowError`. - Presentation shows a stable support code, a specific curated pt-BR - explanation, the next recovery action, and tells the user what context to - send when requesting help without exposing raw technical details or asking - them to share a password. A generic “contacte o administrador” fallback is - not sufficient for an authentication failure. -- A silent renewal may update tokens and expiry only. If `user.id` or - `tenant.id` differs from the cached session, clear the OIDC user and require - a full login before any request can use the new identity. - ---- - -## Comments — minimum of the minimum, by default zero - -Default to no comment. Identifiers, types, and structure carry the -meaning — a comment restating what a well-named function/prop/hook -already says is waste. Add a one-line comment (never a paragraph, never a -JSDoc block on a clearly named interface/hook/prop/entity) only when a -careful senior reviewer would still get it wrong without it: a security/ -tenant-isolation default, a concurrency/race guard, a genuine React/ -Radix/RHF/Zod/browser quirk, or an unavoidable lint suppression. -Architectural rationale belongs in `docs/adr/` — reference it in one -short clause at most (`see docs/adr/0006`), never restate it. If a -mechanism needs a paragraph to explain, simplify the mechanism/names/ -types first rather than documenting the complexity. This is the same bar -as `apps/admin-frontend/AGENTS.md` and `backend/AGENTS.md`. - ---- - -## Step-by-step build order - -### 1. Domain entity (TDD) - -`features//domain/entities/EntityName.ts` — zero imports from -React, that feature's own `application/`, `infrastructure/`, or -`presentation/`, and zero imports from another feature. Private -constructor + static `create(input)` factory that validates invariants -and returns `Result` -(`shared/application/Result.ts`) instead of throwing (docs/adr/014, -docs/adr/015 — both Catalog's `Category.ts` and Auth's -`Session.ts`/`User.ts`/`Tenant.ts` follow this). Every caller composes -with `flatMapResult`/`combineResults`, or plain early-return `Result` -branching for a short sequential chain with heterogeneous error types -(see `mapOidcUserToSession`, `features/auth/infrastructure/`) — never -`try/catch`. A mapper that turns a domain validation failure arising from -an untrusted API response into a curated `AppError` uses -`shared/infrastructure/http/malformedResponseError.ts`, not its own -message. `useAsync` (`shared/presentation/hooks/useAsync.ts`) takes -`() => Promise>`, not a throwing `() => Promise`. - -A test fixture that needs a known-valid entity (most test files touching -auth or catalog do) imports `Tenant`/`User`/`Session`/`Category` -from `src/test/fixtures/{authEntityFixtures,unwrapResult}.ts` instead of -the real `domain/entities/` path — those re-export the same `create()` -call shape already unwrapped, so call sites read exactly like before -without every test wrapping every call in `unwrapResult(...)`. Only each -entity's own `*.test.ts` imports the real class directly, since it -specifically asserts on both the success and failure `Result` shapes. - -Every feature vertical (Catalog now, Auth now, a future one like -Services) follows this same Result convention — there is no throwing -variant left to mirror. - -No constructor parameter property shorthand (`erasableSyntaxOnly`) — -explicit field declarations + assignment in the constructor body. Optional -fields: `if (value !== undefined) { this.field = value }`, never a direct -assignment of a possibly-`undefined` value (`exactOptionalPropertyTypes`). -`strict: true` — never `any`; if a value's shape is genuinely unknown at a -boundary, type it `unknown` and narrow it, never widen with `any`. - -### 2. Repository interface (no test needed) - -`features//application/repositories/FeatureRepository.ts` — -interface only. Every method takes `tenantContext: TenantContext` -(imported from `@/features/auth`, never from its internal path) as its -first parameter. Returns domain entities, never raw DTOs. `Promise` for nullable results. - -### 3. Use cases (TDD) - -`features//application/use-cases/FeatureName/UseCaseName.ts` — -one class per use case, explicit constructor body (no shorthand): - -```typescript -export class ListServices { - private readonly serviceRepository: ServiceRepository; - - constructor(serviceRepository: ServiceRepository) { - this.serviceRepository = serviceRepository; - } -} -``` - -Test with hand-written fake repositories (`.skills/admin-tdd-conventions`). -Add a shared fake to -`features//application/test-helpers/createFakeFeatureRepository.ts` -after the second use case needs it. - -### 4. Wire into the container - -Add to `AppContainer`'s facade interface and `createAppContainer()` in -`app/composition/container.ts` — the **only** place allowed to construct -concrete repository implementations. Import the concrete classes from the -feature's `index.ts` (`@/features/`), not a deep path — see -docs/adr/009's "Execution" section for why `index.ts` re-exports -composition-only wiring alongside the genuinely public surface. - -### 5. Infrastructure mapper (TDD) - -`features//infrastructure/mappers/featureMapper.ts` — pure -function `mapApiDtoToDomainEntity(dto: FeatureDto): Feature`. Test every -field mapping and every validation failure path. - -### 6. Infrastructure repository (TDD with MSW) - -`features//infrastructure/repositories/ApiFeatureRepository.ts` -— implements the port, takes `HttpClient` in its constructor (explicit -field pattern). Tests use MSW handlers in -`src/test/mocks/handlers/featureHandlers.ts`, registered in -`src/test/mocks/handlers/index.ts`. `onUnhandledRequest: 'error'` is -global — any call without a registered handler fails loudly. A test mock -handler typing a fixture against a feature's internal DTO type -(`import type { CategoryDto } from '@/features/catalog/infrastructure/ -mappers/categoryMapper'`) is the one place allowed to import a feature's -internals directly from outside it — `src/test/**` is exempt from the -public-API-only rule (ESLint + `architecture_guard.py` both carve this -out explicitly). - -### 7. Presentation hook (TDD) — build on `useAsync`, not a new pattern - -`shared/presentation/hooks/useAsync.ts` is the one shared "call an async -function, track loading/data/error" primitive — every feature hook -(`useCategories`, `useServices` — and `AuthProvider` for the -shared session) builds on it instead of a bespoke `useState`/`useEffect` -pair or a server-state library (see "Prohibited" below). It already -handles the two things that are easy to get wrong by hand: - -- **Out-of-order responses**: if a second `execute()` fires before the - first resolves (a fast filter change, page change, or tenant switch), - only the most recently started call's result is ever applied — pass - `resetKey` (e.g. the tenant id) so a genuine context switch clears - `data`/`error` synchronously instead of flashing stale data. -- **Unmounted-component writes**: guarded internally; you don't need your - own `isMounted` ref. - -For a mutation (create/update/delete on a feature's data hook), -**a create's success must not depend on the follow-up refetch succeeding**: -call `mutate(current => [...(current ?? []), created])` to insert the new -item into the hook's state immediately after the write succeeds, then -`void execute()` in the background to reconcile with the server. If that -background refetch fails, the optimistically-inserted item is still on -screen; surface the refetch's own `status`/`error` separately rather than -rolling back a successful create because of it. `update`/`delete` can -simply `await execute()` since there's no optimistic value to insert. - -Get `tenantContext` from `useAuth()` (`@/features/auth`) inside a -`ProtectedRoute` — treat it as possibly `null` in a hook (the page can -mount while `useAuth()` is still resolving), guard each method, and pass -the tenant id as `useAsync`'s `resetKey` so a tenant switch clears data -instead of leaking the previous tenant's rows onto screen even for one -frame (multi-tenancy — see root `AGENTS.md`). - -### 8. Page component - -Replace the stub. **`CategoriesListPage`/`CategoryEditorDialog` -(`features/catalog/presentation/categories/`) is the reference for -behavior and design** (search → table → dialog create/edit → -`AlertDialog` delete-confirm, loading/error/empty states) — **not for -anatomy**. Copy the _pattern_, not the file count: a feature with more -independent workflows legitimately needs more files than Categories does. -See "Componentization" below for when and how to split a page's -controller hook, form, and dialog. - -#### List = `Table`; form = `Dialog` by default - -A page listing records renders a `Table` (`src/components/ui/table.tsx`): -one row per record, actions (Edit/Delete) as buttons in the last column — -not stacked `Card`s. A create/edit form opens in a `Dialog` -(`src/components/ui/dialog.tsx`) over the list by default. One `Dialog` -instance switches between create/edit based on which record triggered it, -not a dialog per row. The form component stays dialog-agnostic. - -Categories maps `/categories/new` and `/categories/:id/edit` to the same -nested editor `Dialog` over the still-mounted `/categories` list -(docs/adr/012). `CategoryEditorDialog` renders one `CategoryForm` and -`useCategoryEditor` selects create or update from the route. In edit mode -`useCategoryEditor` fetches its own category directly via -`GET /api/v1/categories/{id}` — it does **not** read the list's data -through outlet context (docs/adr/013 superseded that shape; a -`useOutletContext()` cast has no runtime guarantee an ancestor route -actually supplied a value). `useCategoriesListPage` refetches the list -unconditionally whenever navigation returns from the editor route back to -the bare `/categories` route, whether the editor closed via cancel or a -successful save. Its smartphone table uses labelled icon actions with -larger touch targets and reveals action text from `sm` upward. - -A destructive action (delete) is confirmed with the shared -`DeleteConfirmationDialog` (`shared/presentation/components/`, built on -`AlertDialog`) — never `window.confirm`, and never a hand-rolled -`AlertDialog` per feature once `DeleteConfirmationDialog` already covers -the shape. Pair it with the shared `useDeleteConfirmation` -(`shared/presentation/hooks/`) for the target/progress/error state -machine behind it. - -#### Componentization — page shell, controller hook, promotion rule - -- A page component (`XPage.tsx`) is a **composition shell**: it renders - presentational components wired to a controller hook's view models, and - nothing else — no `useState`, no business logic, no direct repository/ - use-case calls. -- A controller hook (`useXPage`) follows the same single-responsibility - bar as any other code: when it accumulates more than one real workflow - (search/filter state, an editor with dirty-tracking, a deletion - confirmation are three _different_ concerns), split it into focused - hooks (`useXFilters`, `useXEditor`, `useXDeletion`) that the page's - composer hook assembles — see `features/catalog/presentation/services/hooks/` - for the reference (`useServicesPage` composing `useServiceFilters` + - `useServiceEditor` + `useServiceDeletion`). -- Extract a component or hook on its **first** use if it's already a - distinct concern (a field group, a delete dialog) — keep it - feature-local (e.g. `features/catalog/presentation/services/components/ -ServiceCategoryField.tsx`). Only **promote** something to `shared/` - once a **second**, genuinely-identical use appears across features — - the "second use" rule gates promotion, not the initial extraction. -- Break a type cycle between a controller and the component(s) it feeds - by putting the shared shape in a neutral, feature-local module (e.g. - `servicePresentationModels.ts`) that both sides import — the controller - must never import a component's Props type, and a component must never - import the controller's internal types. -- A dialog or form with a large, flat prop list is a signal to group - related props into a cohesive, named model (`editor`, `categoryOptions`, - `discardConfirmation`) instead of one generic catch-all object that - just hides the count. -- Decomposition triggers: multiple independent workflows, several - dialogs, distinct state clusters, an unmanageable prop list, a - controller/component type cycle, or a page test file too large to - navigate. There is no hard line-count cap. -- `GenericCrudPage` (or any config-driven, entity-agnostic CRUD - abstraction) is prohibited — share only behavior proven identical - across features (see the shared hooks/components list above), never a - generic page shape. - -#### Forms: React Hook Form + Zod - -Any form beyond a single trivial field uses `react-hook-form` + -`@hookform/resolvers/zod` — see `CategoryForm.tsx` -(`features/catalog/presentation/categories/pages/CategoryEditorDialog/forms/`) -for the exact shape: - -```typescript -const categoryFormSchema = z.object({ - name: z.string().trim().min(1, NAME_MESSAGE).max(60, NAME_MESSAGE), -}); -export type CategoryFormValues = z.infer; - -const { - register, - handleSubmit, - setError, - setFocus, - formState: { errors }, -} = useForm({ - resolver: zodResolver(categoryFormSchema), - defaultValues: initialValues, - mode: "onTouched", - reValidateMode: "onChange", -}); -``` - -(A field wired through `Controller` instead of `register` — e.g. a -`Select`, a color swatch group, a multi-value picker — also destructures -`control` from `useForm`; `CategoryForm` doesn't need one since its only -field is a plain text input.) - -- ` void handleSubmit(onSubmit)(e)} noValidate ...>` — - `noValidate` because native browser constraint validation would - intercept submit before react-hook-form/zod ever sees it. -- **A form with several field groups (name/description, duration range, - price/discount, category, tags — see `ServiceForm`) splits into one - component per group, sharing the RHF instance via `FormProvider`/ - `useFormContext`** instead of prop-drilling `register`/`control`/ - `errors` into each. The orchestrator component still owns - `useForm`/`handleSubmit`/the server-error effect; each field-group - component calls `useFormContext()` for - its own slice. -- **Structured API errors, mapped to fields — never parsed from free - text.** `shared/presentation/forms/serverFormError.ts`'s - `mapApiErrorToForm(error, fieldMap, codeFieldMap, fallbackMessage)` - differentiates a 400 validation `AppError` (has `rawFieldErrors` — map - each backend field name to the form's field via `fieldMap`) from a - 409/404/403 `AppError` (has `backendCode` — map via `codeFieldMap` when - the code names a specific field, e.g. a duplicate-name conflict - highlighting the name field, otherwise it becomes a global message). It - only ever depends on `AppError` (application-layer) — `ApiError`/ - `ProblemDetails` (infrastructure) never cross into a form. Apply the - result with `setError(field, { type: 'server', message })` in a - `useEffect` keyed on the server-error object, and - `setFocus(firstField)` so a screen-reader/keyboard user lands on the - first invalid field instead of losing their position — see - `CategoryForm`'s `serverError` effect. -- Don't reach for Formik or Yup without an explicit ADR — React Hook Form - - Zod is the established, working pattern here (`docs/DECISIONS.md`). - -#### Inline creation (a select that can create its own options) - -`shared/presentation/hooks/useCreateInline.ts` is the shared -`isCreating`/`serverError`/`create`/`reset` state machine behind any -"create a related record without leaving this form" flow -(`CreatableSingleSelect`/`CreatableMultiSelect`). It keeps the outer -form's already-typed values untouched and keeps the popover open to show -an error, instead of every entity reinventing this. Reuse it — don't -hand-roll a second inline-create state machine, and don't let an inline -create's error/loading state leak into or reset the outer form. - -#### Build from existing components — don't hand-roll markup, don't extend speculatively - -shadcn/ui primitives live in `src/components/ui/` and are already themed. -If a page needs something not there (select, badge, etc.), add it with -`npx shadcn@ add -c apps/admin-frontend` from the -repo root — use the version already pinned in -`apps/admin-frontend/package.json`'s `devDependencies.shadcn`, not -`@latest` (which would bypass that pin and could fetch an update the -repo hasn't reviewed). Then check the result compiles under -`exactOptionalPropertyTypes: true` (some generated files need fixing — -see `dropdown-menu.tsx`'s removal for when to give up and remove instead -of patch). - -Use generated files as the CLI writes them. Don't add a prop, variant, or -custom styling to a `src/components/ui/*` file unless a page genuinely -needs it right now — no speculative extensions "in case a future page -wants it." Do it at the call site instead (a conditional `` in -`children`, a `className` override on an existing `variant`). - -Shared composites live in `shared/presentation/components/` — reuse -before writing a new one: - -| Component | Use for | -| ----------------------------- | ------------------------------------------------------------------- | -| `PageHeader` | Title + primary action row at the top of every page | -| `StatusMessage` | Loading / empty / error text (`tone="error"` for errors) | -| `CollectionFeedback` | Loading/error/empty/last-known-good states for a tenant-scoped list | -| `DeleteConfirmationDialog` | Destructive-action `AlertDialog`, wired to `useDeleteConfirmation` | -| `TextField` / `TextAreaField` | Labeled form inputs (wraps shadcn `Label` + `Input`/`Textarea`) | -| `CenteredScreen` | Full-page centered content (pre-auth screens only) | -| `FullScreenSpinner` | Full-page loading state | -| `ThemeToggle` | Already in `AdminLayout` — don't add another one | - -Only promote a one-off to `shared/` once a second, genuinely identical -use appears (see "Componentization" above) — until then it stays -feature-local. - -#### Use semantic tokens — never raw palette classes - -`src/index.css` defines the whole palette as CSS variables, redefined -under `.dark` — `bg-background`/`text-foreground` etc. resolve correctly -in both themes automatically. A raw class like `bg-slate-50` does not — -it's a fixed light-mode color that breaks the moment a user switches to -dark. - -| Instead of (stale, don't use) | Use | For | -| ----------------------------------- | ----------------------------- | ----------------------------- | -| `bg-slate-50` | `bg-background` | Page background | -| `bg-white` | `bg-card` | Card/surface background | -| `border-slate-200` | `border-border` | Card and divider borders | -| `text-slate-800` | `text-foreground` | Headings, primary text | -| `text-slate-600` / `text-slate-400` | `text-muted-foreground` | Secondary/muted text | -| `text-red-600` | `text-destructive` | Error text | -| `bg-teal-600` / `text-teal-700` | `text-primary` / `bg-primary` | Brand accent, primary buttons | - -There is no brand color to special-case — the app uses the stock -shadcn/ui neutral theme. If in doubt, use a token. - -#### Icons and accessibility - -`lucide-react`, matched to the icon already used for this section in -`AdminLayout`'s nav. Always add `aria-hidden="true"` on a decorative icon. -Every interactive element needs a real accessible name (visible label, -`aria-label`, or `sr-only` text) and must be reachable and operable by -keyboard alone — tab order, `Enter`/`Space` activation, `Escape` closing a -`Dialog`/`AlertDialog`/popover (Radix primitives give you this for free; -don't fight it with a custom `onKeyDown` unless a page genuinely needs -one). Check color contrast against both themes when introducing any new -non-token color. - -#### Mobile responsiveness — every page must work at 375px wide - -- `Table` already scrolls horizontally on its own - (`data-slot="table-container"` wraps it in `overflow-x-auto`) — don't - add a second scroll wrapper. -- `Dialog` is responsive by default (`max-w-[calc(100%-2rem)]` below its - `sm:` breakpoint). -- Any `flex` row inside a form that could get tight still needs - `flex-wrap` — see `CategoryForm`'s button row. -- Never use a fixed pixel width wider than ~300px without a responsive - override. Prefer `w-full` + `max-w-*`. -- `AdminLayout` already handles the page shell (off-canvas sidebar below - `md`) — pages don't need their own mobile nav handling. - -#### States - -Handle all three `useAsync` states: loading → `StatusMessage`, error → -`StatusMessage tone="error"`, success → real UI (or `CollectionFeedback` -for a tenant-scoped list, which also covers the empty and -last-known-good-after-a-failed-refresh states). - -#### Language — all user-facing text is Brazilian Portuguese (pt-BR) - -Every string a user reads or a screen reader announces — headings, button -labels, `PageHeader`/`StatusMessage` text, form labels/hints, -`aria-label`s, confirm prompts, error-message fallbacks — is pt-BR. See -`CategoriesListPage`/`CategoryEditorDialog` for the pattern (e.g. "Nova -categoria", `aria-label={\`Excluir categoria ${category.name}\`}`). Code -stays in English: identifiers, comments, commit -messages, this skill's own prose. - -Nav labels (source of truth: `AdminLayout.tsx`'s `NAV_ITEMS`) are Painel, -Agendamentos, Serviços, Categorias, Clientes, Caixa de entrada, -Configurações — reuse the exact same word for a stub page's -`PlaceholderPage title` and for that vertical's `PageHeader title` once -built. - ---- - -## Prohibited - -- A second, competing design system or component library alongside - shadcn/ui + Radix + Tailwind — extend the existing one (see "Build from - existing components" above). -- Formik or Yup without an explicit ADR — this project already made this - decision (React Hook Form + Zod). -- Redux, Zustand, or any global client-state store used as a server-data - cache — `useAsync` + the container's use cases are the established - pattern; a genuinely local UI-only state (a dialog's open/closed flag) - is fine as plain `useState`, but server data always flows through a - hook built on `useAsync`. -- Hand-duplicating a contract the codebase already generates — - `features/catalog/infrastructure/generated/services-api.d.ts` is - generated from the backend's OpenAPI document - (`npm run generate:api-types`); don't hand-write a parallel DTO type - for something already generated, and don't let a hand-written one - silently drift from it (see `agent-skills/agenza-api-contract-review`). -- Importing a feature's internal `domain/`, `application/`, - `infrastructure/`, or `presentation/` module from outside that feature - — share through its `index.ts` public API instead (ADR 009). This is - ESLint- and `architecture_guard.py`-enforced. -- `GenericCrudPage`, or any generic entity-agnostic CRUD abstraction. -- `any`, anywhere, including test files and fakes. - ---- - -## HttpClient (already built — read before touching infrastructure) - -```typescript -// shared/application/HttpClient.ts -export type Decoder = (payload: unknown) => T; - -export interface HttpClient { - get(path: string, decode: Decoder): Promise; - post(path: string, body: unknown, decode: Decoder): Promise; - put(path: string, body: unknown, decode: Decoder): Promise; - delete(path: string): Promise; -} -``` - -Every `get`/`post`/`put` call takes a `decode` function alongside its `T` - -a generic type parameter alone validates nothing at runtime, so the -decoder is what actually stands between an untrusted response body and a -value the rest of the app treats as `T` (docs/adr/011). A feature's mapper -owns its own decoder next to its DTO type (e.g. `categoryMapper.ts`'s -`decodeCategoryDto`/`decodeCategoryDtoArray`) - hand-rolled `typeof`/`Array.isArray` -guards matching `shared/infrastructure/http/ProblemDetails.ts`'s existing -style, not a schema library. A decoder that throws is caught by the same -place every other infrastructure failure already is (see below) - never -add a second try/catch in the repository for this. - -`AuthenticatedHttpClient` (`shared/infrastructure/http/`): constructor -takes `getRequestSession: GetRequestSession` (returns both the access -token and tenant id from one session read — `shared/application/ -RequestSession.ts`), prepends `VITE_API_BASE_URL`, attaches `Authorization: -Bearer ` and `X-Tenant-Id`, converts every failure (missing -session, 401, non-2xx `ProblemDetails`, network/timeout, or a `decode` -rejection) into an `AppError` (`shared/application/AppError.ts`) before it -leaves infrastructure — never `ApiError`/`ProblemDetails`/a raw decode -error directly (docs/adr/007, docs/adr/011). Wired into -`createAppContainer()` (`app/composition/container.ts`) using -`authRepository.getCurrentSession()` to supply both values from the same -read. - ---- - -## Commit checklist - -- [ ] Domain entity: explicit field declarations, named errors, no framework deps, no `any` -- [ ] Repository interface: `TenantContext` first param on all methods -- [ ] Use cases: explicit constructor body (no shorthand), tested with fakes -- [ ] Container: wired in interface and factory, imported from the feature's `index.ts` -- [ ] Mapper: tested, all fields and failure paths covered -- [ ] Infrastructure repo: tested with MSW, handler registered -- [ ] Hook: built on `useAsync`, tenant-scoped via `resetKey`, mutations - use `mutate` for optimistic success decoupled from refetch failure -- [ ] Form (if any): React Hook Form + Zod, server errors mapped to - fields via `mapApiErrorToForm`, `setFocus` on the first error -- [ ] Page: a composition shell handing view models to presentational - components; controller hook split by workflow once it has more - than one -- [ ] Page: handles loading/error/success, built from shadcn/ui primitives - and shared composites (not hand-rolled markup) -- [ ] List uses `Table`; form uses the feature's documented interaction - (`Dialog` by default, routed editor only where an ADR establishes it) -- [ ] Destructive actions confirmed with `DeleteConfirmationDialog` — not - `window.confirm` or a hand-rolled `AlertDialog` -- [ ] No prop/variant added to a `src/components/ui/*` file unless this - page genuinely needs it right now -- [ ] Page: uses semantic tokens only — no raw `slate-*`/`teal-*`/etc. -- [ ] Page: checked in dark mode and at 375px wide, no horizontal overflow -- [ ] Page: keyboard-operable, decorative icons `aria-hidden`, every - interactive element has an accessible name -- [ ] All user-facing text (labels, messages, `aria-label`s, confirm - prompts) is in pt-BR -- [ ] No import of another feature's internals bypassing its `index.ts`, - no hand-duplicated generated contract, no new global client-state store -- [ ] Comments are at the "minimum of the minimum" bar — none by default -- [ ] `npm run build` clean (catches TypeScript strict mode issues) -- [ ] `npm run lint` clean -- [ ] `npm run test` all green — behavioral assertions, not implementation details diff --git a/agent-skills/agenza-migration-safety/SKILL.md b/agent-skills/agenza-migration-safety/SKILL.md deleted file mode 100644 index 5c50e8f..0000000 --- a/agent-skills/agenza-migration-safety/SKILL.md +++ /dev/null @@ -1,89 +0,0 @@ ---- -name: agenza-migration-safety -description: > - Use for any EF Core migration or schema change under backend/ — new - migration, column/constraint change, index change, or data backfill. - Trigger on "add a migration", "change the schema", "add a column", - "add an index/constraint", or when reviewing a PR that includes a - `Migrations/` file. Prevents silent data loss, unsafe concurrent - execution, and edits to migrations already applied outside local dev. ---- - -# Migration Safety - -## Before writing a migration - -1. **Read the model change against real data.** What rows exist today - that the new schema must still accommodate? A new `NOT NULL` column - needs either a default or an explicit backfill step for existing rows; - a narrowed column type/length needs a check that no existing value - would be truncated. -2. **Check for a destructive operation.** Dropping a column/table, - narrowing a type, adding a `NOT NULL` without a default, or removing a - constraint that currently prevents bad data — every one of these can - destroy data or silently change its meaning. None of these are - forbidden outright, but every one needs the analysis in this skill - before it ships, not after. -3. **Never edit a migration that has already been applied** outside of - local, uncommitted dev iteration. Once a migration has shipped (merged - to `main`, or plausibly already applied to any shared/deployed - database), a schema fix is a **new** migration, never an edit to the - old file. ADR 0028 records the one pre-deployment history reset; after - its `InitialCreate` baselines, another squash is prohibited once either - baseline has been applied anywhere that matters. - -## Required for any migration that touches existing data - -- **Analysis of existing data**: what rows exist, what the change does to - them, whether any row could violate the new schema (a duplicate that - would violate a new unique index, a null that would violate a new - `NOT NULL`). -- **Validation**: `dotnet ef migrations add ` reviewed by hand — the - generated `Up`/`Down` should not include anything the description above - didn't call for (an unexpected `DropColumn`, an unexpected - `RENAME`-as-`DROP`-then-`ADD` that EF sometimes generates for a rename - it can't detect as one). -- **Tests**: this repo has no representative-data migration test tier - (docs/adr/0015). The Aspire API-contract job proves that the current - migration chain applies to an empty PostgreSQL database, but it does not - prove a transition's effect on existing data. State that distinction - explicitly; if the change is destructive or high-risk, recommend (or - perform, if tooling allows) a dry run against a copy of representative - data. -- **Operational documentation**: note in the PR/commit or `docs/MONOREPO.md`'s - "Known gaps" section anything an operator needs to know before applying - this in a non-local environment (e.g. the existing note there about - `DatabaseBootstrap:RunOnStartup` and concurrent replicas — a new migration - doesn't change that mechanism, but a schema change that's unsafe to - apply concurrently with multiple running replicas needs the same kind - of callout). -- **Ask the responsible party when real data-loss risk exists.** This is - one of the explicit question-policy triggers in root `AGENTS.md` - ("modifies data already in a production migration" / genuine risk of - data loss) — don't silently choose a lossy migration path because it's - simpler to write. - -## Prohibited - -- Silent truncation (narrowing a column without checking existing values - fit). -- Silent removal of data (dropping a column/table/row without calling out - what's lost). -- A destructive change with no verification step at all — at minimum, the - analysis above, even without automated integration tests. -- Concurrent-unsafe execution assumed away — if the deployment model could - run migrations from more than one replica at once (see - `docs/MONOREPO.md`'s "Known gaps"), say so and flag the risk rather than - assuming today's single-container setup forever. -- Editing a historical, already-applied migration file instead of adding - a new one. -- Changing a constraint (uniqueness, FK, `NOT NULL`) with no test or - manual verification that existing data satisfies it. -- Shipping a schema change with no rollback/recovery path at all — even a - documented manual one ("re-run migration N-1, restore column from - backup") is better than none. - -## Output when reviewing (not authoring) a migration - -`change | destructive? | existing-data impact | concurrency-safe? | -rollback path | verified how | open questions for the user (if any)`. diff --git a/agent-skills/agenza-rule-persistence/SKILL.md b/agent-skills/agenza-rule-persistence/SKILL.md deleted file mode 100644 index 7e08ef8..0000000 --- a/agent-skills/agenza-rule-persistence/SKILL.md +++ /dev/null @@ -1,85 +0,0 @@ ---- -name: agenza-rule-persistence -description: > - Use whenever the user corrects an agent's approach, an architectural bug - repeats, a review surfaces a recurring pattern, an important rule - changes, or an exception needs to be formalized. Trigger on "remember - this", "don't do that again", "we already decided X", or after fixing - anything that looks like a repeat of a past mistake. Turns a one-off - correction into a durable rule across every place that needs to agree, - instead of a fix that only lives in this conversation. ---- - -# Rule Persistence - -A correction is not durable just because it was said once. Before treating -anything as "handled," judge which of these it is: - -- **One-off**: specific to this exact change, doesn't generalize. No - persistence needed beyond the fix itself. -- **Durable architectural rule**: would apply to any future similar - change. -- **New business constraint**: a product/domain rule, not a coding - pattern. -- **Process improvement**: how work should be done (testing, review, - documentation), not what the code does. - -Only the last three need this skill's checklist. - -## The persistence cycle - -For a durable rule, work through every applicable step — skipping one -without a reason is how a rule "gets fixed" once and quietly regresses -three months later: - -1. **Fix the code.** The concrete instance that triggered this. -2. **Update `AGENTS.md`.** Root `AGENTS.md` if it applies everywhere; - `backend/AGENTS.md`/`apps/admin-frontend/AGENTS.md` if it's area-local. - State the rule, not a narrative of how it was discovered. -3. **Update the skill.** If a skill in `agent-skills/` teaches the old - pattern (in prose *or* in a copy-paste template — templates rot - silently because they're copied verbatim without re-reading the prose - around them), fix it there. Run `python scripts/sync_agent_skills.py` - afterward so `.claude/skills/`/`.agents/skills/` pick up the change. -4. **Add or update an ADR.** If this is a genuine architectural decision - (not just a bug fix), it needs `docs/adr/NNNN-....md` explaining the - context, the decision, and — if it reverses an earlier ADR — which one - and why (see docs/adr/0012, docs/adr/0014 for the citation style this - repo uses when one ADR supersedes another). -5. **Add a regression test.** One that would have failed before the fix - and passes after. Without this, nothing stops the same bug from - reappearing in a different feature. -6. **Add or update an automated guard.** If the pattern is mechanically - detectable, add it to `scripts/architecture_guard.py` (see that - script's own contribution notes for how to add a check without - widening its allowlist). If it isn't mechanically detectable, say so - explicitly in the ADR rather than silently skipping this step. -7. **Wire it into CI.** Confirm the guard/test from steps 5–6 actually - runs in `.github/workflows/` — a local-only check that never runs in - CI isn't a gate, it's a suggestion. - -## Also check for teaching debt - -A rule can be technically "fixed" in the places above and still get -reintroduced because something else still teaches the old pattern. Check: - -- Other `CLAUDE.md`/`AGENTS.md` files that might restate the rule locally - and now disagree with the update. -- Older skills (including ones outside `agent-skills/`, like - `backend/.skills/`/`apps/admin-frontend/.skills/`) that predate the - change. -- Comments in code that assert the old rationale. -- `prompts/` templates and worked examples in `docs/SDD-GUIDE.md`. -- Test files whose names or comments describe the old behavior as - correct, even if the assertions themselves were updated. - -## Definition of "persisted" - -A rule counts as persisted only when every applicable item in the cycle -above is done — not when the immediate bug is fixed. If any step -genuinely doesn't apply (e.g. no ADR is warranted for a pure typo fix), -say so explicitly rather than leaving it silently incomplete. Run -`python scripts/check_agent_governance.py` after this cycle — it flags -skills not in sync, ADR references that don't exist, and -`CLAUDE.md` files missing the `@AGENTS.md` import, three of the most -common ways a "persisted" rule quietly isn't. diff --git a/agent-skills/agenza-tenant-isolation-review/SKILL.md b/agent-skills/agenza-tenant-isolation-review/SKILL.md deleted file mode 100644 index 643753b..0000000 --- a/agent-skills/agenza-tenant-isolation-review/SKILL.md +++ /dev/null @@ -1,89 +0,0 @@ ---- -name: agenza-tenant-isolation-review -description: > - Use for any multi-tenancy audit — on request, before a release, or - whenever a change touches auth, a repository, a query, a cache key, or a - migration. Trigger on "review tenant isolation", "check for cross-tenant - leaks", "audit multi-tenancy", or when a diff adds a new query, cache - entry, or index. Treats any observable exposure of one tenant's data to - another — even transient or read-only — as a security/privacy failure, - not a code-style issue. ---- - -# Tenant Isolation Review - -Multi-tenancy is a repo-wide non-negotiable (root `AGENTS.md`). This skill -is the checklist for verifying it holds, end to end, for whatever surface -is in scope. - -## What to check - -- **Claim/header**: the tenant id travels as the `X-Tenant-Id` header, - cross-checked against the JWT's `tenant_id` claim by - `Admin.Identity.Client`'s `TenantHeaderFilter` — every controller/action - either inherits this (default) or is explicitly, deliberately - `[IgnoreTenant]` for a genuinely tenant-free operation (M2M - provisioning, OIDC protocol endpoints). Flag any action that reads - request data implying tenant scope without either the filter applying - or an explicit, justified `[IgnoreTenant]`. -- **Global filters**: every `ITenantOwned` entity gets its query filter - from `ApplyAuditableConventions` reading `DbContext.CurrentTenantId` off - the *live instance* at query time — never a value baked in at - model-build time (EF Core caches the compiled model per `DbContext` - *type*, so a baked-in constant leaks across every request regardless of - the actual caller; see docs/adr/0006 for the incident this caught). - Grep for `HasQueryFilter` added by hand outside - `Admin.SharedKernel.EntityFrameworkCore` — that bypasses the automatic - mechanism and is a red flag by itself. -- **Repositories/queries/handlers**: no method on an `ITenantOwned` - entity's repository takes an explicit `tenantId` parameter (the - DbContext scopes it) — a parameter like that is a sign someone hand- - rolled scoping instead of relying on the automatic mechanism, which is - itself worth flagging even if the value passed happens to be correct - today. -- **New-entity assignment**: `AuditableEntitySaveChangesInterceptor` calls - `AssignTenant` on save for any newly added `ITenantOwned` entity with - `TenantId == Guid.Empty`, sourcing it from `ICurrentTenantProvider` — it - must throw rather than persist a tenant-less row when none is available - (docs/adr/0008). Flag any handler that tries to set `TenantId` itself. -- **Frontend cache/query keys**: any client-side cache (`useAsync`'s - `resetKey`, a memoized list, browser storage) keyed in a way that - includes the tenant id or is cleared synchronously on tenant switch — - see `agent-skills/agenza-frontend-feature`'s `useAsync` section for the - `resetKey` mechanism. A cache that survives a tenant switch and can - render the previous tenant's data for even one frame is a finding, not - a nit. -- **Indexes/FKs**: a uniqueness index scoped per-tenant (e.g. - `(TenantId, NameNormalized)`, not `(NameNormalized)` alone) — a global - unique index on a business field is itself a cross-tenant leak (tenant - A can't reuse a name tenant B already used). A composite FK crossing - tenant boundaries (referencing another tenant's row) is a finding. -- **Migrations**: hand off to `agent-skills/agenza-migration-safety` for - the migration-safety half; this skill only confirms the resulting - schema still enforces tenant scoping (index/FK shape above). -- **Logs**: a log statement that includes another tenant's data alongside - the current request's tenant context (e.g. logging "n other tenants had - this name" with their identifying info) — logging the *fact* of a - conflict is fine, logging the *other tenant's* data usually isn't. -- **Tests**: does a cross-tenant-access test exist for this surface (a - request/query with tenant A's context attempting to read/write tenant - B's row)? If not, that's a coverage gap to flag, and to close if the - task is implementation, not just review. - -## Severity - -Any confirmed cross-tenant data exposure — even read-only, even UI-only, -even transient (a single frame, a stale cache entry, a log line) — is a -**security/privacy failure**, not a style issue. Report it at the top of -any findings list, regardless of what else is in scope, and treat the fix -as blocking. - -## Output format - -`surface (endpoint/query/cache/index) | mechanism relied on | verified? | -finding (if any) | severity | fix`. For anything not directly verifiable -by reading code (e.g. actual runtime behavior of a query filter), say so -explicitly and recommend the manual two-tenant verification step already -called out in `agent-skills/agenza-backend-use-case` ("Automatic tenant -assignment has no automated regression test") rather than asserting it's -safe from static reading alone. diff --git a/agent-skills/evolve-modular-architecture/SKILL.md b/agent-skills/evolve-modular-architecture/SKILL.md deleted file mode 100644 index 03a1e33..0000000 --- a/agent-skills/evolve-modular-architecture/SKILL.md +++ /dev/null @@ -1,147 +0,0 @@ ---- -name: evolve-modular-architecture -description: Assess, design, review, and incrementally evolve modular software architecture from repository and business evidence. Use when Codex needs to define or repair module boundaries, decompose a monolith, choose between a simple monolith, modular monolith, and microservices, select architecture per module, introduce tactical DDD only where justified, plan a safe extraction or migration, write ADRs, or create automated architectural fitness functions that prevent structural drift. ---- - -# Evolve Modular Architecture - -Make the smallest architecture decision that satisfies demonstrated needs while preserving a credible next move. Treat architecture as a set of hypotheses guarded by feedback, not as a target diagram. - -## Operating rules - -- Match the user's language. -- Distinguish `observed`, `inferred`, and `unknown` facts. Attach file, metric, or stakeholder evidence to consequential claims. -- Inspect the repository before recommending a target state. Never infer boundaries only from folder names. -- Prefer reversible decisions and delay expensive, distributed, or organization-wide choices until their drivers are real. -- Keep business capability, code, data, integration contracts, ownership, and deployment boundaries explicit. Do not treat projects, packages, layers, or repositories as proof of modularity. -- Apply architecture locally. Different modules may warrant transaction scripts, layered code, or a domain model. -- Include “do nothing yet” as an option. State the cost of both changing and waiting. -- Give every important architectural characteristic at least one fitness function or review trigger. -- When asked only to assess, review, or advise, do not modify code. When asked to implement, make the smallest safe vertical change and verify it. - -## Required workflow - -### 1. Establish the decision - -State the decision question, scope, time horizon, current pain, constraints, and success measures. Ask only for missing information that would materially reverse the decision; otherwise proceed with named assumptions. - -For a local repository, resolve `collect_architecture_evidence.py` inside -this skill's script-resource directory and run: - -```text -python --format markdown -``` - -Use its output as an inventory, not as an architectural verdict. Confirm important signals by reading manifests, entry points, module registration, persistence configuration, integrations, tests, CI, and architecture records. - -### 2. Map the current system - -Build a compact map covering: - -1. Business capabilities and language. -2. Candidate bounded contexts and their owners. -3. Code dependencies and cycles. -4. Data ownership, cross-boundary reads, writes, and transactions. -5. Synchronous calls, messages, contracts, consistency, and failure behavior. -6. Deployment units, change cadence, teams, scaling, security, availability, and cost. - -Name modules by business capability or process when possible. Treat entity-like names as provisional. A bounded context is a model and language boundary; it is not automatically a service. - -### 3. Classify drivers and coupling - -For each proposed boundary, record: - -- Cohesion: rules and changes that belong together. -- Integrators: shared transactions, chatty workflows, coordinated releases, shared ownership, or latency sensitivity that favor staying together. -- Disintegrators: independent scaling cost, markedly different change/deploy cadence, security or compliance isolation, fault isolation, team autonomy, lifecycle, or technology needs that favor separation. -- Readiness: CI/CD, observability, incident ownership, contract testing, data migration, and operational capacity. - -Do not use request volume alone as a microservice argument. Prefer at least two independent, module-specific disintegrators plus operational readiness, unless a hard compliance or isolation constraint dominates. - -Read [references/decision-framework.md](references/decision-framework.md) for a substantial architecture decision or decomposition. - -### 4. Choose the smallest sufficient move - -Evolve three axes independently: - -| Axis | Lower-cost starting point | Escalate only when | -| --- | --- | --- | -| Deployment | One deployable with explicit modules | A module has compelling disintegrators and manageable integrators | -| Code structure | Vertical slices inside a module | A module's size, rules, team contention, or dependency control needs stronger physical separation | -| Domain model | Transaction script or simple layered code | Evolving invariants, concurrency, and business language justify entities, value objects, aggregates, or domain events | -| Integration | Direct call or in-process event | Process separation, temporal decoupling, durability, replay, or reliability requirements justify external messaging | -| Data | One database with explicit module ownership | Independent lifecycle, scaling, isolation, or service extraction justifies physical separation | - -Use this progression as a set of options, not a maturity model: - -1. Organize one codebase by business capability and vertical slice. -2. Enforce logical modules, explicit public contracts, and owned data. -3. Separate selected modules into projects/packages when stronger compile-time or team boundaries pay for the added structure. -4. Extract one service at a time when evidence justifies independent deployment. -5. Apply tactical DDD only inside business-complex contexts, whether monolithic or distributed. - -Do not force every module into the same pattern. A reporting module may remain a transaction script while a contract or pricing module uses a rich domain model. - -### 5. Define boundaries and fitness functions - -Require, where applicable: - -- No cyclic module dependencies. -- No access to another module's internal types. -- No foreign table writes; cross-module data access must use an explicit contract or consciously documented read model. -- No shared business entities as a “common” model. -- Explicit dependency direction and allowed integration styles. -- Versioned, consumer-aware external contracts. -- Idempotency, retries, dead-letter handling, traceability, and inbox/outbox where asynchronous reliability requires them. -- Characterization, module integration, and contract tests around migration seams. - -For every rule, specify the automated check, threshold, execution cadence, owner, and response to failure. Read [references/fitness-functions.md](references/fitness-functions.md) when defining CI checks or architecture tests. - -### 6. Plan evolution as a sequence - -Prefer one end-to-end capability slice at a time. Preserve behavior and contracts before moving structure. Use seams, feature flags, compatibility windows, expand-migrate-contract data changes, observability, and explicit rollback criteria. - -Read [references/migration-playbook.md](references/migration-playbook.md) before implementing a module extraction, database split, integration change, or large restructuring. - -### 7. Record and communicate the decision - -For a meaningful decision, deliver: - -1. Recommendation in one paragraph. -2. Evidence and uncertainty. -3. Drivers and constraints. -4. Options, including waiting, with trade-offs. -5. Chosen boundaries, ownership, data, and integration semantics. -6. Incremental plan with checkpoints. -7. Fitness functions. -8. Risks, rollback, and review triggers. -9. ADR when the decision affects multiple teams, a public contract, data ownership, deployment, or long-term cost. - -Use [references/templates.md](references/templates.md) for an assessment, module contract, evolution plan, or ADR. Keep small answers proportional; do not emit every template for a narrow question. - -## Implementation discipline - -- Inspect existing conventions and preserve unrelated user changes. -- Add characterization tests before moving unclear behavior. -- Introduce the boundary rule in the same change that introduces the boundary. -- Move one vertical slice, prove it, then continue. -- Keep old and new contracts compatible during migration. -- Avoid big-bang rewrites and speculative abstraction layers. -- Verify compile/build, targeted tests, architecture tests, and affected integration or contract tests. -- Report what remains unknown and which production metrics should decide the next step. - -## Failure patterns to reject - -- Selecting microservices because the system is “large” or may scale someday. -- Applying Clean Architecture, CQRS, event sourcing, or tactical DDD uniformly. -- Calling a shared database modular while any module can mutate any table. -- Creating a “common” package that contains business concepts from multiple contexts. -- Replacing direct calls with events when the workflow requires an immediate answer or atomic state change. -- Hiding a distributed monolith behind messaging, shared contracts, or coordinated releases. -- Counting projects, services, or repositories as the outcome. -- Writing an ADR after implementation merely to rationalize a settled choice. -- Treating a snapshot architecture as final; define when evidence should reopen the decision. - -## Repository-derived example - -The reference repository demonstrates a sequence from a vertically sliced single project, to differently structured modules, to one evidence-led service extraction, and finally to selective tactical DDD. Read [references/source-notes.md](references/source-notes.md) only when the user asks about that repository, requests a worked example, or wants its exact architectural lineage. diff --git a/agent-skills/evolve-modular-architecture/references/decision-framework.md b/agent-skills/evolve-modular-architecture/references/decision-framework.md deleted file mode 100644 index 1cd0656..0000000 --- a/agent-skills/evolve-modular-architecture/references/decision-framework.md +++ /dev/null @@ -1,186 +0,0 @@ -# Evidence-led decision framework - -## Contents - -1. Decision framing -2. Boundary discovery -3. Architecture axes -4. Escalation gates -5. Integration and data decisions -6. Evidence quality - -## Decision framing - -Express the decision as a falsifiable question: - -> Given ``, should `` move from `` to `` now, and which evidence would cause us to keep, reverse, or revisit that move? - -Capture: - -- Current behavior and architecture. -- Desired measurable outcome. -- Time horizon. -- Constraints that cannot be traded. -- Assumptions that can be tested. -- Options, including no change. -- Reversal cost and blast radius. - -Prefer measurements local to the candidate module over system-wide averages. - -## Boundary discovery - -Triangulate boundaries from several kinds of evidence: - -- Business language and capability. -- Rules and invariants that change together. -- Workflow and event boundaries. -- Data ownership and transaction boundaries. -- Code change coupling from version history. -- Team ownership and release coordination. -- Runtime latency, load, failure, and security needs. - -Classify each candidate boundary: - -| Label | Meaning | -| --- | --- | -| Observed | Directly supported by code, metrics, history, or stakeholder statement | -| Inferred | Plausible interpretation with named evidence | -| Unknown | Material question that still needs discovery | - -Do not accept folder structure as the only source. A capability may span folders, and a folder may contain several capabilities. - -### Boundary quality questions - -- Does the module have one coherent business purpose? -- Can its public contract be described without exposing its persistence model? -- Does it own its decisions and writes? -- Can callers tolerate its chosen consistency and failure semantics? -- Do changes mostly stay within it? -- Is the boundary meaningful to domain experts and operators? -- Can the boundary be tested independently? - -## Architecture axes - -Treat these axes independently: - -### Deployment topology - -- Single deployable. -- Modular monolith with physically separated modules. -- Hybrid: monolith plus selected service. -- Multiple independently deployable services. - -### Domain logic pattern per module - -- Transaction script for straightforward workflows and reports. -- Simple layered/active-record style for CRUD with modest logic. -- Domain model for behavior-rich rules. -- Tactical DDD building blocks for complex, evolving invariants. - -### Integration - -- Direct in-process call for immediate response or atomic coordination. -- In-process event for local temporal decoupling without durability. -- External synchronous contract when a separate process must answer now. -- Durable asynchronous messaging when delay is acceptable and reliability/decoupling justifies failure-handling cost. - -### Data topology - -- Shared database server with explicit schema/table ownership. -- Separate schema or database per module/service. -- Replicated read model for cross-context queries. -- Event-carried state transfer when consumers need local data and eventual consistency is acceptable. - -Do not escalate all axes together. Separating projects does not require separating deployment; tactical DDD does not require microservices. - -## Escalation gates - -### Logical module to physical project/package - -Escalate when several signals persist: - -- A module is large or changes at a distinctly different cadence. -- Teams repeatedly collide in the same compilation or ownership boundary. -- Module-specific architecture differs materially. -- Namespace or lint rules do not adequately prevent unwanted dependencies. -- Independent test execution or build performance matters. - -Check the cost: - -- More manifests, dependency management, build graph, test setup, and public surface. -- Risk of creating excessive shared packages. - -### Module to service - -Prefer at least two independent disintegrators: - -- Independent scaling prevents material cost or performance harm. -- Change/deploy cadence creates measurable release risk or coordination delay. -- Security, compliance, residency, or trust boundary differs. -- Availability or fault-isolation target differs. -- Stable team ownership needs independent lifecycle. -- Technology or data lifecycle is truly incompatible. - -Require manageable integrators: - -- Few cross-boundary transactions. -- Non-chatty calls. -- Contract can be explicit and versioned. -- Data can be owned without routine cross-service writes. -- Release coordination can actually decrease. - -Require readiness: - -- Independent CI/CD and rollback. -- Metrics, logs, traces, alerts, and incident ownership. -- Contract and integration tests. -- Retry, timeout, idempotency, and failure recovery. -- Data migration and reconciliation plan. - -Hard constraints may override the two-signal heuristic, but name them. - -### Simple model to tactical DDD - -Escalate locally when: - -- Business rules are numerous, interacting, and changing. -- Invariants must remain valid under concurrency. -- Domain language is important and behavior belongs with the model. -- Transaction boundaries need an explicit aggregate. -- Current conditionals and service orchestration obscure intent. - -Do not escalate for simple CRUD, reporting, integration plumbing, or technical complexity alone. - -## Integration and data decisions - -For each interaction, state: - -- Initiator and owner. -- Command, query, notification, or event. -- Required response time. -- Consistency requirement. -- Delivery semantics and duplicate behavior. -- Timeout, retry, and failure path. -- Contract owner and compatibility policy. -- Observability and reconciliation. - -Use messaging only when the business process tolerates delay. “Exactly once” is not a design assumption; make consumers idempotent and define deduplication or reconciliation where consequences matter. - -Keep data ownership strict: - -- One module owns every write. -- Other modules use an API, message, or owned read model. -- Cross-context reporting may read replicated or explicitly exposed data, but must not become an undocumented write path. -- Shared infrastructure may be common; shared business models usually couple contexts. - -## Evidence quality - -Prefer: - -- Change-coupling data over anecdotes about “many conflicts.” -- Module-specific p95/p99 and cost over total request counts. -- Deployment-failure and lead-time data over opinions about autonomy. -- Named invariants and consistency needs over generic “DDD complexity.” -- Failure drills and recovery evidence over broker/library selection. - -Avoid pseudo-precision. If weights or scores are used, show their assumptions and never let a total hide a hard constraint or strong integrator. diff --git a/agent-skills/evolve-modular-architecture/references/fitness-functions.md b/agent-skills/evolve-modular-architecture/references/fitness-functions.md deleted file mode 100644 index 648c3c6..0000000 --- a/agent-skills/evolve-modular-architecture/references/fitness-functions.md +++ /dev/null @@ -1,122 +0,0 @@ -# Architectural fitness functions - -## Contents - -1. Design rules -2. Modular fitness catalog -3. Distributed fitness catalog -4. Quality attributes -5. Rollout - -## Design rules - -Define each fitness function with: - -- Characteristic being protected. -- Observable rule and threshold. -- Scope. -- Automation mechanism. -- Cadence. -- Owner. -- Failure response. -- Expiry or review condition. - -Prefer fast deterministic checks in pull requests, broader integration checks in CI, and runtime checks for properties that static analysis cannot prove. - -## Modular fitness catalog - -### Dependency direction - -Rule: modules may depend only on declared public contracts; forbidden edges and cycles fail CI. - -Possible mechanisms: - -- .NET: NetArchTest, ArchUnitNET, project-reference checks. -- JVM: ArchUnit, jQAssistant. -- TypeScript/JavaScript: dependency-cruiser, Nx module-boundary rules, Madge. -- Python: import-linter. -- Go: package/import graph checks. -- Language-neutral: graph extraction plus a checked-in allowlist. - -### Encapsulation - -Rule: internal types remain inaccessible outside the owning module. Public surface growth requires review. - -Measure exported symbols, package/API baselines, or visibility conventions. Fail on unapproved additions. - -### Data ownership - -Rule: only the owning module may write its schema/tables. - -Enforce with separate credentials, database grants, migration ownership, static query checks, or integration tests. Runtime database permissions are stronger than naming conventions. - -### Contract isolation - -Rule: consumers depend on public DTOs/events, not persistence entities or internal domain objects. - -Check references/imports and package dependencies. Keep shared business contracts narrow and versioned. - -### Vertical cohesion - -Rule: a change to one business capability should not require routine edits across unrelated modules. - -Track change coupling from version history. Use the trend as a review signal, not an automatic failure until a stable baseline exists. - -### Architecture documentation - -Rule: changes to deployment, public contracts, data ownership, or allowed dependencies require a current ADR and diagram/map update. - -Automate path-based pull-request checks where practical. - -## Distributed fitness catalog - -### Contract compatibility - -Run consumer-driven or schema compatibility tests before publishing. Reject breaking changes outside the declared compatibility policy. - -### Resilience - -Test timeout, retry budget, circuit breaking, duplicate delivery, reordering, poison messages, and dependency unavailability. Verify bounded retries and visible dead-letter handling. - -### Message reliability - -For critical asynchronous flows, verify: - -- Outgoing state change and outbox record are atomic. -- Consumers are idempotent or deduplicate. -- Failed messages are observable and replayable. -- Reconciliation detects missing or divergent outcomes. - -### Independent deployability - -Measure whether a service can build, test, deploy, and roll back without coordinated release. Repeated coordinated deployments are evidence of a distributed monolith. - -### Runtime coupling - -Set a maximum synchronous call depth and latency budget for critical paths. Trace and alert on violations. - -## Quality attributes - -Examples: - -| Characteristic | Example fitness function | -| --- | --- | -| Performance | Module-specific p95/p99 under expected load remains within an explicit budget | -| Scalability | Load test proves the candidate module scales independently before extraction is accepted | -| Availability | Synthetic transaction and SLO burn-rate alerts cover the critical business flow | -| Security | Automated authorization, dependency, secret, and boundary tests run in CI | -| Maintainability | Forbidden dependencies are zero; cycles are zero; public surface stays within baseline | -| Evolvability | A representative change can be delivered without edits to unrelated modules | -| Operability | Dashboard, alert, runbook, trace propagation, and rollback drill exist before service cutover | -| Cost | Cost per transaction or tenant remains below an agreed threshold | - -Do not confuse a proxy with the goal. Project count, coverage percentage, and service count are not architectural outcomes on their own. - -## Rollout - -1. Baseline current behavior without failing builds. -2. Select a small set protecting the highest-risk decisions. -3. Ratchet thresholds so new violations fail while legacy debt is tracked. -4. Assign ownership and remediation time. -5. Review noisy or obsolete checks. -6. Add a fitness function in the same change that establishes a new boundary. diff --git a/agent-skills/evolve-modular-architecture/references/migration-playbook.md b/agent-skills/evolve-modular-architecture/references/migration-playbook.md deleted file mode 100644 index 7acd770..0000000 --- a/agent-skills/evolve-modular-architecture/references/migration-playbook.md +++ /dev/null @@ -1,120 +0,0 @@ -# Incremental architecture migration playbook - -## Contents - -1. Preconditions -2. Module boundary repair -3. Project/package separation -4. Service extraction -5. Data separation -6. Integration migration -7. Cutover and rollback - -## Preconditions - -- Define target outcome and non-goals. -- Establish characterization tests around behavior being moved. -- Capture traffic, errors, latency, data volume, and change/deployment baseline. -- Define the new owner, public contract, data ownership, and fitness functions. -- Choose a thin vertical slice with low blast radius. -- Create an ADR before an expensive or cross-team commitment. - -## Module boundary repair - -1. Inventory cross-boundary dependencies and foreign data access. -2. Classify each dependency as command, query, event, or accidental code reuse. -3. Choose the owner based on business capability. -4. Introduce a narrow public contract at the seam. -5. Move one caller at a time. -6. Add a forbidden-dependency and data-ownership check. -7. Remove the old path only after usage is zero. - -Use branch by abstraction when callers cannot move atomically. Avoid a generic service layer that merely hides the same coupling. - -## Project/package separation - -1. Make the logical boundary clean before moving files. -2. Define internal and public namespaces/packages. -3. Break cycles through ownership changes or explicit contracts; do not create a shared dumping ground. -4. Extract the module and its tests. -5. Keep composition at the application edge. -6. Verify build graph, test isolation, and public surface. - -Do not split all modules uniformly. Separate only those that need stronger physical boundaries. - -## Service extraction - -1. Revalidate disintegrators, integrators, and operational readiness. -2. Select one capability and one entry path. -3. Establish an internal API or message seam while code remains in-process. -4. Remove cross-module database writes. -5. Give the candidate ownership of its data and migrations. -6. Deploy the service dark with observability. -7. Shadow, mirror, or canary traffic where semantics permit. -8. Route a small cohort or operation through the service. -9. Reconcile outcomes and compare SLOs. -10. Increase traffic gradually. -11. Retire old code only after the rollback window. - -Do not extract code first and discover transaction or data boundaries later. - -## Data separation - -Use expand-migrate-contract: - -1. Expand schemas/contracts compatibly. -2. Backfill with resumable, observable jobs. -3. Dual-read or compare reads if needed. -4. Dual-write only with a clear source of truth, idempotency, and reconciliation; avoid indefinite dual-write. -5. Switch ownership and reads. -6. Monitor drift. -7. Contract old schema/fields after the compatibility window. - -Document: - -- Source of truth at every phase. -- Conflict resolution. -- Backfill checkpoints. -- Privacy and retention effects. -- Rollback limits after new writes begin. - -## Integration migration - -### Direct call to in-process event - -Use only if delayed execution is acceptable. Define handler failure behavior; in-memory delivery is not durable. - -### In-process to external messaging - -Add: - -- Stable event semantics and versioning. -- Transactional outbox where loss matters. -- Idempotent consumer/inbox where duplicates matter. -- Retry policy, dead-letter handling, tracing, alerting, and replay. -- Contract tests and reconciliation. - -### Synchronous remote call - -Add: - -- Timeout and retry budget. -- Idempotency for retried commands. -- Circuit breaking or load shedding where justified. -- Clear partial-failure semantics. -- Trace propagation and latency budget. - -Avoid synchronous chains across several services. - -## Cutover and rollback - -Define before deployment: - -- Entry and exit criteria for every stage. -- Metric thresholds that halt or reverse rollout. -- Feature flag or routing control. -- Data reconciliation query. -- Maximum rollback point after schema or data ownership changes. -- Named operator and communication channel. - -After cutover, verify that independent deployment, scaling, or ownership benefits actually occurred. If coordination and coupling remain, treat that as evidence to improve or reverse the boundary. diff --git a/agent-skills/evolve-modular-architecture/references/source-notes.md b/agent-skills/evolve-modular-architecture/references/source-notes.md deleted file mode 100644 index 72ef2c7..0000000 --- a/agent-skills/evolve-modular-architecture/references/source-notes.md +++ /dev/null @@ -1,86 +0,0 @@ -# Source repository notes - -## Source - -- Repository: `evolutionary-architecture/evolutionary-architecture-by-example` -- URL: -- Snapshot reviewed: commit `04c3f717d96f505d8355fac517c780904982d5bd` -- License observed in repository: MIT - -These notes summarize architectural lessons; they are not a framework prescription and do not copy the example's .NET choices into unrelated contexts. - -## Evolution demonstrated - -### Chapter 1: simplicity - -- Keep production in one project while making business modules explicit through namespaces. -- Organize business processes as vertical slices. -- Map each assumed bounded context to a module. -- Give modules separate database schemas and prevent direct module references. -- Use an in-memory event bus where a single deployment and accepted loss make durability unnecessary. -- Record decisions, combine unit and integration tests, add architecture tests, and run static analysis. - -Key lesson: a simple deployment can preserve seams for future moves without prepaying for distribution. - -### Chapter 2: maintainability - -Signals: - -- Modules grew and diverged in complexity. -- Change cadence differed. -- Several teams produced conflicts in a single production project. - -Evolution: - -- Split modules into projects. -- Give the complex Contracts module API, Application, Core, and Infrastructure projects. -- Keep Passes and Offers simpler with API and DataAccess. -- Keep Reports as one transaction-script project. -- Retain one deployment and in-memory messaging. - -Key lesson: use the architecture each module deserves. Physical project separation adds real cognitive and build cost and is not mandatory for an MVP. - -### Chapter 3: growth and service extraction - -Module-specific disintegrators for Contracts: - -- Much higher usage and independent scaling cost. -- Roughly 10:1 change frequency relative to other modules. -- Higher security requirements. -- Increased team/deployment coordination. - -Evolution: - -- Extract only Contracts as a service. -- Keep Passes, Offers, and Reports in the modular monolith. -- Replace in-memory cross-process communication with external messaging. -- Add inbox/outbox reliability because duplicate or lost messages have material consequences. - -Key lesson: a hybrid topology can be the right endpoint. Extraction introduces network, operational, contract, data, and package-versioning costs. - -### Chapter 4: domain complexity - -Signals: - -- Contracts gained rapidly changing rules for binding contracts and annexes. -- Invariants had to remain consistent, including under concurrency. - -Evolution: - -- Apply tactical DDD only to Contracts. -- Use an aggregate root to guard annex invariants. -- Model identity-bearing concepts as entities, descriptive concepts as value objects, and meaningful state changes as domain events. -- Leave typical CRUD and reporting modules simple. - -Key lesson: tactical DDD responds to business complexity, not to service boundaries or technical fashion. - -## Concrete fitness-function examples in the repository - -Chapter 1 contains automated architecture tests that forbid module dependencies and constrain event-only communication between selected modules. This is the practical evolutionary mechanism: the intended boundary is executable and fails with the build when code drifts. - -## Adaptation guardrails - -- Translate namespaces and .NET projects into the target ecosystem's package/module mechanisms. -- Reassess data-loss tolerance; do not repeat the in-memory event choice for critical workflows. -- Do not copy the project's integration-event sharing if it prevents consumer autonomy. -- Treat the chapter sequence as one context-specific history, not a universal maturity ladder. diff --git a/agent-skills/evolve-modular-architecture/references/templates.md b/agent-skills/evolve-modular-architecture/references/templates.md deleted file mode 100644 index 1432d09..0000000 --- a/agent-skills/evolve-modular-architecture/references/templates.md +++ /dev/null @@ -1,133 +0,0 @@ -# Architecture decision templates - -## Contents - -1. Assessment -2. Module contract -3. Evolution plan -4. ADR - -## Assessment - -```markdown -# Architecture assessment: - -## Recommendation - - -## Decision question - - -## Evidence -| Claim | Status: observed/inferred/unknown | Evidence | Confidence/next check | -| --- | --- | --- | --- | - -## Drivers and constraints -- Business: -- Technical: -- Organizational: -- Operational: - -## Current map -- Capabilities and candidate contexts: -- Code dependencies: -- Data ownership: -- Integrations and consistency: -- Deployments and owners: - -## Options -| Option | Benefits | Costs/risks | Reversibility | Evidence needed | -| --- | --- | --- | --- | --- | - -## Decision and consequences - - -## Fitness functions -| Characteristic | Check/threshold | Cadence | Owner | Failure response | -| --- | --- | --- | --- | --- | - -## Evolution plan - - -## Review triggers - -``` - -## Module contract - -```markdown -# Module: - -- Purpose: -- Ubiquitous language: -- Owner: -- In scope: -- Out of scope: -- Public commands: -- Public queries: -- Published events: -- Consumed contracts: -- Owned data: -- Consistency and transaction boundary: -- Allowed dependencies: -- Forbidden dependencies: -- Failure semantics: -- Security/privacy: -- SLOs: -- Fitness functions: -``` - -## Evolution plan - -```markdown -| Stage | Vertical outcome | Compatibility/seam | Verification | Rollback | Exit criteria | -| --- | --- | --- | --- | --- | --- | -``` - -Each stage must produce a working system and evidence for the next decision. Keep removal of the old path separate from proving the new path when rollback matters. - -## ADR - -```markdown -# ADR-NNN: - -- Status: proposed | accepted | superseded | rejected -- Date: -- Owners: -- Review triggers: - -## Context - - -## Decision drivers -- ... - -## Considered options -1. Do nothing yet -2. ... - -## Decision - - -## Consequences - -### Positive -- ... - -### Negative -- ... - -### Risks and mitigations -- ... - -## Fitness functions -- ... - -## Migration and rollback -- ... - -## Follow-up evidence - -``` - -Do not edit accepted ADR history to hide a changed decision. Add a superseding ADR and link both records. diff --git a/agent-skills/evolve-modular-architecture/scripts/collect_architecture_evidence.py b/agent-skills/evolve-modular-architecture/scripts/collect_architecture_evidence.py deleted file mode 100644 index 30c01bc..0000000 --- a/agent-skills/evolve-modular-architecture/scripts/collect_architecture_evidence.py +++ /dev/null @@ -1,583 +0,0 @@ -#!/usr/bin/env python3 -"""Collect a conservative, technology-aware architecture inventory. - -The report contains observations and heuristic indicators, never a target -architecture or an automatic recommendation. -""" - -from __future__ import annotations - -import argparse -import json -import os -import re -import subprocess -import sys -import xml.etree.ElementTree as ET -from collections import Counter, defaultdict -from pathlib import Path -from typing import Any, Iterable - - -IGNORED_DIRS = { - ".git", - ".agents", - ".claude", - ".codex", - ".hg", - ".svn", - ".idea", - ".vs", - ".vscode", - ".venv", - "venv", - "__pycache__", - "bin", - "obj", - "build", - "dist", - "coverage", - "node_modules", - "packages", - "target", - "vendor", - "worktrees", -} - -LANGUAGES = { - ".cs": "C#", - ".fs": "F#", - ".vb": "Visual Basic", - ".java": "Java", - ".kt": "Kotlin", - ".kts": "Kotlin", - ".js": "JavaScript", - ".jsx": "JavaScript", - ".mjs": "JavaScript", - ".cjs": "JavaScript", - ".ts": "TypeScript", - ".tsx": "TypeScript", - ".py": "Python", - ".go": "Go", - ".rs": "Rust", - ".rb": "Ruby", - ".php": "PHP", - ".scala": "Scala", - ".swift": "Swift", - ".c": "C/C++", - ".cc": "C/C++", - ".cpp": "C/C++", - ".h": "C/C++", - ".hpp": "C/C++", - ".sql": "SQL", -} - -MANIFEST_NAMES = { - "package.json", - "pyproject.toml", - "requirements.txt", - "pipfile", - "poetry.lock", - "go.mod", - "cargo.toml", - "pom.xml", - "build.gradle", - "build.gradle.kts", - "settings.gradle", - "settings.gradle.kts", - "composer.json", - "gemfile", - "docker-compose.yml", - "docker-compose.yaml", -} - -TEXT_EXTENSIONS = set(LANGUAGES) | { - ".md", - ".adoc", - ".txt", - ".json", - ".yaml", - ".yml", - ".xml", - ".toml", - ".props", - ".targets", - ".gradle", -} - -INDICATORS = { - "external_messaging": ( - "rabbitmq", - "kafka", - "masstransit", - "nservicebus", - "nats", - "servicebus", - "sqs", - "pubsub", - ), - "in_process_messaging": ( - "mediatr", - "inmemoryeventbus", - "in-memory event bus", - "in process event", - ), - "message_reliability": ("outbox", "inbox", "idempotent", "dead-letter", "dead letter"), - "domain_modeling": ( - "aggregateroot", - "aggregate root", - "valueobject", - "value object", - "domainevent", - "domain event", - "boundedcontext", - "bounded context", - ), - "feature_flags": ("featuremanagement", "feature flag", "launchdarkly", "unleash"), - "containers_or_orchestration": ( - "dockerfile", - "docker-compose", - "kubernetes", - "kustomize", - "helm", - ), -} - -SOURCE_ROOT_NAMES = {"src", "app", "apps", "modules", "services", "components"} -TEST_PARTS = {"test", "tests", "spec", "specs", "__tests__"} -DATABASE_PARTS = {"migration", "migrations", "database", "schema", "schemas"} -MAX_TEXT_BYTES = 512_000 - - -def relative(path: Path, root: Path) -> str: - return path.relative_to(root).as_posix() - - -def walk_files(root: Path, max_files: int) -> tuple[list[Path], bool]: - files: list[Path] = [] - truncated = False - for current, dirs, names in os.walk(root): - dirs[:] = sorted( - d - for d in dirs - if d.lower() not in IGNORED_DIRS and not (Path(current) / d).is_symlink() - ) - for name in sorted(names): - path = Path(current) / name - if path.is_symlink(): - continue - files.append(path) - if len(files) >= max_files: - truncated = True - return files, truncated - return files, truncated - - -def run_git(root: Path, *args: str) -> str | None: - try: - completed = subprocess.run( - ["git", "-C", str(root), *args], - check=True, - stdout=subprocess.PIPE, - stderr=subprocess.DEVNULL, - text=True, - timeout=5, - ) - value = completed.stdout.strip() - return value or None - except (OSError, subprocess.SubprocessError): - return None - - -def git_metadata(root: Path) -> dict[str, Any]: - return { - "root": run_git(root, "rev-parse", "--show-toplevel"), - "branch": run_git(root, "branch", "--show-current"), - "commit": run_git(root, "rev-parse", "HEAD"), - "remote": run_git(root, "remote", "get-url", "origin"), - "latest_commit_date": run_git(root, "log", "-1", "--format=%cI"), - } - - -def is_manifest(path: Path) -> bool: - name = path.name.lower() - return name in MANIFEST_NAMES or path.suffix.lower() in { - ".csproj", - ".fsproj", - ".vbproj", - ".sln", - ".slnx", - } - - -def read_small_text(path: Path) -> str: - try: - if path.stat().st_size > MAX_TEXT_BYTES: - return "" - return path.read_text(encoding="utf-8", errors="ignore") - except OSError: - return "" - - -def parse_dotnet_project(path: Path, root: Path) -> dict[str, Any]: - record: dict[str, Any] = { - "path": relative(path, root), - "kind": path.suffix.lower().lstrip("."), - } - try: - tree = ET.parse(path) - framework = tree.findtext(".//TargetFramework") or tree.findtext(".//TargetFrameworks") - references = [ - element.attrib.get("Include", "").replace("\\", "/") - for element in tree.findall(".//ProjectReference") - if element.attrib.get("Include") - ] - if framework: - record["target"] = framework - if references: - record["project_references"] = sorted(references) - except (OSError, ET.ParseError): - record["parse_warning"] = "Could not parse XML" - return record - - -def parse_package_json(path: Path, root: Path) -> dict[str, Any]: - record: dict[str, Any] = {"path": relative(path, root), "kind": "package.json"} - try: - data = json.loads(path.read_text(encoding="utf-8")) - if isinstance(data, dict): - if isinstance(data.get("name"), str): - record["name"] = data["name"] - workspaces = data.get("workspaces") - if workspaces: - record["workspaces"] = workspaces - local_dependencies: list[str] = [] - for section in ("dependencies", "devDependencies", "peerDependencies"): - values = data.get(section, {}) - if not isinstance(values, dict): - continue - for name, version in values.items(): - if isinstance(version, str) and ( - version.startswith(("workspace:", "file:", "link:")) or version == "*" - ): - local_dependencies.append(name) - if local_dependencies: - record["local_dependency_candidates"] = sorted(set(local_dependencies)) - except (OSError, UnicodeError, json.JSONDecodeError): - record["parse_warning"] = "Could not parse JSON" - return record - - -def parse_simple_manifest(path: Path, root: Path) -> dict[str, Any]: - record: dict[str, Any] = {"path": relative(path, root), "kind": path.name} - text = read_small_text(path) - if path.name.lower() == "go.mod": - match = re.search(r"(?m)^module\s+(\S+)", text) - if match: - record["name"] = match.group(1) - elif path.name.lower() == "pyproject.toml": - match = re.search(r'(?m)^\s*name\s*=\s*["\']([^"\']+)', text) - if match: - record["name"] = match.group(1) - elif path.name.lower() == "cargo.toml": - package_section = re.search(r"(?ms)^\[package\]\s*(.*?)(?:^\[|\Z)", text) - if package_section: - match = re.search( - r'(?m)^\s*name\s*=\s*["\']([^"\']+)', - package_section.group(1), - ) - if match: - record["name"] = match.group(1) - return record - - -def parse_manifest(path: Path, root: Path) -> dict[str, Any]: - if path.suffix.lower() in {".csproj", ".fsproj", ".vbproj"}: - return parse_dotnet_project(path, root) - if path.name.lower() == "package.json": - return parse_package_json(path, root) - return parse_simple_manifest(path, root) - - -def path_has_part(path: Path, parts: set[str]) -> bool: - return any(part.lower() in parts for part in path.parts) - - -def candidate_source_roots(root: Path, files: Iterable[Path]) -> list[Path]: - roots: set[Path] = set() - for path in files: - current = path.parent - try: - rel_parts = current.relative_to(root).parts - except ValueError: - continue - for index, part in enumerate(rel_parts): - if part.lower() in SOURCE_ROOT_NAMES and index <= 4: - roots.add(root.joinpath(*rel_parts[: index + 1])) - return sorted(roots, key=lambda item: relative(item, root)) - - -def module_candidates(root: Path, files: list[Path], max_items: int) -> list[dict[str, Any]]: - candidates: list[dict[str, Any]] = [] - for source_root in candidate_source_roots(root, files): - try: - children = sorted( - path - for path in source_root.iterdir() - if path.is_dir() and path.name.lower() not in IGNORED_DIRS - ) - except OSError: - continue - for child in children: - child_files = [path for path in files if child == path.parent or child in path.parents] - if not child_files: - continue - candidates.append( - { - "path": relative(child, root), - "source_root": relative(source_root, root), - "files": len(child_files), - "manifests": sum(1 for path in child_files if is_manifest(path)), - } - ) - candidates.sort(key=lambda item: (-item["files"], item["path"])) - return candidates[:max_items] - - -def indicator_evidence(root: Path, files: list[Path]) -> dict[str, dict[str, Any]]: - matches: dict[str, set[str]] = {name: set() for name in INDICATORS} - for path in files: - rel = relative(path, root) - haystack = rel.lower() - if path.suffix.lower() in TEXT_EXTENSIONS or is_manifest(path): - text = read_small_text(path) - if text: - haystack += "\n" + text.lower() - for category, terms in INDICATORS.items(): - if any(term in haystack for term in terms): - matches[category].add(rel) - return { - category: {"files": len(paths), "examples": sorted(paths)[:8]} - for category, paths in matches.items() - if paths - } - - -def collect(root: Path, max_files: int, max_items: int) -> dict[str, Any]: - files, truncated = walk_files(root, max_files) - language_counts = Counter( - LANGUAGES[path.suffix.lower()] - for path in files - if path.suffix.lower() in LANGUAGES - ) - extension_counts = Counter(path.suffix.lower() or "" for path in files) - manifests = [parse_manifest(path, root) for path in files if is_manifest(path)] - adr_files = [ - relative(path, root) - for path in files - if ( - "architecturedecisionlog" - in relative(path, root).lower().replace("-", "").replace("_", "") - or re.search(r"(^|/)(adr|adrs)(/|$)", relative(path, root).lower()) - or re.match(r"^\d{3,4}[-_].*\.(md|adoc)$", path.name.lower()) - ) - ] - test_files = [ - relative(path, root) - for path in files - if path_has_part(path.relative_to(root), TEST_PARTS) - or re.search( - r"(^|[._-])(test|tests|spec|specs)([._-]|$)", - path.name.lower(), - ) - ] - database_files = [ - relative(path, root) - for path in files - if path_has_part(path.relative_to(root), DATABASE_PARTS) - or path.suffix.lower() == ".sql" - ] - ci_files = [ - relative(path, root) - for path in files - if relative(path, root).startswith((".github/workflows/", ".gitlab/")) - or path.name.lower() in {"azure-pipelines.yml", ".gitlab-ci.yml", "jenkinsfile"} - ] - - top_level_counts: Counter[str] = Counter() - for path in files: - parts = path.relative_to(root).parts - top_level_counts[parts[0] if len(parts) > 1 else ""] += 1 - - return { - "report_kind": "architecture evidence inventory", - "notice": ( - "Observations and heuristic indicators only. Confirm boundaries, ownership, " - "runtime behavior, and business drivers before making architecture decisions." - ), - "repository": str(root), - "git": git_metadata(root), - "scan": { - "files_scanned": len(files), - "truncated": truncated, - "max_files": max_files, - }, - "languages": dict(language_counts.most_common()), - "top_extensions": dict(extension_counts.most_common(20)), - "top_level_areas": dict(top_level_counts.most_common(max_items)), - "manifests": manifests[:max_items], - "manifest_count": len(manifests), - "candidate_source_modules": module_candidates(root, files, max_items), - "architecture_records": { - "count": len(adr_files), - "examples": sorted(adr_files)[:max_items], - }, - "tests": {"count": len(test_files), "examples": sorted(test_files)[:20]}, - "database_artifacts": { - "count": len(database_files), - "examples": sorted(database_files)[:20], - }, - "ci_artifacts": {"count": len(ci_files), "examples": sorted(ci_files)[:20]}, - "heuristic_indicators": indicator_evidence(root, files), - } - - -def markdown_list(items: Iterable[str], empty: str = "None observed") -> list[str]: - values = list(items) - return [f"- {item}" for item in values] if values else [f"- {empty}"] - - -def render_markdown(report: dict[str, Any]) -> str: - lines = [ - "# Architecture evidence inventory", - "", - f"Repository: `{report['repository']}`", - "", - f"> {report['notice']}", - "", - "## Scan", - "", - f"- Files scanned: {report['scan']['files_scanned']}", - f"- Truncated: {str(report['scan']['truncated']).lower()}", - ] - git = report["git"] - lines.extend(["", "## Git", ""]) - lines.extend(markdown_list(f"{key}: `{value}`" for key, value in git.items() if value)) - - lines.extend(["", "## Languages", ""]) - lines.extend( - markdown_list(f"{name}: {count} files" for name, count in report["languages"].items()) - ) - - lines.extend(["", "## Top-level areas", ""]) - lines.extend( - markdown_list( - f"`{name}`: {count} files" - for name, count in report["top_level_areas"].items() - ) - ) - - lines.extend(["", "## Build/package manifests", ""]) - lines.append(f"Observed: {report['manifest_count']}") - for manifest in report["manifests"]: - details = [manifest["kind"]] - if manifest.get("name"): - details.append(f"name={manifest['name']}") - if manifest.get("target"): - details.append(f"target={manifest['target']}") - if manifest.get("project_references"): - details.append(f"project_refs={len(manifest['project_references'])}") - lines.append(f"- `{manifest['path']}` ({', '.join(details)})") - - lines.extend(["", "## Candidate source areas", ""]) - lines.append( - "_Directory candidates only; validate against business capabilities and ownership._" - ) - for item in report["candidate_source_modules"]: - lines.append( - f"- `{item['path']}`: {item['files']} files, " - f"{item['manifests']} manifests" - ) - if not report["candidate_source_modules"]: - lines.append("- None observed") - - for heading, key in ( - ("Architecture records", "architecture_records"), - ("Tests", "tests"), - ("Database artifacts", "database_artifacts"), - ("CI artifacts", "ci_artifacts"), - ): - data = report[key] - lines.extend(["", f"## {heading}", "", f"Observed: {data['count']}"]) - lines.extend(markdown_list(f"`{item}`" for item in data["examples"])) - - lines.extend(["", "## Heuristic indicators", ""]) - lines.append( - "_Text/path matches are discovery leads, not proof that a pattern is correctly implemented._" - ) - if not report["heuristic_indicators"]: - lines.append("- None observed") - for category, data in report["heuristic_indicators"].items(): - lines.append(f"- {category}: {data['files']} matching files") - for example in data["examples"]: - lines.append(f" - `{example}`") - - lines.extend( - [ - "", - "## Required follow-up", - "", - "- Confirm business capabilities and bounded-context language with domain evidence.", - "- Trace actual code, data, runtime, deployment, and ownership dependencies.", - "- Measure module-specific change cadence, load, cost, failures, and release coordination.", - "- Classify consequential claims as observed, inferred, or unknown.", - ] - ) - return "\n".join(lines) + "\n" - - -def parse_args(argv: list[str]) -> argparse.Namespace: - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument( - "repository", - nargs="?", - default=".", - help="Repository or source directory", - ) - parser.add_argument("--format", choices=("json", "markdown"), default="json") - parser.add_argument("--output", help="Write the report to this path instead of stdout") - parser.add_argument("--max-files", type=int, default=200_000) - parser.add_argument("--max-items", type=int, default=200) - return parser.parse_args(argv) - - -def main(argv: list[str] | None = None) -> int: - args = parse_args(argv or sys.argv[1:]) - root = Path(args.repository).expanduser().resolve() - if not root.is_dir(): - print(f"error: repository directory does not exist: {root}", file=sys.stderr) - return 2 - if args.max_files < 1 or args.max_items < 1: - print("error: --max-files and --max-items must be positive", file=sys.stderr) - return 2 - - report = collect(root, args.max_files, args.max_items) - rendered = ( - json.dumps(report, indent=2, ensure_ascii=False) + "\n" - if args.format == "json" - else render_markdown(report) - ) - if args.output: - output = Path(args.output).expanduser().resolve() - output.parent.mkdir(parents=True, exist_ok=True) - output.write_text(rendered, encoding="utf-8") - else: - try: - sys.stdout.write(rendered) - except UnicodeEncodeError: - sys.stdout.buffer.write(rendered.encode("utf-8")) - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/agent-skills/split-large-coderabbit-pr/SKILL.md b/agent-skills/split-large-coderabbit-pr/SKILL.md deleted file mode 100644 index a4fb95d..0000000 --- a/agent-skills/split-large-coderabbit-pr/SKILL.md +++ /dev/null @@ -1,175 +0,0 @@ ---- -name: split-large-coderabbit-pr -description: > - Split an oversized GitHub pull request into coherent smaller PRs so - CodeRabbit can review each part separately. Use when CodeRabbit reports - too many changes or states a review limit, or when a user asks to - fragment a large PR for segregated AI review. Do not trigger solely from - a fixed changed-file threshold. ---- - -# Split Large CodeRabbit PR - -Turn one oversized PR into reviewable, dependency-aware PRs while preserving -every original change. - -## Guardrails - -- Do not assume a fixed changed-file threshold. Capture the exact metric and - limit from CodeRabbit or the repository when one exists. -- If no numeric limit is stated, do not invent one. Split only because - CodeRabbit declined the review or the user explicitly requested - fragmentation, and report the resulting file counts. -- Keep every replacement PR below an explicit applicable limit. -- Group changes by behavior, module, dependency, or migration step. Never split - only by arbitrary file count. -- Keep implementation, tests, fixtures, schemas, migrations, and relevant - documentation together. -- Follow the repository's branch, worktree, test, documentation, and tenant - isolation rules. -- Never stack a branch on another unmerged branch in this repository. Open - independent PRs in parallel; publish dependent PRs sequentially from the - updated `origin/main` after their prerequisites merge. -- Do not discard uncommitted work, force-push, rewrite a shared branch, close - the original PR, or delete branches without explicit authorization. -- Never claim that all original changes were preserved until an integration - comparison proves it. - -## Workflow - -### 1. Inspect the oversized PR - -1. Read the CodeRabbit comment and capture its exact limit and metric. -2. Resolve the target branch, original head branch, PR number, changed files, - commits, labels, reviewers, and CI status. -3. Read repository instructions and identify architecture boundaries. -4. Check `git status` before changing branches. Preserve unrelated user work. -5. Record immutable commit IDs for the original base and head. - -Use the GitHub connector for PR metadata when available. Use `gh` and local -`git` when branch, diff, review-thread, or check details require them. - -If CodeRabbit gives no numeric metric, calculate the changed-file count from -the merge-base diff for planning and reporting only. Do not turn that count -into an inferred review limit, and do not interpret added or deleted lines as -files. - -### 2. Design the split - -Build a change inventory and assign every changed path to exactly one proposed -PR. Order foundational work before consumers: - -1. schemas, contracts, shared types, or migrations -2. domain and application behavior -3. adapters, API, UI, or integration code -4. cleanup that depends on the new behavior - -Choose one delivery shape: - -- **Independent series:** Branch every group from `origin/main` and open the - PRs in parallel only when each group builds, tests, and explains a complete - behavior independently. -- **Sequential series:** Merge the prerequisite PR first, update - `origin/main`, then branch and publish the dependent group. Never use an - unmerged replacement branch as another PR's base. -- **Hybrid series:** Open independent groups in parallel and serialize only - genuine dependencies. - -Present a compact split map before mutation. Include the proposed title, -branch, dependency, purpose, approximate file count, and validation for every -PR. Ask for direction only when valid alternatives would materially change -delivery or reviewer workflow. - -### 3. Create isolated branches - -Create each branch from the up-to-date `origin/main` using the repository's -allowed `/` convention. Use an isolated worktree when another -agent or human may be working in the main checkout. - -Prefer the least invasive extraction method: - -1. Cherry-pick existing commits when they are already cohesive and - dependency-safe. -2. Otherwise apply only the assigned paths or hunks from the recorded original - head. -3. Split a file by hunk only when the changes are genuinely independent. Keep - the file in one PR when splitting would make either PR misleading or - unbuildable. -4. Commit coherent behavior with a message that describes the change, not the - mechanics of splitting. - -Do not mutate the original PR branch while constructing the series. - -### 4. Validate every replacement - -For each proposed PR: - -1. Confirm its changed-file count is within the applicable limit. -2. Review the diff for gaps, duplicates, accidental files, generated - artifacts, secrets, and unrelated changes. -3. Run every repository gate applicable to the files it changes. -4. Confirm the PR is understandable and testable without hidden changes from a - later PR. -5. Stop before publication if validation fails or the scope is incomplete. - -For a sequential series, repeat these checks against the updated `origin/main` -after every prerequisite merge. - -### 5. Prove coverage of the original diff - -Before publishing, build a temporary local integration branch or equivalent -tree from the recorded original base and combine all proposed groups in -dependency order. Compare the result with the recorded original head. - -Require no unexplained diff. Classify any intentional difference explicitly, -such as an omitted generated artifact, and fix every gap or duplicate before -opening replacement PRs. Repeat the proof after rebases if `main` changes. - -### 6. Publish the series - -Push only validated replacement branches. Open draft PRs unless the user asks -for ready-for-review PRs and every required gate already passes. - -For every PR: - -- Use a title such as `[1/N] `. -- Explain its scope and why it is separated. -- Link the oversized PR and every replacement PR already available. -- State whether it is independent or which merged PR it follows. -- Include the changed-file count and test evidence. -- Preserve relevant labels and reviewers when appropriate. -- Tell reviewers that the PR is an independently reviewable segment of the - original diff. - -Do not invent a CodeRabbit re-review command. Use an established repository -command when one exists; otherwise rely on configured PR automation. - -### 7. Handle the original PR - -After replacement PRs exist and coverage is verified, add a summary comment to -the original PR with: - -- the reason for the split -- the ordered replacement list -- dependency and merge-order information -- coverage and test results -- remaining risks or unpublished dependent groups - -Leave the original PR open unless the user explicitly authorizes closing it. -If authorized, close it only after the summary is posted and every replacement -URL is confirmed. - -### 8. Report - -Return: - -- the original PR URL -- ordered replacement PR URLs and pending sequential groups -- independent, sequential, or hybrid topology -- changed-file count for each PR -- validation and coverage-comparison results -- CI, review, and merge-order follow-up - -If permissions, authentication, branch protection, or validation blocks -publication, leave recoverable local branches intact and report the exact -blocker and next action. diff --git a/apps/admin-frontend/.agent.md b/apps/admin-frontend/.agent.md deleted file mode 100644 index 826cbab..0000000 --- a/apps/admin-frontend/.agent.md +++ /dev/null @@ -1,113 +0,0 @@ -# Senior React & TypeScript Developer - -You are a Staff-level React and TypeScript developer specializing in **clean, type-safe React** for the Agenza admin-frontend SaaS application. You write code that adheres to strict architectural principles, Clean Architecture layering, and modern React best practices. - -## Scope - -This agent handles: - -- **React/TypeScript code generation and review** for features within `apps/admin-frontend/` -- **Architecture compliance** — ensuring feature isolation (ADR 009), dependency inversion, and layer boundaries -- **Type safety** — strict TypeScript config enforcement, no `any`, proper discriminated unions -- **Testing strategy** — TDD workflows, fake repositories, MSW mocking, React Testing Library patterns - -When the user asks about features, infrastructure, or cross-cutting concerns, propose solutions grounded in the project's ADRs (`docs/adr/`). - -## Non-Scope - -- Backend API implementation (refer to `backend/AGENTS.md`) -- DevOps, CI/CD, deployment tooling -- Package/dependency upgrades (ask the user first) - -## Principles - -### Type Safety First - -- Never use `any`. Use strict TypeScript types, discriminated unions for state, and narrow types properly. -- Leverage `unknown` for dynamic data; validate at runtime (especially for externally-supplied arrays and numeric IDs). -- When a domain entity's input comes from the generated API, validate at runtime in the domain's `create()` factory, not just at the type level. - -### Modern React (v18/v19) - -- Functional components and hooks only; no class components or `React.FC`. -- Props are explicit interfaces/types. -- Prefer `useCallback` and `useMemo` judiciously — only when avoiding re-renders solves a real problem (expensive computation, reference stability for deps). -- Never use array index as list key when items can reorder or be deleted. - -### Clean Architecture & Separation of Concerns - -- **Layering:** Each feature's `domain/` → `application/` → `infrastructure/`/`presentation/` layers are hermetic. Dependencies point inward only. -- **Presentation layer** never imports infrastructure directly; all errors are `AppError` before leaving infrastructure. -- **Hooks/Controllers** are thin wrappers around use cases; complex multi-workflow logic is split into focused hooks, not monolithic controllers. -- **Composition root** is `app/main.tsx`; only place that constructs `AppContainer`. - -### Componentization - -- A page component is a shell that wires a controller hook's view models into UI components — nothing else. -- Extract a component/hook on first distinct concern; promote to `shared/` only on _second_ identical use. -- Use `src/components/ui/*` (shadcn/ui) as-is; extend only when a real need surfaces. -- Reference implementations: `CategoriesListPage`/`CategoryEditorDialog` (behavior, design & form structure), `AdminLayout` (page shell). - -### Testing - -- Use case tests → hand-written fake repositories -- Infrastructure tests → MSW handlers (real `HttpClient` code path) -- Presentation tests → fake `AppContainer` from `src/test/fixtures/createFakeAppContainer.ts` -- Every HTTP call needs a registered MSW handler; `onUnhandledRequest: 'error'` -- Add `jest-axe` accessibility checks to new/changed forms and routed pages - -### Comments — Minimal by Default - -- No comment unless a senior reviewer would get it wrong without it. -- Security/tenant-isolation defaults, concurrency guards, React/Radix/RHF/Zod quirks, or lint suppression only. -- Architectural rationale belongs in `docs/adr/`; reference it in one clause (`see docs/adr/0006`), never restate. - -### Configuration Compliance - -- `erasableSyntaxOnly: true` — no constructor parameter property shorthand; explicit field + `this.x = x`. -- `exactOptionalPropertyTypes: true` — always guard optional fields; never assign `maybeUndefined` directly. -- `noUncheckedIndexedAccess: true` — index access returns `T | undefined`; always guard. -- `strict: true` — no `any`. -- ESLint rules for layer/feature isolation must not be disabled (`no-restricted-imports`). - -## Workflow - -1. **Read context first:** If the user mentions a feature, file, or domain, understand the existing code & ADRs before proposing changes. -2. **Type-safe design:** Propose types upfront, validate at runtime where necessary. -3. **Incremental changes:** Prefer small, testable changes; explain architectural decisions with ADR references when non-obvious. -4. **Test coverage:** After code changes, confirm tests pass (`npm run test:coverage --workspace=apps/admin-frontend`). -5. **No speculative code:** Don't add variants, props, or styling "just in case." Add only what's needed now. - -## Key Files to Reference - -- `.AGENTS.md` — repo-wide rules and non-negotiable constraints -- `docs/adr/` — architectural decisions and their rationale -- `docs/STATUS.md` — current feature state and blockers -- `docs/DOMAIN.md` — domain entity definitions -- `agent-skills/agenza-frontend-feature` — design language, component inventory, mobile checklist -- `.skills/admin-tdd-conventions/SKILL.md` — testing patterns & MSW usage -- `src/test/fixtures/createFakeAppContainer.ts` — fake container for presentation tests - -## Communication Style - -- **Professional, concise, direct:** Address the immediate request; omit unrelated details. -- **Educate via reference:** When a decision seems surprising, reference the relevant ADR or doc; don't re-explain. -- **Show, don't tell:** Provide working code/tests; minimal preamble. -- **Language:** All user-facing text (labels, messages, aria-labels) is Brazilian Portuguese (pt-BR). - -## Invocation Triggers - -Use this agent when: - -- Building a new feature vertical or page component -- Refactoring existing React/TypeScript code for type safety or architectural compliance -- Writing or debugging tests (especially MSW mocking, fake containers, presentation/use-case layers) -- Reviewing code for adherence to Clean Architecture, layer isolation, or TypeScript strictness -- Questions about why a constraint or pattern exists — refer to ADRs - -**Example prompts:** - -- "Add a new `ClientsPage` following the Categories reference pattern" -- "Review this controller hook for over-complexity; should I split it?" -- "Why does the component forward a `ref` to the DOM? When is that required?" -- "Add MSW handlers for these endpoints and wire them into the test" diff --git a/apps/admin-frontend/.prettierignore b/apps/admin-frontend/.prettierignore index 5575648..ef0a134 100644 --- a/apps/admin-frontend/.prettierignore +++ b/apps/admin-frontend/.prettierignore @@ -2,7 +2,6 @@ dist coverage node_modules *.tsbuildinfo -graphify-out src/features/catalog/infrastructure/generated playwright-report test-results diff --git a/apps/admin-frontend/.skills/admin-api-contract/SKILL.md b/apps/admin-frontend/.skills/admin-api-contract/SKILL.md deleted file mode 100644 index c70a2da..0000000 --- a/apps/admin-frontend/.skills/admin-api-contract/SKILL.md +++ /dev/null @@ -1,148 +0,0 @@ ---- -name: admin-api-contract -description: > - Use this skill whenever the user provides an API spec (endpoint paths, - request/response shapes, field names, status codes) for a feature in the - admin panel project. Covers: defining typed DTOs from the spec, building - the infrastructure mapper (DTO → domain entity), writing MSW test - handlers that match the real API's shape, and identifying mapping edge - cases before writing any code. Trigger on phrases like "here's the API - spec", "the endpoint is...", "the response looks like...", or when the - user pastes JSON shapes or OpenAPI snippets. Do not guess at field names - or response shapes — always derive them from what the user provides. ---- - -# Admin API Contract - -This skill governs the translation from an external API spec into the -project's infrastructure layer. The goal is a single, well-tested seam -where "what the API sends" becomes "what the domain layer works with." - ---- - -## Step 1: Parse the spec - -When the user provides a spec, extract and confirm: - -| Item | Where it goes | -| ----------------------------------- | --------------------------------------------------------------------------------------------------------------------- | -| Base path (e.g. `/api/v1/services`) | `AuthenticatedHttpClient` prefix + repository method paths | -| HTTP method per operation | Repository method implementation | -| Request body shape | `CreateXInput` / `UpdateXInput` interfaces in the repository | -| Response body shape | DTO interface in the feature's `infrastructure/mappers/` | -| Error codes and shapes | `AppError.code`/`backendCode` - the repository doesn't catch anything, `AuthenticatedHttpClient` already converted it | -| Tenant scoping mechanism | HTTP header? Path param? Query param? Confirm with user | - -If anything is ambiguous or missing, ask before writing code. - ---- - -## Step 2: Define the DTO interface - -Location: alongside the mapper, e.g. -`features/catalog/infrastructure/mappers/serviceMapper.ts` - -Rules: - -- DTO fields must exactly match the API's JSON keys (snake_case if the API uses it) -- All fields optional-or-required must match what the API actually sends -- Use `unknown` for fields whose type you're not certain of — never `any` -- If a field could be null OR absent, use `field?: string | null` - -Example: - -```typescript -interface ServiceDto { - id: string - tenant_id: string - name: string - duration_minutes: number - price_cents: number - description?: string | null - is_active: boolean - created_at: string -} -``` - ---- - -## Step 3: Write the mapper (TDD first) - -The mapper is a pure function. Write the test before the implementation. - -Test checklist: - -- [ ] Maps every field correctly (including unit conversions, e.g. cents → Money) -- [ ] Handles optional/nullable fields correctly (absent vs null vs present) -- [ ] Throws a named error when a required field is missing or invalid -- [ ] Converts date strings to `Date` objects where appropriate - -Watch for `exactOptionalPropertyTypes` — when passing optional fields into -`Entity.create()`, use conditional spread: - -```typescript -const entity = Entity.create({ - id: dto.id, - ...(dto.description != null ? { description: dto.description } : {}), -}) -``` - -Watch for `noUncheckedIndexedAccess` — when reading from the DTO object -via index (e.g. `dto['tenant_id']`), the type is `unknown`. Add a -`typeof x === 'string'` guard before use. - ---- - -## Step 4: Write MSW handlers - -Location: `src/test/mocks/handlers/featureHandlers.ts` - -Rules: - -- Use `http.get`, `http.post`, `http.put`, `http.delete` from `msw` -- Match the exact path the repository will call (including base URL if relevant) -- Return realistic DTO shapes that match the DTO interface exactly -- Include at least one error handler variant (e.g. 404, 422) per endpoint - to test the repository's error handling - -Register in `src/test/mocks/handlers/index.ts`: - -```typescript -import { featureHandlers } from './featureHandlers' -export const handlers: RequestHandler[] = [...featureHandlers] -``` - -Remember: `onUnhandledRequest: 'error'` is configured globally — any -repository call without a registered handler will fail tests loudly. - ---- - -## Step 5: Confirm tenant scoping mechanism - -Before writing the repository, confirm HOW the API expects the tenant to -be identified. Common patterns: - -| Pattern | How to implement | -| -------------------------------------------------- | ------------------------------------------------ | -| JWT claim (most likely — IdentityServer issues it) | Already in the Bearer token, no extra work | -| `X-Tenant-Id` header | Add to `AuthenticatedHttpClient` default headers | -| Path prefix `/api/tenants/{id}/services` | Include in repository path construction | -| Query param `?tenant_id=...` | Append in repository method | - -If the JWT claim covers it (most likely given this project's IdentityServer -setup), no extra work is needed — the token already proves tenant identity. - ---- - -## Step 6: Common field translation patterns - -| API type | Domain type | Notes | -| ------------------- | ------------------------------- | --------------------------------------- | -| `string` (ISO 8601) | `Date` | `new Date(dto.created_at)` | -| `number` (cents) | `Money` value object | `Money.fromCents(dto.price_cents)` | -| `number` (minutes) | plain `number` | keep as-is unless duration needs a VO | -| `string` (enum) | TypeScript union or enum | validate against known values in mapper | -| `null \| string` | `string \| undefined` on domain | strip nulls at the mapper boundary | - -The domain layer should never see `null` — convert API nulls to `undefined` -or omit the field entirely in `Entity.create()`. diff --git a/apps/admin-frontend/.skills/admin-feature-vertical/SKILL.md b/apps/admin-frontend/.skills/admin-feature-vertical/SKILL.md deleted file mode 100644 index e8baf5b..0000000 --- a/apps/admin-frontend/.skills/admin-feature-vertical/SKILL.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -name: admin-feature-vertical -description: > - OBSOLETE — superseded by agent-skills/agenza-frontend-feature. Do not - use this file; read the canonical skill instead. ---- - -# Admin Feature Vertical (obsolete — moved) - -This skill's content moved to -[`agent-skills/agenza-frontend-feature/SKILL.md`](../../../../agent-skills/agenza-frontend-feature/SKILL.md) -(distributed to `.claude/skills/agenza-frontend-feature/` and -`.agents/skills/agenza-frontend-feature/` by -`scripts/sync_agent_skills.py`) as part of the cross-tool governance -migration (see `docs/AGENT-GOVERNANCE.md`). - -The migration also caught this file describing forms as "a plain, -dialog-agnostic ``" with no mention of React Hook Form, Zod, or -structured server-error-to-field mapping — the codebase moved past that -description (see `TagForm.tsx`, `serverFormError.ts`, -`useCreateInline.ts`) without this skill being updated to match. The -canonical skill documents the current form/error/inline-creation pattern. -Read the canonical skill, not this file. diff --git a/apps/admin-frontend/.skills/admin-tdd-conventions/SKILL.md b/apps/admin-frontend/.skills/admin-tdd-conventions/SKILL.md deleted file mode 100644 index 73decaa..0000000 --- a/apps/admin-frontend/.skills/admin-tdd-conventions/SKILL.md +++ /dev/null @@ -1,242 +0,0 @@ ---- -name: admin-tdd-conventions -description: > - Use this skill whenever writing or reviewing tests in the admin panel - project. Covers the project's specific testing patterns, TypeScript - strict-mode workarounds that appear repeatedly in tests, how to structure - fake repositories vs MSW handlers vs component test wrappers, and known - lint/compiler gotchas. Trigger on: writing any test file, debugging a - test failure, setting up a new fake, or when TypeScript strict mode - errors appear in test files. Do NOT skip this skill when testing — the - project has several non-obvious constraints that are easy to get wrong. ---- - -# Admin TDD Conventions - -## Which mock strategy for which layer - -| Layer being tested | Mock strategy | -| --------------------------- | ------------------------------------------------------------------ | -| Domain entities | No mocks — pure functions/classes only | -| Application use cases | Hand-written fake repositories (`createFakeXRepository`) | -| Infrastructure repositories | MSW handlers — mock the HTTP boundary, not the repo | -| Presentation hooks | Fake container via `AppContainerContext.Provider` | -| Presentation components | Fake container via `AppContainerContext.Provider` + `MemoryRouter` | - -Never mix strategies. Don't use MSW to test use cases. Don't use fake -repos to test infrastructure repositories. - ---- - -## Fake repository pattern - -Always extract to a shared test helper after the second use case needs it: - -```typescript -// features/catalog/application/test-helpers/createFakeServiceRepository.ts -import type { ServiceRepository } from '../repositories/ServiceRepository' - -export function createFakeServiceRepository( - overrides: Partial = {}, -): ServiceRepository { - return { - listAll: () => Promise.resolve([]), - findById: () => Promise.resolve(null), - create: () => Promise.reject(new Error('not implemented in this fake')), - update: () => Promise.reject(new Error('not implemented in this fake')), - delete: () => Promise.resolve(), - ...overrides, - } -} -``` - -Use `vi.fn()` for spying in specific tests: - -```typescript -const listAllSpy = vi.fn(() => Promise.resolve([serviceFixture])) -const repo = createFakeServiceRepository({ listAll: listAllSpy }) -``` - ---- - -## Fake container pattern (presentation tests) - -`AppContainer`'s public shape is `{ auth, catalog }` — grouped facades, -never a raw repository or use-case bag (docs/adr/008). Build a fake with -`createFakeAppContainer` (`src/test/fixtures/createFakeAppContainer.ts`), -overriding only the `execute` functions a test actually exercises: - -```typescript -const container = createFakeAppContainer({ - auth: { getCurrentSession: { execute: vi.fn(() => Promise.resolve(null)) } }, - catalog: { listServices: { execute: vi.fn(() => Promise.resolve(pagedServices)) } }, -}) -``` - -Each facade member's type is `Pick` (structural, -drops the class's private-field nominal typing), so a plain -`{ execute: vi.fn(...) }` object satisfies it directly — no -`as unknown as AppContainer` cast needed anywhere in this pattern. - ---- - -## TypeScript strict mode gotchas in tests - -### erasableSyntaxOnly — no constructor parameter shorthand - -Applies to ALL classes, including test helpers and fakes: - -```typescript -// WRONG — fails tsc even if vitest passes -class FakeThing { - constructor(private readonly value: string) {} -} - -// CORRECT -class FakeThing { - private readonly value: string - constructor(value: string) { - this.value = value - } -} -``` - -### exactOptionalPropertyTypes — conditional spread for optional fields - -```typescript -// WRONG -Entity.create({ id: 'x', optionalField: maybeUndefined }) - -// CORRECT -Entity.create({ - id: 'x', - ...(maybeUndefined !== undefined ? { optionalField: maybeUndefined } : {}), -}) -``` - -### never-resolving Promise in tests - -Testing "loading" or "in-flight" states requires a Promise that never -settles. The empty executor `() => {}` triggers `no-empty-function`. -Use an eslint-disable comment: - -```typescript -// eslint-disable-next-line @typescript-eslint/no-empty-function -const neverResolves = vi.fn(() => new Promise(() => {})) -``` - -### renderHook generic types - -Always provide explicit generics to `renderHook` — without them, -`result.current` is untyped and triggers `no-unsafe-*` rules: - -```typescript -import { type RenderHookResult, renderHook } from '@testing-library/react' -import { type UseServicesResult } from './useServices' - -function renderUseServices(container: AppContainer): RenderHookResult { - return renderHook(() => useServices(), { - wrapper: /* ... */, - }) -} -``` - -### Wrapper return type - -Always add an explicit return type on render/renderHook helper functions -to satisfy `explicit-function-return-type`. If the function doesn't use -the render return value, type it as `void`: - -```typescript -function renderComponent(container: AppContainer): void { - render(, { wrapper: makeWrapper(container) }) -} -``` - ---- - -## MSW handler conventions - -```typescript -// src/test/mocks/handlers/serviceHandlers.ts -import { http, HttpResponse } from 'msw' - -const BASE = 'http://localhost' // or use the env var value - -export const serviceHandlers = [ - http.get(`${BASE}/api/v1/services`, () => { - return HttpResponse.json([ - { id: 'svc-1', name: 'Haircut', duration_minutes: 30, price_cents: 2500 }, - ]) - }), - - http.post(`${BASE}/api/v1/services`, async ({ request }) => { - const body = await request.json() - return HttpResponse.json({ id: 'svc-new', ...body }, { status: 201 }) - }), - - http.delete(`${BASE}/api/v1/services/:id`, () => { - return new HttpResponse(null, { status: 204 }) - }), -] -``` - -Register in `src/test/mocks/handlers/index.ts`: - -```typescript -import { serviceHandlers } from './serviceHandlers' -export const handlers = [...serviceHandlers] -``` - -`onUnhandledRequest: 'error'` is globally configured — any unhandled -request fails loudly. This is intentional: it catches missing handlers -rather than silently hanging. - ---- - -## waitFor and act conventions - -- Use `await waitFor(() => { expect(...).toBe(...) })` with braces — not - shorthand arrow returns (triggers `no-confusing-void-expression`) -- Use `await act(async () => { ... })` when triggering events that cause - state updates -- For auth-wrapped components, always `await waitFor` for the initial - session check to settle before asserting on the page's real state - ---- - -## react-hooks/set-state-in-effect suppression - -`useAsync`'s `void execute()` call inside a `useEffect` triggers this -rule as a false positive (the rule traces async call graphs and flags -setState calls that happen after awaits). The suppression is documented -and intentional: - -```typescript -// eslint-disable-next-line react-hooks/set-state-in-effect -void execute() -``` - -Do not remove this comment. Do not add it to other places — if you see -this lint error elsewhere it's likely a real violation, not a false positive. - ---- - -## Running targeted tests - -```bash -# Single file -npx vitest run src/features/catalog/domain/entities/Service.test.ts - -# Whole layer, within a feature -npx vitest run src/features/catalog/application/ - -# All tests -npm run test - -# Watch mode during TDD -npm run test:watch -``` - -Always run `npm run build` after `npm run test` — tsc catches type errors -that vitest/esbuild silently ignores. Both must pass before committing. diff --git a/apps/admin-frontend/AGENTS.md b/apps/admin-frontend/AGENTS.md index 32a99ff..b652e15 100644 --- a/apps/admin-frontend/AGENTS.md +++ b/apps/admin-frontend/AGENTS.md @@ -1,302 +1,99 @@ -# Admin Panel (frontend) — Agent Instructions - -Read [../../AGENTS.md](../../AGENTS.md) first for repo-wide rules (question -policy, tenant scoping, exception policy, rule persistence). This file -covers what's specific to `apps/admin-frontend/`. - -## What this project is - -A multi-tenant SaaS admin panel for small healthcare/wellness businesses. -Built with Clean Architecture, TDD, and strict TypeScript, organized by -feature (ADR 009: `app/`, `features/{auth,catalog}/`, `shared/`). The Auth, -Categories, and Services verticals are complete end-to-end (frontend + -backend). Tags was removed from the frontend (docs/adr/016) — the backend -`Tag` domain/`/api/v1/tags` endpoints are intentionally retained. The -remaining feature verticals (Appointments, Clients, Inbox, -Dashboard, Settings) are stubs awaiting implementation, under `app/pages/` -until each graduates into its own feature. - ---- - -## Read these before doing any work - -### Skills (how-to guides) - -| Skill | When to read | -| ----------------------------------------- | --------------------------------------------------------- | -| `agent-skills/agenza-frontend-feature` | Building any new feature vertical — canonical, portable | -| `.skills/admin-api-contract/SKILL.md` | User provides an API spec | -| `.skills/admin-tdd-conventions/SKILL.md` | Writing or debugging any test | -| `agent-skills/agenza-api-contract-review` | Auditing FE/BE contract drift (DTOs, enums, error shapes) | - -### Docs (reference) - -| Doc | When to read | -| ------------------- | ------------------------------------------------------------- | -| `docs/STATUS.md` | Before starting any work — see what exists and what's blocked | -| `docs/DOMAIN.md` | Before designing any domain entity | -| `docs/DECISIONS.md` | When a convention seems strange — the reason is in there | -| `docs/API.md` | Before building any infrastructure repository | -| `docs/adr/` | Key architectural decisions with rationale | - ---- - -## Critical constraints (non-negotiable) - -### TypeScript - -- `erasableSyntaxOnly: true` — NO constructor parameter property shorthand. - Always explicit field declaration + `this.x = x` in the constructor body. -- `exactOptionalPropertyTypes: true` — use `if (value !== undefined) { this.field = value }` - for optional fields. Never assign `this.field = maybeUndefined` directly. -- `noUncheckedIndexedAccess: true` — index access returns `T | undefined`. Always guard. -- `strict: true` — no `any`, ever. - -### Architecture (ADR 009: `app/`, `features/{auth,catalog}/`, `shared/`) - -- Every feature (`features/auth/`, `features/catalog/`) keeps its own - `domain/ → application/ → infrastructure/`/`presentation/` layering - inside it, dependencies pointing inward only. `domain/` and - `application/` never import React, react-router, or anything from that - feature's own `infrastructure/`/`presentation/` — ESLint enforces this - per feature and in `shared/`, do not disable those rules. -- A feature's internals are reached from outside that feature **only** - through its `index.ts` public API (`@/features/auth`, `@/features/catalog`) - — never by importing a deep path into its `domain/application/ -infrastructure/presentation`. ESLint (`no-restricted-imports`) and - `scripts/architecture_guard.py`'s `check_cross_feature_internal_imports` - both enforce this. Two narrow, documented exceptions: `src/test/**` - (MSW fixtures need a feature's internal DTOs) and `app/routes/router.tsx` - lazy-loading catalog's pages by their own path (code-splitting — see - docs/adr/009's "Execution" section for why). -- Categories and Services share one `features/catalog/` feature - (not one each) — they collaborate in the same business context and - cross-reference each other (a Service has a `categoryId` and `tags`). -- `app/composition/container.ts` is the ONLY place allowed to construct - concrete repository/auth implementations. `AppContainer`'s public shape is - `{ auth, catalog }` — grouped application facades, never a raw - repository or `HttpClient` (docs/adr/008). Add a new use case to the - matching facade's interface (`Pick`), not as a - new top-level container field. -- `AppProviders` (`app/providers/`) receives an already-built `AppContainer` - as a prop; it never calls `createAppContainer()` itself. `app/main.tsx` - is the composition root — the only place that does. -- Every repository interface method takes `TenantContext` as first param. -- `useAuth()` (`features/auth`) is a pure consumer of `AuthProvider`'s - shared session state — it has no state of its own. Never re-fetch the - session from a page or hook directly; read `useAuth()` instead (docs/adr/006). -- `AuthenticatedHttpClient` (`shared/infrastructure/http/`) reads the - access token and tenant id together from one `GetRequestSession` call - per request (`shared/application/RequestSession.ts`) — never two - independent reads, so they can't end up from different moments of a - session transition. A 401/missing-session reaches `AuthProvider` through - the `SessionEventBus` port (`shared/application/SessionEventBus.ts`), not - a direct callback — infrastructure never imports React. -- Silent renewal must preserve both `user.id` and `tenant.id`. If either - claim changes, discard the renewed OIDC user and require a full login; - never let a refreshed token for one identity reach React state that is - still keyed to another identity. -- `/login` is an automatic OIDC orchestrator, not a second confirmation - screen. It explains the redirect while session state resolves, starts - login once when unauthenticated, sends the current `light` or `dark` theme - as a validated OIDC extension parameter, and sends an - already-authenticated user to the validated return path. Authentication - failures use a stable - `AuthFlowError` code, a specific curated pt-BR explanation, a recovery - action, and instructions for requesting help without sharing a password; - never replace these with a generic “contacte o administrador” message. -- Routed, tenant-scoped page content renders inside `TenantBoundary` - (already wired in `AdminLayout`) so a session/tenant switch remounts it — - don't bypass this with a page that renders outside `AdminLayout`'s - `Outlet`. -- `presentation/` (in any feature, `shared/`, or `app/` outside - `app/composition/`) must never import `infrastructure/` directly - (ESLint-enforced). Any caught error is an `AppError` - (`shared/application/AppError.ts`) by the time it reaches a hook/ - component — `AuthenticatedHttpClient` converts everything (missing - session, 401, `ProblemDetails`, network/timeout failure) before it - leaves infrastructure (docs/adr/007). Never render a caught error's raw - `.message` directly for an unexpected/network/timeout/unauthorized - failure — use the `AppError`'s own curated message. - -### Comments — minimum of the minimum, by default zero - -Default to no comment. This team is senior; identifiers, types, and -structure carry the meaning — a comment restating what a well-named -function/prop/hook already says is waste, not documentation. Add a -one-line comment (never a paragraph, never a JSDoc block on a clearly -named interface/hook/prop) only when a careful senior reviewer would -still get it wrong without it: a security/tenant-isolation default, a -concurrency/race guard, a genuine React/Radix/RHF/Zod/browser quirk, or -an unavoidable lint suppression. Architectural rationale belongs in -`docs/adr/` — reference it in one short clause at most (`see docs/adr/0006`), -never restate it. When in doubt, cut the comment; do not add one "to be -safe" or to explain a correction — fix the code/naming instead so the -comment isn't needed. This applies retroactively to existing code, not -just new code, and mirrors `backend/AGENTS.md`'s "Comments — minimal, by -default zero" — the same bar, applied here too. - -### Componentization - -- A page (`XPage.tsx`) is a composition shell: it wires a controller hook's - view models into presentational components and renders nothing else. A - controller hook (`useXPage`) follows the same single-responsibility bar — - when it grows more than one real workflow (filters, editor, deletion, - dirty-tracking are each their own concern), split it into focused hooks - the page's controller composes, not one hook doing everything. -- Extract a component or hook on its _first_ use if it's already a distinct - concern (a field group, a delete-confirmation dialog); keep it - feature-local. Only _promote_ something to `shared/` on its _second_, - genuinely-identical use across features — the "wait for the second use" - rule gates promotion to `shared/`, not the initial extraction. -- `CategoriesListPage`/`CategoryEditorDialog` (`features/catalog/presentation/categories/`) - is the reference for _behavior and design_ (search → table → dialog - create/edit → `AlertDialog` delete-confirm, loading/error/empty states) — - not for _anatomy_. Categories' create/edit dialog is routed - (`/categories/new`, `/categories/:id/edit`, docs/adr/012) rather than - toggled by local state; that routing detail is Categories-specific, not a - requirement for every feature. A feature with more workflows (Services: - filters + pagination + dirty-tracking + inline-create) needs more files - than Categories does; that's a correctly-sized decomposition, not a - deviation. -- Decomposition triggers: multiple independent workflows in one - hook/component, several dialogs, distinct state clusters, a prop list a - reader can't hold in their head, a type cycle between a controller and - the component it feeds, or a page test file so large it's hard to find - the right assertion. There is no hard line-count cap — size alone is not - a trigger, and splitting a genuinely cohesive 150-line component to hit - a number is not the goal. -- `GenericCrudPage` (or any generic entity-agnostic CRUD abstraction) is - prohibited. Categories/Services each keep their own page, form, and - table — share only behavior that's proven identical (`useDialogTarget`, - `useDeleteConfirmation`, `DeleteConfirmationDialog`, - `CollectionFeedback`, all in `shared/`), never a config-driven generic - page. - -### Testing - -- Use case tests → hand-written fake repositories -- Infrastructure tests → MSW handlers (real HttpClient code path) -- Presentation tests → fake `AppContainer` via `AppContainerContext.Provider`, - built with `createFakeAppContainer({ auth: {...}, catalog: {...} })` - from `src/test/fixtures/createFakeAppContainer.ts` — fully typed, no - `as unknown as AppContainer` cast needed (docs/adr/008). Any component - that (transitively) calls `useAuth()` also needs `AuthProvider` wrapped - around it. -- `onUnhandledRequest: 'error'` — every HTTP call needs a registered MSW handler. -- `jest-axe` (`import { axe } from 'jest-axe'`, `expect(container).toHaveNoViolations()`) - is available for accessibility assertions — the matcher is registered - globally in `src/test/setup.ts`. Add it to any new or changed form/page - that a screen-reader or keyboard-only user would rely on; see - `CategoriesRoutes.test.tsx` for the pattern. -- A form field wired through `Controller` (not `register()`) needs its - rendered component to forward a `ref` to a real, focusable DOM node - (`CreatableSingleSelect`/`CreatableMultiSelect` both do) - otherwise - `setFocus(fieldName)` silently does nothing when a server error targets - that field. -- A PUT body still includes the resource's own id even though the backend - always overwrites it with the route id (docs/adr/0007, docs/adr/010) - - build it explicitly against the generated `Update*Command` type, keyed - on the same `id` the URL uses, never a separately-sourced value. -- A domain entity whose input can come from the generated API types - (`number | string`-widened fields, docs/adr/010) must validate at - runtime (finite, correct type, integer where required) in its `create()` - factory - a type-level narrowing in the mapper is not enough. Store any - externally-supplied array as a defensive copy, not by reference. -- `e2e/` holds a Playwright suite (`npm run test:e2e`) - separate from the - Vitest unit/component suite and not part of its coverage gate. Runs - against the production build (`vite build` + `vite preview`), not - `vite dev`, since dev-only StrictMode effect double-invocation would - make its request-count assertions nondeterministic. See "End-to-end - tests" in docs/STATUS.md for what it covers, what's deliberately left to - unit tests instead, and why it isn't wired into CI yet. - -### Both must pass before every commit +# Admin frontend — agent instructions + +Read [../../AGENTS.md](../../AGENTS.md) first. This file contains only +durable rules specific to `apps/admin-frontend/`; current feature progress +belongs in [docs/STATUS.md](docs/STATUS.md), package versions belong in +`package.json`, and decision rationale belongs in [docs/adr/README.md](docs/adr/README.md). + +## Read by task + +| Task | Read | +| ------------------------------------ | ----------------------------------------------- | +| Any React/TypeScript feature change | `.agents/skills/agenza-frontend-feature` | +| Backend/frontend contract audit | `.agents/skills/agenza-api-contract-review` | +| Exploratory screen/accessibility QA | `.agents/skills/agenza-frontend-exploratory-qa` | +| Current implementation status | `docs/STATUS.md` | +| Domain terminology or a new entity | `docs/DOMAIN.md` | +| REST endpoint or generated type work | `docs/API.md` and the generated OpenAPI types | +| Architectural rationale | `docs/adr/README.md`, then only the routed ADRs | + +Do not read every frontend document by default. Inspect the code and config +that own the behavior before relying on prose. + +## Non-negotiable architecture + +- Keep feature internals under `src/features//{domain,application, +infrastructure,presentation}`. Dependencies point inward. Domain and + application do not import React or infrastructure/presentation. +- Outside a feature, import through its `index.ts`; the narrow test and route + lazy-loading exceptions are enforced by ESLint and + `scripts/architecture_guard.py`. +- `src/app/composition/container.ts` is the only place that constructs concrete + repositories, auth adapters, or `AuthenticatedHttpClient`. `src/app/main.tsx` + is the composition root. +- A facade entry mirrors a repository method directly when it is only a + pass-through. Introduce a use-case class when it owns orchestration or policy, + not merely to add an `execute` wrapper. +- Repository methods do **not** accept `TenantContext`. The + `AuthenticatedHttpClient` obtains the access token and tenant id atomically + from `GetRequestSession` and attaches both `Authorization` and + `X-Tenant-Id`. Feature repositories never set or choose tenant headers. +- Tenant-scoped UI state must clear synchronously on tenant change. Hooks built + on `useAsync` pass the tenant id as `resetKey`; routed content stays inside + `TenantBoundary`. +- Expected domain, auth, HTTP, decode, network, and backend failures flow as + `Result` values. `AuthenticatedHttpClient` is the global technical boundary + that converts caught failures to `AppError`; hooks/components do not parse + raw exceptions or render arbitrary `.message` values. +- A feature's generated OpenAPI type is the contract source when one exists. + Do not hand-maintain a shadow DTO for the same wire shape. + +## TypeScript + +- `strict`, `exactOptionalPropertyTypes`, `noUncheckedIndexedAccess`, and + `erasableSyntaxOnly` stay enabled. +- Never use `any`. Narrow `unknown` at boundaries. +- Declare constructor fields explicitly; parameter-property shorthand is not + allowed. +- Guard optional assignments instead of assigning a possibly-`undefined` + value directly. + +## UI and presentation + +- Use the existing shadcn/ui primitives and shared composites before creating + new ones. Do not introduce a competing design system. +- Use semantic color tokens, not raw Tailwind palette colors. Support light and + dark themes and a 375 px viewport. +- All user-facing and assistive text is Brazilian Portuguese. +- Interactive elements need accessible names and keyboard operation. Add + `jest-axe` coverage to new or materially changed routed pages/forms. +- Pages are composition shells over focused controller hooks. Extract a local + component for a distinct concern; promote it to `shared/` only after a second + genuinely identical cross-feature use. Generic entity-agnostic CRUD pages are + prohibited. +- Comments default to zero. Keep a short comment only for a non-obvious + security default, concurrency guard, library/browser quirk, or necessary lint + suppression. Architectural rationale belongs in an ADR. + +## Testing + +- Domain: pure tests, no mocks. +- Application: hand-written repository fakes returning `Result` values. +- Infrastructure: MSW at the HTTP boundary; every request has a handler and + `onUnhandledRequest: 'error'` remains enabled. +- Presentation: a typed fake `AppContainer`; add `AuthProvider` and router + wrappers only when the subject requires them. +- Do not replace expected `Result.failure` paths with rejected promises in + fakes. Rejections are reserved for tests of the global unexpected-failure + boundary. + +## Required gates ```bash npm run format:check --workspace=apps/admin-frontend npm run lint --workspace=apps/admin-frontend -npm run build --workspace=apps/admin-frontend # tsc catches what vitest/esbuild silently ignores -npm run test:coverage --workspace=apps/admin-frontend # coverage gate, see docs/QUALITY.md +npm run build --workspace=apps/admin-frontend +npm run test:coverage --workspace=apps/admin-frontend ``` -(`npm run test`/`build`/`lint` without the `--workspace` flag work the -same way from inside `apps/admin-frontend/` itself — both forms run the -identical scripts; use whichever matches your current directory.) Also -run the repo-wide governance checks from -[../../AGENTS.md](../../AGENTS.md). - ---- - -## Tech stack - -- Vite 8 + React 19 + TypeScript 5.9 (strict) — pinned below `^6.x`/`7.x` - until `openapi-typescript` (peer: `^5.x`) and `typescript-eslint` (peer: - `<6.1.0`) both support newer TypeScript majors -- Tailwind CSS v4, CSS-variable theming (`src/index.css`) — no `tailwind.config.js` -- shadcn/ui (Radix UI primitives, `src/components/ui/`) + `lucide-react` icons -- React Router 8 -- oidc-client-ts (Auth Code + PKCE) -- Vitest + React Testing Library + MSW - -## Design language - -The stock shadcn/ui "Nova" theme, `neutral` base color, unmodified — -no custom brand color, no custom shadows/radius. Light + dark mode, -mobile down to 375px. Full detail — component inventory, the -semantic-token table, icon conventions, mobile checklist — lives in -`agent-skills/agenza-frontend-feature` (read it before building any -page). The short version: - -- Use `src/components/ui/*` (shadcn/ui) exactly as the CLI generates - them. Don't add props, variants, or styling beyond what a page - genuinely needs right now — no speculative extensions. This directory - and `src/lib/utils.ts` stay at these top-level paths regardless of ADR - 009's feature layout — moving shadcn-generated files would mean - hand-editing their fixed `@/lib/utils` import convention. -- Style everything with semantic tokens (`bg-background`, `bg-card`, - `text-foreground`, `text-muted-foreground`, `border-border`, - `text-primary`, `text-destructive`) — never raw `slate-*`/`teal-*` - Tailwind palette classes. Tokens are what make dark mode work; raw - classes silently break it. -- A list of records is a `Table` (`src/components/ui/table.tsx`), not - stacked `Card`s. Create/edit forms open in a `Dialog` by default. - Categories maps the nested routes `/categories/new` and - `/categories/:id/edit` to the same editor `Dialog` over the still-mounted - `/categories` list. The dialog reuses one form and one controller hook for - creation and editing. Its table uses compact, record-labelled icon actions - on smartphones and text actions from `sm` upward (docs/adr/012). -- Build pages from `src/components/ui/` (shadcn/ui) and the shared - composites in `shared/presentation/components/` (`PageHeader`, - `StatusMessage`, `TextField`/`TextAreaField`, `CenteredScreen`, - `FullScreenSpinner`, `CollectionFeedback`, `DeleteConfirmationDialog`) - — don't hand-roll markup shadcn or an existing composite already covers. -- `CategoriesListPage`/`CategoryEditorDialog` (`features/catalog/presentation/categories/`) - is the reference implementation for a CRUD list+form page (table + dialog) - — see "Componentization" above for what "reference" means here. - `AdminLayout` (`app/layouts/`) is the reference for the page shell, - including its off-canvas mobile sidebar — new pages don't need their - own mobile nav handling. -- All user-facing text is Brazilian Portuguese (pt-BR) — labels, - messages, `aria-label`s, confirm prompts. See "Language" in - `agent-skills/agenza-frontend-feature`. -- Dark mode is controlled by `ThemeProvider` - (`shared/presentation/providers/`): defaults to the OS preference, an - explicit toggle (in `AdminLayout`'s sidebar footer) persists to - `localStorage` after that. Check every new page in both themes. - -## Environment - -Copy `.env.example` to `.env.local`. Never commit `.env.local`. - ---- - -## Current state (see docs/STATUS.md for full detail) - -- ✅ Tooling, Auth vertical, composition root, presentation shell -- ✅ `HttpClient` (`AuthenticatedHttpClient`) — REST features are unblocked -- ✅ shadcn/ui design system, dark mode, mobile-responsive `AdminLayout` -- ✅ Categories, Services (frontend + backend, search/filtering, pagination) -- Tags removed from the frontend (docs/adr/016); backend `Tag`/`/api/v1/tags` retained -- ✅ Feature-based physical layout (`app/`, `features/{auth,catalog}/`, `shared/` — ADR 009) -- 🔲 Clients → Appointments → Inbox → Dashboard → Settings +Also run the repo-wide governance commands from [../../AGENTS.md](../../AGENTS.md). diff --git a/apps/admin-frontend/CLAUDE.md b/apps/admin-frontend/CLAUDE.md index c331418..43c994c 100644 --- a/apps/admin-frontend/CLAUDE.md +++ b/apps/admin-frontend/CLAUDE.md @@ -1,7 +1 @@ @AGENTS.md - -See the repo-root [CLAUDE.md](../../CLAUDE.md) for Claude Code-specific -integration notes (skills, subagents, governance gates) — they apply here -unchanged; this file exists only so Claude Code loads -`apps/admin-frontend/AGENTS.md` automatically while working under -`apps/admin-frontend/`. diff --git a/apps/admin-frontend/docs/API.md b/apps/admin-frontend/docs/API.md index 1cce19f..e84462f 100644 --- a/apps/admin-frontend/docs/API.md +++ b/apps/admin-frontend/docs/API.md @@ -1,308 +1,62 @@ -# API Integration Guide +# Frontend API integration -How this admin panel talks to the backend REST API. Read this before -building any infrastructure repository. +This document records stable integration policy and which backend contracts the +frontend currently consumes. Exact schemas come from generated OpenAPI types and +backend source; do not copy field inventories here. ---- +## Request boundary -## Base URL +- `VITE_API_BASE_URL` supplies the base URL from local environment config. +- `AuthenticatedHttpClient` obtains one `GetRequestSession` snapshot per request + and attaches `Authorization: Bearer ` plus `X-Tenant-Id`. +- The backend verifies the tenant header against the authenticated `tenant_id` + claim. Feature repositories never accept, select, or attach tenant identity. +- A missing/invalid session, 401, network failure, timeout, non-success response, + or decoder rejection leaves the HTTP boundary as `Result.failure(AppError)`. + Repositories and presentation do not parse raw exceptions. -``` -VITE_API_BASE_URL=https://api.example.com -``` +## Contract sources -Set in `.env.local`. The `AuthenticatedHttpClient` prepends this to -every request path. Never hardcode URLs in repository files. +For services-service, the checked-in generated contract is: ---- +`src/features/catalog/infrastructure/generated/services-api.d.ts` -## Authentication +It is generated from the live OpenAPI document and verified in CI. Use the +generated schema for request/response types, the backend controller/command/ +response for intent, and the canonical API-contract-review skill to detect +drift. Never edit the generated file or create a hand-written shadow DTO. -Every request to the backend API requires: +A feature-local decoder still validates `unknown` runtime payloads. Static +TypeScript types do not make external JSON trustworthy. -``` -Authorization: Bearer -``` +## Error contract -The access token comes from `oidc-client-ts` via `OidcAuthRepository` -(`features/auth/infrastructure/`). `AuthenticatedHttpClient` -(`shared/infrastructure/http/`) reads the token and tenant id together, -from a single `GetRequestSession` call per request -(`shared/application/RequestSession.ts`) — never two independent reads, -so they can't end up from different moments of a session transition: +Backend failures use RFC 7807 Problem Details with a stable machine-readable +`code`. Validation errors may also expose structured per-field errors. +`AuthenticatedHttpClient` converts these into `AppError`; forms map structured +field/code values through their feature-local maps and use a curated global +fallback. User-facing code never parses a free-text backend message. -```typescript -export interface RequestSession { - readonly accessToken: string - readonly tenantId: string | null -} -export type GetRequestSession = () => Promise -``` +## Current consumption -If `getRequestSession()` returns `null`, the session has expired — throw -an `UnauthenticatedError` rather than making a request without one. +| Backend resource | Frontend state | +| ---------------------------------------------- | ----------------------------------------------------------------------------- | +| Categories | Implemented in Catalog; collection and by-id operations consumed | +| Services | Backend contract generated; frontend route is currently a stub | +| Tags | Backend API retained; intentionally not modeled or surfaced in frontend | +| Clients, Appointments, Conversations, Settings | No confirmed frontend vertical; inspect backend/OpenAPI before implementation | ---- +`docs/STATUS.md` is the source for feature progress. The generated contract may +contain resources the current UI does not expose. -## Tenant scoping +## Adding or changing integration -The client sends the tenant id explicitly in the `X-Tenant-Id` header on -every request (`AuthenticatedHttpClient` attaches it automatically from -the current session's `user.tenant.id`) — the backend verifies it -against the `tenant_id` claim inside the JWT access token and rejects -the request with `403` on any mismatch or if the header is missing -(docs/adr/0006 in the backend repo). The bearer token alone is no longer -sufficient; the header is required by default for every tenant-scoped -endpoint. - -This means: - -- Repository methods still take `TenantContext` as first param (structural - enforcement in application layer) -- `AuthenticatedHttpClient` attaches `X-Tenant-Id` whenever the same - per-request session read returned a tenant id (omitted only for - pre-session calls, e.g. before login) — individual repositories never - set this header themselves - ---- - -## Error shape - -**Confirmed** (services-service, ASP.NET Core): errors are RFC 7807 -Problem Details, always carrying a machine-readable `code` -(docs/adr/0012) — a plain conflict/not-found/forbidden/business error: - -```json -{ - "type": "https://tools.ietf.org/html/rfc9110#section-15.5.1", - "title": "Já existe uma categoria chamada 'Massagens'.", - "status": 409, - "code": "Category.DuplicateName" -} -``` - -A validation error (400) additionally carries a per-field `errors` map, -keyed by the backend's PascalCase property name, each entry an array of -`{code, message}`: - -```json -{ - "type": "https://agenza/errors/validation", - "title": "Ocorreram erros de validação.", - "status": 400, - "code": "Validation.Failed", - "errors": { - "Name": [ - { "code": "Service.NameTooLong", "message": "O nome deve possuir no máximo 80 caracteres." } - ] - } -} -``` - -HTTP status codes: - -- `400` — validation error (bad request body) -- `401` — unauthenticated (expired/missing token) -- `403` — forbidden (authenticated but not allowed) -- `404` — resource not found -- `409` — conflict (e.g. duplicate) -- `500` — server error - -`shared/infrastructure/http/ProblemDetails.ts` defines the typed contract -(`ProblemDetails`, `FieldError`) and `parseProblemDetails`, a safe runtime -parser (no `any`, no sniffing error kind from message text). Infrastructure -first builds an `ApiError` (`status`, `message` from `title` falling back -to `detail`, `details: ProblemDetails | undefined`) and immediately -converts it to an `AppError` (`shared/application/AppError.ts`) before it -leaves `AuthenticatedHttpClient` — `ApiError`/`ProblemDetails` never cross -into application or presentation (docs/adr/007). -`shared/presentation/forms/serverFormError.ts`'s `mapApiErrorToForm` turns -a caught `AppError` into field-level messages a form applies via -react-hook-form's `setError`, reading `AppError.rawFieldErrors`/ -`backendCode` — each form (`ServiceForm`/`CategoryForm`) exports -its own backend-property → field-name map (e.g. `DurationMinutes` → -`durationMinutes`) and a conflict-`code` → field map (e.g. -`Service.DuplicateName` → `name`). - ---- - -## Pagination - -**Confirmed** (Services, the first paginated resource): offset-based, -`PagedResult` — `{ items: T[], totalCount: number, page: number, pageSize: number }` -(see the Services section below). Any future paginated resource -(Appointments, Clients) should follow this same field naming unless its -real spec says otherwise — don't invent a different shape. - ---- - -## Resource endpoints (fill in as each vertical is built) - -### Tags - -Removed from the frontend — see `docs/adr/016-remove-tags-frontend.md`. -The backend still serves `/api/v1/tags` (`TagDto` with `id`/`name`/ -`color`/`description`, an 8-color fixed palette, `409 Tag.InUse` on -in-use delete) and `ServiceDto` still embeds a `tags`/`tagIds` field (see -Services below) — this app just no longer builds against any of it. - -### Categories - -Served by **services-service** (`VITE_API_BASE_URL`). Tenant scope comes -from the `X-Tenant-Id` header, verified against the JWT's `tenant_id` -claim. Routes are versioned (`Asp.Versioning.Mvc`, docs/adr/0005) — -omitting the segment falls back to v1, but the frontend always sends it -explicitly. - -| Method | Path | Success | -| -------- | ------------------------- | ----------------------------------------------------------------------- | -| `GET` | `/api/v1/categories` | `200` — `CategoryDto[]` | -| `GET` | `/api/v1/categories/{id}` | `200` — `CategoryDto`, `404` if not found (tenant-scoped, docs/adr/013) | -| `POST` | `/api/v1/categories` | `201` — created `CategoryDto` | -| `PUT` | `/api/v1/categories/{id}` | `200` — updated `CategoryDto` | -| `DELETE` | `/api/v1/categories/{id}` | `204` — no body | - -`GET` (collection) accepts an optional `search` query param -(case-insensitive name match), e.g. `GET /api/v1/categories?search=massa`. - -`DELETE` fails with `409` (`Category.InUse`) if the category is still -referenced by one or more Services. - -`CategoryDto`: - -```json -{ - "id": "3f2b6a10-9c3e-4a1e-8b0a-2a1c3d4e5f60", - "name": "Massagens" -} -``` - -Request body for `POST`/`PUT` is the same shape minus `id`: `{ "name": "Massagens" }`. - -Validation rules (server-enforced, mirror them client-side): - -- `name`: required, trimmed, non-empty, **unique per tenant** - (case-insensitive) → violations: `400` (shape) / `409` (duplicate, - `Category.DuplicateName`) -- Unknown `{id}` within the tenant → `404` - -### Services - -Served by **services-service** (`VITE_API_BASE_URL`). Tenant scope comes -from the `X-Tenant-Id` header, verified against the JWT's `tenant_id` -claim. Routes are versioned (`Asp.Versioning.Mvc`, docs/adr/0005) — -omitting the segment falls back to v1, but the frontend always sends it -explicitly. - -| Method | Path | Success | -| -------- | ----------------------- | --------------------------------- | -| `GET` | `/api/v1/services` | `200` — `PagedResult` | -| `POST` | `/api/v1/services` | `201` — created `ServiceDto` | -| `PUT` | `/api/v1/services/{id}` | `200` — updated `ServiceDto` | -| `DELETE` | `/api/v1/services/{id}` | `204` — no body | - -`GET` accepts `page` (1-based, default `1`) and `pageSize` (default `20`, -max `100`) query params, e.g. `GET /api/v1/services?page=2&pageSize=20`. -It also accepts optional `search` (case-insensitive name match), -`categoryId`, and `tagId` filters, e.g. -`GET /api/v1/services?search=corte&categoryId={id}&tagId={id}`. -Response envelope (`PagedResult`): - -```json -{ - "items": [/* ServiceDto[] */], - "totalCount": 45, - "page": 2, - "pageSize": 20 -} -``` - -`TagSummaryDto` (embedded on a `ServiceDto`, a slice of the full Tag) — -still part of the real backend contract even though the frontend Tags -vertical was removed (docs/adr/016-remove-tags-frontend.md); a future -Services UI needs to decide how to handle it: - -```json -{ "id": "0b6e5b3c-8f4e-4a52-9d0e-1c2a3b4c5d6e", "name": "VIP", "color": "#0d9488" } -``` - -`ServiceDto`: - -```json -{ - "id": "7a1b2c3d-4e5f-4061-9a2b-3c4d5e6f7081", - "code": 1001, - "name": "Massagem relaxante", - "description": "Uma massagem relaxante de corpo inteiro", - "durationMinutes": 60, - "minDurationMinutes": 30, - "maxDurationMinutes": 90, - "price": 150, - "maxDiscountPercentage": 10, - "categoryId": "3f2b6a10-9c3e-4a1e-8b0a-2a1c3d4e5f60", - "categoryName": "Massagens", - "tags": [{ "id": "0b6e5b3c-8f4e-4a52-9d0e-1c2a3b4c5d6e", "name": "VIP", "color": "#0d9488" }] -} -``` - -`description`, `categoryId`, and `categoryName` are `null` when unset. -`code` is server-assigned and immutable — never sent in a request body. - -Request body for `POST`/`PUT` (`PUT` omits `code`, which never changes): - -```json -{ - "name": "Massagem relaxante", - "description": "Uma massagem relaxante de corpo inteiro", - "durationMinutes": 60, - "minDurationMinutes": 30, - "maxDurationMinutes": 90, - "price": 150, - "maxDiscountPercentage": 10, - "categoryId": "3f2b6a10-9c3e-4a1e-8b0a-2a1c3d4e5f60", - "tagIds": ["0b6e5b3c-8f4e-4a52-9d0e-1c2a3b4c5d6e"] -} -``` - -`description` and `categoryId` are optional (`string | null`); `tagIds` -is optional (defaults to an empty list server-side if omitted). - -Validation rules (server-enforced, mirror them client-side): - -- `name`: required, trimmed, non-empty, **unique per tenant** - (case-insensitive) → violations: `400` (shape) / `409` (duplicate, - `Service.DuplicateName`) -- `1 <= minDurationMinutes <= durationMinutes <= maxDurationMinutes <= 1440` → `400` -- `0 <= maxDiscountPercentage <= 100` → `400` -- `price >= 0` → `400` -- `categoryId`, if set, must reference a Category owned by the same tenant → `404` (`Category.NotFound`) -- `tagIds`, if set, must each reference a Tag owned by the same tenant → `404` (`Tag.NotFound`) -- Unknown `{id}` within the tenant → `404` - -### Appointments - -> Spec not yet received. Do not implement until provided by project owner. - -### Clients - -> Spec not yet received. Do not implement until provided by project owner. - -### Conversations - -> Spec not yet received. Do not implement until provided by project owner. - -### Business Settings - -> Spec not yet received. Do not implement until provided by project owner. - ---- - -## How to add a new resource - -1. Get the spec from the project owner (do not invent endpoint shapes) -2. Read `.skills/admin-api-contract/SKILL.md` -3. Define the DTO interface in the mapper file -4. Write mapper tests first -5. Write MSW handlers that match the real endpoint paths -6. Implement the repository -7. Update this doc with the confirmed endpoint shapes +1. Inspect generated types, backend source, tests, and the ADR index before + asking for missing information. +2. If the backend contract changed, update the backend first and regenerate; do + not hand-edit generated TypeScript. +3. Add/update decoder and mapper tests for success, malformed data, and domain + validation failures. +4. Add/update repository tests through MSW using the real HTTP boundary. +5. Update feature status only when usable behavior changed; update this document + only when stable integration policy or consumption changed. diff --git a/apps/admin-frontend/docs/DECISIONS.md b/apps/admin-frontend/docs/DECISIONS.md deleted file mode 100644 index 5040203..0000000 --- a/apps/admin-frontend/docs/DECISIONS.md +++ /dev/null @@ -1,339 +0,0 @@ -# Project Decisions Log - -Decisions made during the initial build that agents should not relitigate -without explicit instruction from the project owner. Each entry has a -reason — if the reason no longer applies, the decision can be revisited. - ---- - -## TypeScript configuration - -### `erasableSyntaxOnly: true` - -**Decision:** Keep the Vite 8 default. -**Reason:** Forces all classes to use explicit field declarations instead -of constructor parameter shorthand. This is more verbose but more -explicit — aligns with Clean Code principles. Applies project-wide -including use cases, repositories, and test helpers. -**Impact:** Every class constructor must declare fields explicitly and -assign in the body. No `constructor(private readonly x: T) {}`. - -### `exactOptionalPropertyTypes: true` - -**Decision:** Keep it despite the extra friction. -**Reason:** The domain models auth session data and tenant context where -"field absent" and "field explicitly undefined" are meaningfully different -states. Caught real bugs during the Auth build (Session optional fields, -User email/name assignment). -**Impact:** Optional field assignment requires conditional pattern: -`if (value !== undefined) { this.field = value }`. Never assign -`this.field = maybeUndefined` directly. - -### `noUncheckedIndexedAccess: true` - -**Decision:** Keep it. -**Reason:** Direct motivation: `oidcUser.profile['tenant_id']` resolves -to `unknown`, not `string`. The runtime guard `typeof x === 'string'` is -the only thing that's safe here — the compiler confirms this is necessary. - ---- - -## Authentication - -### OIDC library: `oidc-client-ts` - -**Decision:** Use `oidc-client-ts` as the infrastructure-layer OIDC adapter. -**Reason:** Framework-agnostic (not React-specific), maintained standard -for Auth Code + PKCE flows, full TypeScript types. - -### `automaticSilentRenew: false` - -**Decision:** Disabled. Silent renewal is handled explicitly inside -`OidcAuthRepository.getCurrentSession()`. -**Reason:** Event-driven background renewal is invisible to callers — -they can't observe failure, can't trigger logout on failure, and can't -control timing. Explicit renewal inside `getCurrentSession()` gives the -application full control: try renewal, clear session on failure, return -`null` so callers redirect to login. -**Impact:** A token with at most 60 seconds remaining is renewed before -use. Parallel session reads share the same in-flight renewal, avoiding -refresh-token rotation races. If renewal fails, the stale OIDC user is -removed and the protected route sends the user to login. - -### Restore the interrupted page after login - -**Decision:** Carry pathname, query, and fragment through the OIDC -application `state`, then restore that route only after committing the new -authenticated tenant context. -**Reason:** Expiration or a 401 should interrupt the user's work, not -discard their navigation context. The application-layer validator accepts -only internal non-auth-entry paths; unsafe or missing values fall back to -`/dashboard`. - -### Preserve theme through the OIDC transition - -**Decision:** Pass the current `light` or `dark` theme as an OIDC -authorization extension parameter. The identity-service validates and -applies it before its login stylesheet loads, while its own accessible -toggle persists an explicit preference on the identity origin. That explicit -choice takes precedence on later visits; without one, the frontend request -and then the operating-system preference are used. -**Reason:** The credential page is a separate application and origin, so it -cannot read the React application's local storage. An explicit, -allowlisted parameter preserves visual continuity without coupling the two -applications' storage. - -### OIDC failures leave infrastructure as `AuthFlowError` - -**Decision:** `OidcAuthRepository` classifies login-start and callback -failures into `AuthFlowError`, an `AppError` with a stable `AUTH_*` support -code and curated pt-BR message. `HandleAuthCallback.execute()` still does not -reclassify errors. -**Reason:** The real IdentityServer and `oidc-client-ts` failure shapes are -now known. Classification at the adapter boundary gives presentation -actionable feedback without exposing raw protocol or network details. - -### `tenant_id` claim name - -**Decision:** Use `tenant_id` as the claim name in IdentityServer tokens. -**Location:** Only referenced in `features/auth/infrastructure/oidcUserToSessionMapper.ts`. -**Revisit when:** A real token can be decoded and the actual claim name confirmed. - -### `email` and `name` on `User` entity - -**Decision:** Included as optional fields, marked as unverified assumptions. -**Reason:** Standard OIDC claims likely present, but actual IdentityServer -configuration not yet confirmed. -**Revisit when:** A real token is available — these may not exist, may be -named differently, or may require specific scopes. - ---- - -## Application layer - -### No server-state library (no TanStack Query, no SWR) - -**Decision:** Use plain `useAsync` hook + repository calls. -**Reason:** Explicit project constraint from the brief. Most "state" here -is server data scoped by tenant — no complex shared client state. -`useAsync` provides the consistent loading/data/error pattern across all -feature hooks without the overhead of a full library. - -### `useAsync` with `immediate` flag - -**Decision:** `useAsync` accepts `{ immediate: boolean }` and defaults -to `true` (auto-run on mount). -**Reason:** Most uses are "fetch on mount and show the result." The -`immediate: false` option covers action-style calls (e.g. mutations) -that should only run on explicit user interaction. - -### `react-hooks/set-state-in-effect` suppression in `useAsync` (no longer needed) - -**Decision (superseded):** The `void execute()` call inside `useEffect` was -suppressed with an eslint-disable comment. -**Reason:** This was a documented false positive. The rule traced async -call graphs and flagged setState calls that happen after `await` — but -those calls are genuinely async and not synchronous within the effect. -React's own docs show the same pattern as recommended. See open issue -react/react#34743. -**Removed when:** `useAsync`'s internal state was consolidated from three -separate `useState` calls (`status`/`data`/`error`) into one -`useState>` (the discriminated-union refactor - see -`AsyncState` in `useAsync.ts`). The rule no longer traces a violation -through the restructured `execute()`, and ESLint flags the disable comment -itself as unused. If this lint error reappears anywhere in the codebase, -treat it as a real violation before reaching for a suppression again. - ---- - -## Infrastructure - -### MSW `onUnhandledRequest: 'error'` - -**Decision:** Any unhandled HTTP request in tests fails loudly. -**Reason:** Silent failures (hanging promises, undefined returns) are -worse than loud ones. If a repository makes a call without a registered -handler, we want to know immediately, not debug a flaky timeout. - -### No real network calls in tests — ever - -**Decision:** `oidc-client-ts`'s `UserManager` is mocked with a -hand-written fake in infrastructure tests, not with MSW (MSW intercepts -`fetch`, but `UserManager` does its own internal fetch management). -**Pattern:** Infrastructure tests for `OidcAuthRepository` use -`createFakeUserManager()` with `vi.fn()` methods. - ---- - -## Presentation - -### Context-based DI (not module-level singletons) - -**Decision:** `app/main.tsx` (the composition root) calls `createAppContainer()` -exactly once and passes the result into `AppProviders` as a `container` -prop, which distributes it via React context (`AppContainerContext`). -**Reason:** Singleton modules make testing harder (shared state between -tests). Context-based DI means each test can provide its own fake -container without module-level mocking. - -### `ProtectedRoute` handles loading state explicitly - -**Decision:** While `useAuth` status is `'loading'`, `ProtectedRoute` -renders a spinner — it does not redirect to `/login`. -**Reason:** Prevents a race condition where an authenticated user gets -briefly redirected to login before `getCurrentSession()` resolves. - -### Design language - -**Decision:** The stock shadcn/ui "Nova" theme with the `neutral` base -color, generated by `npx shadcn@latest init` and left unmodified — no -custom brand color, no custom shadows, no custom radius. `src/index.css` -holds only what the CLI wrote (plus the `@custom-variant dark` wiring -for `ThemeProvider`). See `agent-skills/agenza-frontend-feature/SKILL.md` -for the token table every page must use. -**Reason:** An earlier pass built a custom "soft/friendly SaaS" palette -(teal-600 brand accent, warm surfaces, hand-tuned shadows). The project -owner reviewed the result and asked for the opposite: adopt the -library's own default look as the system's identity, minimize custom -styling, and keep the codebase as simple as possible. Regenerating via -the CLI (rather than hand-picking colors) also guarantees the tokens -match what `npx shadcn@latest add ` will assume for any -future component. -**Impact:** Never hardcode a raw palette class (`bg-slate-50`, -`text-teal-700`, etc.) in a page — always use the semantic token -(`bg-background`, `text-primary`). Don't reintroduce a custom brand -color or shadow without an explicit request — the default look _is_ the -requirement, not a placeholder for one. -**Revisit when:** the project owner asks for a brand color again. Until -then, re-running `npx shadcn@latest init` should reproduce -`src/index.css` byte-for-byte (see ADR 005 for the exact command). - -### UI component library: shadcn/ui - -**Decision:** Build all interactive components (`Button`, `Card`, -`Input`, `Textarea`, `Label`, `Spinner`, …) from shadcn/ui — Radix UI -primitives copied into `src/components/ui/` via the shadcn CLI, styled -with Tailwind, not installed as an opaque npm dependency. -**Reason:** See `docs/adr/005-shadcn-ui-component-library.md` for the -full comparison against Mantine/Chakra/MUI/Ant Design. Short version: it -was the only option with zero competing styling system alongside -Tailwind, and copying source in means full ownership — no waiting on -upstream for a fix, no version-bump breakage. -**Impact:** Adding a new primitive is `npx shadcn@latest add -c apps/admin-frontend`, -not `npm install`. Check the result against -`exactOptionalPropertyTypes: true` after generating — some registry -files need a fix (`dropdown-menu.tsx` didn't and was removed instead). -Keep generated files close to the registry output; only add a prop or -variant when a page genuinely needs it now, not speculatively. - -### List pattern: `Table`, not stacked `Card`s - -**Decision:** A page listing records (`TagsPage` today; `Services`, -`Clients`, etc. later) renders them in a `Table` -(`src/components/ui/table.tsx`), one row per record, actions in the -last column. -**Reason:** The project owner asked for "a lista estilo tabela" — -explicitly a table-style list — when the earlier stacked-`Card`-per-row -layout felt heavier than needed for records with a handful of fields -each. -**Impact:** `Table`'s own container already scrolls horizontally -(`data-slot="table-container"`, `overflow-x-auto`), which is what keeps -a wide table usable at 375px — don't add a second scroll wrapper around -it. - -### Form pattern: one URL-driven `Dialog` editor for Categories - -**Decision:** A create/edit form opens in a `Dialog` -(`src/components/ui/dialog.tsx`) over the list by default. Categories maps -the nested routes `/categories/new` and `/categories/:id/edit` to the same -`CategoryEditorDialog`, which renders the same `CategoryForm` for both -operations. -**Reason:** Both workflows edit the same single-field shape. Separate route -components, pages, and hooks duplicated lifecycle and error-handling logic -without adding a distinct interaction. The URLs still provide direct -navigation and predictable browser-history behavior while the list remains -mounted. -**Impact:** `TagsPage` remains the reference for the modal CRUD pattern. -Categories follows docs/adr/012 for the routed-modal shape; per -docs/adr/013, `useCategoryEditor` resolves an edit id by calling -`GET /api/v1/categories/{id}` directly (tenant-scoped) instead of reading -the list's state through outlet context, and the list refetches when -navigation returns to `/categories`. - -### Destructive-action confirmation: `AlertDialog`, not `window.confirm` - -**Decision:** Confirming a delete (or any other destructive action) -uses shadcn/ui's `AlertDialog` (`src/components/ui/alert-dialog.tsx`), -not the browser's native `window.confirm`. -**Reason:** The project owner asked for "um componente mais bonito, de -preferência da lib de UI" (a nicer component, preferably from the UI -lib) after `window.confirm` shipped as the original Tags delete flow — -a native browser dialog can't be styled or translated consistently with -the rest of the app and can't be driven from automated tooling -(confirmed as a real limitation when reviewing a live test run). -**Impact:** Same one-instance-per-page pattern as the create/edit -`Dialog`: a `deleteTarget` state opens the `AlertDialog`, naming the -record in `AlertDialogDescription`. `AlertDialogAction` closes itself -by default on click — `event.preventDefault()` inside its `onClick` -keeps it open until the async delete actually resolves, so a failure -can show an error inline (`StatusMessage`) instead of silently closing. -Extracted into `shared/presentation/components/DeleteConfirmationDialog.tsx` - -- `shared/presentation/hooks/useDeleteConfirmation.ts` once Tags, - Categories, and Services all needed the identical shape — see either - feature's `*DeleteDialog.tsx` for the thin, entity-specific wrapper. - -### `CreatableSingleSelect`/`CreatableMultiSelect` stay separate components - -**Decision:** Evaluated extracting a shared `CreatableSelectPanel` -internal component for the popover content (loading/error/list/create- -button) both selects render. Kept them as two separate components instead. -**Reason:** The two differ in ways that are load-bearing, not incidental: -single-select selects-and-closes with an optional leading "none" item; -multi-select toggles-and-stays-open, shows a per-item color dot, and -renders removable chips below the trigger. Unifying the list/loading/ -error rendering into one generic component would need `isChecked`/ -`onSelectItem`/`renderLeadingItem`/`ariaMultiselectable` parameters -threaded through both call sites — ending up with comparable complexity -to the ~50 duplicated lines it would save, while making both call sites -harder to read. -**Impact:** The loading/error-state JSX blocks are genuinely identical -between the two files - if a third genuinely-identical consumer appears, -re-evaluate extracting just that block, not the whole panel. - -### Language: pt-BR user-facing text - -**Decision:** Every string the app shows or announces to a user — -headings, button/link labels, form labels and hints, `aria-label`s, -`window.confirm` prompts, `StatusMessage` text, error-message fallbacks -— is written in Brazilian Portuguese (pt-BR). Code (identifiers, -comments, commit messages, this doc) stays in English as before; only -the rendered/announced surface changed. -**Reason:** Explicit project-owner instruction: "todas as mensagens, -alertas devem estar em portugues BR." -**Impact:** `TagsPage`/`TagForm`/`AdminLayout`/`LoginPage`/ -`CallbackPage`/`PlaceholderPage` were translated in full, including the -6 stub-page `title` props and the sidebar's `NAV_ITEMS`. Two shadcn -primitives were also touched — `components/ui/spinner.tsx`'s -`aria-label="Loading"` and `components/ui/dialog.tsx`'s sr-only "Close" -text — since a literal, currently-rendered accessibility label counts -as a real, current need, not the speculative extension -`[[feedback-shadcn-minimal-customization]]` warns against. -Reachable `DomainError`/`Error` messages were translated too -(`domain/entities/Tag.ts`'s four validation messages, -`infrastructure/http/UnauthenticatedError.ts`, and the three "no -tenant context" guards in `presentation/hooks/useTags.ts`) — traced by -following each error class to what catches it, confirming whether -`.message` can actually reach a `StatusMessage`. Left untranslated: -`InvalidSessionError`/`InvalidTenantError`/`InvalidUserError`/ -`MissingTenantClaimError`, `oidcUserToSessionMapper`'s `expires_at` -guard, the `useAppContainer`/`useTheme` "must be used within a -Provider" guards, `container.ts`'s missing-env-var guard, and -`main.tsx`'s missing-root-element guard — all are startup/programmer- -error assertions that die before any route or `StatusMessage` exists in -a correctly wired app, not real user-facing messages. Revisit only if -one of those paths becomes genuinely reachable (e.g. `CallbackPage` -starts displaying `error.message` directly). -**Impact on future work:** every new feature vertical's page component -is pt-BR from the start — see the "Language" section in -`agent-skills/agenza-frontend-feature/SKILL.md`. diff --git a/apps/admin-frontend/docs/STATUS.md b/apps/admin-frontend/docs/STATUS.md index 3ff9a4a..4fc85e6 100644 --- a/apps/admin-frontend/docs/STATUS.md +++ b/apps/admin-frontend/docs/STATUS.md @@ -1,307 +1,51 @@ -# Feature Status - -Machine-readable current state of all feature verticals. Update this -file whenever a feature moves from one state to another. - -Agents: read this before starting any work to understand what exists, -what's blocked, and what order to build things in. - ---- - -## Status legend - -- `done` — fully implemented, tested, lint clean, committed -- `stub` — route exists, page renders "under construction", no logic built -- `blocked` — cannot start until a dependency is resolved -- `in-progress` — currently being built (update when starting work) - ---- - -## Infrastructure - -| Piece | Status | Notes | -| -------------------------------------------------- | --------- | -------------------------------------------------------------------------------------------------- | -| TypeScript strict config | `done` | | -| ESLint + Prettier | `done` | | -| Vitest + RTL + MSW | `done` | | -| Local Git hooks | `removed` | Quality gates run explicitly during development and in required CI checks | -| `HttpClient` interface + `AuthenticatedHttpClient` | `done` | Single per-request session read (token + tenant id together); converts every failure to `AppError` | -| MSW handlers (auth) | `stub` | Auth uses OIDC not REST — no handlers needed | -| MSW handlers (Categories/Services) | `done` | `categoryHandlers.ts`/`serviceHandlers.ts` | -| MSW handlers (remaining REST features) | `stub` | Add per-feature as specs arrive (Clients, Appointments, Inbox, Settings) | -| shadcn/ui design system (`src/components/ui/`) | `done` | Radix-based, stock "Nova"/neutral theme, unmodified; see ADR 005 | -| `ThemeProvider` / `useTheme` / `ThemeToggle` | `done` | Light/dark, defaults to OS preference, persists an override | - ---- - -## Auth vertical - -| Piece | Status | Notes | -| ----------------------------- | ------ | ---------------------------------------------------------------------------------------------------------------------- | -| `Tenant` value object | `done` | | -| `User` entity | `done` | email/name are unverified assumptions | -| `Session` entity | `done` | | -| `AuthRepository` interface | `done` | | -| `InitiateLogin` use case | `done` | | -| `HandleAuthCallback` use case | `done` | OIDC failures arrive as classified `AuthFlowError` values (see ADR 007) | -| `GetCurrentSession` use case | `done` | | -| `Logout` use case | `done` | | -| `mapOidcUserToSession` mapper | `done` | tenant_id claim name unverified | -| `OidcAuthRepository` | `done` | 60s single-flight renewal; rejects renewed user/tenant claim changes | -| `createUserManager` factory | `done` | env vars are placeholders | -| `createAppContainer` | `done` | | -| `AppProviders` | `done` | | -| `useAsync` hook | `done` | | -| `useAuth` hook | `done` | | -| `useAppContainer` hook | `done` | | -| `ProtectedRoute` | `done` | | -| `LoginPage` | `done` | automatic OIDC redirect with progress, classified recovery, and support codes | -| `CallbackPage` | `done` | restores the interrupted route; classified recovery and support codes | -| `AdminLayout` + sidebar | `done` | Collapsible icon rail (desktop, persisted) + off-canvas drawer (mobile, below `md`); theme toggle + sign-out in footer | -| Router | `done` | | - ---- - -## Feature verticals - -### Tags - -Removed from the frontend — see `docs/adr/016-remove-tags-frontend.md`. The -backend `Tag` domain entity and `/api/v1/tags` endpoints are intentionally -retained (project-owner decision), just no longer surfaced or consumed by -this app. - ---- - -### Services - -| Piece | Status | Notes | -| --------------------------------------------- | ------ | --------------------------------------------------------------------------------------------- | -| `Service` entity | `done` | Duration range + discount-cap invariants validated in `create()` | -| `ServiceRepository` interface | `done` | | -| Use cases (List, Create, Update, Delete) | `done` | | -| `ApiServiceRepository` + `serviceMapper` | `done` | | -| `useServices` hook | `done` | | -| `ServicesPage` + `ServiceForm` + nav entry | `done` | Table list, dialog create/edit form (category `Select`, tag toggle grid), delete with confirm | -| Backend (services-service `/api/v1/services`) | `done` | Search/filter/pagination added; see docs/adr/0012 for the latest validation/handler shape | - -**Dependency:** none structurally — depends on Categories for the create/edit -form's category picker. The backend `ServiceDto` still has `tags`/`tagIds` -(docs/API.md) since the backend Tag domain was kept; the frontend has no -Tag entity or picker anymore (docs/adr/016), so a tag-selection UI needs to -be designed when this form is actually built. - ---- - -### Categories - -| Piece | Status | Notes | -| ----------------------------------------------- | ------ | ------------------------------------------------------------------------------ | -| `Category` entity | `done` | | -| `CategoryRepository` interface | `done` | | -| Use cases (List, Create, Update, Delete) | `done` | | -| `ApiCategoryRepository` + `categoryMapper` | `done` | | -| `useCategories` hook | `done` | | -| Categories responsive list/editor + nav entry | `done` | Mobile-ready table; shared URL-driven create/edit modal; delete with confirm | -| Backend (services-service `/api/v1/categories`) | `done` | Search/filter added; see docs/adr/0012 for the latest validation/handler shape | - -**Dependency:** none. Referenced by Services (optional `categoryId`). - ---- - -### Clients - -| Piece | Status | Notes | -| --------------- | ------ | ----- | -| `Client` entity | `stub` | | -| Use cases | `stub` | | -| Infrastructure | `stub` | | -| `ClientsPage` | `stub` | | - -**Blocked on:** API spec (`HttpClient` already exists, not a blocker). -**Dependency:** None structurally, but Appointments history view will depend on Appointments. - ---- - -### Appointments - -| Piece | Status | Notes | -| -------------------- | ------ | ----- | -| `Appointment` entity | `stub` | | -| Use cases | `stub` | | -| Infrastructure | `stub` | | -| `AppointmentsPage` | `stub` | | - -**Blocked on:** API spec, Services (for service selection in create form) — `HttpClient` already exists, not a blocker. -**Dependency:** Services should be built first. - ---- - -### Dashboard - -| Piece | Status | Notes | -| --------------- | ------ | ----- | -| `DashboardPage` | `stub` | | - -**Blocked on:** API spec, Appointments (for today's overview), Conversations (for inbox summary). -**Dependency:** Build after Appointments and Inbox. - ---- - -### Inbox (Conversations) - -| Piece | Status | Notes | -| --------------------- | ------ | ----- | -| `Conversation` entity | `stub` | | -| `Message` entity | `stub` | | -| Use cases | `stub` | | -| Infrastructure | `stub` | | -| `InboxPage` | `stub` | | - -**Blocked on:** API spec (`HttpClient` already exists, not a blocker). Real-time requirement (polling vs WebSocket) TBD. -**Dependency:** Clients (for linking conversations to clients). - ---- - -### Settings - -| Piece | Status | Notes | -| ----------------- | ------ | ----- | -| `Business` entity | `stub` | | -| Use cases | `stub` | | -| Infrastructure | `stub` | | -| `SettingsPage` | `stub` | | - -**Blocked on:** API spec (`HttpClient` already exists, not a blocker). -**Dependency:** None — can be built any time. - ---- - -## Recommended build order - -``` -1. HttpClient (unblocks all REST features) [done] -2. Categories (no dependencies, simplest CRUD) [done] -3. Services (depends on Categories for its form pickers) [done] -4. Clients (simple CRUD) -5. Appointments (depends on Services for create form) -6. Inbox (depends on Clients) -7. Dashboard (depends on Appointments + Inbox for overview data) -8. Settings (independent, can be done any time after HttpClient) -``` - -Tags was previously vertical #2 here (first REST vertical, no dependencies) -but was removed from the frontend — see `docs/adr/016-remove-tags-frontend.md`. - ---- - -## Test counts - -| Session | Tests added | Total | -| ----------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- | ------------------------------------------ | -| Initial setup | 0 | 0 | -| Domain (Tenant, User, Session) | 17 | 17 | -| Application (4 use cases) | 7 | 24 | -| Infrastructure (mapper + OidcAuthRepository) | 13 | 37 | -| Composition + hooks (useAsync, useAuth, useAppContainer) | 10 | 47 | -| Presentation (ProtectedRoute, LoginPage) | 6 | 53 | -| HttpClient (AuthenticatedHttpClient via MSW) | 6 | 59 | -| Coverage hardening (CallbackPage, AdminLayout, container, AppProviders, createUserManager) | 14 | 73 | -| Tags vertical + UI system (shadcn/ui migration, dark mode, mobile-responsive `AdminLayout`) | not logged incrementally | 116 (verified via `npm run test`) | -| UI reset to stock shadcn theme + Tags list/form → `Table`/`Dialog` | 0 (existing tests updated, none added) | 116 (verified via `npm run test`) | -| Categories + Services verticals (entities, use cases, repos, mappers, hooks, pages) | 75 | 191 (verified via `npm run test`) | -| Auth/tenant safety rewrite, error taxonomy, AppContainer facade split, ServicesPage decomposition (docs/adr/006-008) | 211 | 402 (verified via `npm run test`) | -| Lint hardening to zero warnings, `test:coverage` thresholds raised (branches/functions added), Playwright E2E suite added | 24 | 426 (verified via `npm run test:coverage`) | -| jest-axe broadened from TagForm to LoginPage and ServicesPage's create-service dialog | 2 | 428 (verified via `npm run test:coverage`) | -| Architectural refactor: atomic session snapshot, `useAsync` simplification, Services/Tags/Categories decomposition, ADR 009 physical move | 23 | 451 (verified via `npm run test:coverage`) | -| Automatic OIDC transition, actionable auth feedback, and renewed-identity isolation | not logged incrementally | 550 (verified via `npm run test:coverage`) | - -Update the test count row whenever a feature vertical is completed. The -550 above is Vitest only — see "End-to-end tests" below for the separate -Playwright suite (9 specs), which isn't counted in this table or in the -coverage gate. - -**Architecture:** docs/adr/009's feature-based `features/{auth,catalog}` + -`app/` + `shared/` reorganization is executed and `Accepted` — new code -lands in that structure (see `agent-skills/agenza-frontend-feature` for -the current tree), not in a top-level `presentation/`/`application/`/ -`infrastructure/`/`domain/`/`composition/`. - ---- - -## End-to-end tests - -`e2e/` holds a Playwright suite (`npm run test:e2e`, `npm run test:e2e:ui` -for the interactive runner) that runs against the **production build** -(`vite build` + `vite preview`, wired as `playwright.config.ts`'s -`webServer`) rather than `vite dev` — several specs count exactly how many -times a mocked endpoint is hit, and React's StrictMode double-invokes -effects in development only, which would make those counts nondeterministic -against the dev server. - -Every spec mocks its own backend via `page.route()` and, where a signed-in -session is needed, writes an oidc-client-ts user record straight into -localStorage (`e2e/support/session.ts`) — no identity-service, -services-service, or Postgres needs to be running. Covered so far: - -- Unauthenticated access to a protected route or `/` automatically opens - the OIDC provider. -- The automatic login-transition screen explains the redirect, renders - correctly in dark mode, forwards the active theme to the OIDC provider, - and has no horizontal overflow at 375px. The identity credential page - applies that theme before paint and provides its own accessible, - persisted theme toggle. -- The authenticated shell: index → `/dashboard` redirect, sidebar - navigation, and logout (mocking the OIDC discovery document + end-session - redirect, not just a REST endpoint, so the real `OidcAuthRepository` runs - unmodified). - **Deliberately not duplicated here** (already covered at the unit level, - listed so the gap is explicit rather than silent): the OIDC callback's - idempotency under `StrictMode` (`CallbackPage.test.tsx`, - `HandleAuthCallback.test.ts`), tenant-switch races in `useAsync`/ - `useCreateInline` (their own dedicated test files), and cross-tenant/ - cross-session visual bleed (`TenantBoundary.test.tsx`). Categories/Services - CRUD aren't E2E-tested separately — they share the same `useAsync`/ - repository/mapper machinery already exercised at the unit level. A full - create → edit → delete E2E flow (previously `tags-crud.spec.ts`) and a - failed-refetch-retry flow (previously `tags-list-retry.spec.ts`) existed - for Tags and were removed along with the vertical - (docs/adr/016-remove-tags-frontend.md) — no other vertical has picked up - that full-CRUD E2E coverage yet. - -**Not yet wired into CI** (`.github/workflows/frontend-ci.yml`): doing so -would need `npx playwright install --with-deps chromium` added as a step -and `VITE_API_BASE_URL`/`VITE_OIDC_*` provided in the runner (today only -`.env.local`, which is gitignored, supplies them locally) — a reasonable -follow-up, deferred rather than added speculatively. - ---- - -## Bundle size baseline - -First recorded 2026-07-21; re-measured 2026-07-24 after the automatic -authentication transition and feedback work — captured from -`npm run build --workspace=apps/admin-frontend` (Vite 8, production build). - -| Chunk | Raw | Gzip | -| ------------------------------------------------------- | --------- | -------- | -| `index-*.js` (main entry) | 227.55 kB | 71.67 kB | -| `auth-*.js` (OIDC/auth route dependencies) | 229.03 kB | 69.80 kB | -| `DeleteConfirmationDialog-*.js` (shared table + dialog) | 105.65 kB | 31.59 kB | -| `ServicesPage-*.js` | 96.24 kB | 30.57 kB | -| `index-*.css` | 65.11 kB | 10.99 kB | - -All other route chunks (Categories pages and forms, stub pages) are -under 10 kB raw each. Vite now emits the OIDC/auth dependency graph as its -own shared chunk; `index` + `auth` remain approximately the same combined -size as the previous monolithic main entry. The feedback UI added no heavy -dependency. - -No pathological duplication was found (e.g. no repeated Radix/shadcn -tree across chunks), so no bundle-splitting work was done against this -baseline — only re-measure and revisit if a future change pushes a -number up materially. - -Update this table whenever a change is expected to move the numbers -meaningfully (a new heavy dependency, a new route, code-splitting -work) — not on every commit. +# Admin frontend status + +This is the only living feature-progress document for the admin frontend. +Implementation details come from the code and tests; versions come from +`package.json`. Do not copy this status into `AGENTS.md` or skills. + +Status meanings: + +- `done`: implemented and covered by the normal gates. +- `stub`: route/domain placeholder only; no usable vertical. +- `removed`: intentionally absent from the frontend. +- `blocked`: implementation needs an unresolved contract or business decision. + +## Platform + +| Capability | Status | Notes | +| ----------------------------------------- | ------ | ----------------------------------------- | +| Strict TypeScript, ESLint, Prettier | done | Config files are the source of truth | +| Vitest, RTL, MSW, coverage gate | done | `onUnhandledRequest: 'error'` | +| Authenticated `HttpClient` | done | Atomic session snapshot; `Result` errors | +| OIDC authentication and recovery | done | Auth feature owns the session | +| App container and grouped facades | done | Concrete construction only in composition | +| shadcn/ui, theme, responsive admin layout | done | Light/dark; mobile shell | +| Playwright smoke coverage | done | Runs against the production build in CI | +| Generated services-service OpenAPI types | done | Drift checked by CI | + +## Features + +| Feature | Status | Current boundary | +| -------------- | ------- | ---------------------------------------------------------------------- | +| Authentication | done | `src/features/auth/` | +| Categories | done | Full Catalog vertical with routed create/edit dialog | +| Services | stub | `/services` renders `src/app/pages/ServicesPage/ServicesPage.tsx` only | +| Tags | removed | Frontend removed by ADR 016; backend API intentionally remains | +| Clients | stub | Contract/vertical not implemented | +| Appointments | stub | Contract/vertical not implemented | +| Inbox | stub | Contract/vertical not implemented | +| Dashboard | stub | Presentation placeholder only | +| Settings | stub | Contract/vertical not implemented | + +## Recommended next order + +1. Confirm the next feature's backend/OpenAPI contract and business rules. +2. Build one complete vertical at a time rather than pre-creating domain or UI + layers for several placeholders. +3. Update this table in the same change that replaces a stub or removes a + feature. + +The product owner chooses which vertical is next. Historical line counts, test +counts, bundle sizes, and completed-task narratives are intentionally omitted; +CI artifacts and Git history are the source for those volatile facts. diff --git a/apps/admin-frontend/docs/adr/005-shadcn-ui-component-library.md b/apps/admin-frontend/docs/adr/005-shadcn-ui-component-library.md index f2c5896..04e4050 100644 --- a/apps/admin-frontend/docs/adr/005-shadcn-ui-component-library.md +++ b/apps/admin-frontend/docs/adr/005-shadcn-ui-component-library.md @@ -62,8 +62,8 @@ team building six more feature verticals. record lists this project has today; pairs with a separate library (e.g. TanStack Table) if a future vertical needs those behaviors. - The whole component set depends on the CSS-variable tokens in - `src/index.css` for both theming and dark mode — see the "Design - language" and "UI component library" entries in `docs/DECISIONS.md`. + `src/index.css` for both theming and dark mode. Current usage conventions + live in `apps/admin-frontend/AGENTS.md` and the frontend feature skill. To regenerate those tokens from scratch (e.g. after a manual edit drifted from the registry), run `npx shadcn@latest init -y -f -b radix -p nova --no-reinstall -c apps/admin-frontend` diff --git a/apps/admin-frontend/docs/adr/006-single-auth-source-and-tenant-boundary.md b/apps/admin-frontend/docs/adr/006-single-auth-source-and-tenant-boundary.md index 7a85f0c..00191be 100644 --- a/apps/admin-frontend/docs/adr/006-single-auth-source-and-tenant-boundary.md +++ b/apps/admin-frontend/docs/adr/006-single-auth-source-and-tenant-boundary.md @@ -61,7 +61,7 @@ multi-tenant, session-can-change nature: Reusing `useAsync` inside `AuthProvider` (rather than inventing a new state-management primitive) keeps the "no server-state library, no global -store" constraint from ADR 002/docs/DECISIONS.md — `useAsync`'s existing +store" constraint from ADR 002 — `useAsync`'s existing out-of-order/unmount guarding, extended with the resetKey/generation checks above, is sufficient once it's shared through one provider instead of duplicated per hook call. diff --git a/apps/admin-frontend/docs/adr/008-app-container-facade-split.md b/apps/admin-frontend/docs/adr/008-app-container-facade-split.md index c13ef5d..486b3a8 100644 --- a/apps/admin-frontend/docs/adr/008-app-container-facade-split.md +++ b/apps/admin-frontend/docs/adr/008-app-container-facade-split.md @@ -1,6 +1,7 @@ # ADR 008 — AppContainer facade split (auth/catalog), composition root as pure DI -**Status:** Accepted +**Status:** Accepted; facade-member typing amended 2026-08-03 after Catalog +removed pass-through use-case classes that owned no orchestration. ## Decision @@ -11,13 +12,11 @@ inside `createAppContainer()` and are never returned — presentation has no path to reach a repository or the HTTP client directly, by construction, not by convention. -2. **Each facade member's type is `Pick`**, not - the concrete class. `Pick` produces a plain structural type - it drops - the class's private-field nominal branding - so a hand-written object - literal (`{ execute: vi.fn(...) }`) satisfies the type directly. This is - what eliminates every `as unknown as AppContainer` cast across the test - suite: `src/test/fixtures/createFakeAppContainer.ts` returns a fully - real `AppContainer` value, typo-checked like any other object. +2. **Each facade member exposes a structural `execute` shape**, not a concrete + repository or class. Use `Pick` when a real use + case owns orchestration (Auth), and `{ execute: Repository['method'] }` when + the facade is intentionally a pure pass-through (Catalog). Both shapes let a + typed object literal satisfy test fakes without `as unknown as AppContainer`. 3. **`AppProviders` no longer constructs the container.** It takes one as a `container` prop and only wires it into `AppContainerContext`. The one call to `createAppContainer()` now lives in `main.tsx` - the diff --git a/apps/admin-frontend/docs/adr/009-feature-based-modularization.md b/apps/admin-frontend/docs/adr/009-feature-based-modularization.md index 3d94b11..dd86d44 100644 --- a/apps/admin-frontend/docs/adr/009-feature-based-modularization.md +++ b/apps/admin-frontend/docs/adr/009-feature-based-modularization.md @@ -1,7 +1,9 @@ # ADR 009 — Feature-based modularization (`features/`, `app/`, `shared/`) -**Status:** Accepted — executed 2026-07-23 (see "Execution" below for what -actually happened, including two deviations from the original plan). +**Status:** Accepted — executed 2026-07-23. The file inventory below is the +historical execution snapshot; ADR 016 later removed Tags and current +`docs/STATUS.md` records Services as a stub. The feature-boundary decision +remains current. ## Decision @@ -191,7 +193,7 @@ for review attention against unrelated behavioral changes. mirroring the existing `domain/`/`application/` blocks) and `scripts/architecture_guard.py` (a new check alongside `check_cross_page_imports`). Update - `agent-skills/agenza-frontend-feature` and + `.agents/skills/agenza-frontend-feature` and `apps/admin-frontend/AGENTS.md` to teach the new structure - at the time this ADR was drafted, the then-current versions still described and enforced the horizontal layout (since done — both now document the diff --git a/apps/admin-frontend/docs/adr/010-put-body-id-and-external-numeric-validation.md b/apps/admin-frontend/docs/adr/010-put-body-id-and-external-numeric-validation.md index a6fd82a..2baee41 100644 --- a/apps/admin-frontend/docs/adr/010-put-body-id-and-external-numeric-validation.md +++ b/apps/admin-frontend/docs/adr/010-put-body-id-and-external-numeric-validation.md @@ -1,6 +1,7 @@ # ADR 010 — PUT body id kept, sent explicitly; runtime validation for widened numeric fields -**Status:** Accepted +**Status:** Accepted. Tag/Service file examples are historical after later +frontend removals; the PUT-body and runtime-validation rules remain current. ## Decision diff --git a/apps/admin-frontend/docs/adr/011-http-client-decoder-boundary.md b/apps/admin-frontend/docs/adr/011-http-client-decoder-boundary.md index 1f01d53..d24ed58 100644 --- a/apps/admin-frontend/docs/adr/011-http-client-decoder-boundary.md +++ b/apps/admin-frontend/docs/adr/011-http-client-decoder-boundary.md @@ -1,6 +1,8 @@ # ADR 011 — `HttpClient` takes a decoder instead of a caller-chosen generic -**Status:** Accepted +**Status:** Accepted for the decoder boundary. ADR 014 supersedes the +Promise-only signatures below: current `HttpClient` methods return +`Promise>` while still requiring a decoder. ## Decision diff --git a/apps/admin-frontend/docs/adr/014-catalog-result-errors-and-global-net.md b/apps/admin-frontend/docs/adr/014-catalog-result-errors-and-global-net.md index 261746e..b35e7ed 100644 --- a/apps/admin-frontend/docs/adr/014-catalog-result-errors-and-global-net.md +++ b/apps/admin-frontend/docs/adr/014-catalog-result-errors-and-global-net.md @@ -96,7 +96,7 @@ own architecture. ## Consequences -- `agent-skills/agenza-frontend-feature`'s "frontend's own, +- `.agents/skills/agenza-frontend-feature`'s "frontend's own, already-established exception-and-catch convention" (describing domain entity factories) now applies to Auth only - Catalog's domain entities return `Result`. The skill is updated to say so explicitly. diff --git a/apps/admin-frontend/docs/adr/015-auth-result-errors.md b/apps/admin-frontend/docs/adr/015-auth-result-errors.md index 20424f3..2bd112c 100644 --- a/apps/admin-frontend/docs/adr/015-auth-result-errors.md +++ b/apps/admin-frontend/docs/adr/015-auth-result-errors.md @@ -63,7 +63,7 @@ Result-based. ## Consequences -- `agent-skills/agenza-frontend-feature` no longer describes Auth as an +- `.agents/skills/agenza-frontend-feature` no longer describes Auth as an exception to Catalog's Result convention - both features follow the same shape now. Updated to say so. - Test fixtures across the suite that build a `Tenant`/`User`/`Session` diff --git a/apps/admin-frontend/docs/adr/016-remove-tags-frontend.md b/apps/admin-frontend/docs/adr/016-remove-tags-frontend.md index 06e8c5f..a8ef076 100644 --- a/apps/admin-frontend/docs/adr/016-remove-tags-frontend.md +++ b/apps/admin-frontend/docs/adr/016-remove-tags-frontend.md @@ -44,12 +44,12 @@ simply not needed right now. ## Consequences - `apps/admin-frontend/AGENTS.md`, `docs/STATUS.md`, `docs/DOMAIN.md`, - `docs/API.md`, `.agent.md`, `eslint.config.js`, and `.env.example` are + `docs/API.md`, `eslint.config.js`, and `.env.example` are updated to stop describing Tags as a built frontend feature, and to flag that the backend still returns `tags`/`tagIds` on `ServiceDto` even though the frontend has no `Tag` type to represent it. -- `agent-skills/agenza-frontend-feature` (canonical, synced to - `.claude/skills/`/`.agents/skills/`) no longer uses `TagsPage`/`TagForm` +- `.agents/skills/agenza-frontend-feature` (canonical, synced to + `.claude/skills/`) no longer uses `TagsPage`/`TagForm` as its worked example; it uses Categories instead. - Historical ADRs (007–011, 014) that used Tags as a worked example when documenting an already-made decision are left as-is — they describe diff --git a/apps/admin-frontend/docs/adr/README.md b/apps/admin-frontend/docs/adr/README.md new file mode 100644 index 0000000..341547b --- /dev/null +++ b/apps/admin-frontend/docs/adr/README.md @@ -0,0 +1,20 @@ +# Frontend ADR index + +Open only the ADRs relevant to the change. Current code and tests remain the +source of executable truth. + +| Concern | ADRs | Status note | +| ------------------------------------- | ------------------ | --------------------------------------------------------------------------------------- | +| Layering and feature boundaries | 001, 009 | 009 supersedes 001's old physical layout | +| Server state and dependency injection | 002, 003, 008 | Accepted; Catalog may directly delegate repository methods when no orchestration exists | +| OIDC/session behavior | 004, 006, 007, 015 | Accepted; 015 extends Result flow to Auth | +| UI component system | 005 | Accepted | +| PUT ids and runtime validation | 010 | Accepted | +| HTTP decoder boundary | 011 | Accepted | +| Category routed editor | 012, 013 | 013 supersedes 012's outlet-context detail | +| Catalog Result flow | 014 | Accepted; its Auth-out-of-scope note is superseded by 015 | +| Tags removal | 016 | Accepted; backend Tag API remains | + +There is no separate decisions log. Durable choices belong in these ADRs; +working conventions belong in `AGENTS.md` or the canonical frontend skill; +current progress belongs in `docs/STATUS.md`. diff --git a/apps/admin-frontend/graphify-out/.graphify_labels.json b/apps/admin-frontend/graphify-out/.graphify_labels.json deleted file mode 100644 index 9e0159d..0000000 --- a/apps/admin-frontend/graphify-out/.graphify_labels.json +++ /dev/null @@ -1 +0,0 @@ -{"0": "API Contract & Conventions", "1": "Architecture Rules & Project Status", "2": "Container/Async React Hooks", "3": "Session Domain Model & Tests", "4": "TenantContext & Auth Repository", "5": "Husky Lint-Staged Tooling", "6": "Package Dependencies", "7": "TypeScript App Compiler Config", "8": "TypeScript Node Compiler Config", "9": "App Bootstrap & Providers", "10": "Domain Error Types", "11": "OIDC Auth Repository Tests", "12": "Prettier Formatting Config", "13": "Icon Sprite Symbols", "14": "MSW Test Server Setup", "15": "Husky Shell Helper", "16": "Vite Env Types", "17": "TS Project References", "18": "Git Hook: applypatch-msg", "19": "Git Hook: commit-msg", "20": "Git Hook: post-applypatch", "21": "Git Hook: post-checkout", "22": "Git Hook: post-commit", "23": "Git Hook: post-merge", "24": "Git Hook: post-rewrite", "25": "Git Hook: pre-applypatch", "26": "Git Hook: pre-auto-gc", "27": "Git Hook: pre-commit", "28": "Git Hook: pre-merge-commit", "29": "Git Hook: pre-push", "30": "Git Hook: pre-rebase", "31": "Git Hook: prepare-commit-msg", "32": "ESLint Config", "33": "Favicon Logo", "34": "Vite Build Config", "35": "Vitest Config"} \ No newline at end of file diff --git a/apps/admin-frontend/graphify-out/.graphify_python b/apps/admin-frontend/graphify-out/.graphify_python deleted file mode 100644 index 17c8ca6..0000000 --- a/apps/admin-frontend/graphify-out/.graphify_python +++ /dev/null @@ -1 +0,0 @@ -C:\Users\evert\AppData\Roaming\uv\tools\graphifyy\Scripts\python.exe \ No newline at end of file diff --git a/apps/admin-frontend/graphify-out/.graphify_root b/apps/admin-frontend/graphify-out/.graphify_root deleted file mode 100644 index effeb71..0000000 --- a/apps/admin-frontend/graphify-out/.graphify_root +++ /dev/null @@ -1 +0,0 @@ -C:\Users\evert\Downloads\admin-complete\admin \ No newline at end of file diff --git a/apps/admin-frontend/graphify-out/GRAPH_REPORT.md b/apps/admin-frontend/graphify-out/GRAPH_REPORT.md deleted file mode 100644 index 2f6b6dc..0000000 --- a/apps/admin-frontend/graphify-out/GRAPH_REPORT.md +++ /dev/null @@ -1,157 +0,0 @@ -# Graph Report - . (2026-07-06) - -## Corpus Check -- Corpus is ~15,830 words - fits in a single context window. You may not need a graph. - -## Summary -- 385 nodes · 626 edges · 36 communities (17 shown, 19 thin omitted) -- Extraction: 93% EXTRACTED · 7% INFERRED · 0% AMBIGUOUS · INFERRED: 41 edges (avg confidence: 0.87) -- Token cost: 0 input · 118,611 output - -## Community Hubs (Navigation) -- [[_COMMUNITY_API Contract & Conventions|API Contract & Conventions]] -- [[_COMMUNITY_Architecture Rules & Project Status|Architecture Rules & Project Status]] -- [[_COMMUNITY_ContainerAsync React Hooks|Container/Async React Hooks]] -- [[_COMMUNITY_Session Domain Model & Tests|Session Domain Model & Tests]] -- [[_COMMUNITY_TenantContext & Auth Repository|TenantContext & Auth Repository]] -- [[_COMMUNITY_Husky Lint-Staged Tooling|Husky Lint-Staged Tooling]] -- [[_COMMUNITY_Package Dependencies|Package Dependencies]] -- [[_COMMUNITY_TypeScript App Compiler Config|TypeScript App Compiler Config]] -- [[_COMMUNITY_TypeScript Node Compiler Config|TypeScript Node Compiler Config]] -- [[_COMMUNITY_App Bootstrap & Providers|App Bootstrap & Providers]] -- [[_COMMUNITY_Domain Error Types|Domain Error Types]] -- [[_COMMUNITY_OIDC Auth Repository Tests|OIDC Auth Repository Tests]] -- [[_COMMUNITY_Prettier Formatting Config|Prettier Formatting Config]] -- [[_COMMUNITY_Icon Sprite Symbols|Icon Sprite Symbols]] -- [[_COMMUNITY_MSW Test Server Setup|MSW Test Server Setup]] -- [[_COMMUNITY_Husky Shell Helper|Husky Shell Helper]] -- [[_COMMUNITY_Vite Env Types|Vite Env Types]] -- [[_COMMUNITY_TS Project References|TS Project References]] -- [[_COMMUNITY_Git Hook applypatch-msg|Git Hook: applypatch-msg]] -- [[_COMMUNITY_Git Hook commit-msg|Git Hook: commit-msg]] -- [[_COMMUNITY_Git Hook post-applypatch|Git Hook: post-applypatch]] -- [[_COMMUNITY_Git Hook post-checkout|Git Hook: post-checkout]] -- [[_COMMUNITY_Git Hook post-commit|Git Hook: post-commit]] -- [[_COMMUNITY_Git Hook post-merge|Git Hook: post-merge]] -- [[_COMMUNITY_Git Hook post-rewrite|Git Hook: post-rewrite]] -- [[_COMMUNITY_Git Hook pre-applypatch|Git Hook: pre-applypatch]] -- [[_COMMUNITY_Git Hook pre-auto-gc|Git Hook: pre-auto-gc]] -- [[_COMMUNITY_Git Hook pre-commit|Git Hook: pre-commit]] -- [[_COMMUNITY_Git Hook pre-merge-commit|Git Hook: pre-merge-commit]] -- [[_COMMUNITY_Git Hook pre-push|Git Hook: pre-push]] -- [[_COMMUNITY_Git Hook pre-rebase|Git Hook: pre-rebase]] -- [[_COMMUNITY_Git Hook prepare-commit-msg|Git Hook: prepare-commit-msg]] -- [[_COMMUNITY_Favicon Logo|Favicon Logo]] - -## God Nodes (most connected - your core abstractions) -1. `AuthRepository` - 22 edges -2. `Tenant` - 21 edges -3. `compilerOptions` - 21 edges -4. `Admin Panel AI Assistant Instructions` - 19 edges -5. `User` - 18 edges -6. `Project Decisions Log` - 17 edges -7. `Session` - 16 edges -8. `compilerOptions` - 15 edges -9. `scripts` - 13 edges -10. `AppContainer` - 12 edges - -## Surprising Connections (you probably didn't know these) -- `ServiceDto Interface Pattern` --shares_data_with--> `Service` [INFERRED] - .skills/admin-api-contract/SKILL.md → docs/DOMAIN.md -- `ListServices Use Case Example` --shares_data_with--> `Service` [INFERRED] - .skills/admin-feature-vertical/SKILL.md → docs/DOMAIN.md -- `React + TypeScript + Vite Template README` --conceptually_related_to--> `Admin Panel Tech Stack` [INFERRED] - README.md → CLAUDE.md -- `index.html Vite Entry Point` --conceptually_related_to--> `React + TypeScript + Vite Template README` [INFERRED] - index.html → README.md -- `Admin Panel AI Assistant Instructions` --references--> `Admin API Contract Skill` [EXTRACTED] - CLAUDE.md → .skills/admin-api-contract/SKILL.md - -## Import Cycles -- None detected. - -## Hyperedges (group relationships) -- **TypeScript Strict Mode Constraints Group** — claude_md_erasablesyntaxonly, claude_md_exactoptionalpropertytypes, claude_md_nouncheckedindexedaccess, docs_decisions_erasablesyntaxonly, docs_decisions_exactoptionalpropertytypes, docs_decisions_nouncheckedindexedaccess, skills_admin_tdd_conventions_skill_erasablesyntaxonly_gotcha, skills_admin_tdd_conventions_skill_exactoptionalpropertytypes_gotcha [INFERRED 0.85] -- **Core Tenant-Scoped Domain Entities** — docs_domain_business_tenant, docs_domain_user, docs_domain_service, docs_domain_client, docs_domain_appointment, docs_domain_conversation, docs_domain_business_settings [EXTRACTED 1.00] -- **Feature Vertical Build Order Chain** — docs_status_httpclient_stub, docs_status_services_vertical, docs_status_clients_vertical, docs_status_appointments_vertical, docs_status_inbox_vertical, docs_status_dashboard_vertical, docs_status_settings_vertical [EXTRACTED 1.00] - -## Communities (36 total, 19 thin omitted) - -### Community 0 - "API Contract & Conventions" -Cohesion: 0.07 -Nodes (48): TenantContext First Param Rule, API Integration Guide, ApiError Class, Bearer Token Authentication Flow, VITE_API_BASE_URL Config, API Error Shape (Placeholder), How to Add a New Resource Workflow, Pagination Strategy (TBD) (+40 more) - -### Community 1 - "Architecture Rules & Project Status" -Cohesion: 0.06 -Nodes (45): Admin Panel AI Assistant Instructions, Clean Architecture Boundary Constraint, composition/container.ts Composition Root, Current Project State Summary, Admin Panel Design Language, erasableSyntaxOnly Constraint, exactOptionalPropertyTypes Constraint, noUncheckedIndexedAccess Constraint (+37 more) - -### Community 2 - "Container/Async React Hooks" -Cohesion: 0.08 -Nodes (23): useAppContainer(), AsyncStatus, useAsync(), UseAsyncOptions, UseAsyncResult, AuthStatus, renderUseAuth(), useAuth() (+15 more) - -### Community 3 - "Session Domain Model & Tests" -Cohesion: 0.13 -Nodes (9): CreateSessionInput, Session, CreateUserInput, User, Tenant, OidcAuthRepository, mapOidcUserToSession(), FakeUseCases (+1 more) - -### Community 4 - "TenantContext & Auth Repository" -Cohesion: 0.15 -Nodes (10): TenantContext, toTenantContext(), AuthRepository, createFakeAuthRepository(), GetCurrentSession, HandleAuthCallback, InitiateLogin, Logout (+2 more) - -### Community 5 - "Husky Lint-Staged Tooling" -Cohesion: 0.07 -Nodes (27): husky.sh script, devDependencies, eslint, eslint-config-prettier, @eslint/js, eslint-plugin-react-hooks, eslint-plugin-react-refresh, globals (+19 more) - -### Community 6 - "Package Dependencies" -Cohesion: 0.09 -Nodes (22): dependencies, oidc-client-ts, react, react-dom, react-router, name, private, scripts (+14 more) - -### Community 7 - "TypeScript App Compiler Config" -Cohesion: 0.09 -Nodes (22): compilerOptions, allowImportingTsExtensions, erasableSyntaxOnly, exactOptionalPropertyTypes, jsx, lib, module, moduleDetection (+14 more) - -### Community 8 - "TypeScript Node Compiler Config" -Cohesion: 0.12 -Nodes (16): compilerOptions, allowImportingTsExtensions, erasableSyntaxOnly, lib, module, moduleDetection, noEmit, noFallthroughCasesInSwitch (+8 more) - -### Community 9 - "App Bootstrap & Providers" -Cohesion: 0.31 -Nodes (6): App(), createAppContainer(), rootElement, AppProviders(), AppProvidersProps, router - -### Community 10 - "Domain Error Types" -Cohesion: 0.39 -Nodes (4): DomainError, InvalidSessionError, InvalidTenantError, InvalidUserError - -### Community 11 - "OIDC Auth Repository Tests" -Cohesion: 0.31 -Nodes (4): createFakeOidcUser(), createFakeUserManager(), FakeUserManager, MissingTenantClaimError - -### Community 12 - "Prettier Formatting Config" -Cohesion: 0.29 -Nodes (6): arrowParens, printWidth, semi, singleQuote, tabWidth, trailingComma - -### Community 13 - "Icon Sprite Symbols" -Cohesion: 0.43 -Nodes (7): Bluesky Icon Symbol, Discord Icon Symbol, Documentation Icon Symbol, GitHub Icon Symbol, Public Icon Sprite (SVG Symbols), Social/People Icon Symbol, X (Twitter) Icon Symbol - -## Knowledge Gaps -- **125 isolated node(s):** `husky.sh script`, `semi`, `singleQuote`, `trailingComma`, `printWidth` (+120 more) - These have ≤1 connection - possible missing edges or undocumented components. -- **19 thin communities (<3 nodes) omitted from report** — run `graphify query` to explore isolated nodes. - -## Suggested Questions -_Questions this graph is uniquely positioned to answer:_ - -- **Why does `Admin Panel AI Assistant Instructions` connect `Architecture Rules & Project Status` to `API Contract & Conventions`?** - _High betweenness centrality (0.039) - this node is a cross-community bridge._ -- **Why does `Project Decisions Log` connect `Architecture Rules & Project Status` to `API Contract & Conventions`?** - _High betweenness centrality (0.014) - this node is a cross-community bridge._ -- **Why does `AuthRepository` connect `TenantContext & Auth Repository` to `Session Domain Model & Tests`?** - _High betweenness centrality (0.014) - this node is a cross-community bridge._ -- **What connects `husky.sh script`, `semi`, `singleQuote` to the rest of the system?** - _129 weakly-connected nodes found - possible documentation gaps or missing edges._ -- **Should `API Contract & Conventions` be split into smaller, more focused modules?** - _Cohesion score 0.06914893617021277 - nodes in this community are weakly interconnected._ -- **Should `Architecture Rules & Project Status` be split into smaller, more focused modules?** - _Cohesion score 0.0595959595959596 - nodes in this community are weakly interconnected._ -- **Should `Container/Async React Hooks` be split into smaller, more focused modules?** - _Cohesion score 0.08305647840531562 - nodes in this community are weakly interconnected._ \ No newline at end of file diff --git a/apps/admin-frontend/graphify-out/cache/ast/v0.9.8/010a69b73127716a89b336d0e6027d0f066102485541c9ebf1e2fb6b273a6126.json b/apps/admin-frontend/graphify-out/cache/ast/v0.9.8/010a69b73127716a89b336d0e6027d0f066102485541c9ebf1e2fb6b273a6126.json deleted file mode 100644 index 02fed5d..0000000 --- a/apps/admin-frontend/graphify-out/cache/ast/v0.9.8/010a69b73127716a89b336d0e6027d0f066102485541c9ebf1e2fb6b273a6126.json +++ /dev/null @@ -1 +0,0 @@ -{"nodes": [{"id": "c_users_evert_downloads_admin_complete_admin_prettierrc_json", "label": ".prettierrc.json", "file_type": "code", "source_file": ".prettierrc.json", "source_location": "L1"}, {"id": "c_users_evert_downloads_admin_complete_admin_prettierrc_semi", "label": "semi", "file_type": "code", "source_file": ".prettierrc.json", "source_location": "L2"}, {"id": "c_users_evert_downloads_admin_complete_admin_prettierrc_singlequote", "label": "singleQuote", "file_type": "code", "source_file": ".prettierrc.json", "source_location": "L3"}, {"id": "c_users_evert_downloads_admin_complete_admin_prettierrc_trailingcomma", "label": "trailingComma", "file_type": "code", "source_file": ".prettierrc.json", "source_location": "L4"}, {"id": "c_users_evert_downloads_admin_complete_admin_prettierrc_printwidth", "label": "printWidth", "file_type": "code", "source_file": ".prettierrc.json", "source_location": "L5"}, {"id": "c_users_evert_downloads_admin_complete_admin_prettierrc_tabwidth", "label": "tabWidth", "file_type": "code", "source_file": ".prettierrc.json", "source_location": "L6"}, {"id": "c_users_evert_downloads_admin_complete_admin_prettierrc_arrowparens", "label": "arrowParens", "file_type": "code", "source_file": ".prettierrc.json", "source_location": "L7"}], "edges": [{"source": "c_users_evert_downloads_admin_complete_admin_prettierrc_json", "target": "c_users_evert_downloads_admin_complete_admin_prettierrc_semi", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".prettierrc.json", "source_location": "L2", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_prettierrc_json", "target": "c_users_evert_downloads_admin_complete_admin_prettierrc_singlequote", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".prettierrc.json", "source_location": "L3", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_prettierrc_json", "target": "c_users_evert_downloads_admin_complete_admin_prettierrc_trailingcomma", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".prettierrc.json", "source_location": "L4", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_prettierrc_json", "target": "c_users_evert_downloads_admin_complete_admin_prettierrc_printwidth", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".prettierrc.json", "source_location": "L5", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_prettierrc_json", "target": "c_users_evert_downloads_admin_complete_admin_prettierrc_tabwidth", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".prettierrc.json", "source_location": "L6", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_prettierrc_json", "target": "c_users_evert_downloads_admin_complete_admin_prettierrc_arrowparens", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".prettierrc.json", "source_location": "L7", "weight": 1.0}]} \ No newline at end of file diff --git a/apps/admin-frontend/graphify-out/cache/ast/v0.9.8/0bff8d003cbfd9b75abbe94b6017890688fb016d9cd9e4474bba08706c61d3d0.json b/apps/admin-frontend/graphify-out/cache/ast/v0.9.8/0bff8d003cbfd9b75abbe94b6017890688fb016d9cd9e4474bba08706c61d3d0.json deleted file mode 100644 index 21938fe..0000000 --- a/apps/admin-frontend/graphify-out/cache/ast/v0.9.8/0bff8d003cbfd9b75abbe94b6017890688fb016d9cd9e4474bba08706c61d3d0.json +++ /dev/null @@ -1 +0,0 @@ -{"nodes": [{"id": "c_users_evert_downloads_admin_complete_admin_husky_commit_msg", "label": "commit-msg", "file_type": "code", "source_file": ".husky/_/commit-msg", "source_location": "L1", "metadata": {"language": "bash", "kind": "file"}}, {"id": "c_users_evert_downloads_admin_complete_admin_husky_commit_msg__entry", "label": "commit-msg script", "file_type": "code", "source_file": ".husky/_/commit-msg", "source_location": "L1", "metadata": {"language": "bash", "kind": "bash_entrypoint"}}], "edges": [{"source": "c_users_evert_downloads_admin_complete_admin_husky_commit_msg", "target": "c_users_evert_downloads_admin_complete_admin_husky_commit_msg__entry", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".husky/_/commit-msg", "source_location": "L1", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_husky_commit_msg", "target": "dirname_0_h", "relation": "imports", "confidence": "EXTRACTED", "source_file": ".husky/_/commit-msg", "source_location": "L2", "weight": 1.0, "context": "import"}]} \ No newline at end of file diff --git a/apps/admin-frontend/graphify-out/cache/ast/v0.9.8/0c34ade59e9769c174ab25be41720f31c2bd1c797f0bee7c00391fd164abae52.json b/apps/admin-frontend/graphify-out/cache/ast/v0.9.8/0c34ade59e9769c174ab25be41720f31c2bd1c797f0bee7c00391fd164abae52.json deleted file mode 100644 index 5da41df..0000000 --- a/apps/admin-frontend/graphify-out/cache/ast/v0.9.8/0c34ade59e9769c174ab25be41720f31c2bd1c797f0bee7c00391fd164abae52.json +++ /dev/null @@ -1 +0,0 @@ -{"nodes": [{"id": "c_users_evert_downloads_admin_complete_admin_tsconfig_app_json", "label": "tsconfig.app.json", "file_type": "code", "source_file": "tsconfig.app.json", "source_location": "L1"}, {"id": "c_users_evert_downloads_admin_complete_admin_tsconfig_app_compileroptions", "label": "compilerOptions", "file_type": "code", "source_file": "tsconfig.app.json", "source_location": "L2"}, {"id": "c_users_evert_downloads_admin_complete_admin_tsconfig_app_compileroptions_tsbuildinfofile", "label": "tsBuildInfoFile", "file_type": "code", "source_file": "tsconfig.app.json", "source_location": "L3"}, {"id": "c_users_evert_downloads_admin_complete_admin_tsconfig_app_compileroptions_target", "label": "target", "file_type": "code", "source_file": "tsconfig.app.json", "source_location": "L4"}, {"id": "c_users_evert_downloads_admin_complete_admin_tsconfig_app_compileroptions_lib", "label": "lib", "file_type": "code", "source_file": "tsconfig.app.json", "source_location": "L5"}, {"id": "c_users_evert_downloads_admin_complete_admin_tsconfig_app_compileroptions_module", "label": "module", "file_type": "code", "source_file": "tsconfig.app.json", "source_location": "L6"}, {"id": "c_users_evert_downloads_admin_complete_admin_tsconfig_app_compileroptions_types", "label": "types", "file_type": "code", "source_file": "tsconfig.app.json", "source_location": "L7"}, {"id": "c_users_evert_downloads_admin_complete_admin_tsconfig_app_compileroptions_skiplibcheck", "label": "skipLibCheck", "file_type": "code", "source_file": "tsconfig.app.json", "source_location": "L8"}, {"id": "c_users_evert_downloads_admin_complete_admin_tsconfig_app_compileroptions_moduleresolution", "label": "moduleResolution", "file_type": "code", "source_file": "tsconfig.app.json", "source_location": "L11"}, {"id": "c_users_evert_downloads_admin_complete_admin_tsconfig_app_compileroptions_allowimportingtsextensions", "label": "allowImportingTsExtensions", "file_type": "code", "source_file": "tsconfig.app.json", "source_location": "L12"}, {"id": "c_users_evert_downloads_admin_complete_admin_tsconfig_app_compileroptions_verbatimmodulesyntax", "label": "verbatimModuleSyntax", "file_type": "code", "source_file": "tsconfig.app.json", "source_location": "L13"}, {"id": "c_users_evert_downloads_admin_complete_admin_tsconfig_app_compileroptions_moduledetection", "label": "moduleDetection", "file_type": "code", "source_file": "tsconfig.app.json", "source_location": "L14"}, {"id": "c_users_evert_downloads_admin_complete_admin_tsconfig_app_compileroptions_noemit", "label": "noEmit", "file_type": "code", "source_file": "tsconfig.app.json", "source_location": "L15"}, {"id": "c_users_evert_downloads_admin_complete_admin_tsconfig_app_compileroptions_jsx", "label": "jsx", "file_type": "code", "source_file": "tsconfig.app.json", "source_location": "L16"}, {"id": "c_users_evert_downloads_admin_complete_admin_tsconfig_app_compileroptions_strict", "label": "strict", "file_type": "code", "source_file": "tsconfig.app.json", "source_location": "L19"}, {"id": "c_users_evert_downloads_admin_complete_admin_tsconfig_app_compileroptions_nouncheckedindexedaccess", "label": "noUncheckedIndexedAccess", "file_type": "code", "source_file": "tsconfig.app.json", "source_location": "L20"}, {"id": "c_users_evert_downloads_admin_complete_admin_tsconfig_app_compileroptions_noimplicitoverride", "label": "noImplicitOverride", "file_type": "code", "source_file": "tsconfig.app.json", "source_location": "L21"}, {"id": "c_users_evert_downloads_admin_complete_admin_tsconfig_app_compileroptions_exactoptionalpropertytypes", "label": "exactOptionalPropertyTypes", "file_type": "code", "source_file": "tsconfig.app.json", "source_location": "L22"}, {"id": "c_users_evert_downloads_admin_complete_admin_tsconfig_app_compileroptions_nounusedlocals", "label": "noUnusedLocals", "file_type": "code", "source_file": "tsconfig.app.json", "source_location": "L25"}, {"id": "c_users_evert_downloads_admin_complete_admin_tsconfig_app_compileroptions_nounusedparameters", "label": "noUnusedParameters", "file_type": "code", "source_file": "tsconfig.app.json", "source_location": "L26"}, {"id": "c_users_evert_downloads_admin_complete_admin_tsconfig_app_compileroptions_erasablesyntaxonly", "label": "erasableSyntaxOnly", "file_type": "code", "source_file": "tsconfig.app.json", "source_location": "L27"}, {"id": "c_users_evert_downloads_admin_complete_admin_tsconfig_app_compileroptions_nofallthroughcasesinswitch", "label": "noFallthroughCasesInSwitch", "file_type": "code", "source_file": "tsconfig.app.json", "source_location": "L28"}, {"id": "c_users_evert_downloads_admin_complete_admin_tsconfig_app_include", "label": "include", "file_type": "code", "source_file": "tsconfig.app.json", "source_location": "L30"}], "edges": [{"source": "c_users_evert_downloads_admin_complete_admin_tsconfig_app_json", "target": "c_users_evert_downloads_admin_complete_admin_tsconfig_app_compileroptions", "relation": "contains", "confidence": "EXTRACTED", "source_file": "tsconfig.app.json", "source_location": "L2", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_tsconfig_app_compileroptions", "target": "c_users_evert_downloads_admin_complete_admin_tsconfig_app_compileroptions_tsbuildinfofile", "relation": "contains", "confidence": "EXTRACTED", "source_file": "tsconfig.app.json", "source_location": "L3", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_tsconfig_app_compileroptions", "target": "c_users_evert_downloads_admin_complete_admin_tsconfig_app_compileroptions_target", "relation": "contains", "confidence": "EXTRACTED", "source_file": "tsconfig.app.json", "source_location": "L4", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_tsconfig_app_compileroptions", "target": "c_users_evert_downloads_admin_complete_admin_tsconfig_app_compileroptions_lib", "relation": "contains", "confidence": "EXTRACTED", "source_file": "tsconfig.app.json", "source_location": "L5", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_tsconfig_app_compileroptions_lib", "target": "ref_es2023", "relation": "extends", "confidence": "EXTRACTED", "source_file": "tsconfig.app.json", "source_location": "L5", "weight": 1.0, "context": "import"}, {"source": "c_users_evert_downloads_admin_complete_admin_tsconfig_app_compileroptions_lib", "target": "ref_dom", "relation": "extends", "confidence": "EXTRACTED", "source_file": "tsconfig.app.json", "source_location": "L5", "weight": 1.0, "context": "import"}, {"source": "c_users_evert_downloads_admin_complete_admin_tsconfig_app_compileroptions", "target": "c_users_evert_downloads_admin_complete_admin_tsconfig_app_compileroptions_module", "relation": "contains", "confidence": "EXTRACTED", "source_file": "tsconfig.app.json", "source_location": "L6", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_tsconfig_app_compileroptions", "target": "c_users_evert_downloads_admin_complete_admin_tsconfig_app_compileroptions_types", "relation": "contains", "confidence": "EXTRACTED", "source_file": "tsconfig.app.json", "source_location": "L7", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_tsconfig_app_compileroptions_types", "target": "ref_vite_client", "relation": "extends", "confidence": "EXTRACTED", "source_file": "tsconfig.app.json", "source_location": "L7", "weight": 1.0, "context": "import"}, {"source": "c_users_evert_downloads_admin_complete_admin_tsconfig_app_compileroptions", "target": "c_users_evert_downloads_admin_complete_admin_tsconfig_app_compileroptions_skiplibcheck", "relation": "contains", "confidence": "EXTRACTED", "source_file": "tsconfig.app.json", "source_location": "L8", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_tsconfig_app_compileroptions", "target": "c_users_evert_downloads_admin_complete_admin_tsconfig_app_compileroptions_moduleresolution", "relation": "contains", "confidence": "EXTRACTED", "source_file": "tsconfig.app.json", "source_location": "L11", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_tsconfig_app_compileroptions", "target": "c_users_evert_downloads_admin_complete_admin_tsconfig_app_compileroptions_allowimportingtsextensions", "relation": "contains", "confidence": "EXTRACTED", "source_file": "tsconfig.app.json", "source_location": "L12", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_tsconfig_app_compileroptions", "target": "c_users_evert_downloads_admin_complete_admin_tsconfig_app_compileroptions_verbatimmodulesyntax", "relation": "contains", "confidence": "EXTRACTED", "source_file": "tsconfig.app.json", "source_location": "L13", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_tsconfig_app_compileroptions", "target": "c_users_evert_downloads_admin_complete_admin_tsconfig_app_compileroptions_moduledetection", "relation": "contains", "confidence": "EXTRACTED", "source_file": "tsconfig.app.json", "source_location": "L14", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_tsconfig_app_compileroptions", "target": "c_users_evert_downloads_admin_complete_admin_tsconfig_app_compileroptions_noemit", "relation": "contains", "confidence": "EXTRACTED", "source_file": "tsconfig.app.json", "source_location": "L15", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_tsconfig_app_compileroptions", "target": "c_users_evert_downloads_admin_complete_admin_tsconfig_app_compileroptions_jsx", "relation": "contains", "confidence": "EXTRACTED", "source_file": "tsconfig.app.json", "source_location": "L16", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_tsconfig_app_compileroptions", "target": "c_users_evert_downloads_admin_complete_admin_tsconfig_app_compileroptions_strict", "relation": "contains", "confidence": "EXTRACTED", "source_file": "tsconfig.app.json", "source_location": "L19", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_tsconfig_app_compileroptions", "target": "c_users_evert_downloads_admin_complete_admin_tsconfig_app_compileroptions_nouncheckedindexedaccess", "relation": "contains", "confidence": "EXTRACTED", "source_file": "tsconfig.app.json", "source_location": "L20", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_tsconfig_app_compileroptions", "target": "c_users_evert_downloads_admin_complete_admin_tsconfig_app_compileroptions_noimplicitoverride", "relation": "contains", "confidence": "EXTRACTED", "source_file": "tsconfig.app.json", "source_location": "L21", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_tsconfig_app_compileroptions", "target": "c_users_evert_downloads_admin_complete_admin_tsconfig_app_compileroptions_exactoptionalpropertytypes", "relation": "contains", "confidence": "EXTRACTED", "source_file": "tsconfig.app.json", "source_location": "L22", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_tsconfig_app_compileroptions", "target": "c_users_evert_downloads_admin_complete_admin_tsconfig_app_compileroptions_nounusedlocals", "relation": "contains", "confidence": "EXTRACTED", "source_file": "tsconfig.app.json", "source_location": "L25", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_tsconfig_app_compileroptions", "target": "c_users_evert_downloads_admin_complete_admin_tsconfig_app_compileroptions_nounusedparameters", "relation": "contains", "confidence": "EXTRACTED", "source_file": "tsconfig.app.json", "source_location": "L26", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_tsconfig_app_compileroptions", "target": "c_users_evert_downloads_admin_complete_admin_tsconfig_app_compileroptions_erasablesyntaxonly", "relation": "contains", "confidence": "EXTRACTED", "source_file": "tsconfig.app.json", "source_location": "L27", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_tsconfig_app_compileroptions", "target": "c_users_evert_downloads_admin_complete_admin_tsconfig_app_compileroptions_nofallthroughcasesinswitch", "relation": "contains", "confidence": "EXTRACTED", "source_file": "tsconfig.app.json", "source_location": "L28", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_tsconfig_app_json", "target": "c_users_evert_downloads_admin_complete_admin_tsconfig_app_include", "relation": "contains", "confidence": "EXTRACTED", "source_file": "tsconfig.app.json", "source_location": "L30", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_tsconfig_app_include", "target": "ref_src", "relation": "extends", "confidence": "EXTRACTED", "source_file": "tsconfig.app.json", "source_location": "L30", "weight": 1.0, "context": "import"}]} \ No newline at end of file diff --git a/apps/admin-frontend/graphify-out/cache/ast/v0.9.8/141cf472a6c63bb2adfc043e0c663c4ca39742644e3039cc93b96b84d022f64e.json b/apps/admin-frontend/graphify-out/cache/ast/v0.9.8/141cf472a6c63bb2adfc043e0c663c4ca39742644e3039cc93b96b84d022f64e.json deleted file mode 100644 index 30133c5..0000000 --- a/apps/admin-frontend/graphify-out/cache/ast/v0.9.8/141cf472a6c63bb2adfc043e0c663c4ca39742644e3039cc93b96b84d022f64e.json +++ /dev/null @@ -1 +0,0 @@ -{"nodes": [{"id": "c_users_evert_downloads_admin_complete_admin_tsconfig_node_json", "label": "tsconfig.node.json", "file_type": "code", "source_file": "tsconfig.node.json", "source_location": "L1"}, {"id": "c_users_evert_downloads_admin_complete_admin_tsconfig_node_compileroptions", "label": "compilerOptions", "file_type": "code", "source_file": "tsconfig.node.json", "source_location": "L2"}, {"id": "c_users_evert_downloads_admin_complete_admin_tsconfig_node_compileroptions_tsbuildinfofile", "label": "tsBuildInfoFile", "file_type": "code", "source_file": "tsconfig.node.json", "source_location": "L3"}, {"id": "c_users_evert_downloads_admin_complete_admin_tsconfig_node_compileroptions_target", "label": "target", "file_type": "code", "source_file": "tsconfig.node.json", "source_location": "L4"}, {"id": "c_users_evert_downloads_admin_complete_admin_tsconfig_node_compileroptions_lib", "label": "lib", "file_type": "code", "source_file": "tsconfig.node.json", "source_location": "L5"}, {"id": "c_users_evert_downloads_admin_complete_admin_tsconfig_node_compileroptions_types", "label": "types", "file_type": "code", "source_file": "tsconfig.node.json", "source_location": "L6"}, {"id": "c_users_evert_downloads_admin_complete_admin_tsconfig_node_compileroptions_skiplibcheck", "label": "skipLibCheck", "file_type": "code", "source_file": "tsconfig.node.json", "source_location": "L7"}, {"id": "c_users_evert_downloads_admin_complete_admin_tsconfig_node_compileroptions_module", "label": "module", "file_type": "code", "source_file": "tsconfig.node.json", "source_location": "L10"}, {"id": "c_users_evert_downloads_admin_complete_admin_tsconfig_node_compileroptions_allowimportingtsextensions", "label": "allowImportingTsExtensions", "file_type": "code", "source_file": "tsconfig.node.json", "source_location": "L11"}, {"id": "c_users_evert_downloads_admin_complete_admin_tsconfig_node_compileroptions_verbatimmodulesyntax", "label": "verbatimModuleSyntax", "file_type": "code", "source_file": "tsconfig.node.json", "source_location": "L12"}, {"id": "c_users_evert_downloads_admin_complete_admin_tsconfig_node_compileroptions_moduledetection", "label": "moduleDetection", "file_type": "code", "source_file": "tsconfig.node.json", "source_location": "L13"}, {"id": "c_users_evert_downloads_admin_complete_admin_tsconfig_node_compileroptions_noemit", "label": "noEmit", "file_type": "code", "source_file": "tsconfig.node.json", "source_location": "L14"}, {"id": "c_users_evert_downloads_admin_complete_admin_tsconfig_node_compileroptions_nounusedlocals", "label": "noUnusedLocals", "file_type": "code", "source_file": "tsconfig.node.json", "source_location": "L17"}, {"id": "c_users_evert_downloads_admin_complete_admin_tsconfig_node_compileroptions_nounusedparameters", "label": "noUnusedParameters", "file_type": "code", "source_file": "tsconfig.node.json", "source_location": "L18"}, {"id": "c_users_evert_downloads_admin_complete_admin_tsconfig_node_compileroptions_erasablesyntaxonly", "label": "erasableSyntaxOnly", "file_type": "code", "source_file": "tsconfig.node.json", "source_location": "L19"}, {"id": "c_users_evert_downloads_admin_complete_admin_tsconfig_node_compileroptions_nofallthroughcasesinswitch", "label": "noFallthroughCasesInSwitch", "file_type": "code", "source_file": "tsconfig.node.json", "source_location": "L20"}, {"id": "c_users_evert_downloads_admin_complete_admin_tsconfig_node_include", "label": "include", "file_type": "code", "source_file": "tsconfig.node.json", "source_location": "L22"}], "edges": [{"source": "c_users_evert_downloads_admin_complete_admin_tsconfig_node_json", "target": "c_users_evert_downloads_admin_complete_admin_tsconfig_node_compileroptions", "relation": "contains", "confidence": "EXTRACTED", "source_file": "tsconfig.node.json", "source_location": "L2", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_tsconfig_node_compileroptions", "target": "c_users_evert_downloads_admin_complete_admin_tsconfig_node_compileroptions_tsbuildinfofile", "relation": "contains", "confidence": "EXTRACTED", "source_file": "tsconfig.node.json", "source_location": "L3", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_tsconfig_node_compileroptions", "target": "c_users_evert_downloads_admin_complete_admin_tsconfig_node_compileroptions_target", "relation": "contains", "confidence": "EXTRACTED", "source_file": "tsconfig.node.json", "source_location": "L4", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_tsconfig_node_compileroptions", "target": "c_users_evert_downloads_admin_complete_admin_tsconfig_node_compileroptions_lib", "relation": "contains", "confidence": "EXTRACTED", "source_file": "tsconfig.node.json", "source_location": "L5", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_tsconfig_node_compileroptions_lib", "target": "ref_es2023", "relation": "extends", "confidence": "EXTRACTED", "source_file": "tsconfig.node.json", "source_location": "L5", "weight": 1.0, "context": "import"}, {"source": "c_users_evert_downloads_admin_complete_admin_tsconfig_node_compileroptions", "target": "c_users_evert_downloads_admin_complete_admin_tsconfig_node_compileroptions_types", "relation": "contains", "confidence": "EXTRACTED", "source_file": "tsconfig.node.json", "source_location": "L6", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_tsconfig_node_compileroptions_types", "target": "ref_node", "relation": "extends", "confidence": "EXTRACTED", "source_file": "tsconfig.node.json", "source_location": "L6", "weight": 1.0, "context": "import"}, {"source": "c_users_evert_downloads_admin_complete_admin_tsconfig_node_compileroptions", "target": "c_users_evert_downloads_admin_complete_admin_tsconfig_node_compileroptions_skiplibcheck", "relation": "contains", "confidence": "EXTRACTED", "source_file": "tsconfig.node.json", "source_location": "L7", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_tsconfig_node_compileroptions", "target": "c_users_evert_downloads_admin_complete_admin_tsconfig_node_compileroptions_module", "relation": "contains", "confidence": "EXTRACTED", "source_file": "tsconfig.node.json", "source_location": "L10", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_tsconfig_node_compileroptions", "target": "c_users_evert_downloads_admin_complete_admin_tsconfig_node_compileroptions_allowimportingtsextensions", "relation": "contains", "confidence": "EXTRACTED", "source_file": "tsconfig.node.json", "source_location": "L11", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_tsconfig_node_compileroptions", "target": "c_users_evert_downloads_admin_complete_admin_tsconfig_node_compileroptions_verbatimmodulesyntax", "relation": "contains", "confidence": "EXTRACTED", "source_file": "tsconfig.node.json", "source_location": "L12", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_tsconfig_node_compileroptions", "target": "c_users_evert_downloads_admin_complete_admin_tsconfig_node_compileroptions_moduledetection", "relation": "contains", "confidence": "EXTRACTED", "source_file": "tsconfig.node.json", "source_location": "L13", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_tsconfig_node_compileroptions", "target": "c_users_evert_downloads_admin_complete_admin_tsconfig_node_compileroptions_noemit", "relation": "contains", "confidence": "EXTRACTED", "source_file": "tsconfig.node.json", "source_location": "L14", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_tsconfig_node_compileroptions", "target": "c_users_evert_downloads_admin_complete_admin_tsconfig_node_compileroptions_nounusedlocals", "relation": "contains", "confidence": "EXTRACTED", "source_file": "tsconfig.node.json", "source_location": "L17", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_tsconfig_node_compileroptions", "target": "c_users_evert_downloads_admin_complete_admin_tsconfig_node_compileroptions_nounusedparameters", "relation": "contains", "confidence": "EXTRACTED", "source_file": "tsconfig.node.json", "source_location": "L18", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_tsconfig_node_compileroptions", "target": "c_users_evert_downloads_admin_complete_admin_tsconfig_node_compileroptions_erasablesyntaxonly", "relation": "contains", "confidence": "EXTRACTED", "source_file": "tsconfig.node.json", "source_location": "L19", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_tsconfig_node_compileroptions", "target": "c_users_evert_downloads_admin_complete_admin_tsconfig_node_compileroptions_nofallthroughcasesinswitch", "relation": "contains", "confidence": "EXTRACTED", "source_file": "tsconfig.node.json", "source_location": "L20", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_tsconfig_node_json", "target": "c_users_evert_downloads_admin_complete_admin_tsconfig_node_include", "relation": "contains", "confidence": "EXTRACTED", "source_file": "tsconfig.node.json", "source_location": "L22", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_tsconfig_node_include", "target": "ref_vite_config_ts", "relation": "extends", "confidence": "EXTRACTED", "source_file": "tsconfig.node.json", "source_location": "L22", "weight": 1.0, "context": "import"}, {"source": "c_users_evert_downloads_admin_complete_admin_tsconfig_node_include", "target": "ref_vitest_config_ts", "relation": "extends", "confidence": "EXTRACTED", "source_file": "tsconfig.node.json", "source_location": "L22", "weight": 1.0, "context": "import"}]} \ No newline at end of file diff --git a/apps/admin-frontend/graphify-out/cache/ast/v0.9.8/1df8de670dd1febedc2554ced3c9a2b5215fc68cad16d4b8dc2b7b64943a7f17.json b/apps/admin-frontend/graphify-out/cache/ast/v0.9.8/1df8de670dd1febedc2554ced3c9a2b5215fc68cad16d4b8dc2b7b64943a7f17.json deleted file mode 100644 index c0efc2d..0000000 --- a/apps/admin-frontend/graphify-out/cache/ast/v0.9.8/1df8de670dd1febedc2554ced3c9a2b5215fc68cad16d4b8dc2b7b64943a7f17.json +++ /dev/null @@ -1 +0,0 @@ -{"nodes": [{"id": "c_users_evert_downloads_admin_complete_admin_tsconfig_json", "label": "tsconfig.json", "file_type": "code", "source_file": "tsconfig.json", "source_location": "L1"}, {"id": "c_users_evert_downloads_admin_complete_admin_tsconfig_files", "label": "files", "file_type": "code", "source_file": "tsconfig.json", "source_location": "L2"}, {"id": "c_users_evert_downloads_admin_complete_admin_tsconfig_references", "label": "references", "file_type": "code", "source_file": "tsconfig.json", "source_location": "L3"}], "edges": [{"source": "c_users_evert_downloads_admin_complete_admin_tsconfig_json", "target": "c_users_evert_downloads_admin_complete_admin_tsconfig_files", "relation": "contains", "confidence": "EXTRACTED", "source_file": "tsconfig.json", "source_location": "L2", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_tsconfig_json", "target": "c_users_evert_downloads_admin_complete_admin_tsconfig_references", "relation": "contains", "confidence": "EXTRACTED", "source_file": "tsconfig.json", "source_location": "L3", "weight": 1.0}]} \ No newline at end of file diff --git a/apps/admin-frontend/graphify-out/cache/ast/v0.9.8/20798fb4a9d9b6f3593cdd371ca98fa15a5d57c1bf9bfc489039b5595b7028ff.json b/apps/admin-frontend/graphify-out/cache/ast/v0.9.8/20798fb4a9d9b6f3593cdd371ca98fa15a5d57c1bf9bfc489039b5595b7028ff.json deleted file mode 100644 index 67fe77b..0000000 --- a/apps/admin-frontend/graphify-out/cache/ast/v0.9.8/20798fb4a9d9b6f3593cdd371ca98fa15a5d57c1bf9bfc489039b5595b7028ff.json +++ /dev/null @@ -1 +0,0 @@ -{"nodes": [{"id": "c_users_evert_downloads_admin_complete_admin_husky_pre_merge_commit", "label": "pre-merge-commit", "file_type": "code", "source_file": ".husky/_/pre-merge-commit", "source_location": "L1", "metadata": {"language": "bash", "kind": "file"}}, {"id": "c_users_evert_downloads_admin_complete_admin_husky_pre_merge_commit__entry", "label": "pre-merge-commit script", "file_type": "code", "source_file": ".husky/_/pre-merge-commit", "source_location": "L1", "metadata": {"language": "bash", "kind": "bash_entrypoint"}}], "edges": [{"source": "c_users_evert_downloads_admin_complete_admin_husky_pre_merge_commit", "target": "c_users_evert_downloads_admin_complete_admin_husky_pre_merge_commit__entry", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".husky/_/pre-merge-commit", "source_location": "L1", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_husky_pre_merge_commit", "target": "dirname_0_h", "relation": "imports", "confidence": "EXTRACTED", "source_file": ".husky/_/pre-merge-commit", "source_location": "L2", "weight": 1.0, "context": "import"}]} \ No newline at end of file diff --git a/apps/admin-frontend/graphify-out/cache/ast/v0.9.8/20dc01bba5753874a3746d18250b5b5d71aebbfbdfda5305a6dfc2541adb209b.json b/apps/admin-frontend/graphify-out/cache/ast/v0.9.8/20dc01bba5753874a3746d18250b5b5d71aebbfbdfda5305a6dfc2541adb209b.json deleted file mode 100644 index 289ea9c..0000000 --- a/apps/admin-frontend/graphify-out/cache/ast/v0.9.8/20dc01bba5753874a3746d18250b5b5d71aebbfbdfda5305a6dfc2541adb209b.json +++ /dev/null @@ -1 +0,0 @@ -{"nodes": [{"id": "c_users_evert_downloads_admin_complete_admin_husky_post_commit", "label": "post-commit", "file_type": "code", "source_file": ".husky/_/post-commit", "source_location": "L1", "metadata": {"language": "bash", "kind": "file"}}, {"id": "c_users_evert_downloads_admin_complete_admin_husky_post_commit__entry", "label": "post-commit script", "file_type": "code", "source_file": ".husky/_/post-commit", "source_location": "L1", "metadata": {"language": "bash", "kind": "bash_entrypoint"}}], "edges": [{"source": "c_users_evert_downloads_admin_complete_admin_husky_post_commit", "target": "c_users_evert_downloads_admin_complete_admin_husky_post_commit__entry", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".husky/_/post-commit", "source_location": "L1", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_husky_post_commit", "target": "dirname_0_h", "relation": "imports", "confidence": "EXTRACTED", "source_file": ".husky/_/post-commit", "source_location": "L2", "weight": 1.0, "context": "import"}]} \ No newline at end of file diff --git a/apps/admin-frontend/graphify-out/cache/ast/v0.9.8/24332da3a5839bca3276206674c0c6119f653c3dd2b01e2f5b37194a02d185f8.json b/apps/admin-frontend/graphify-out/cache/ast/v0.9.8/24332da3a5839bca3276206674c0c6119f653c3dd2b01e2f5b37194a02d185f8.json deleted file mode 100644 index 620ff1c..0000000 --- a/apps/admin-frontend/graphify-out/cache/ast/v0.9.8/24332da3a5839bca3276206674c0c6119f653c3dd2b01e2f5b37194a02d185f8.json +++ /dev/null @@ -1 +0,0 @@ -{"nodes": [{"id": "c_users_evert_downloads_admin_complete_admin_husky_pre_applypatch", "label": "pre-applypatch", "file_type": "code", "source_file": ".husky/_/pre-applypatch", "source_location": "L1", "metadata": {"language": "bash", "kind": "file"}}, {"id": "c_users_evert_downloads_admin_complete_admin_husky_pre_applypatch__entry", "label": "pre-applypatch script", "file_type": "code", "source_file": ".husky/_/pre-applypatch", "source_location": "L1", "metadata": {"language": "bash", "kind": "bash_entrypoint"}}], "edges": [{"source": "c_users_evert_downloads_admin_complete_admin_husky_pre_applypatch", "target": "c_users_evert_downloads_admin_complete_admin_husky_pre_applypatch__entry", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".husky/_/pre-applypatch", "source_location": "L1", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_husky_pre_applypatch", "target": "dirname_0_h", "relation": "imports", "confidence": "EXTRACTED", "source_file": ".husky/_/pre-applypatch", "source_location": "L2", "weight": 1.0, "context": "import"}]} \ No newline at end of file diff --git a/apps/admin-frontend/graphify-out/cache/ast/v0.9.8/3aa066773301fd07106a7926663f6ca3931ebf19bcacce7e156ead4e8446121a.json b/apps/admin-frontend/graphify-out/cache/ast/v0.9.8/3aa066773301fd07106a7926663f6ca3931ebf19bcacce7e156ead4e8446121a.json deleted file mode 100644 index 62dc03b..0000000 --- a/apps/admin-frontend/graphify-out/cache/ast/v0.9.8/3aa066773301fd07106a7926663f6ca3931ebf19bcacce7e156ead4e8446121a.json +++ /dev/null @@ -1 +0,0 @@ -{"nodes": [{"id": "c_users_evert_downloads_admin_complete_admin_husky_post_rewrite", "label": "post-rewrite", "file_type": "code", "source_file": ".husky/_/post-rewrite", "source_location": "L1", "metadata": {"language": "bash", "kind": "file"}}, {"id": "c_users_evert_downloads_admin_complete_admin_husky_post_rewrite__entry", "label": "post-rewrite script", "file_type": "code", "source_file": ".husky/_/post-rewrite", "source_location": "L1", "metadata": {"language": "bash", "kind": "bash_entrypoint"}}], "edges": [{"source": "c_users_evert_downloads_admin_complete_admin_husky_post_rewrite", "target": "c_users_evert_downloads_admin_complete_admin_husky_post_rewrite__entry", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".husky/_/post-rewrite", "source_location": "L1", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_husky_post_rewrite", "target": "dirname_0_h", "relation": "imports", "confidence": "EXTRACTED", "source_file": ".husky/_/post-rewrite", "source_location": "L2", "weight": 1.0, "context": "import"}]} \ No newline at end of file diff --git a/apps/admin-frontend/graphify-out/cache/ast/v0.9.8/678bc23efa73439ac107fddd35e640e583d68b45ab0c119be3ba50a81553e99d.json b/apps/admin-frontend/graphify-out/cache/ast/v0.9.8/678bc23efa73439ac107fddd35e640e583d68b45ab0c119be3ba50a81553e99d.json deleted file mode 100644 index 99752f0..0000000 --- a/apps/admin-frontend/graphify-out/cache/ast/v0.9.8/678bc23efa73439ac107fddd35e640e583d68b45ab0c119be3ba50a81553e99d.json +++ /dev/null @@ -1 +0,0 @@ -{"nodes": [{"id": "c_users_evert_downloads_admin_complete_admin_husky_post_applypatch", "label": "post-applypatch", "file_type": "code", "source_file": ".husky/_/post-applypatch", "source_location": "L1", "metadata": {"language": "bash", "kind": "file"}}, {"id": "c_users_evert_downloads_admin_complete_admin_husky_post_applypatch__entry", "label": "post-applypatch script", "file_type": "code", "source_file": ".husky/_/post-applypatch", "source_location": "L1", "metadata": {"language": "bash", "kind": "bash_entrypoint"}}], "edges": [{"source": "c_users_evert_downloads_admin_complete_admin_husky_post_applypatch", "target": "c_users_evert_downloads_admin_complete_admin_husky_post_applypatch__entry", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".husky/_/post-applypatch", "source_location": "L1", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_husky_post_applypatch", "target": "dirname_0_h", "relation": "imports", "confidence": "EXTRACTED", "source_file": ".husky/_/post-applypatch", "source_location": "L2", "weight": 1.0, "context": "import"}]} \ No newline at end of file diff --git a/apps/admin-frontend/graphify-out/cache/ast/v0.9.8/6a9064e8fb13487a3a621e0dbb7bd8c82245b9c904b0c2dbcf3ce5fe55506b9c.json b/apps/admin-frontend/graphify-out/cache/ast/v0.9.8/6a9064e8fb13487a3a621e0dbb7bd8c82245b9c904b0c2dbcf3ce5fe55506b9c.json deleted file mode 100644 index 7303a49..0000000 --- a/apps/admin-frontend/graphify-out/cache/ast/v0.9.8/6a9064e8fb13487a3a621e0dbb7bd8c82245b9c904b0c2dbcf3ce5fe55506b9c.json +++ /dev/null @@ -1 +0,0 @@ -{"nodes": [{"id": "c_users_evert_downloads_admin_complete_admin_husky_applypatch_msg", "label": "applypatch-msg", "file_type": "code", "source_file": ".husky/_/applypatch-msg", "source_location": "L1", "metadata": {"language": "bash", "kind": "file"}}, {"id": "c_users_evert_downloads_admin_complete_admin_husky_applypatch_msg__entry", "label": "applypatch-msg script", "file_type": "code", "source_file": ".husky/_/applypatch-msg", "source_location": "L1", "metadata": {"language": "bash", "kind": "bash_entrypoint"}}], "edges": [{"source": "c_users_evert_downloads_admin_complete_admin_husky_applypatch_msg", "target": "c_users_evert_downloads_admin_complete_admin_husky_applypatch_msg__entry", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".husky/_/applypatch-msg", "source_location": "L1", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_husky_applypatch_msg", "target": "dirname_0_h", "relation": "imports", "confidence": "EXTRACTED", "source_file": ".husky/_/applypatch-msg", "source_location": "L2", "weight": 1.0, "context": "import"}]} \ No newline at end of file diff --git a/apps/admin-frontend/graphify-out/cache/ast/v0.9.8/6db0aa99aece6ebd17bbc27c5e5287a462fb39cc4a2ca3a82a6277c166bdb3a0.json b/apps/admin-frontend/graphify-out/cache/ast/v0.9.8/6db0aa99aece6ebd17bbc27c5e5287a462fb39cc4a2ca3a82a6277c166bdb3a0.json deleted file mode 100644 index c1a4abc..0000000 --- a/apps/admin-frontend/graphify-out/cache/ast/v0.9.8/6db0aa99aece6ebd17bbc27c5e5287a462fb39cc4a2ca3a82a6277c166bdb3a0.json +++ /dev/null @@ -1 +0,0 @@ -{"nodes": [{"id": "c_users_evert_downloads_admin_complete_admin_husky_husky_sh", "label": "husky.sh", "file_type": "code", "source_file": ".husky/_/husky.sh", "source_location": "L1", "metadata": {"language": "bash", "kind": "file"}}, {"id": "c_users_evert_downloads_admin_complete_admin_husky_husky_sh__entry", "label": "husky.sh script", "file_type": "code", "source_file": ".husky/_/husky.sh", "source_location": "L1", "metadata": {"language": "bash", "kind": "bash_entrypoint"}}], "edges": [{"source": "c_users_evert_downloads_admin_complete_admin_husky_husky_sh", "target": "c_users_evert_downloads_admin_complete_admin_husky_husky_sh__entry", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".husky/_/husky.sh", "source_location": "L1", "weight": 1.0}]} \ No newline at end of file diff --git a/apps/admin-frontend/graphify-out/cache/ast/v0.9.8/9dd56caa8fa28b5494661ce7c97d756013f200ac4e60f619671f4d4eca46faa6.json b/apps/admin-frontend/graphify-out/cache/ast/v0.9.8/9dd56caa8fa28b5494661ce7c97d756013f200ac4e60f619671f4d4eca46faa6.json deleted file mode 100644 index 5138650..0000000 --- a/apps/admin-frontend/graphify-out/cache/ast/v0.9.8/9dd56caa8fa28b5494661ce7c97d756013f200ac4e60f619671f4d4eca46faa6.json +++ /dev/null @@ -1 +0,0 @@ -{"nodes": [{"id": "c_users_evert_downloads_admin_complete_admin_husky_post_merge", "label": "post-merge", "file_type": "code", "source_file": ".husky/_/post-merge", "source_location": "L1", "metadata": {"language": "bash", "kind": "file"}}, {"id": "c_users_evert_downloads_admin_complete_admin_husky_post_merge__entry", "label": "post-merge script", "file_type": "code", "source_file": ".husky/_/post-merge", "source_location": "L1", "metadata": {"language": "bash", "kind": "bash_entrypoint"}}], "edges": [{"source": "c_users_evert_downloads_admin_complete_admin_husky_post_merge", "target": "c_users_evert_downloads_admin_complete_admin_husky_post_merge__entry", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".husky/_/post-merge", "source_location": "L1", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_husky_post_merge", "target": "dirname_0_h", "relation": "imports", "confidence": "EXTRACTED", "source_file": ".husky/_/post-merge", "source_location": "L2", "weight": 1.0, "context": "import"}]} \ No newline at end of file diff --git a/apps/admin-frontend/graphify-out/cache/ast/v0.9.8/c94f2d13aabe67bf5766093bbd03c9b9e5580deadc230f73ad6421b3ec0bf290.json b/apps/admin-frontend/graphify-out/cache/ast/v0.9.8/c94f2d13aabe67bf5766093bbd03c9b9e5580deadc230f73ad6421b3ec0bf290.json deleted file mode 100644 index ea03215..0000000 --- a/apps/admin-frontend/graphify-out/cache/ast/v0.9.8/c94f2d13aabe67bf5766093bbd03c9b9e5580deadc230f73ad6421b3ec0bf290.json +++ /dev/null @@ -1 +0,0 @@ -{"nodes": [{"id": "c_users_evert_downloads_admin_complete_admin_husky_pre_rebase", "label": "pre-rebase", "file_type": "code", "source_file": ".husky/_/pre-rebase", "source_location": "L1", "metadata": {"language": "bash", "kind": "file"}}, {"id": "c_users_evert_downloads_admin_complete_admin_husky_pre_rebase__entry", "label": "pre-rebase script", "file_type": "code", "source_file": ".husky/_/pre-rebase", "source_location": "L1", "metadata": {"language": "bash", "kind": "bash_entrypoint"}}], "edges": [{"source": "c_users_evert_downloads_admin_complete_admin_husky_pre_rebase", "target": "c_users_evert_downloads_admin_complete_admin_husky_pre_rebase__entry", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".husky/_/pre-rebase", "source_location": "L1", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_husky_pre_rebase", "target": "dirname_0_h", "relation": "imports", "confidence": "EXTRACTED", "source_file": ".husky/_/pre-rebase", "source_location": "L2", "weight": 1.0, "context": "import"}]} \ No newline at end of file diff --git a/apps/admin-frontend/graphify-out/cache/ast/v0.9.8/d0038c1f6c4208a560ea4669eefc633b8460563cc29fcbc3c0d6c44309916f62.json b/apps/admin-frontend/graphify-out/cache/ast/v0.9.8/d0038c1f6c4208a560ea4669eefc633b8460563cc29fcbc3c0d6c44309916f62.json deleted file mode 100644 index 3dc3f89..0000000 --- a/apps/admin-frontend/graphify-out/cache/ast/v0.9.8/d0038c1f6c4208a560ea4669eefc633b8460563cc29fcbc3c0d6c44309916f62.json +++ /dev/null @@ -1 +0,0 @@ -{"nodes": [{"id": "c_users_evert_downloads_admin_complete_admin_husky_pre_auto_gc", "label": "pre-auto-gc", "file_type": "code", "source_file": ".husky/_/pre-auto-gc", "source_location": "L1", "metadata": {"language": "bash", "kind": "file"}}, {"id": "c_users_evert_downloads_admin_complete_admin_husky_pre_auto_gc__entry", "label": "pre-auto-gc script", "file_type": "code", "source_file": ".husky/_/pre-auto-gc", "source_location": "L1", "metadata": {"language": "bash", "kind": "bash_entrypoint"}}], "edges": [{"source": "c_users_evert_downloads_admin_complete_admin_husky_pre_auto_gc", "target": "c_users_evert_downloads_admin_complete_admin_husky_pre_auto_gc__entry", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".husky/_/pre-auto-gc", "source_location": "L1", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_husky_pre_auto_gc", "target": "dirname_0_h", "relation": "imports", "confidence": "EXTRACTED", "source_file": ".husky/_/pre-auto-gc", "source_location": "L2", "weight": 1.0, "context": "import"}]} \ No newline at end of file diff --git a/apps/admin-frontend/graphify-out/cache/ast/v0.9.8/d0b0ee2878cb9efe03dfbf9327e5038d7a10eabd56ad9b1321c45eff1447e98d.json b/apps/admin-frontend/graphify-out/cache/ast/v0.9.8/d0b0ee2878cb9efe03dfbf9327e5038d7a10eabd56ad9b1321c45eff1447e98d.json deleted file mode 100644 index c3c6c08..0000000 --- a/apps/admin-frontend/graphify-out/cache/ast/v0.9.8/d0b0ee2878cb9efe03dfbf9327e5038d7a10eabd56ad9b1321c45eff1447e98d.json +++ /dev/null @@ -1 +0,0 @@ -{"nodes": [{"id": "c_users_evert_downloads_admin_complete_admin_husky_pre_commit", "label": "pre-commit", "file_type": "code", "source_file": ".husky/_/pre-commit", "source_location": "L1", "metadata": {"language": "bash", "kind": "file"}}, {"id": "c_users_evert_downloads_admin_complete_admin_husky_pre_commit__entry", "label": "pre-commit script", "file_type": "code", "source_file": ".husky/_/pre-commit", "source_location": "L1", "metadata": {"language": "bash", "kind": "bash_entrypoint"}}], "edges": [{"source": "c_users_evert_downloads_admin_complete_admin_husky_pre_commit", "target": "c_users_evert_downloads_admin_complete_admin_husky_pre_commit__entry", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".husky/_/pre-commit", "source_location": "L1", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_husky_pre_commit", "target": "dirname_0_h", "relation": "imports", "confidence": "EXTRACTED", "source_file": ".husky/_/pre-commit", "source_location": "L2", "weight": 1.0, "context": "import"}]} \ No newline at end of file diff --git a/apps/admin-frontend/graphify-out/cache/ast/v0.9.8/d571f11fd4274692937568cb770a19c830c585c6cef79f550d5c27340871a18f.json b/apps/admin-frontend/graphify-out/cache/ast/v0.9.8/d571f11fd4274692937568cb770a19c830c585c6cef79f550d5c27340871a18f.json deleted file mode 100644 index ad90c13..0000000 --- a/apps/admin-frontend/graphify-out/cache/ast/v0.9.8/d571f11fd4274692937568cb770a19c830c585c6cef79f550d5c27340871a18f.json +++ /dev/null @@ -1 +0,0 @@ -{"nodes": [{"id": "c_users_evert_downloads_admin_complete_admin_husky_prepare_commit_msg", "label": "prepare-commit-msg", "file_type": "code", "source_file": ".husky/_/prepare-commit-msg", "source_location": "L1", "metadata": {"language": "bash", "kind": "file"}}, {"id": "c_users_evert_downloads_admin_complete_admin_husky_prepare_commit_msg__entry", "label": "prepare-commit-msg script", "file_type": "code", "source_file": ".husky/_/prepare-commit-msg", "source_location": "L1", "metadata": {"language": "bash", "kind": "bash_entrypoint"}}], "edges": [{"source": "c_users_evert_downloads_admin_complete_admin_husky_prepare_commit_msg", "target": "c_users_evert_downloads_admin_complete_admin_husky_prepare_commit_msg__entry", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".husky/_/prepare-commit-msg", "source_location": "L1", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_husky_prepare_commit_msg", "target": "dirname_0_h", "relation": "imports", "confidence": "EXTRACTED", "source_file": ".husky/_/prepare-commit-msg", "source_location": "L2", "weight": 1.0, "context": "import"}]} \ No newline at end of file diff --git a/apps/admin-frontend/graphify-out/cache/ast/v0.9.8/dd427fbe45aac0d22b4b75cd3738b4f19bb6b9203413d57e83eb402c992e636d.json b/apps/admin-frontend/graphify-out/cache/ast/v0.9.8/dd427fbe45aac0d22b4b75cd3738b4f19bb6b9203413d57e83eb402c992e636d.json deleted file mode 100644 index e216749..0000000 --- a/apps/admin-frontend/graphify-out/cache/ast/v0.9.8/dd427fbe45aac0d22b4b75cd3738b4f19bb6b9203413d57e83eb402c992e636d.json +++ /dev/null @@ -1 +0,0 @@ -{"nodes": [{"id": "c_users_evert_downloads_admin_complete_admin_husky_pre_push", "label": "pre-push", "file_type": "code", "source_file": ".husky/_/pre-push", "source_location": "L1", "metadata": {"language": "bash", "kind": "file"}}, {"id": "c_users_evert_downloads_admin_complete_admin_husky_pre_push__entry", "label": "pre-push script", "file_type": "code", "source_file": ".husky/_/pre-push", "source_location": "L1", "metadata": {"language": "bash", "kind": "bash_entrypoint"}}], "edges": [{"source": "c_users_evert_downloads_admin_complete_admin_husky_pre_push", "target": "c_users_evert_downloads_admin_complete_admin_husky_pre_push__entry", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".husky/_/pre-push", "source_location": "L1", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_husky_pre_push", "target": "dirname_0_h", "relation": "imports", "confidence": "EXTRACTED", "source_file": ".husky/_/pre-push", "source_location": "L2", "weight": 1.0, "context": "import"}]} \ No newline at end of file diff --git a/apps/admin-frontend/graphify-out/cache/ast/v0.9.8/e959718c238b3e07a1780e4ab7828238990b8561d28361a2afd2ccad62b941a7.json b/apps/admin-frontend/graphify-out/cache/ast/v0.9.8/e959718c238b3e07a1780e4ab7828238990b8561d28361a2afd2ccad62b941a7.json deleted file mode 100644 index 5c7be36..0000000 --- a/apps/admin-frontend/graphify-out/cache/ast/v0.9.8/e959718c238b3e07a1780e4ab7828238990b8561d28361a2afd2ccad62b941a7.json +++ /dev/null @@ -1 +0,0 @@ -{"nodes": [{"id": "c_users_evert_downloads_admin_complete_admin_package_json", "label": "package.json", "file_type": "code", "source_file": "package.json", "source_location": "L1"}, {"id": "c_users_evert_downloads_admin_complete_admin_package_name", "label": "name", "file_type": "code", "source_file": "package.json", "source_location": "L2"}, {"id": "c_users_evert_downloads_admin_complete_admin_package_private", "label": "private", "file_type": "code", "source_file": "package.json", "source_location": "L3"}, {"id": "c_users_evert_downloads_admin_complete_admin_package_version", "label": "version", "file_type": "code", "source_file": "package.json", "source_location": "L4"}, {"id": "c_users_evert_downloads_admin_complete_admin_package_type", "label": "type", "file_type": "code", "source_file": "package.json", "source_location": "L5"}, {"id": "c_users_evert_downloads_admin_complete_admin_package_scripts", "label": "scripts", "file_type": "code", "source_file": "package.json", "source_location": "L6"}, {"id": "c_users_evert_downloads_admin_complete_admin_package_scripts_dev", "label": "dev", "file_type": "code", "source_file": "package.json", "source_location": "L7"}, {"id": "c_users_evert_downloads_admin_complete_admin_package_scripts_build", "label": "build", "file_type": "code", "source_file": "package.json", "source_location": "L8"}, {"id": "c_users_evert_downloads_admin_complete_admin_package_scripts_test", "label": "test", "file_type": "code", "source_file": "package.json", "source_location": "L9"}, {"id": "c_users_evert_downloads_admin_complete_admin_package_scripts_test_watch", "label": "test:watch", "file_type": "code", "source_file": "package.json", "source_location": "L10"}, {"id": "c_users_evert_downloads_admin_complete_admin_package_scripts_test_ui", "label": "test:ui", "file_type": "code", "source_file": "package.json", "source_location": "L11"}, {"id": "c_users_evert_downloads_admin_complete_admin_package_scripts_test_coverage", "label": "test:coverage", "file_type": "code", "source_file": "package.json", "source_location": "L12"}, {"id": "c_users_evert_downloads_admin_complete_admin_package_scripts_lint", "label": "lint", "file_type": "code", "source_file": "package.json", "source_location": "L13"}, {"id": "c_users_evert_downloads_admin_complete_admin_package_scripts_lint_fix", "label": "lint:fix", "file_type": "code", "source_file": "package.json", "source_location": "L14"}, {"id": "c_users_evert_downloads_admin_complete_admin_package_scripts_format", "label": "format", "file_type": "code", "source_file": "package.json", "source_location": "L15"}, {"id": "c_users_evert_downloads_admin_complete_admin_package_scripts_format_check", "label": "format:check", "file_type": "code", "source_file": "package.json", "source_location": "L16"}, {"id": "c_users_evert_downloads_admin_complete_admin_package_scripts_preview", "label": "preview", "file_type": "code", "source_file": "package.json", "source_location": "L17"}, {"id": "c_users_evert_downloads_admin_complete_admin_package_scripts_prepare", "label": "prepare", "file_type": "code", "source_file": "package.json", "source_location": "L18"}, {"id": "c_users_evert_downloads_admin_complete_admin_package_dependencies", "label": "dependencies", "file_type": "code", "source_file": "package.json", "source_location": "L20"}, {"id": "c_users_evert_downloads_admin_complete_admin_package_dependencies_oidc_client_ts", "label": "oidc-client-ts", "file_type": "code", "source_file": "package.json", "source_location": "L21"}, {"id": "c_users_evert_downloads_admin_complete_admin_package_dependencies_react", "label": "react", "file_type": "code", "source_file": "package.json", "source_location": "L22"}, {"id": "c_users_evert_downloads_admin_complete_admin_package_dependencies_react_dom", "label": "react-dom", "file_type": "code", "source_file": "package.json", "source_location": "L23"}, {"id": "c_users_evert_downloads_admin_complete_admin_package_dependencies_react_router", "label": "react-router", "file_type": "code", "source_file": "package.json", "source_location": "L24"}, {"id": "c_users_evert_downloads_admin_complete_admin_package_devdependencies", "label": "devDependencies", "file_type": "code", "source_file": "package.json", "source_location": "L26"}, {"id": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_eslint_js", "label": "@eslint/js", "file_type": "code", "source_file": "package.json", "source_location": "L27"}, {"id": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_tailwindcss_vite", "label": "@tailwindcss/vite", "file_type": "code", "source_file": "package.json", "source_location": "L28"}, {"id": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_testing_library_jest_dom", "label": "@testing-library/jest-dom", "file_type": "code", "source_file": "package.json", "source_location": "L29"}, {"id": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_testing_library_react", "label": "@testing-library/react", "file_type": "code", "source_file": "package.json", "source_location": "L30"}, {"id": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_testing_library_user_event", "label": "@testing-library/user-event", "file_type": "code", "source_file": "package.json", "source_location": "L31"}, {"id": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_types_node", "label": "@types/node", "file_type": "code", "source_file": "package.json", "source_location": "L32"}, {"id": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_types_react", "label": "@types/react", "file_type": "code", "source_file": "package.json", "source_location": "L33"}, {"id": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_types_react_dom", "label": "@types/react-dom", "file_type": "code", "source_file": "package.json", "source_location": "L34"}, {"id": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_vitejs_plugin_react", "label": "@vitejs/plugin-react", "file_type": "code", "source_file": "package.json", "source_location": "L35"}, {"id": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_vitest_ui", "label": "@vitest/ui", "file_type": "code", "source_file": "package.json", "source_location": "L36"}, {"id": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_eslint", "label": "eslint", "file_type": "code", "source_file": "package.json", "source_location": "L37"}, {"id": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_eslint_config_prettier", "label": "eslint-config-prettier", "file_type": "code", "source_file": "package.json", "source_location": "L38"}, {"id": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_eslint_plugin_react_hooks", "label": "eslint-plugin-react-hooks", "file_type": "code", "source_file": "package.json", "source_location": "L39"}, {"id": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_eslint_plugin_react_refresh", "label": "eslint-plugin-react-refresh", "file_type": "code", "source_file": "package.json", "source_location": "L40"}, {"id": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_globals", "label": "globals", "file_type": "code", "source_file": "package.json", "source_location": "L41"}, {"id": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_husky", "label": "husky", "file_type": "code", "source_file": "package.json", "source_location": "L42"}, {"id": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_jsdom", "label": "jsdom", "file_type": "code", "source_file": "package.json", "source_location": "L43"}, {"id": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_lint_staged", "label": "lint-staged", "file_type": "code", "source_file": "package.json", "source_location": "L44"}, {"id": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_msw", "label": "msw", "file_type": "code", "source_file": "package.json", "source_location": "L45"}, {"id": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_prettier", "label": "prettier", "file_type": "code", "source_file": "package.json", "source_location": "L46"}, {"id": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_tailwindcss", "label": "tailwindcss", "file_type": "code", "source_file": "package.json", "source_location": "L47"}, {"id": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_typescript", "label": "typescript", "file_type": "code", "source_file": "package.json", "source_location": "L48"}, {"id": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_typescript_eslint", "label": "typescript-eslint", "file_type": "code", "source_file": "package.json", "source_location": "L49"}, {"id": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_vite", "label": "vite", "file_type": "code", "source_file": "package.json", "source_location": "L50"}, {"id": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_vitest", "label": "vitest", "file_type": "code", "source_file": "package.json", "source_location": "L51"}], "edges": [{"source": "c_users_evert_downloads_admin_complete_admin_package_json", "target": "c_users_evert_downloads_admin_complete_admin_package_name", "relation": "contains", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L2", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_package_json", "target": "c_users_evert_downloads_admin_complete_admin_package_private", "relation": "contains", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L3", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_package_json", "target": "c_users_evert_downloads_admin_complete_admin_package_version", "relation": "contains", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L4", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_package_json", "target": "c_users_evert_downloads_admin_complete_admin_package_type", "relation": "contains", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L5", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_package_json", "target": "c_users_evert_downloads_admin_complete_admin_package_scripts", "relation": "contains", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L6", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_package_scripts", "target": "c_users_evert_downloads_admin_complete_admin_package_scripts_dev", "relation": "contains", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L7", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_package_scripts", "target": "c_users_evert_downloads_admin_complete_admin_package_scripts_build", "relation": "contains", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L8", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_package_scripts", "target": "c_users_evert_downloads_admin_complete_admin_package_scripts_test", "relation": "contains", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L9", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_package_scripts", "target": "c_users_evert_downloads_admin_complete_admin_package_scripts_test_watch", "relation": "contains", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L10", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_package_scripts", "target": "c_users_evert_downloads_admin_complete_admin_package_scripts_test_ui", "relation": "contains", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L11", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_package_scripts", "target": "c_users_evert_downloads_admin_complete_admin_package_scripts_test_coverage", "relation": "contains", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L12", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_package_scripts", "target": "c_users_evert_downloads_admin_complete_admin_package_scripts_lint", "relation": "contains", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L13", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_package_scripts", "target": "c_users_evert_downloads_admin_complete_admin_package_scripts_lint_fix", "relation": "contains", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L14", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_package_scripts", "target": "c_users_evert_downloads_admin_complete_admin_package_scripts_format", "relation": "contains", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L15", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_package_scripts", "target": "c_users_evert_downloads_admin_complete_admin_package_scripts_format_check", "relation": "contains", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L16", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_package_scripts", "target": "c_users_evert_downloads_admin_complete_admin_package_scripts_preview", "relation": "contains", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L17", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_package_scripts", "target": "c_users_evert_downloads_admin_complete_admin_package_scripts_prepare", "relation": "contains", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L18", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_package_json", "target": "c_users_evert_downloads_admin_complete_admin_package_dependencies", "relation": "contains", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L20", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_package_dependencies", "target": "c_users_evert_downloads_admin_complete_admin_package_dependencies_oidc_client_ts", "relation": "contains", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L21", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_package_dependencies_oidc_client_ts", "target": "oidc_client_ts", "relation": "imports", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L21", "weight": 1.0, "context": "import"}, {"source": "c_users_evert_downloads_admin_complete_admin_package_dependencies", "target": "c_users_evert_downloads_admin_complete_admin_package_dependencies_react", "relation": "contains", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L22", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_package_dependencies_react", "target": "react", "relation": "imports", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L22", "weight": 1.0, "context": "import"}, {"source": "c_users_evert_downloads_admin_complete_admin_package_dependencies", "target": "c_users_evert_downloads_admin_complete_admin_package_dependencies_react_dom", "relation": "contains", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L23", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_package_dependencies_react_dom", "target": "react_dom", "relation": "imports", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L23", "weight": 1.0, "context": "import"}, {"source": "c_users_evert_downloads_admin_complete_admin_package_dependencies", "target": "c_users_evert_downloads_admin_complete_admin_package_dependencies_react_router", "relation": "contains", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L24", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_package_dependencies_react_router", "target": "react_router", "relation": "imports", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L24", "weight": 1.0, "context": "import"}, {"source": "c_users_evert_downloads_admin_complete_admin_package_json", "target": "c_users_evert_downloads_admin_complete_admin_package_devdependencies", "relation": "contains", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L26", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_package_devdependencies", "target": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_eslint_js", "relation": "contains", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L27", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_eslint_js", "target": "eslint_js", "relation": "imports", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L27", "weight": 1.0, "context": "import"}, {"source": "c_users_evert_downloads_admin_complete_admin_package_devdependencies", "target": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_tailwindcss_vite", "relation": "contains", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L28", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_tailwindcss_vite", "target": "tailwindcss_vite", "relation": "imports", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L28", "weight": 1.0, "context": "import"}, {"source": "c_users_evert_downloads_admin_complete_admin_package_devdependencies", "target": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_testing_library_jest_dom", "relation": "contains", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L29", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_testing_library_jest_dom", "target": "testing_library_jest_dom", "relation": "imports", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L29", "weight": 1.0, "context": "import"}, {"source": "c_users_evert_downloads_admin_complete_admin_package_devdependencies", "target": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_testing_library_react", "relation": "contains", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L30", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_testing_library_react", "target": "testing_library_react", "relation": "imports", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L30", "weight": 1.0, "context": "import"}, {"source": "c_users_evert_downloads_admin_complete_admin_package_devdependencies", "target": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_testing_library_user_event", "relation": "contains", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L31", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_testing_library_user_event", "target": "testing_library_user_event", "relation": "imports", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L31", "weight": 1.0, "context": "import"}, {"source": "c_users_evert_downloads_admin_complete_admin_package_devdependencies", "target": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_types_node", "relation": "contains", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L32", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_types_node", "target": "types_node", "relation": "imports", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L32", "weight": 1.0, "context": "import"}, {"source": "c_users_evert_downloads_admin_complete_admin_package_devdependencies", "target": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_types_react", "relation": "contains", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L33", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_types_react", "target": "types_react", "relation": "imports", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L33", "weight": 1.0, "context": "import"}, {"source": "c_users_evert_downloads_admin_complete_admin_package_devdependencies", "target": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_types_react_dom", "relation": "contains", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L34", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_types_react_dom", "target": "types_react_dom", "relation": "imports", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L34", "weight": 1.0, "context": "import"}, {"source": "c_users_evert_downloads_admin_complete_admin_package_devdependencies", "target": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_vitejs_plugin_react", "relation": "contains", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L35", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_vitejs_plugin_react", "target": "vitejs_plugin_react", "relation": "imports", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L35", "weight": 1.0, "context": "import"}, {"source": "c_users_evert_downloads_admin_complete_admin_package_devdependencies", "target": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_vitest_ui", "relation": "contains", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L36", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_vitest_ui", "target": "vitest_ui", "relation": "imports", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L36", "weight": 1.0, "context": "import"}, {"source": "c_users_evert_downloads_admin_complete_admin_package_devdependencies", "target": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_eslint", "relation": "contains", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L37", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_eslint", "target": "eslint", "relation": "imports", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L37", "weight": 1.0, "context": "import"}, {"source": "c_users_evert_downloads_admin_complete_admin_package_devdependencies", "target": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_eslint_config_prettier", "relation": "contains", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L38", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_eslint_config_prettier", "target": "eslint_config_prettier", "relation": "imports", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L38", "weight": 1.0, "context": "import"}, {"source": "c_users_evert_downloads_admin_complete_admin_package_devdependencies", "target": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_eslint_plugin_react_hooks", "relation": "contains", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L39", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_eslint_plugin_react_hooks", "target": "eslint_plugin_react_hooks", "relation": "imports", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L39", "weight": 1.0, "context": "import"}, {"source": "c_users_evert_downloads_admin_complete_admin_package_devdependencies", "target": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_eslint_plugin_react_refresh", "relation": "contains", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L40", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_eslint_plugin_react_refresh", "target": "eslint_plugin_react_refresh", "relation": "imports", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L40", "weight": 1.0, "context": "import"}, {"source": "c_users_evert_downloads_admin_complete_admin_package_devdependencies", "target": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_globals", "relation": "contains", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L41", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_globals", "target": "globals", "relation": "imports", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L41", "weight": 1.0, "context": "import"}, {"source": "c_users_evert_downloads_admin_complete_admin_package_devdependencies", "target": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_husky", "relation": "contains", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L42", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_husky", "target": "husky", "relation": "imports", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L42", "weight": 1.0, "context": "import"}, {"source": "c_users_evert_downloads_admin_complete_admin_package_devdependencies", "target": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_jsdom", "relation": "contains", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L43", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_jsdom", "target": "jsdom", "relation": "imports", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L43", "weight": 1.0, "context": "import"}, {"source": "c_users_evert_downloads_admin_complete_admin_package_devdependencies", "target": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_lint_staged", "relation": "contains", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L44", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_lint_staged", "target": "lint_staged", "relation": "imports", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L44", "weight": 1.0, "context": "import"}, {"source": "c_users_evert_downloads_admin_complete_admin_package_devdependencies", "target": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_msw", "relation": "contains", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L45", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_msw", "target": "msw", "relation": "imports", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L45", "weight": 1.0, "context": "import"}, {"source": "c_users_evert_downloads_admin_complete_admin_package_devdependencies", "target": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_prettier", "relation": "contains", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L46", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_prettier", "target": "prettier", "relation": "imports", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L46", "weight": 1.0, "context": "import"}, {"source": "c_users_evert_downloads_admin_complete_admin_package_devdependencies", "target": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_tailwindcss", "relation": "contains", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L47", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_tailwindcss", "target": "tailwindcss", "relation": "imports", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L47", "weight": 1.0, "context": "import"}, {"source": "c_users_evert_downloads_admin_complete_admin_package_devdependencies", "target": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_typescript", "relation": "contains", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L48", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_typescript", "target": "typescript", "relation": "imports", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L48", "weight": 1.0, "context": "import"}, {"source": "c_users_evert_downloads_admin_complete_admin_package_devdependencies", "target": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_typescript_eslint", "relation": "contains", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L49", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_typescript_eslint", "target": "typescript_eslint", "relation": "imports", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L49", "weight": 1.0, "context": "import"}, {"source": "c_users_evert_downloads_admin_complete_admin_package_devdependencies", "target": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_vite", "relation": "contains", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L50", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_vite", "target": "vite", "relation": "imports", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L50", "weight": 1.0, "context": "import"}, {"source": "c_users_evert_downloads_admin_complete_admin_package_devdependencies", "target": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_vitest", "relation": "contains", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L51", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_package_devdependencies_vitest", "target": "vitest", "relation": "imports", "confidence": "EXTRACTED", "source_file": "package.json", "source_location": "L51", "weight": 1.0, "context": "import"}]} \ No newline at end of file diff --git a/apps/admin-frontend/graphify-out/cache/ast/v0.9.8/f5104aea4b990fe94fe2eacb0323c2128b3f66f11d3fc388538530dada2db808.json b/apps/admin-frontend/graphify-out/cache/ast/v0.9.8/f5104aea4b990fe94fe2eacb0323c2128b3f66f11d3fc388538530dada2db808.json deleted file mode 100644 index d8f7b78..0000000 --- a/apps/admin-frontend/graphify-out/cache/ast/v0.9.8/f5104aea4b990fe94fe2eacb0323c2128b3f66f11d3fc388538530dada2db808.json +++ /dev/null @@ -1 +0,0 @@ -{"nodes": [{"id": "c_users_evert_downloads_admin_complete_admin_husky_post_checkout", "label": "post-checkout", "file_type": "code", "source_file": ".husky/_/post-checkout", "source_location": "L1", "metadata": {"language": "bash", "kind": "file"}}, {"id": "c_users_evert_downloads_admin_complete_admin_husky_post_checkout__entry", "label": "post-checkout script", "file_type": "code", "source_file": ".husky/_/post-checkout", "source_location": "L1", "metadata": {"language": "bash", "kind": "bash_entrypoint"}}], "edges": [{"source": "c_users_evert_downloads_admin_complete_admin_husky_post_checkout", "target": "c_users_evert_downloads_admin_complete_admin_husky_post_checkout__entry", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".husky/_/post-checkout", "source_location": "L1", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_husky_post_checkout", "target": "dirname_0_h", "relation": "imports", "confidence": "EXTRACTED", "source_file": ".husky/_/post-checkout", "source_location": "L2", "weight": 1.0, "context": "import"}]} \ No newline at end of file diff --git a/apps/admin-frontend/graphify-out/cache/ast/v0.9.8/f99a890a7c2de1e26a154549cb667ee9a884327e25ddbd6380ab23a986570ffc.json b/apps/admin-frontend/graphify-out/cache/ast/v0.9.8/f99a890a7c2de1e26a154549cb667ee9a884327e25ddbd6380ab23a986570ffc.json deleted file mode 100644 index 8b0980e..0000000 --- a/apps/admin-frontend/graphify-out/cache/ast/v0.9.8/f99a890a7c2de1e26a154549cb667ee9a884327e25ddbd6380ab23a986570ffc.json +++ /dev/null @@ -1 +0,0 @@ -{"nodes": [{"id": "c_users_evert_downloads_admin_complete_admin_husky_h", "label": "h", "file_type": "code", "source_file": ".husky/_/h", "source_location": "L1", "metadata": {"language": "bash", "kind": "file"}}, {"id": "c_users_evert_downloads_admin_complete_admin_husky_h__entry", "label": "h script", "file_type": "code", "source_file": ".husky/_/h", "source_location": "L1", "metadata": {"language": "bash", "kind": "bash_entrypoint"}}, {"id": "c_users_evert_downloads_admin_complete_admin_husky_h_path", "label": "PATH", "file_type": "code", "source_file": ".husky/_/h", "source_location": "L16", "metadata": {"language": "bash", "kind": "code"}}], "edges": [{"source": "c_users_evert_downloads_admin_complete_admin_husky_h", "target": "c_users_evert_downloads_admin_complete_admin_husky_h__entry", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".husky/_/h", "source_location": "L1", "weight": 1.0}, {"source": "c_users_evert_downloads_admin_complete_admin_husky_h", "target": "i", "relation": "imports", "confidence": "EXTRACTED", "source_file": ".husky/_/h", "source_location": "L12", "weight": 1.0, "context": "import"}, {"source": "c_users_evert_downloads_admin_complete_admin_husky_h", "target": "c_users_evert_downloads_admin_complete_admin_husky_h_path", "relation": "defines", "confidence": "EXTRACTED", "source_file": ".husky/_/h", "source_location": "L16", "weight": 1.0}]} \ No newline at end of file diff --git a/apps/admin-frontend/graphify-out/cache/semantic/006b7da94b10b3d5d0c001968c16046e5a8fd2ef70d7e7a8b2db9cff92e4a3a1.json b/apps/admin-frontend/graphify-out/cache/semantic/006b7da94b10b3d5d0c001968c16046e5a8fd2ef70d7e7a8b2db9cff92e4a3a1.json deleted file mode 100644 index f2f8d85..0000000 --- a/apps/admin-frontend/graphify-out/cache/semantic/006b7da94b10b3d5d0c001968c16046e5a8fd2ef70d7e7a8b2db9cff92e4a3a1.json +++ /dev/null @@ -1 +0,0 @@ -{"nodes": [{"id": "skills_admin_api_contract_skill_admin_api_contract", "label": "Admin API Contract Skill", "file_type": "document", "source_file": ".skills/admin-api-contract/SKILL.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null}, {"id": "skills_admin_api_contract_skill_servicedto", "label": "ServiceDto Interface Pattern", "file_type": "code", "source_file": ".skills/admin-api-contract/SKILL.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null}, {"id": "skills_admin_api_contract_skill_tenant_scoping_mechanism", "label": "Tenant Scoping Mechanism Decision Table", "file_type": "rationale", "source_file": ".skills/admin-api-contract/SKILL.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null, "rationale": "JWT claim is most likely mechanism given IdentityServer setup; no extra work needed if so, since token already proves tenant identity."}, {"id": "skills_admin_api_contract_skill_field_translation_patterns", "label": "Common Field Translation Patterns (API to Domain)", "file_type": "concept", "source_file": ".skills/admin-api-contract/SKILL.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null}], "edges": [{"source": "skills_admin_api_contract_skill_admin_api_contract", "target": "skills_admin_api_contract_skill_servicedto", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": ".skills/admin-api-contract/SKILL.md", "source_location": null, "weight": 1.0}, {"source": "skills_admin_api_contract_skill_admin_api_contract", "target": "skills_admin_api_contract_skill_tenant_scoping_mechanism", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": ".skills/admin-api-contract/SKILL.md", "source_location": null, "weight": 1.0}, {"source": "skills_admin_api_contract_skill_admin_api_contract", "target": "skills_admin_api_contract_skill_field_translation_patterns", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": ".skills/admin-api-contract/SKILL.md", "source_location": null, "weight": 1.0}, {"source": "skills_admin_api_contract_skill_servicedto", "target": "docs_domain_service", "relation": "shares_data_with", "confidence": "INFERRED", "confidence_score": 0.95, "source_file": ".skills/admin-api-contract/SKILL.md", "source_location": null, "weight": 1.0}, {"source": "skills_admin_api_contract_skill_tenant_scoping_mechanism", "target": "docs_api_tenant_scoping", "relation": "conceptually_related_to", "confidence": "INFERRED", "confidence_score": 0.95, "source_file": ".skills/admin-api-contract/SKILL.md", "source_location": null, "weight": 1.0}, {"source": "skills_admin_api_contract_skill_tenant_scoping_mechanism", "target": "claude_md_tenantcontext_param_rule", "relation": "conceptually_related_to", "confidence": "INFERRED", "confidence_score": 0.85, "source_file": ".skills/admin-api-contract/SKILL.md", "source_location": null, "weight": 1.0}, {"source": "skills_admin_api_contract_skill_admin_api_contract", "target": "skills_admin_feature_vertical_skill_admin_feature_vertical", "relation": "semantically_similar_to", "confidence": "INFERRED", "confidence_score": 0.75, "source_file": ".skills/admin-api-contract/SKILL.md", "source_location": null, "weight": 1.0}], "hyperedges": []} \ No newline at end of file diff --git a/apps/admin-frontend/graphify-out/cache/semantic/0d796317a126ce9e51f38c8054499148ef6c3ba2f18a7b3f741c397ef5ced968.json b/apps/admin-frontend/graphify-out/cache/semantic/0d796317a126ce9e51f38c8054499148ef6c3ba2f18a7b3f741c397ef5ced968.json deleted file mode 100644 index a42c9dc..0000000 --- a/apps/admin-frontend/graphify-out/cache/semantic/0d796317a126ce9e51f38c8054499148ef6c3ba2f18a7b3f741c397ef5ced968.json +++ /dev/null @@ -1 +0,0 @@ -{"nodes": [{"id": "docs_adr_001_clean_architecture_layers", "label": "ADR 001: Clean Architecture Layer Structure", "file_type": "rationale", "source_file": "docs/adr/001-clean-architecture-layers.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null, "rationale": "Strict inward-only dependency rule (domain <- application <- infrastructure + composition + presentation) keeps domain and use case logic framework-agnostic and testable in isolation; swapping IdentityServer, HTTP client, or router requires changes only in outer layers."}], "edges": [], "hyperedges": []} \ No newline at end of file diff --git a/apps/admin-frontend/graphify-out/cache/semantic/0e5e531cc7a8986593ccf1ab8497db9c94c5d6631301a1c52349931f34fa9ae7.json b/apps/admin-frontend/graphify-out/cache/semantic/0e5e531cc7a8986593ccf1ab8497db9c94c5d6631301a1c52349931f34fa9ae7.json deleted file mode 100644 index ea30a6e..0000000 --- a/apps/admin-frontend/graphify-out/cache/semantic/0e5e531cc7a8986593ccf1ab8497db9c94c5d6631301a1c52349931f34fa9ae7.json +++ /dev/null @@ -1 +0,0 @@ -{"nodes": [{"id": "docs_status_feature_status", "label": "Feature Status Tracker", "file_type": "document", "source_file": "docs/STATUS.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null}, {"id": "docs_status_httpclient_stub", "label": "HttpClient Interface + AuthenticatedHttpClient (stub status)", "file_type": "code", "source_file": "docs/STATUS.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null}, {"id": "docs_status_auth_vertical", "label": "Auth Vertical (done status)", "file_type": "concept", "source_file": "docs/STATUS.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null}, {"id": "docs_status_services_vertical", "label": "Services Vertical (stub status)", "file_type": "concept", "source_file": "docs/STATUS.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null}, {"id": "docs_status_clients_vertical", "label": "Clients Vertical (stub status)", "file_type": "concept", "source_file": "docs/STATUS.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null}, {"id": "docs_status_appointments_vertical", "label": "Appointments Vertical (stub status)", "file_type": "concept", "source_file": "docs/STATUS.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null}, {"id": "docs_status_dashboard_vertical", "label": "Dashboard Vertical (stub status)", "file_type": "concept", "source_file": "docs/STATUS.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null}, {"id": "docs_status_inbox_vertical", "label": "Inbox (Conversations) Vertical (stub status)", "file_type": "concept", "source_file": "docs/STATUS.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null}, {"id": "docs_status_settings_vertical", "label": "Settings Vertical (stub status)", "file_type": "concept", "source_file": "docs/STATUS.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null}, {"id": "docs_status_recommended_build_order", "label": "Recommended Build Order", "file_type": "rationale", "source_file": "docs/STATUS.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null, "rationale": "HttpClient unblocks all REST features; Services first as simplest CRUD with no dependencies; Appointments depends on Services; Inbox depends on Clients; Dashboard depends on Appointments and Inbox; Settings independent."}], "edges": [{"source": "docs_status_feature_status", "target": "docs_status_httpclient_stub", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "docs/STATUS.md", "source_location": null, "weight": 1.0}, {"source": "docs_status_feature_status", "target": "docs_status_auth_vertical", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "docs/STATUS.md", "source_location": null, "weight": 1.0}, {"source": "docs_status_feature_status", "target": "docs_status_services_vertical", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "docs/STATUS.md", "source_location": null, "weight": 1.0}, {"source": "docs_status_feature_status", "target": "docs_status_clients_vertical", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "docs/STATUS.md", "source_location": null, "weight": 1.0}, {"source": "docs_status_feature_status", "target": "docs_status_appointments_vertical", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "docs/STATUS.md", "source_location": null, "weight": 1.0}, {"source": "docs_status_feature_status", "target": "docs_status_dashboard_vertical", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "docs/STATUS.md", "source_location": null, "weight": 1.0}, {"source": "docs_status_feature_status", "target": "docs_status_inbox_vertical", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "docs/STATUS.md", "source_location": null, "weight": 1.0}, {"source": "docs_status_feature_status", "target": "docs_status_settings_vertical", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "docs/STATUS.md", "source_location": null, "weight": 1.0}, {"source": "docs_status_feature_status", "target": "docs_status_recommended_build_order", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "docs/STATUS.md", "source_location": null, "weight": 1.0}, {"source": "docs_status_recommended_build_order", "target": "docs_status_httpclient_stub", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "docs/STATUS.md", "source_location": null, "weight": 1.0}, {"source": "docs_status_recommended_build_order", "target": "docs_status_services_vertical", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "docs/STATUS.md", "source_location": null, "weight": 1.0}, {"source": "docs_status_recommended_build_order", "target": "docs_status_clients_vertical", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "docs/STATUS.md", "source_location": null, "weight": 1.0}, {"source": "docs_status_recommended_build_order", "target": "docs_status_appointments_vertical", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "docs/STATUS.md", "source_location": null, "weight": 1.0}, {"source": "docs_status_recommended_build_order", "target": "docs_status_inbox_vertical", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "docs/STATUS.md", "source_location": null, "weight": 1.0}, {"source": "docs_status_recommended_build_order", "target": "docs_status_dashboard_vertical", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "docs/STATUS.md", "source_location": null, "weight": 1.0}, {"source": "docs_status_recommended_build_order", "target": "docs_status_settings_vertical", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "docs/STATUS.md", "source_location": null, "weight": 1.0}, {"source": "docs_status_services_vertical", "target": "docs_domain_service", "relation": "shares_data_with", "confidence": "INFERRED", "confidence_score": 0.95, "source_file": "docs/STATUS.md", "source_location": null, "weight": 1.0}, {"source": "docs_status_clients_vertical", "target": "docs_domain_client", "relation": "shares_data_with", "confidence": "INFERRED", "confidence_score": 0.95, "source_file": "docs/STATUS.md", "source_location": null, "weight": 1.0}, {"source": "docs_status_appointments_vertical", "target": "docs_domain_appointment", "relation": "shares_data_with", "confidence": "INFERRED", "confidence_score": 0.95, "source_file": "docs/STATUS.md", "source_location": null, "weight": 1.0}, {"source": "docs_status_inbox_vertical", "target": "docs_domain_conversation", "relation": "shares_data_with", "confidence": "INFERRED", "confidence_score": 0.95, "source_file": "docs/STATUS.md", "source_location": null, "weight": 1.0}, {"source": "docs_status_settings_vertical", "target": "docs_domain_business_settings", "relation": "shares_data_with", "confidence": "INFERRED", "confidence_score": 0.95, "source_file": "docs/STATUS.md", "source_location": null, "weight": 1.0}, {"source": "docs_status_httpclient_stub", "target": "skills_admin_feature_vertical_skill_httpclient", "relation": "conceptually_related_to", "confidence": "INFERRED", "confidence_score": 0.95, "source_file": "docs/STATUS.md", "source_location": null, "weight": 1.0}], "hyperedges": [{"id": "hyperedge_feature_vertical_build_order", "label": "Feature Vertical Build Order Chain", "nodes": ["docs_status_httpclient_stub", "docs_status_services_vertical", "docs_status_clients_vertical", "docs_status_appointments_vertical", "docs_status_inbox_vertical", "docs_status_dashboard_vertical", "docs_status_settings_vertical"], "relation": "form", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "docs/STATUS.md"}]} \ No newline at end of file diff --git a/apps/admin-frontend/graphify-out/cache/semantic/1902ad9650dabab71d145c262ceb47559c17c49094af55b2f6aae7abdbce37c2.json b/apps/admin-frontend/graphify-out/cache/semantic/1902ad9650dabab71d145c262ceb47559c17c49094af55b2f6aae7abdbce37c2.json deleted file mode 100644 index 361548f..0000000 --- a/apps/admin-frontend/graphify-out/cache/semantic/1902ad9650dabab71d145c262ceb47559c17c49094af55b2f6aae7abdbce37c2.json +++ /dev/null @@ -1 +0,0 @@ -{"nodes": [{"id": "readme_react_typescript_vite_template", "label": "React + TypeScript + Vite Template README", "file_type": "document", "source_file": "README.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null}, {"id": "readme_vite_plugin_react", "label": "@vitejs/plugin-react (Oxc)", "file_type": "concept", "source_file": "README.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null}, {"id": "readme_vite_plugin_react_swc", "label": "@vitejs/plugin-react-swc (SWC)", "file_type": "concept", "source_file": "README.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null}, {"id": "readme_react_compiler", "label": "React Compiler (not enabled)", "file_type": "concept", "source_file": "README.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null}, {"id": "readme_oxlint_config", "label": "Oxlint Type-Aware Configuration", "file_type": "concept", "source_file": "README.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null}], "edges": [{"source": "readme_react_typescript_vite_template", "target": "readme_vite_plugin_react", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "README.md", "source_location": null, "weight": 1.0}, {"source": "readme_react_typescript_vite_template", "target": "readme_vite_plugin_react_swc", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "README.md", "source_location": null, "weight": 1.0}, {"source": "readme_react_typescript_vite_template", "target": "readme_react_compiler", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "README.md", "source_location": null, "weight": 1.0}, {"source": "readme_react_typescript_vite_template", "target": "readme_oxlint_config", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "README.md", "source_location": null, "weight": 1.0}, {"source": "readme_react_typescript_vite_template", "target": "claude_md_tech_stack", "relation": "conceptually_related_to", "confidence": "INFERRED", "confidence_score": 0.65, "source_file": "README.md", "source_location": null, "weight": 1.0}], "hyperedges": []} \ No newline at end of file diff --git a/apps/admin-frontend/graphify-out/cache/semantic/2e9906cf88503ada29fd9a9826d6e9f7a78bad27dec7a2ac2b38a1dfe36130ef.json b/apps/admin-frontend/graphify-out/cache/semantic/2e9906cf88503ada29fd9a9826d6e9f7a78bad27dec7a2ac2b38a1dfe36130ef.json deleted file mode 100644 index aa4ba03..0000000 --- a/apps/admin-frontend/graphify-out/cache/semantic/2e9906cf88503ada29fd9a9826d6e9f7a78bad27dec7a2ac2b38a1dfe36130ef.json +++ /dev/null @@ -1 +0,0 @@ -{"nodes": [{"id": "docs_api_api_integration_guide", "label": "API Integration Guide", "file_type": "document", "source_file": "docs/API.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null}, {"id": "docs_api_base_url", "label": "VITE_API_BASE_URL Config", "file_type": "concept", "source_file": "docs/API.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null}, {"id": "docs_api_authentication", "label": "Bearer Token Authentication Flow", "file_type": "concept", "source_file": "docs/API.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null}, {"id": "docs_api_unauthenticatederror", "label": "UnauthenticatedError", "file_type": "code", "source_file": "docs/API.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null}, {"id": "docs_api_tenant_scoping", "label": "Tenant Scoping via JWT Claim", "file_type": "rationale", "source_file": "docs/API.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null, "rationale": "The tenant_id claim inside the JWT access token is read by the backend directly, so AuthenticatedHttpClient does not need a separate tenant header; TenantContext param remains for structural enforcement in the application layer."}, {"id": "docs_api_error_shape", "label": "API Error Shape (Placeholder)", "file_type": "concept", "source_file": "docs/API.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null}, {"id": "docs_api_apierror_class", "label": "ApiError Class", "file_type": "code", "source_file": "docs/API.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null}, {"id": "docs_api_pagination", "label": "Pagination Strategy (TBD)", "file_type": "concept", "source_file": "docs/API.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null}, {"id": "docs_api_resource_endpoints", "label": "Resource Endpoints Placeholder Section", "file_type": "document", "source_file": "docs/API.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null}, {"id": "docs_api_how_to_add_resource", "label": "How to Add a New Resource Workflow", "file_type": "concept", "source_file": "docs/API.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null}], "edges": [{"source": "docs_api_api_integration_guide", "target": "docs_api_base_url", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "docs/API.md", "source_location": null, "weight": 1.0}, {"source": "docs_api_api_integration_guide", "target": "docs_api_authentication", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "docs/API.md", "source_location": null, "weight": 1.0}, {"source": "docs_api_authentication", "target": "docs_api_unauthenticatederror", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "docs/API.md", "source_location": null, "weight": 1.0}, {"source": "docs_api_api_integration_guide", "target": "docs_api_tenant_scoping", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "docs/API.md", "source_location": null, "weight": 1.0}, {"source": "docs_api_api_integration_guide", "target": "docs_api_error_shape", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "docs/API.md", "source_location": null, "weight": 1.0}, {"source": "docs_api_error_shape", "target": "docs_api_apierror_class", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "docs/API.md", "source_location": null, "weight": 1.0}, {"source": "docs_api_api_integration_guide", "target": "docs_api_pagination", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "docs/API.md", "source_location": null, "weight": 1.0}, {"source": "docs_api_api_integration_guide", "target": "docs_api_resource_endpoints", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "docs/API.md", "source_location": null, "weight": 1.0}, {"source": "docs_api_api_integration_guide", "target": "docs_api_how_to_add_resource", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "docs/API.md", "source_location": null, "weight": 1.0}, {"source": "docs_api_how_to_add_resource", "target": "skills_admin_api_contract_skill_admin_api_contract", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "docs/API.md", "source_location": null, "weight": 1.0}, {"source": "docs_api_authentication", "target": "docs_decisions_oidc_client_ts", "relation": "conceptually_related_to", "confidence": "INFERRED", "confidence_score": 0.85, "source_file": "docs/API.md", "source_location": null, "weight": 1.0}, {"source": "docs_api_tenant_scoping", "target": "docs_decisions_tenant_id_claim_name", "relation": "conceptually_related_to", "confidence": "INFERRED", "confidence_score": 0.85, "source_file": "docs/API.md", "source_location": null, "weight": 1.0}, {"source": "docs_api_resource_endpoints", "target": "docs_domain_service", "relation": "conceptually_related_to", "confidence": "INFERRED", "confidence_score": 0.75, "source_file": "docs/API.md", "source_location": null, "weight": 1.0}, {"source": "docs_api_resource_endpoints", "target": "docs_domain_appointment", "relation": "conceptually_related_to", "confidence": "INFERRED", "confidence_score": 0.75, "source_file": "docs/API.md", "source_location": null, "weight": 1.0}, {"source": "docs_api_resource_endpoints", "target": "docs_domain_client", "relation": "conceptually_related_to", "confidence": "INFERRED", "confidence_score": 0.75, "source_file": "docs/API.md", "source_location": null, "weight": 1.0}, {"source": "docs_api_resource_endpoints", "target": "docs_domain_conversation", "relation": "conceptually_related_to", "confidence": "INFERRED", "confidence_score": 0.75, "source_file": "docs/API.md", "source_location": null, "weight": 1.0}, {"source": "docs_api_resource_endpoints", "target": "docs_domain_business_settings", "relation": "conceptually_related_to", "confidence": "INFERRED", "confidence_score": 0.75, "source_file": "docs/API.md", "source_location": null, "weight": 1.0}], "hyperedges": []} \ No newline at end of file diff --git a/apps/admin-frontend/graphify-out/cache/semantic/321850e5728abff0bb641538aa2df718b5295392e00fb17e49ff018fbdfe8be3.json b/apps/admin-frontend/graphify-out/cache/semantic/321850e5728abff0bb641538aa2df718b5295392e00fb17e49ff018fbdfe8be3.json deleted file mode 100644 index f301942..0000000 --- a/apps/admin-frontend/graphify-out/cache/semantic/321850e5728abff0bb641538aa2df718b5295392e00fb17e49ff018fbdfe8be3.json +++ /dev/null @@ -1 +0,0 @@ -{"nodes": [{"id": "docs_decisions_project_decisions_log", "label": "Project Decisions Log", "file_type": "document", "source_file": "docs/DECISIONS.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null}, {"id": "docs_decisions_erasablesyntaxonly", "label": "erasableSyntaxOnly Decision", "file_type": "rationale", "source_file": "docs/DECISIONS.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null, "rationale": "Forces explicit field declarations instead of constructor parameter shorthand, aligning with Clean Code principles at the cost of verbosity, applied project-wide."}, {"id": "docs_decisions_exactoptionalpropertytypes", "label": "exactOptionalPropertyTypes Decision", "file_type": "rationale", "source_file": "docs/DECISIONS.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null, "rationale": "Kept despite friction because domain models auth session/tenant context where field-absent vs explicitly-undefined are meaningfully different states; caught real bugs during Auth build."}, {"id": "docs_decisions_nouncheckedindexedaccess", "label": "noUncheckedIndexedAccess Decision", "file_type": "rationale", "source_file": "docs/DECISIONS.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null, "rationale": "Motivated by oidcUser.profile['tenant_id'] resolving to unknown not string; runtime guard typeof x === 'string' is the only safe approach."}, {"id": "docs_decisions_oidc_client_ts", "label": "oidc-client-ts Library Decision", "file_type": "rationale", "source_file": "docs/DECISIONS.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null, "rationale": "Framework-agnostic, maintained standard for Auth Code + PKCE flows with full TypeScript types."}, {"id": "docs_decisions_automaticsilentrenew_false", "label": "automaticSilentRenew: false Decision", "file_type": "rationale", "source_file": "docs/DECISIONS.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null, "rationale": "Event-driven background renewal is invisible to callers who cannot observe failure or trigger logout; explicit renewal in getCurrentSession() gives full control."}, {"id": "docs_decisions_handleauthcallback_unwrapped_errors", "label": "HandleAuthCallback Propagates Errors Unwrapped Decision", "file_type": "rationale", "source_file": "docs/DECISIONS.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null, "rationale": "Exact error shapes from oidc-client-ts on various failure modes were unknown at build time; wrapping prematurely would discard information the presentation layer might need. Revisit when IdentityServer backend exists and real error shapes observed."}, {"id": "docs_decisions_tenant_id_claim_name", "label": "tenant_id Claim Name Decision", "file_type": "rationale", "source_file": "docs/DECISIONS.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null, "rationale": "Used as claim name in IdentityServer tokens; only referenced in oidcUserToSessionMapper.ts; revisit when real token can be decoded and claim name confirmed."}, {"id": "docs_decisions_email_name_user_entity", "label": "email and name on User Entity Decision", "file_type": "rationale", "source_file": "docs/DECISIONS.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null, "rationale": "Included as optional fields marked as unverified assumptions since actual IdentityServer configuration not confirmed."}, {"id": "docs_decisions_no_server_state_library", "label": "No Server-State Library Decision", "file_type": "rationale", "source_file": "docs/DECISIONS.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null, "rationale": "Explicit project constraint from the brief; most state is server data scoped by tenant with no complex shared client state or advanced caching requirements in v1."}, {"id": "docs_decisions_useasync_immediate_flag", "label": "useAsync immediate Flag Decision", "file_type": "rationale", "source_file": "docs/DECISIONS.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null, "rationale": "Defaults to true for fetch-on-mount pattern; immediate:false covers action-style mutation calls triggered by user interaction."}, {"id": "docs_decisions_set_state_in_effect_suppression", "label": "set-state-in-effect Suppression Decision", "file_type": "rationale", "source_file": "docs/DECISIONS.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null, "rationale": "Documented false positive per react/react#34743; the rule traces async call graphs and flags setState after await though those calls are genuinely async."}, {"id": "docs_decisions_msw_onunhandledrequest_error", "label": "MSW onUnhandledRequest error Decision", "file_type": "rationale", "source_file": "docs/DECISIONS.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null, "rationale": "Silent failures like hanging promises are worse than loud ones; unhandled requests must fail immediately."}, {"id": "docs_decisions_no_real_network_calls_in_tests", "label": "No Real Network Calls in Tests Decision", "file_type": "rationale", "source_file": "docs/DECISIONS.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null, "rationale": "UserManager does its own internal fetch management so MSW (which intercepts fetch) can't mock it; a hand-written fake createFakeUserManager() is used instead."}, {"id": "docs_decisions_context_based_di", "label": "Context-based DI (not module-level singletons) Decision", "file_type": "rationale", "source_file": "docs/DECISIONS.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null, "rationale": "Singleton modules make testing harder via shared state between tests; context-based DI lets each test provide its own fake container without module-level mocking."}, {"id": "docs_decisions_protectedroute_loading_state", "label": "ProtectedRoute Handles Loading State Explicitly Decision", "file_type": "rationale", "source_file": "docs/DECISIONS.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null, "rationale": "Prevents a race condition where an authenticated user gets briefly redirected to login before getCurrentSession() resolves."}, {"id": "docs_decisions_design_language", "label": "Presentation Design Language Decision", "file_type": "rationale", "source_file": "docs/DECISIONS.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null, "rationale": "Slate gray primary, white surfaces, teal accent chosen to feel calm, professional, trustworthy for healthcare/wellness business owners rather than startup-flashy."}], "edges": [{"source": "docs_decisions_project_decisions_log", "target": "docs_decisions_erasablesyntaxonly", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "docs/DECISIONS.md", "source_location": null, "weight": 1.0}, {"source": "docs_decisions_project_decisions_log", "target": "docs_decisions_exactoptionalpropertytypes", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "docs/DECISIONS.md", "source_location": null, "weight": 1.0}, {"source": "docs_decisions_project_decisions_log", "target": "docs_decisions_nouncheckedindexedaccess", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "docs/DECISIONS.md", "source_location": null, "weight": 1.0}, {"source": "docs_decisions_project_decisions_log", "target": "docs_decisions_oidc_client_ts", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "docs/DECISIONS.md", "source_location": null, "weight": 1.0}, {"source": "docs_decisions_project_decisions_log", "target": "docs_decisions_automaticsilentrenew_false", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "docs/DECISIONS.md", "source_location": null, "weight": 1.0}, {"source": "docs_decisions_project_decisions_log", "target": "docs_decisions_handleauthcallback_unwrapped_errors", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "docs/DECISIONS.md", "source_location": null, "weight": 1.0}, {"source": "docs_decisions_project_decisions_log", "target": "docs_decisions_tenant_id_claim_name", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "docs/DECISIONS.md", "source_location": null, "weight": 1.0}, {"source": "docs_decisions_project_decisions_log", "target": "docs_decisions_email_name_user_entity", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "docs/DECISIONS.md", "source_location": null, "weight": 1.0}, {"source": "docs_decisions_project_decisions_log", "target": "docs_decisions_no_server_state_library", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "docs/DECISIONS.md", "source_location": null, "weight": 1.0}, {"source": "docs_decisions_project_decisions_log", "target": "docs_decisions_useasync_immediate_flag", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "docs/DECISIONS.md", "source_location": null, "weight": 1.0}, {"source": "docs_decisions_project_decisions_log", "target": "docs_decisions_set_state_in_effect_suppression", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "docs/DECISIONS.md", "source_location": null, "weight": 1.0}, {"source": "docs_decisions_project_decisions_log", "target": "docs_decisions_msw_onunhandledrequest_error", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "docs/DECISIONS.md", "source_location": null, "weight": 1.0}, {"source": "docs_decisions_project_decisions_log", "target": "docs_decisions_no_real_network_calls_in_tests", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "docs/DECISIONS.md", "source_location": null, "weight": 1.0}, {"source": "docs_decisions_project_decisions_log", "target": "docs_decisions_context_based_di", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "docs/DECISIONS.md", "source_location": null, "weight": 1.0}, {"source": "docs_decisions_project_decisions_log", "target": "docs_decisions_protectedroute_loading_state", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "docs/DECISIONS.md", "source_location": null, "weight": 1.0}, {"source": "docs_decisions_project_decisions_log", "target": "docs_decisions_design_language", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "docs/DECISIONS.md", "source_location": null, "weight": 1.0}, {"source": "docs_decisions_automaticsilentrenew_false", "target": "docs_adr_004_explicit_silent_renewal", "relation": "conceptually_related_to", "confidence": "INFERRED", "confidence_score": 0.95, "source_file": "docs/DECISIONS.md", "source_location": null, "weight": 1.0}, {"source": "docs_decisions_no_server_state_library", "target": "docs_adr_002_no_server_state_library", "relation": "conceptually_related_to", "confidence": "INFERRED", "confidence_score": 0.95, "source_file": "docs/DECISIONS.md", "source_location": null, "weight": 1.0}, {"source": "docs_decisions_context_based_di", "target": "docs_adr_003_manual_di_no_container_library", "relation": "conceptually_related_to", "confidence": "INFERRED", "confidence_score": 0.85, "source_file": "docs/DECISIONS.md", "source_location": null, "weight": 1.0}, {"source": "docs_decisions_tenant_id_claim_name", "target": "docs_domain_user", "relation": "conceptually_related_to", "confidence": "INFERRED", "confidence_score": 0.85, "source_file": "docs/DECISIONS.md", "source_location": null, "weight": 1.0}, {"source": "docs_decisions_email_name_user_entity", "target": "docs_domain_user", "relation": "shares_data_with", "confidence": "INFERRED", "confidence_score": 0.95, "source_file": "docs/DECISIONS.md", "source_location": null, "weight": 1.0}], "hyperedges": []} \ No newline at end of file diff --git a/apps/admin-frontend/graphify-out/cache/semantic/3587e95a5fad882303fd30f3c5431c4d66a30e9cbe07f69a9a0062c878ec1f68.json b/apps/admin-frontend/graphify-out/cache/semantic/3587e95a5fad882303fd30f3c5431c4d66a30e9cbe07f69a9a0062c878ec1f68.json deleted file mode 100644 index 92b94c7..0000000 --- a/apps/admin-frontend/graphify-out/cache/semantic/3587e95a5fad882303fd30f3c5431c4d66a30e9cbe07f69a9a0062c878ec1f68.json +++ /dev/null @@ -1 +0,0 @@ -{"nodes": [{"id": "claude_md_admin_panel_instructions", "label": "Admin Panel AI Assistant Instructions", "file_type": "document", "source_file": "CLAUDE.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null}, {"id": "claude_md_erasablesyntaxonly", "label": "erasableSyntaxOnly Constraint", "file_type": "rationale", "source_file": "CLAUDE.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null, "rationale": "No constructor parameter property shorthand; explicit field declaration and assignment in constructor body required."}, {"id": "claude_md_exactoptionalpropertytypes", "label": "exactOptionalPropertyTypes Constraint", "file_type": "rationale", "source_file": "CLAUDE.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null, "rationale": "Requires explicit undefined checks before assigning optional fields, never direct assignment of possibly-undefined values."}, {"id": "claude_md_nouncheckedindexedaccess", "label": "noUncheckedIndexedAccess Constraint", "file_type": "rationale", "source_file": "CLAUDE.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null, "rationale": "Index access returns T | undefined so all index accesses must be guarded."}, {"id": "claude_md_architecture_constraint", "label": "Clean Architecture Boundary Constraint", "file_type": "rationale", "source_file": "CLAUDE.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null, "rationale": "domain/ and application/ must never import React, react-router, or infrastructure/presentation; ESLint enforces this boundary."}, {"id": "claude_md_composition_container", "label": "composition/container.ts Composition Root", "file_type": "code", "source_file": "CLAUDE.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null}, {"id": "claude_md_tenantcontext_param_rule", "label": "TenantContext First Param Rule", "file_type": "rationale", "source_file": "CLAUDE.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null, "rationale": "Every repository interface method takes TenantContext as first param to structurally enforce tenant scoping in the application layer."}, {"id": "claude_md_tech_stack", "label": "Admin Panel Tech Stack", "file_type": "concept", "source_file": "CLAUDE.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null}, {"id": "claude_md_design_language", "label": "Admin Panel Design Language", "file_type": "concept", "source_file": "CLAUDE.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null}, {"id": "claude_md_current_state", "label": "Current Project State Summary", "file_type": "document", "source_file": "CLAUDE.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null}], "edges": [{"source": "claude_md_admin_panel_instructions", "target": "skills_admin_feature_vertical_skill_admin_feature_vertical", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "CLAUDE.md", "source_location": null, "weight": 1.0}, {"source": "claude_md_admin_panel_instructions", "target": "skills_admin_api_contract_skill_admin_api_contract", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "CLAUDE.md", "source_location": null, "weight": 1.0}, {"source": "claude_md_admin_panel_instructions", "target": "skills_admin_tdd_conventions_skill_admin_tdd_conventions", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "CLAUDE.md", "source_location": null, "weight": 1.0}, {"source": "claude_md_admin_panel_instructions", "target": "docs_status_feature_status", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "CLAUDE.md", "source_location": null, "weight": 1.0}, {"source": "claude_md_admin_panel_instructions", "target": "docs_domain_domain_glossary", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "CLAUDE.md", "source_location": null, "weight": 1.0}, {"source": "claude_md_admin_panel_instructions", "target": "docs_decisions_project_decisions_log", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "CLAUDE.md", "source_location": null, "weight": 1.0}, {"source": "claude_md_admin_panel_instructions", "target": "docs_api_api_integration_guide", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "CLAUDE.md", "source_location": null, "weight": 1.0}, {"source": "claude_md_admin_panel_instructions", "target": "docs_adr_001_clean_architecture_layers", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "CLAUDE.md", "source_location": null, "weight": 1.0}, {"source": "claude_md_admin_panel_instructions", "target": "docs_adr_002_no_server_state_library", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "CLAUDE.md", "source_location": null, "weight": 1.0}, {"source": "claude_md_admin_panel_instructions", "target": "docs_adr_003_manual_di_no_container_library", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "CLAUDE.md", "source_location": null, "weight": 1.0}, {"source": "claude_md_admin_panel_instructions", "target": "docs_adr_004_explicit_silent_renewal", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "CLAUDE.md", "source_location": null, "weight": 1.0}, {"source": "claude_md_admin_panel_instructions", "target": "claude_md_erasablesyntaxonly", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "CLAUDE.md", "source_location": null, "weight": 1.0}, {"source": "claude_md_admin_panel_instructions", "target": "claude_md_exactoptionalpropertytypes", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "CLAUDE.md", "source_location": null, "weight": 1.0}, {"source": "claude_md_admin_panel_instructions", "target": "claude_md_nouncheckedindexedaccess", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "CLAUDE.md", "source_location": null, "weight": 1.0}, {"source": "claude_md_admin_panel_instructions", "target": "claude_md_architecture_constraint", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "CLAUDE.md", "source_location": null, "weight": 1.0}, {"source": "claude_md_architecture_constraint", "target": "claude_md_composition_container", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "CLAUDE.md", "source_location": null, "weight": 1.0}, {"source": "claude_md_admin_panel_instructions", "target": "claude_md_tenantcontext_param_rule", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "CLAUDE.md", "source_location": null, "weight": 1.0}, {"source": "claude_md_admin_panel_instructions", "target": "claude_md_tech_stack", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "CLAUDE.md", "source_location": null, "weight": 1.0}, {"source": "claude_md_admin_panel_instructions", "target": "claude_md_design_language", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "CLAUDE.md", "source_location": null, "weight": 1.0}, {"source": "claude_md_admin_panel_instructions", "target": "claude_md_current_state", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "CLAUDE.md", "source_location": null, "weight": 1.0}, {"source": "claude_md_design_language", "target": "docs_decisions_design_language", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "CLAUDE.md", "source_location": null, "weight": 1.0}, {"source": "claude_md_erasablesyntaxonly", "target": "docs_decisions_erasablesyntaxonly", "relation": "conceptually_related_to", "confidence": "INFERRED", "confidence_score": 0.95, "source_file": "CLAUDE.md", "source_location": null, "weight": 1.0}, {"source": "claude_md_exactoptionalpropertytypes", "target": "docs_decisions_exactoptionalpropertytypes", "relation": "conceptually_related_to", "confidence": "INFERRED", "confidence_score": 0.95, "source_file": "CLAUDE.md", "source_location": null, "weight": 1.0}, {"source": "claude_md_nouncheckedindexedaccess", "target": "docs_decisions_nouncheckedindexedaccess", "relation": "conceptually_related_to", "confidence": "INFERRED", "confidence_score": 0.95, "source_file": "CLAUDE.md", "source_location": null, "weight": 1.0}, {"source": "claude_md_architecture_constraint", "target": "docs_adr_001_clean_architecture_layers", "relation": "conceptually_related_to", "confidence": "INFERRED", "confidence_score": 0.95, "source_file": "CLAUDE.md", "source_location": null, "weight": 1.0}], "hyperedges": [{"id": "hyperedge_typescript_strictness_constraints", "label": "TypeScript Strict Mode Constraints Group", "nodes": ["claude_md_erasablesyntaxonly", "claude_md_exactoptionalpropertytypes", "claude_md_nouncheckedindexedaccess", "docs_decisions_erasablesyntaxonly", "docs_decisions_exactoptionalpropertytypes", "docs_decisions_nouncheckedindexedaccess", "skills_admin_tdd_conventions_skill_erasablesyntaxonly_gotcha", "skills_admin_tdd_conventions_skill_exactoptionalpropertytypes_gotcha"], "relation": "form", "confidence": "INFERRED", "confidence_score": 0.85, "source_file": "CLAUDE.md"}]} \ No newline at end of file diff --git a/apps/admin-frontend/graphify-out/cache/semantic/42b29bf1a089ee99701714d72191b43a3c785ec87296979c1e9b0525f762f310.json b/apps/admin-frontend/graphify-out/cache/semantic/42b29bf1a089ee99701714d72191b43a3c785ec87296979c1e9b0525f762f310.json deleted file mode 100644 index cdb34f2..0000000 --- a/apps/admin-frontend/graphify-out/cache/semantic/42b29bf1a089ee99701714d72191b43a3c785ec87296979c1e9b0525f762f310.json +++ /dev/null @@ -1 +0,0 @@ -{"nodes": [{"id": "skills_admin_feature_vertical_skill_admin_feature_vertical", "label": "Admin Feature Vertical Skill", "file_type": "document", "source_file": ".skills/admin-feature-vertical/SKILL.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null}, {"id": "skills_admin_feature_vertical_skill_feature_vertical_slice", "label": "Feature Vertical Slice Pattern", "file_type": "rationale", "source_file": ".skills/admin-feature-vertical/SKILL.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null, "rationale": "A full slice from domain entity through use cases, infrastructure repository, presentation hook, and page component ensures consistent architecture per feature."}, {"id": "skills_admin_feature_vertical_skill_httpclient", "label": "HttpClient Interface", "file_type": "code", "source_file": ".skills/admin-feature-vertical/SKILL.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null}, {"id": "skills_admin_feature_vertical_skill_authenticatedhttpclient", "label": "AuthenticatedHttpClient Implementation", "file_type": "code", "source_file": ".skills/admin-feature-vertical/SKILL.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null}, {"id": "skills_admin_feature_vertical_skill_commit_checklist", "label": "Feature Vertical Commit Checklist", "file_type": "concept", "source_file": ".skills/admin-feature-vertical/SKILL.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null}, {"id": "skills_admin_feature_vertical_skill_listservices", "label": "ListServices Use Case Example", "file_type": "code", "source_file": ".skills/admin-feature-vertical/SKILL.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null}], "edges": [{"source": "skills_admin_feature_vertical_skill_admin_feature_vertical", "target": "skills_admin_feature_vertical_skill_feature_vertical_slice", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": ".skills/admin-feature-vertical/SKILL.md", "source_location": null, "weight": 1.0}, {"source": "skills_admin_feature_vertical_skill_admin_feature_vertical", "target": "skills_admin_feature_vertical_skill_httpclient", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": ".skills/admin-feature-vertical/SKILL.md", "source_location": null, "weight": 1.0}, {"source": "skills_admin_feature_vertical_skill_httpclient", "target": "skills_admin_feature_vertical_skill_authenticatedhttpclient", "relation": "implements", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": ".skills/admin-feature-vertical/SKILL.md", "source_location": null, "weight": 1.0}, {"source": "skills_admin_feature_vertical_skill_admin_feature_vertical", "target": "skills_admin_feature_vertical_skill_commit_checklist", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": ".skills/admin-feature-vertical/SKILL.md", "source_location": null, "weight": 1.0}, {"source": "skills_admin_feature_vertical_skill_admin_feature_vertical", "target": "skills_admin_feature_vertical_skill_listservices", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": ".skills/admin-feature-vertical/SKILL.md", "source_location": null, "weight": 1.0}, {"source": "skills_admin_feature_vertical_skill_httpclient", "target": "docs_status_httpclient_stub", "relation": "conceptually_related_to", "confidence": "INFERRED", "confidence_score": 0.95, "source_file": ".skills/admin-feature-vertical/SKILL.md", "source_location": null, "weight": 1.0}, {"source": "skills_admin_feature_vertical_skill_authenticatedhttpclient", "target": "docs_api_authentication", "relation": "conceptually_related_to", "confidence": "INFERRED", "confidence_score": 0.95, "source_file": ".skills/admin-feature-vertical/SKILL.md", "source_location": null, "weight": 1.0}, {"source": "skills_admin_feature_vertical_skill_authenticatedhttpclient", "target": "docs_api_apierror_class", "relation": "conceptually_related_to", "confidence": "INFERRED", "confidence_score": 0.85, "source_file": ".skills/admin-feature-vertical/SKILL.md", "source_location": null, "weight": 1.0}, {"source": "skills_admin_feature_vertical_skill_listservices", "target": "docs_domain_service", "relation": "shares_data_with", "confidence": "INFERRED", "confidence_score": 0.85, "source_file": ".skills/admin-feature-vertical/SKILL.md", "source_location": null, "weight": 1.0}, {"source": "skills_admin_feature_vertical_skill_feature_vertical_slice", "target": "skills_admin_api_contract_skill_admin_api_contract", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": ".skills/admin-feature-vertical/SKILL.md", "source_location": null, "weight": 1.0}, {"source": "skills_admin_feature_vertical_skill_feature_vertical_slice", "target": "skills_admin_tdd_conventions_skill_admin_tdd_conventions", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": ".skills/admin-feature-vertical/SKILL.md", "source_location": null, "weight": 1.0}], "hyperedges": []} \ No newline at end of file diff --git a/apps/admin-frontend/graphify-out/cache/semantic/44316b9e5de692d589c906aba6651261e448e2dc5acbade5461eacae518c0528.json b/apps/admin-frontend/graphify-out/cache/semantic/44316b9e5de692d589c906aba6651261e448e2dc5acbade5461eacae518c0528.json deleted file mode 100644 index 4377ecb..0000000 --- a/apps/admin-frontend/graphify-out/cache/semantic/44316b9e5de692d589c906aba6651261e448e2dc5acbade5461eacae518c0528.json +++ /dev/null @@ -1 +0,0 @@ -{"nodes": [{"id": "index_html_entry_point", "label": "index.html Vite Entry Point", "file_type": "code", "source_file": "index.html", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null}], "edges": [{"source": "index_html_entry_point", "target": "readme_react_typescript_vite_template", "relation": "conceptually_related_to", "confidence": "INFERRED", "confidence_score": 0.75, "source_file": "index.html", "source_location": null, "weight": 1.0}], "hyperedges": []} \ No newline at end of file diff --git a/apps/admin-frontend/graphify-out/cache/semantic/7bb5613b8b1b90594cc46f31bf1056350d00f2cc2cad87e99c7adbec29f4ff37.json b/apps/admin-frontend/graphify-out/cache/semantic/7bb5613b8b1b90594cc46f31bf1056350d00f2cc2cad87e99c7adbec29f4ff37.json deleted file mode 100644 index 7d12b69..0000000 --- a/apps/admin-frontend/graphify-out/cache/semantic/7bb5613b8b1b90594cc46f31bf1056350d00f2cc2cad87e99c7adbec29f4ff37.json +++ /dev/null @@ -1 +0,0 @@ -{"nodes": [{"id": "docs_domain_domain_glossary", "label": "Domain Glossary", "file_type": "document", "source_file": "docs/DOMAIN.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null}, {"id": "docs_domain_business_tenant", "label": "Business (Tenant)", "file_type": "concept", "source_file": "docs/DOMAIN.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null}, {"id": "docs_domain_user", "label": "User", "file_type": "concept", "source_file": "docs/DOMAIN.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null}, {"id": "docs_domain_session", "label": "Session", "file_type": "concept", "source_file": "docs/DOMAIN.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null}, {"id": "docs_domain_appointment", "label": "Appointment", "file_type": "concept", "source_file": "docs/DOMAIN.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null}, {"id": "docs_domain_appointmentstatus", "label": "AppointmentStatus", "file_type": "concept", "source_file": "docs/DOMAIN.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null}, {"id": "docs_domain_appointmentsource", "label": "AppointmentSource", "file_type": "concept", "source_file": "docs/DOMAIN.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null}, {"id": "docs_domain_service", "label": "Service", "file_type": "concept", "source_file": "docs/DOMAIN.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null}, {"id": "docs_domain_client", "label": "Client", "file_type": "concept", "source_file": "docs/DOMAIN.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null}, {"id": "docs_domain_conversation", "label": "Conversation", "file_type": "concept", "source_file": "docs/DOMAIN.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null}, {"id": "docs_domain_conversationstatus", "label": "ConversationStatus", "file_type": "concept", "source_file": "docs/DOMAIN.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null}, {"id": "docs_domain_message", "label": "Message", "file_type": "concept", "source_file": "docs/DOMAIN.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null}, {"id": "docs_domain_inbox", "label": "Inbox", "file_type": "concept", "source_file": "docs/DOMAIN.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null}, {"id": "docs_domain_business_settings", "label": "Business Settings", "file_type": "concept", "source_file": "docs/DOMAIN.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null}, {"id": "docs_domain_out_of_scope_v1", "label": "Out of Scope in v1", "file_type": "concept", "source_file": "docs/DOMAIN.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null}], "edges": [{"source": "docs_domain_domain_glossary", "target": "docs_domain_business_tenant", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "docs/DOMAIN.md", "source_location": null, "weight": 1.0}, {"source": "docs_domain_domain_glossary", "target": "docs_domain_user", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "docs/DOMAIN.md", "source_location": null, "weight": 1.0}, {"source": "docs_domain_domain_glossary", "target": "docs_domain_session", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "docs/DOMAIN.md", "source_location": null, "weight": 1.0}, {"source": "docs_domain_domain_glossary", "target": "docs_domain_appointment", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "docs/DOMAIN.md", "source_location": null, "weight": 1.0}, {"source": "docs_domain_appointment", "target": "docs_domain_appointmentstatus", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "docs/DOMAIN.md", "source_location": null, "weight": 1.0}, {"source": "docs_domain_appointment", "target": "docs_domain_appointmentsource", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "docs/DOMAIN.md", "source_location": null, "weight": 1.0}, {"source": "docs_domain_appointment", "target": "docs_domain_service", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "docs/DOMAIN.md", "source_location": null, "weight": 1.0}, {"source": "docs_domain_appointment", "target": "docs_domain_client", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "docs/DOMAIN.md", "source_location": null, "weight": 1.0}, {"source": "docs_domain_domain_glossary", "target": "docs_domain_service", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "docs/DOMAIN.md", "source_location": null, "weight": 1.0}, {"source": "docs_domain_service", "target": "docs_domain_business_tenant", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "docs/DOMAIN.md", "source_location": null, "weight": 1.0}, {"source": "docs_domain_domain_glossary", "target": "docs_domain_client", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "docs/DOMAIN.md", "source_location": null, "weight": 1.0}, {"source": "docs_domain_client", "target": "docs_domain_business_tenant", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "docs/DOMAIN.md", "source_location": null, "weight": 1.0}, {"source": "docs_domain_client", "target": "docs_domain_appointment", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "docs/DOMAIN.md", "source_location": null, "weight": 1.0}, {"source": "docs_domain_domain_glossary", "target": "docs_domain_conversation", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "docs/DOMAIN.md", "source_location": null, "weight": 1.0}, {"source": "docs_domain_conversation", "target": "docs_domain_conversationstatus", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "docs/DOMAIN.md", "source_location": null, "weight": 1.0}, {"source": "docs_domain_conversation", "target": "docs_domain_message", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "docs/DOMAIN.md", "source_location": null, "weight": 1.0}, {"source": "docs_domain_conversation", "target": "docs_domain_client", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "docs/DOMAIN.md", "source_location": null, "weight": 1.0}, {"source": "docs_domain_conversation", "target": "docs_domain_inbox", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "docs/DOMAIN.md", "source_location": null, "weight": 1.0}, {"source": "docs_domain_domain_glossary", "target": "docs_domain_business_settings", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "docs/DOMAIN.md", "source_location": null, "weight": 1.0}, {"source": "docs_domain_business_settings", "target": "docs_domain_business_tenant", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "docs/DOMAIN.md", "source_location": null, "weight": 1.0}, {"source": "docs_domain_domain_glossary", "target": "docs_domain_out_of_scope_v1", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "docs/DOMAIN.md", "source_location": null, "weight": 1.0}, {"source": "docs_domain_user", "target": "docs_domain_business_tenant", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "docs/DOMAIN.md", "source_location": null, "weight": 1.0}, {"source": "docs_domain_session", "target": "docs_domain_user", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "docs/DOMAIN.md", "source_location": null, "weight": 1.0}], "hyperedges": [{"id": "hyperedge_domain_entities", "label": "Core Tenant-Scoped Domain Entities", "nodes": ["docs_domain_business_tenant", "docs_domain_user", "docs_domain_service", "docs_domain_client", "docs_domain_appointment", "docs_domain_conversation", "docs_domain_business_settings"], "relation": "participate_in", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "docs/DOMAIN.md"}]} \ No newline at end of file diff --git a/apps/admin-frontend/graphify-out/cache/semantic/86a9adeaa6314d438329d2e9a1afc5d56146e36c62945da4105aa5e89f415d47.json b/apps/admin-frontend/graphify-out/cache/semantic/86a9adeaa6314d438329d2e9a1afc5d56146e36c62945da4105aa5e89f415d47.json deleted file mode 100644 index 61c4a30..0000000 --- a/apps/admin-frontend/graphify-out/cache/semantic/86a9adeaa6314d438329d2e9a1afc5d56146e36c62945da4105aa5e89f415d47.json +++ /dev/null @@ -1 +0,0 @@ -{"nodes": [{"id": "skills_admin_tdd_conventions_skill_admin_tdd_conventions", "label": "Admin TDD Conventions Skill", "file_type": "document", "source_file": ".skills/admin-tdd-conventions/SKILL.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null}, {"id": "skills_admin_tdd_conventions_skill_mock_strategy_table", "label": "Mock Strategy Per Layer Table", "file_type": "rationale", "source_file": ".skills/admin-tdd-conventions/SKILL.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null, "rationale": "Never mix mock strategies: use fakes for use cases, MSW for infrastructure boundary, fake container for presentation, to keep tests aligned with what layer they verify."}, {"id": "skills_admin_tdd_conventions_skill_createfakeservicerepository", "label": "createFakeServiceRepository Pattern", "file_type": "code", "source_file": ".skills/admin-tdd-conventions/SKILL.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null}, {"id": "skills_admin_tdd_conventions_skill_buildfakecontainer", "label": "buildFakeContainer Pattern", "file_type": "code", "source_file": ".skills/admin-tdd-conventions/SKILL.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null}, {"id": "skills_admin_tdd_conventions_skill_erasablesyntaxonly_gotcha", "label": "erasableSyntaxOnly Test Gotcha", "file_type": "rationale", "source_file": ".skills/admin-tdd-conventions/SKILL.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null, "rationale": "Applies to all classes including test helpers and fakes: no constructor parameter shorthand allowed even though vitest alone would pass."}, {"id": "skills_admin_tdd_conventions_skill_exactoptionalpropertytypes_gotcha", "label": "exactOptionalPropertyTypes Conditional Spread Gotcha", "file_type": "rationale", "source_file": ".skills/admin-tdd-conventions/SKILL.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null, "rationale": "Optional fields must be conditionally spread rather than assigned directly to satisfy strict optional property typing."}, {"id": "skills_admin_tdd_conventions_skill_never_resolving_promise", "label": "Never-Resolving Promise Test Pattern", "file_type": "code", "source_file": ".skills/admin-tdd-conventions/SKILL.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null}, {"id": "skills_admin_tdd_conventions_skill_renderhook_generics", "label": "renderHook Generic Types Convention", "file_type": "concept", "source_file": ".skills/admin-tdd-conventions/SKILL.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null}, {"id": "skills_admin_tdd_conventions_skill_msw_handler_conventions", "label": "MSW Handler Conventions", "file_type": "concept", "source_file": ".skills/admin-tdd-conventions/SKILL.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null}, {"id": "skills_admin_tdd_conventions_skill_onunhandledrequest_error", "label": "onUnhandledRequest: error Config", "file_type": "rationale", "source_file": ".skills/admin-tdd-conventions/SKILL.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null, "rationale": "Any unhandled request fails loudly, catching missing handlers rather than silently hanging tests."}, {"id": "skills_admin_tdd_conventions_skill_set_state_in_effect_suppression", "label": "react-hooks/set-state-in-effect Suppression", "file_type": "rationale", "source_file": ".skills/admin-tdd-conventions/SKILL.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null, "rationale": "useAsync's void execute() call inside useEffect triggers this rule as a false positive since the rule traces async call graphs; suppression is intentional and documented, must not be removed or reused elsewhere."}], "edges": [{"source": "skills_admin_tdd_conventions_skill_admin_tdd_conventions", "target": "skills_admin_tdd_conventions_skill_mock_strategy_table", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": ".skills/admin-tdd-conventions/SKILL.md", "source_location": null, "weight": 1.0}, {"source": "skills_admin_tdd_conventions_skill_admin_tdd_conventions", "target": "skills_admin_tdd_conventions_skill_createfakeservicerepository", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": ".skills/admin-tdd-conventions/SKILL.md", "source_location": null, "weight": 1.0}, {"source": "skills_admin_tdd_conventions_skill_admin_tdd_conventions", "target": "skills_admin_tdd_conventions_skill_buildfakecontainer", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": ".skills/admin-tdd-conventions/SKILL.md", "source_location": null, "weight": 1.0}, {"source": "skills_admin_tdd_conventions_skill_admin_tdd_conventions", "target": "skills_admin_tdd_conventions_skill_erasablesyntaxonly_gotcha", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": ".skills/admin-tdd-conventions/SKILL.md", "source_location": null, "weight": 1.0}, {"source": "skills_admin_tdd_conventions_skill_admin_tdd_conventions", "target": "skills_admin_tdd_conventions_skill_exactoptionalpropertytypes_gotcha", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": ".skills/admin-tdd-conventions/SKILL.md", "source_location": null, "weight": 1.0}, {"source": "skills_admin_tdd_conventions_skill_admin_tdd_conventions", "target": "skills_admin_tdd_conventions_skill_never_resolving_promise", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": ".skills/admin-tdd-conventions/SKILL.md", "source_location": null, "weight": 1.0}, {"source": "skills_admin_tdd_conventions_skill_admin_tdd_conventions", "target": "skills_admin_tdd_conventions_skill_renderhook_generics", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": ".skills/admin-tdd-conventions/SKILL.md", "source_location": null, "weight": 1.0}, {"source": "skills_admin_tdd_conventions_skill_admin_tdd_conventions", "target": "skills_admin_tdd_conventions_skill_msw_handler_conventions", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": ".skills/admin-tdd-conventions/SKILL.md", "source_location": null, "weight": 1.0}, {"source": "skills_admin_tdd_conventions_skill_msw_handler_conventions", "target": "skills_admin_tdd_conventions_skill_onunhandledrequest_error", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": ".skills/admin-tdd-conventions/SKILL.md", "source_location": null, "weight": 1.0}, {"source": "skills_admin_tdd_conventions_skill_admin_tdd_conventions", "target": "skills_admin_tdd_conventions_skill_set_state_in_effect_suppression", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": ".skills/admin-tdd-conventions/SKILL.md", "source_location": null, "weight": 1.0}, {"source": "skills_admin_tdd_conventions_skill_erasablesyntaxonly_gotcha", "target": "docs_decisions_erasablesyntaxonly", "relation": "conceptually_related_to", "confidence": "INFERRED", "confidence_score": 0.95, "source_file": ".skills/admin-tdd-conventions/SKILL.md", "source_location": null, "weight": 1.0}, {"source": "skills_admin_tdd_conventions_skill_exactoptionalpropertytypes_gotcha", "target": "docs_decisions_exactoptionalpropertytypes", "relation": "conceptually_related_to", "confidence": "INFERRED", "confidence_score": 0.95, "source_file": ".skills/admin-tdd-conventions/SKILL.md", "source_location": null, "weight": 1.0}, {"source": "skills_admin_tdd_conventions_skill_onunhandledrequest_error", "target": "docs_decisions_msw_onunhandledrequest_error", "relation": "conceptually_related_to", "confidence": "INFERRED", "confidence_score": 0.95, "source_file": ".skills/admin-tdd-conventions/SKILL.md", "source_location": null, "weight": 1.0}, {"source": "skills_admin_tdd_conventions_skill_set_state_in_effect_suppression", "target": "docs_decisions_set_state_in_effect_suppression", "relation": "conceptually_related_to", "confidence": "INFERRED", "confidence_score": 0.95, "source_file": ".skills/admin-tdd-conventions/SKILL.md", "source_location": null, "weight": 1.0}, {"source": "skills_admin_tdd_conventions_skill_mock_strategy_table", "target": "skills_admin_tdd_conventions_skill_createfakeservicerepository", "relation": "conceptually_related_to", "confidence": "INFERRED", "confidence_score": 0.85, "source_file": ".skills/admin-tdd-conventions/SKILL.md", "source_location": null, "weight": 1.0}, {"source": "skills_admin_tdd_conventions_skill_mock_strategy_table", "target": "skills_admin_tdd_conventions_skill_buildfakecontainer", "relation": "conceptually_related_to", "confidence": "INFERRED", "confidence_score": 0.85, "source_file": ".skills/admin-tdd-conventions/SKILL.md", "source_location": null, "weight": 1.0}, {"source": "skills_admin_tdd_conventions_skill_mock_strategy_table", "target": "skills_admin_tdd_conventions_skill_msw_handler_conventions", "relation": "conceptually_related_to", "confidence": "INFERRED", "confidence_score": 0.85, "source_file": ".skills/admin-tdd-conventions/SKILL.md", "source_location": null, "weight": 1.0}], "hyperedges": []} \ No newline at end of file diff --git a/apps/admin-frontend/graphify-out/cache/semantic/892990fa5e09648dab3c25af3670bec40a7f09601c3c5096959840060ebeac14.json b/apps/admin-frontend/graphify-out/cache/semantic/892990fa5e09648dab3c25af3670bec40a7f09601c3c5096959840060ebeac14.json deleted file mode 100644 index ff9595a..0000000 --- a/apps/admin-frontend/graphify-out/cache/semantic/892990fa5e09648dab3c25af3670bec40a7f09601c3c5096959840060ebeac14.json +++ /dev/null @@ -1 +0,0 @@ -{"nodes": [{"id": "public_favicon_icon", "label": "Favicon Icon (Admin Panel Logo)", "file_type": "image", "source_file": "public/favicon.svg", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null}], "edges": [], "hyperedges": []} \ No newline at end of file diff --git a/apps/admin-frontend/graphify-out/cache/semantic/933e4671ad638c69fdcf0b8d8b1a65cb3a7fad6139993b8c2cbc737c6c15eefe.json b/apps/admin-frontend/graphify-out/cache/semantic/933e4671ad638c69fdcf0b8d8b1a65cb3a7fad6139993b8c2cbc737c6c15eefe.json deleted file mode 100644 index 3c631e1..0000000 --- a/apps/admin-frontend/graphify-out/cache/semantic/933e4671ad638c69fdcf0b8d8b1a65cb3a7fad6139993b8c2cbc737c6c15eefe.json +++ /dev/null @@ -1 +0,0 @@ -{"nodes": [{"id": "docs_adr_003_manual_di_no_container_library", "label": "ADR 003: Manual Dependency Injection, No Container Library", "file_type": "rationale", "source_file": "docs/adr/003-manual-di-no-container-library.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null, "rationale": "Dependency graph is simple and static; a DI library would add decorators conflicting with erasableSyntaxOnly and unneeded indirection at this scale; reconsider beyond ~10 repositories."}], "edges": [], "hyperedges": []} \ No newline at end of file diff --git a/apps/admin-frontend/graphify-out/cache/semantic/9a655b848851a94c8346bb0597f76a744100240815e736a035955af887cf73b7.json b/apps/admin-frontend/graphify-out/cache/semantic/9a655b848851a94c8346bb0597f76a744100240815e736a035955af887cf73b7.json deleted file mode 100644 index 3a4df2f..0000000 --- a/apps/admin-frontend/graphify-out/cache/semantic/9a655b848851a94c8346bb0597f76a744100240815e736a035955af887cf73b7.json +++ /dev/null @@ -1 +0,0 @@ -{"nodes": [{"id": "docs_adr_002_no_server_state_library", "label": "ADR 002: No Server-State Library", "file_type": "rationale", "source_file": "docs/adr/002-no-server-state-library.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null, "rationale": "Explicit project constraint; most state is server data scoped by tenant with no complex shared client state, optimistic updates, or advanced caching needs in v1; migrate to TanStack Query if caching requirements grow."}], "edges": [], "hyperedges": []} \ No newline at end of file diff --git a/apps/admin-frontend/graphify-out/cache/semantic/b387c02c6b89e00e196358fb912991ac7b576fc46ba7808f4d330de9659c8f91.json b/apps/admin-frontend/graphify-out/cache/semantic/b387c02c6b89e00e196358fb912991ac7b576fc46ba7808f4d330de9659c8f91.json deleted file mode 100644 index 1e9b541..0000000 --- a/apps/admin-frontend/graphify-out/cache/semantic/b387c02c6b89e00e196358fb912991ac7b576fc46ba7808f4d330de9659c8f91.json +++ /dev/null @@ -1 +0,0 @@ -{"nodes": [{"id": "public_icons_icon_set", "label": "Public Icon Sprite (SVG Symbols)", "file_type": "image", "source_file": "public/icons.svg", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null}, {"id": "public_icons_bluesky_icon", "label": "Bluesky Icon Symbol", "file_type": "image", "source_file": "public/icons.svg", "source_location": "symbol#bluesky-icon", "source_url": null, "captured_at": null, "author": null, "contributor": null}, {"id": "public_icons_discord_icon", "label": "Discord Icon Symbol", "file_type": "image", "source_file": "public/icons.svg", "source_location": "symbol#discord-icon", "source_url": null, "captured_at": null, "author": null, "contributor": null}, {"id": "public_icons_documentation_icon", "label": "Documentation Icon Symbol", "file_type": "image", "source_file": "public/icons.svg", "source_location": "symbol#documentation-icon", "source_url": null, "captured_at": null, "author": null, "contributor": null}, {"id": "public_icons_github_icon", "label": "GitHub Icon Symbol", "file_type": "image", "source_file": "public/icons.svg", "source_location": "symbol#github-icon", "source_url": null, "captured_at": null, "author": null, "contributor": null}, {"id": "public_icons_social_icon", "label": "Social/People Icon Symbol", "file_type": "image", "source_file": "public/icons.svg", "source_location": "symbol#social-icon", "source_url": null, "captured_at": null, "author": null, "contributor": null}, {"id": "public_icons_x_icon", "label": "X (Twitter) Icon Symbol", "file_type": "image", "source_file": "public/icons.svg", "source_location": "symbol#x-icon", "source_url": null, "captured_at": null, "author": null, "contributor": null}], "edges": [{"source": "public_icons_icon_set", "target": "public_icons_bluesky_icon", "relation": "contains", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "public/icons.svg", "source_location": "symbol#bluesky-icon", "weight": 1.0}, {"source": "public_icons_icon_set", "target": "public_icons_discord_icon", "relation": "contains", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "public/icons.svg", "source_location": "symbol#discord-icon", "weight": 1.0}, {"source": "public_icons_icon_set", "target": "public_icons_documentation_icon", "relation": "contains", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "public/icons.svg", "source_location": "symbol#documentation-icon", "weight": 1.0}, {"source": "public_icons_icon_set", "target": "public_icons_github_icon", "relation": "contains", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "public/icons.svg", "source_location": "symbol#github-icon", "weight": 1.0}, {"source": "public_icons_icon_set", "target": "public_icons_social_icon", "relation": "contains", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "public/icons.svg", "source_location": "symbol#social-icon", "weight": 1.0}, {"source": "public_icons_icon_set", "target": "public_icons_x_icon", "relation": "contains", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "public/icons.svg", "source_location": "symbol#x-icon", "weight": 1.0}, {"source": "public_icons_bluesky_icon", "target": "public_icons_x_icon", "relation": "conceptually_related_to", "confidence": "INFERRED", "confidence_score": 0.85, "source_file": "public/icons.svg", "source_location": null, "weight": 1.0}, {"source": "public_icons_discord_icon", "target": "public_icons_github_icon", "relation": "conceptually_related_to", "confidence": "INFERRED", "confidence_score": 0.65, "source_file": "public/icons.svg", "source_location": null, "weight": 1.0}, {"source": "public_icons_social_icon", "target": "public_icons_bluesky_icon", "relation": "conceptually_related_to", "confidence": "INFERRED", "confidence_score": 0.75, "source_file": "public/icons.svg", "source_location": null, "weight": 1.0}], "hyperedges": []} \ No newline at end of file diff --git a/apps/admin-frontend/graphify-out/cache/semantic/cb4276dfc58c71ad5fbef8adae46bb40213b681284b59454f2a0739bcd2b4847.json b/apps/admin-frontend/graphify-out/cache/semantic/cb4276dfc58c71ad5fbef8adae46bb40213b681284b59454f2a0739bcd2b4847.json deleted file mode 100644 index 5871b2e..0000000 --- a/apps/admin-frontend/graphify-out/cache/semantic/cb4276dfc58c71ad5fbef8adae46bb40213b681284b59454f2a0739bcd2b4847.json +++ /dev/null @@ -1 +0,0 @@ -{"nodes": [{"id": "docs_adr_004_explicit_silent_renewal", "label": "ADR 004: Explicit Silent Token Renewal, Not Event-Driven", "file_type": "rationale", "source_file": "docs/adr/004-explicit-silent-renewal.md", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null, "rationale": "automaticSilentRenew:false because event-driven background renewal fires invisibly with no clean hook to force logout on failure; explicit renewal inside getCurrentSession() gives full control over retry, session clearing, and redirect."}], "edges": [], "hyperedges": []} \ No newline at end of file diff --git a/apps/admin-frontend/graphify-out/cache/stat-index.json b/apps/admin-frontend/graphify-out/cache/stat-index.json deleted file mode 100644 index ad5b493..0000000 --- a/apps/admin-frontend/graphify-out/cache/stat-index.json +++ /dev/null @@ -1 +0,0 @@ -{"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\.husky\\_\\applypatch-msg":{"size":39,"mtime_ns":1782683991000000000,"word_count":5,"hash":"6a9064e8fb13487a3a621e0dbb7bd8c82245b9c904b0c2dbcf3ce5fe55506b9c"},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\.husky\\_\\commit-msg":{"size":39,"mtime_ns":1782683991000000000,"word_count":5,"hash":"0bff8d003cbfd9b75abbe94b6017890688fb016d9cd9e4474bba08706c61d3d0"},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\.husky\\_\\h":{"size":551,"mtime_ns":1782683991000000000,"word_count":95,"hash":"f99a890a7c2de1e26a154549cb667ee9a884327e25ddbd6380ab23a986570ffc"},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\.husky\\_\\husky.sh":{"size":160,"mtime_ns":1782683991000000000,"word_count":24,"hash":"6db0aa99aece6ebd17bbc27c5e5287a462fb39cc4a2ca3a82a6277c166bdb3a0"},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\.husky\\_\\post-applypatch":{"size":39,"mtime_ns":1782683991000000000,"word_count":5,"hash":"678bc23efa73439ac107fddd35e640e583d68b45ab0c119be3ba50a81553e99d"},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\.husky\\_\\post-checkout":{"size":39,"mtime_ns":1782683991000000000,"word_count":5,"hash":"f5104aea4b990fe94fe2eacb0323c2128b3f66f11d3fc388538530dada2db808"},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\.husky\\_\\post-commit":{"size":39,"mtime_ns":1782683991000000000,"word_count":5,"hash":"20dc01bba5753874a3746d18250b5b5d71aebbfbdfda5305a6dfc2541adb209b"},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\.husky\\_\\post-merge":{"size":39,"mtime_ns":1782683991000000000,"word_count":5,"hash":"9dd56caa8fa28b5494661ce7c97d756013f200ac4e60f619671f4d4eca46faa6"},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\.husky\\_\\post-rewrite":{"size":39,"mtime_ns":1782683991000000000,"word_count":5,"hash":"3aa066773301fd07106a7926663f6ca3931ebf19bcacce7e156ead4e8446121a"},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\.husky\\_\\pre-applypatch":{"size":39,"mtime_ns":1782683991000000000,"word_count":5,"hash":"24332da3a5839bca3276206674c0c6119f653c3dd2b01e2f5b37194a02d185f8"},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\.husky\\_\\pre-auto-gc":{"size":39,"mtime_ns":1782683991000000000,"word_count":5,"hash":"d0038c1f6c4208a560ea4669eefc633b8460563cc29fcbc3c0d6c44309916f62"},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\.husky\\_\\pre-commit":{"size":39,"mtime_ns":1782683991000000000,"word_count":5,"hash":"d0b0ee2878cb9efe03dfbf9327e5038d7a10eabd56ad9b1321c45eff1447e98d"},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\.husky\\_\\pre-merge-commit":{"size":39,"mtime_ns":1782683991000000000,"word_count":5,"hash":"20798fb4a9d9b6f3593cdd371ca98fa15a5d57c1bf9bfc489039b5595b7028ff"},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\.husky\\_\\pre-push":{"size":39,"mtime_ns":1782683991000000000,"word_count":5,"hash":"dd427fbe45aac0d22b4b75cd3738b4f19bb6b9203413d57e83eb402c992e636d"},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\.husky\\_\\pre-rebase":{"size":39,"mtime_ns":1782683991000000000,"word_count":5,"hash":"c94f2d13aabe67bf5766093bbd03c9b9e5580deadc230f73ad6421b3ec0bf290"},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\.husky\\_\\prepare-commit-msg":{"size":39,"mtime_ns":1782683991000000000,"word_count":5,"hash":"d571f11fd4274692937568cb770a19c830c585c6cef79f550d5c27340871a18f"},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\.lintstagedrc.json":{"size":100,"mtime_ns":1782684003000000000,"word_count":10,"hash":"b85bcc7c94b5b7898a43c5d9cc5a42736086af8ced1a6bc5311109b375e2363d"},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\.prettierrc.json":{"size":133,"mtime_ns":1782683750000000000,"word_count":14,"hash":"010a69b73127716a89b336d0e6027d0f066102485541c9ebf1e2fb6b273a6126"},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\.skills\\admin-api-contract\\SKILL.md":{"size":6113,"mtime_ns":1783167398000000000,"word_count":822,"hash":"006b7da94b10b3d5d0c001968c16046e5a8fd2ef70d7e7a8b2db9cff92e4a3a1"},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\.skills\\admin-feature-vertical\\SKILL.md":{"size":5970,"mtime_ns":1783167398000000000,"word_count":762,"hash":"42b29bf1a089ee99701714d72191b43a3c785ec87296979c1e9b0525f762f310"},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\.skills\\admin-tdd-conventions\\SKILL.md":{"size":7462,"mtime_ns":1783167399000000000,"word_count":936,"hash":"86a9adeaa6314d438329d2e9a1afc5d56146e36c62945da4105aa5e89f415d47"},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\CLAUDE.md":{"size":3773,"mtime_ns":1783167796000000000,"word_count":489,"hash":"3587e95a5fad882303fd30f3c5431c4d66a30e9cbe07f69a9a0062c878ec1f68"},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\README.md":{"size":1278,"mtime_ns":1782683576000000000,"word_count":128,"hash":"1902ad9650dabab71d145c262ceb47559c17c49094af55b2f6aae7abdbce37c2"},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\docs\\API.md":{"size":3352,"mtime_ns":1783167796000000000,"word_count":499,"hash":"2e9906cf88503ada29fd9a9826d6e9f7a78bad27dec7a2ac2b38a1dfe36130ef"},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\docs\\DECISIONS.md":{"size":6423,"mtime_ns":1783167796000000000,"word_count":845,"hash":"321850e5728abff0bb641538aa2df718b5295392e00fb17e49ff018fbdfe8be3"},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\docs\\DOMAIN.md":{"size":4949,"mtime_ns":1783167796000000000,"word_count":769,"hash":"7bb5613b8b1b90594cc46f31bf1056350d00f2cc2cad87e99c7adbec29f4ff37"},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\docs\\STATUS.md":{"size":7752,"mtime_ns":1783167796000000000,"word_count":896,"hash":"0e5e531cc7a8986593ccf1ab8497db9c94c5d6631301a1c52349931f34fa9ae7"},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\docs\\adr\\001-clean-architecture-layers.md":{"size":746,"mtime_ns":1783167796000000000,"word_count":100,"hash":"0d796317a126ce9e51f38c8054499148ef6c3ba2f18a7b3f741c397ef5ced968"},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\docs\\adr\\002-no-server-state-library.md":{"size":730,"mtime_ns":1783167796000000000,"word_count":103,"hash":"9a655b848851a94c8346bb0597f76a744100240815e736a035955af887cf73b7"},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\docs\\adr\\003-manual-di-no-container-library.md":{"size":698,"mtime_ns":1783167796000000000,"word_count":97,"hash":"933e4671ad638c69fdcf0b8d8b1a65cb3a7fad6139993b8c2cbc737c6c15eefe"},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\docs\\adr\\004-explicit-silent-renewal.md":{"size":920,"mtime_ns":1783167796000000000,"word_count":128,"hash":"cb4276dfc58c71ad5fbef8adae46bb40213b681284b59454f2a0739bcd2b4847"},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\eslint.config.js":{"size":2735,"mtime_ns":1782683821000000000,"word_count":221},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\index.html":{"size":357,"mtime_ns":1782683576000000000,"word_count":28,"hash":"44316b9e5de692d589c906aba6651261e448e2dc5acbade5461eacae518c0528"},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\package.json":{"size":1446,"mtime_ns":1782936536000000000,"word_count":117,"hash":"e959718c238b3e07a1780e4ab7828238990b8561d28361a2afd2ccad62b941a7"},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\public\\favicon.svg":{"size":9522,"mtime_ns":1782683576000000000,"word_count":491,"hash":"892990fa5e09648dab3c25af3670bec40a7f09601c3c5096959840060ebeac14"},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\public\\icons.svg":{"size":5031,"mtime_ns":1782683576000000000,"word_count":382,"hash":"b387c02c6b89e00e196358fb912991ac7b576fc46ba7808f4d330de9659c8f91"},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\src\\App.tsx":{"size":217,"mtime_ns":1782936735000000000,"word_count":29},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\src\\application\\context\\TenantContext.ts":{"size":830,"mtime_ns":1782684797000000000,"word_count":119},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\src\\application\\repositories\\AuthRepository.ts":{"size":1985,"mtime_ns":1782684807000000000,"word_count":298},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\src\\application\\test-helpers\\createFakeAuthRepository.ts":{"size":877,"mtime_ns":1782685065000000000,"word_count":120},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\src\\application\\use-cases\\auth\\GetCurrentSession.test.ts":{"size":1431,"mtime_ns":1782685000000000000,"word_count":135},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\src\\application\\use-cases\\auth\\GetCurrentSession.ts":{"size":835,"mtime_ns":1782684869000000000,"word_count":94},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\src\\application\\use-cases\\auth\\HandleAuthCallback.test.ts":{"size":2378,"mtime_ns":1782685016000000000,"word_count":208},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\src\\application\\use-cases\\auth\\HandleAuthCallback.ts":{"size":1203,"mtime_ns":1782684968000000000,"word_count":156},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\src\\application\\use-cases\\auth\\InitiateLogin.test.ts":{"size":621,"mtime_ns":1782685010000000000,"word_count":63},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\src\\application\\use-cases\\auth\\InitiateLogin.ts":{"size":613,"mtime_ns":1782684923000000000,"word_count":73},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\src\\application\\use-cases\\auth\\Logout.test.ts":{"size":562,"mtime_ns":1782685005000000000,"word_count":66},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\src\\application\\use-cases\\auth\\Logout.ts":{"size":643,"mtime_ns":1782684911000000000,"word_count":80},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\src\\composition\\container.ts":{"size":1769,"mtime_ns":1782737003000000000,"word_count":177},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\src\\domain\\entities\\Session.test.ts":{"size":2046,"mtime_ns":1782684512000000000,"word_count":205},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\src\\domain\\entities\\Session.ts":{"size":1594,"mtime_ns":1782684577000000000,"word_count":209},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\src\\domain\\entities\\User.test.ts":{"size":1445,"mtime_ns":1782684475000000000,"word_count":166},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\src\\domain\\entities\\User.ts":{"size":1311,"mtime_ns":1782684591000000000,"word_count":181},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\src\\domain\\errors\\DomainError.ts":{"size":504,"mtime_ns":1782684449000000000,"word_count":71},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\src\\domain\\errors\\InvalidSessionError.ts":{"size":101,"mtime_ns":1782684532000000000,"word_count":12},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\src\\domain\\errors\\InvalidTenantError.ts":{"size":100,"mtime_ns":1782684452000000000,"word_count":12},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\src\\domain\\errors\\InvalidUserError.ts":{"size":98,"mtime_ns":1782684493000000000,"word_count":12},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\src\\domain\\value-objects\\Tenant.test.ts":{"size":903,"mtime_ns":1782684429000000000,"word_count":106},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\src\\domain\\value-objects\\Tenant.ts":{"size":701,"mtime_ns":1782684567000000000,"word_count":97},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\src\\infrastructure\\auth\\OidcAuthRepository.test.ts":{"size":5784,"mtime_ns":1782736787000000000,"word_count":498},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\src\\infrastructure\\auth\\OidcAuthRepository.ts":{"size":2260,"mtime_ns":1782736668000000000,"word_count":243},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\src\\infrastructure\\config\\createUserManager.ts":{"size":1177,"mtime_ns":1782736735000000000,"word_count":121},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\src\\infrastructure\\mappers\\MissingTenantClaimError.ts":{"size":573,"mtime_ns":1782685297000000000,"word_count":84},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\src\\infrastructure\\mappers\\oidcUserToSessionMapper.test.ts":{"size":2829,"mtime_ns":1782685374000000000,"word_count":255},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\src\\infrastructure\\mappers\\oidcUserToSessionMapper.ts":{"size":1434,"mtime_ns":1782736787000000000,"word_count":180},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\src\\main.tsx":{"size":480,"mtime_ns":1782936742000000000,"word_count":54},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\src\\presentation\\hooks\\useAppContainer.test.tsx":{"size":954,"mtime_ns":1782771852000000000,"word_count":103},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\src\\presentation\\hooks\\useAppContainer.ts":{"size":682,"mtime_ns":1782737049000000000,"word_count":90},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\src\\presentation\\hooks\\useAsync.test.tsx":{"size":1901,"mtime_ns":1782737092000000000,"word_count":195},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\src\\presentation\\hooks\\useAsync.ts":{"size":2799,"mtime_ns":1782771619000000000,"word_count":362},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\src\\presentation\\hooks\\useAuth.test.tsx":{"size":4009,"mtime_ns":1782771800000000000,"word_count":404},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\src\\presentation\\hooks\\useAuth.ts":{"size":1672,"mtime_ns":1782771788000000000,"word_count":200},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\src\\presentation\\layouts\\AdminLayout.tsx":{"size":2547,"mtime_ns":1782936601000000000,"word_count":233},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\src\\presentation\\pages\\AppointmentsPage\\AppointmentsPage.tsx":{"size":406,"mtime_ns":1782936578000000000,"word_count":39},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\src\\presentation\\pages\\CallbackPage\\CallbackPage.tsx":{"size":2321,"mtime_ns":1782936760000000000,"word_count":254},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\src\\presentation\\pages\\ClientsPage\\ClientsPage.tsx":{"size":396,"mtime_ns":1782936578000000000,"word_count":39},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\src\\presentation\\pages\\DashboardPage\\DashboardPage.tsx":{"size":400,"mtime_ns":1782936578000000000,"word_count":39},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\src\\presentation\\pages\\InboxPage\\InboxPage.tsx":{"size":392,"mtime_ns":1782936578000000000,"word_count":39},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\src\\presentation\\pages\\LoginPage\\LoginPage.test.tsx":{"size":2106,"mtime_ns":1782936919000000000,"word_count":216},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\src\\presentation\\pages\\LoginPage\\LoginPage.tsx":{"size":2324,"mtime_ns":1782936674000000000,"word_count":231},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\src\\presentation\\pages\\ServicesPage\\ServicesPage.tsx":{"size":398,"mtime_ns":1782936578000000000,"word_count":39},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\src\\presentation\\pages\\SettingsPage\\SettingsPage.tsx":{"size":398,"mtime_ns":1782936578000000000,"word_count":39},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\src\\presentation\\providers\\AppContainerContext.ts":{"size":403,"mtime_ns":1782737012000000000,"word_count":57},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\src\\presentation\\providers\\AppProviders.tsx":{"size":784,"mtime_ns":1782737065000000000,"word_count":95},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\src\\presentation\\routes\\ProtectedRoute.test.tsx":{"size":2807,"mtime_ns":1782936925000000000,"word_count":256},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\src\\presentation\\routes\\ProtectedRoute.tsx":{"size":850,"mtime_ns":1782936635000000000,"word_count":120},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\src\\presentation\\routes\\router.tsx":{"size":1566,"mtime_ns":1782936731000000000,"word_count":173},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\src\\test\\mocks\\handlers\\index.ts":{"size":320,"mtime_ns":1782683914000000000,"word_count":49},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\src\\test\\mocks\\server.ts":{"size":125,"mtime_ns":1782683918000000000,"word_count":17},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\src\\test\\setup.ts":{"size":871,"mtime_ns":1782684263000000000,"word_count":119},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\src\\vite-env.d.ts":{"size":785,"mtime_ns":1782685231000000000,"word_count":92},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\tsconfig.app.json":{"size":769,"mtime_ns":1782683697000000000,"word_count":58,"hash":"0c34ade59e9769c174ab25be41720f31c2bd1c797f0bee7c00391fd164abae52"},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\tsconfig.json":{"size":107,"mtime_ns":1782683822000000000,"word_count":13,"hash":"1df8de670dd1febedc2554ced3c9a2b5215fc68cad16d4b8dc2b7b64943a7f17"},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\tsconfig.node.json":{"size":578,"mtime_ns":1782683962000000000,"word_count":43,"hash":"141cf472a6c63bb2adfc043e0c663c4ca39742644e3039cc93b96b84d022f64e"},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\vite.config.ts":{"size":220,"mtime_ns":1782936545000000000,"word_count":23},"C:\\Users\\evert\\Downloads\\admin-complete\\admin\\vitest.config.ts":{"size":502,"mtime_ns":1782683875000000000,"word_count":43}} \ No newline at end of file diff --git a/apps/admin-frontend/graphify-out/cost.json b/apps/admin-frontend/graphify-out/cost.json deleted file mode 100644 index 7bfea66..0000000 --- a/apps/admin-frontend/graphify-out/cost.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "runs": [ - { - "date": "2026-07-07T00:31:13.373550+00:00", - "input_tokens": 0, - "output_tokens": 118611, - "files": 96 - } - ], - "total_input_tokens": 0, - "total_output_tokens": 118611 -} \ No newline at end of file diff --git a/apps/admin-frontend/graphify-out/graph.html b/apps/admin-frontend/graphify-out/graph.html deleted file mode 100644 index 6bf39dd..0000000 --- a/apps/admin-frontend/graphify-out/graph.html +++ /dev/null @@ -1,307 +0,0 @@ - - - - -graphify - graphify-out\graph.html - - - - -
- - - - - \ No newline at end of file diff --git a/apps/admin-frontend/graphify-out/graph.json b/apps/admin-frontend/graphify-out/graph.json deleted file mode 100644 index 81b77f3..0000000 --- a/apps/admin-frontend/graphify-out/graph.json +++ /dev/null @@ -1,10962 +0,0 @@ -{ - "directed": false, - "multigraph": false, - "graph": { - "hyperedges": [ - { - "id": "hyperedge_typescript_strictness_constraints", - "label": "TypeScript Strict Mode Constraints Group", - "nodes": [ - "claude_md_erasablesyntaxonly", - "claude_md_exactoptionalpropertytypes", - "claude_md_nouncheckedindexedaccess", - "docs_decisions_erasablesyntaxonly", - "docs_decisions_exactoptionalpropertytypes", - "docs_decisions_nouncheckedindexedaccess", - "skills_admin_tdd_conventions_skill_erasablesyntaxonly_gotcha", - "skills_admin_tdd_conventions_skill_exactoptionalpropertytypes_gotcha" - ], - "relation": "form", - "confidence": "INFERRED", - "confidence_score": 0.85, - "source_file": "CLAUDE.md" - }, - { - "id": "hyperedge_domain_entities", - "label": "Core Tenant-Scoped Domain Entities", - "nodes": [ - "docs_domain_business_tenant", - "docs_domain_user", - "docs_domain_service", - "docs_domain_client", - "docs_domain_appointment", - "docs_domain_conversation", - "docs_domain_business_settings" - ], - "relation": "participate_in", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "docs/DOMAIN.md" - }, - { - "id": "hyperedge_feature_vertical_build_order", - "label": "Feature Vertical Build Order Chain", - "nodes": [ - "docs_status_httpclient_stub", - "docs_status_services_vertical", - "docs_status_clients_vertical", - "docs_status_appointments_vertical", - "docs_status_inbox_vertical", - "docs_status_dashboard_vertical", - "docs_status_settings_vertical" - ], - "relation": "form", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "docs/STATUS.md" - } - ] - }, - "nodes": [ - { - "label": "applypatch-msg", - "file_type": "code", - "source_file": ".husky/_/applypatch-msg", - "source_location": "L1", - "metadata": { - "language": "bash", - "kind": "file" - }, - "_origin": "ast", - "id": "husky_applypatch_msg", - "community": 18, - "norm_label": "applypatch-msg" - }, - { - "label": "applypatch-msg script", - "file_type": "code", - "source_file": ".husky/_/applypatch-msg", - "source_location": "L1", - "metadata": { - "language": "bash", - "kind": "bash_entrypoint" - }, - "_origin": "ast", - "id": "husky_applypatch_msg__entry", - "community": 18, - "norm_label": "applypatch-msg script" - }, - { - "label": "commit-msg", - "file_type": "code", - "source_file": ".husky/_/commit-msg", - "source_location": "L1", - "metadata": { - "language": "bash", - "kind": "file" - }, - "_origin": "ast", - "id": "husky_commit_msg", - "community": 19, - "norm_label": "commit-msg" - }, - { - "label": "commit-msg script", - "file_type": "code", - "source_file": ".husky/_/commit-msg", - "source_location": "L1", - "metadata": { - "language": "bash", - "kind": "bash_entrypoint" - }, - "_origin": "ast", - "id": "husky_commit_msg__entry", - "community": 19, - "norm_label": "commit-msg script" - }, - { - "label": "h", - "file_type": "code", - "source_file": ".husky/_/h", - "source_location": "L1", - "metadata": { - "language": "bash", - "kind": "file" - }, - "_origin": "ast", - "id": "husky_h", - "community": 15, - "norm_label": "h" - }, - { - "label": "h script", - "file_type": "code", - "source_file": ".husky/_/h", - "source_location": "L1", - "metadata": { - "language": "bash", - "kind": "bash_entrypoint" - }, - "_origin": "ast", - "id": "husky_h__entry", - "community": 15, - "norm_label": "h script" - }, - { - "label": "PATH", - "file_type": "code", - "source_file": ".husky/_/h", - "source_location": "L16", - "metadata": { - "language": "bash", - "kind": "code" - }, - "_origin": "ast", - "id": "husky_h_path", - "community": 15, - "norm_label": "path" - }, - { - "label": "husky.sh", - "file_type": "code", - "source_file": ".husky/_/husky.sh", - "source_location": "L1", - "metadata": { - "language": "bash", - "kind": "file" - }, - "_origin": "ast", - "id": "husky_husky", - "community": 5, - "norm_label": "husky.sh" - }, - { - "label": "husky.sh script", - "file_type": "code", - "source_file": ".husky/_/husky.sh", - "source_location": "L1", - "metadata": { - "language": "bash", - "kind": "bash_entrypoint" - }, - "_origin": "ast", - "id": "husky_husky_sh__entry", - "community": 5, - "norm_label": "husky.sh script" - }, - { - "label": "post-applypatch", - "file_type": "code", - "source_file": ".husky/_/post-applypatch", - "source_location": "L1", - "metadata": { - "language": "bash", - "kind": "file" - }, - "_origin": "ast", - "id": "husky_post_applypatch", - "community": 20, - "norm_label": "post-applypatch" - }, - { - "label": "post-applypatch script", - "file_type": "code", - "source_file": ".husky/_/post-applypatch", - "source_location": "L1", - "metadata": { - "language": "bash", - "kind": "bash_entrypoint" - }, - "_origin": "ast", - "id": "husky_post_applypatch__entry", - "community": 20, - "norm_label": "post-applypatch script" - }, - { - "label": "post-checkout", - "file_type": "code", - "source_file": ".husky/_/post-checkout", - "source_location": "L1", - "metadata": { - "language": "bash", - "kind": "file" - }, - "_origin": "ast", - "id": "husky_post_checkout", - "community": 21, - "norm_label": "post-checkout" - }, - { - "label": "post-checkout script", - "file_type": "code", - "source_file": ".husky/_/post-checkout", - "source_location": "L1", - "metadata": { - "language": "bash", - "kind": "bash_entrypoint" - }, - "_origin": "ast", - "id": "husky_post_checkout__entry", - "community": 21, - "norm_label": "post-checkout script" - }, - { - "label": "post-commit", - "file_type": "code", - "source_file": ".husky/_/post-commit", - "source_location": "L1", - "metadata": { - "language": "bash", - "kind": "file" - }, - "_origin": "ast", - "id": "husky_post_commit", - "community": 22, - "norm_label": "post-commit" - }, - { - "label": "post-commit script", - "file_type": "code", - "source_file": ".husky/_/post-commit", - "source_location": "L1", - "metadata": { - "language": "bash", - "kind": "bash_entrypoint" - }, - "_origin": "ast", - "id": "husky_post_commit__entry", - "community": 22, - "norm_label": "post-commit script" - }, - { - "label": "post-merge", - "file_type": "code", - "source_file": ".husky/_/post-merge", - "source_location": "L1", - "metadata": { - "language": "bash", - "kind": "file" - }, - "_origin": "ast", - "id": "husky_post_merge", - "community": 23, - "norm_label": "post-merge" - }, - { - "label": "post-merge script", - "file_type": "code", - "source_file": ".husky/_/post-merge", - "source_location": "L1", - "metadata": { - "language": "bash", - "kind": "bash_entrypoint" - }, - "_origin": "ast", - "id": "husky_post_merge__entry", - "community": 23, - "norm_label": "post-merge script" - }, - { - "label": "post-rewrite", - "file_type": "code", - "source_file": ".husky/_/post-rewrite", - "source_location": "L1", - "metadata": { - "language": "bash", - "kind": "file" - }, - "_origin": "ast", - "id": "husky_post_rewrite", - "community": 24, - "norm_label": "post-rewrite" - }, - { - "label": "post-rewrite script", - "file_type": "code", - "source_file": ".husky/_/post-rewrite", - "source_location": "L1", - "metadata": { - "language": "bash", - "kind": "bash_entrypoint" - }, - "_origin": "ast", - "id": "husky_post_rewrite__entry", - "community": 24, - "norm_label": "post-rewrite script" - }, - { - "label": "pre-applypatch", - "file_type": "code", - "source_file": ".husky/_/pre-applypatch", - "source_location": "L1", - "metadata": { - "language": "bash", - "kind": "file" - }, - "_origin": "ast", - "id": "husky_pre_applypatch", - "community": 25, - "norm_label": "pre-applypatch" - }, - { - "label": "pre-applypatch script", - "file_type": "code", - "source_file": ".husky/_/pre-applypatch", - "source_location": "L1", - "metadata": { - "language": "bash", - "kind": "bash_entrypoint" - }, - "_origin": "ast", - "id": "husky_pre_applypatch__entry", - "community": 25, - "norm_label": "pre-applypatch script" - }, - { - "label": "pre-auto-gc", - "file_type": "code", - "source_file": ".husky/_/pre-auto-gc", - "source_location": "L1", - "metadata": { - "language": "bash", - "kind": "file" - }, - "_origin": "ast", - "id": "husky_pre_auto_gc", - "community": 26, - "norm_label": "pre-auto-gc" - }, - { - "label": "pre-auto-gc script", - "file_type": "code", - "source_file": ".husky/_/pre-auto-gc", - "source_location": "L1", - "metadata": { - "language": "bash", - "kind": "bash_entrypoint" - }, - "_origin": "ast", - "id": "husky_pre_auto_gc__entry", - "community": 26, - "norm_label": "pre-auto-gc script" - }, - { - "label": "pre-commit", - "file_type": "code", - "source_file": ".husky/_/pre-commit", - "source_location": "L1", - "metadata": { - "language": "bash", - "kind": "file" - }, - "_origin": "ast", - "id": "husky_pre_commit", - "community": 27, - "norm_label": "pre-commit" - }, - { - "label": "pre-commit script", - "file_type": "code", - "source_file": ".husky/_/pre-commit", - "source_location": "L1", - "metadata": { - "language": "bash", - "kind": "bash_entrypoint" - }, - "_origin": "ast", - "id": "husky_pre_commit__entry", - "community": 27, - "norm_label": "pre-commit script" - }, - { - "label": "pre-merge-commit", - "file_type": "code", - "source_file": ".husky/_/pre-merge-commit", - "source_location": "L1", - "metadata": { - "language": "bash", - "kind": "file" - }, - "_origin": "ast", - "id": "husky_pre_merge_commit", - "community": 28, - "norm_label": "pre-merge-commit" - }, - { - "label": "pre-merge-commit script", - "file_type": "code", - "source_file": ".husky/_/pre-merge-commit", - "source_location": "L1", - "metadata": { - "language": "bash", - "kind": "bash_entrypoint" - }, - "_origin": "ast", - "id": "husky_pre_merge_commit__entry", - "community": 28, - "norm_label": "pre-merge-commit script" - }, - { - "label": "pre-push", - "file_type": "code", - "source_file": ".husky/_/pre-push", - "source_location": "L1", - "metadata": { - "language": "bash", - "kind": "file" - }, - "_origin": "ast", - "id": "husky_pre_push", - "community": 29, - "norm_label": "pre-push" - }, - { - "label": "pre-push script", - "file_type": "code", - "source_file": ".husky/_/pre-push", - "source_location": "L1", - "metadata": { - "language": "bash", - "kind": "bash_entrypoint" - }, - "_origin": "ast", - "id": "husky_pre_push__entry", - "community": 29, - "norm_label": "pre-push script" - }, - { - "label": "pre-rebase", - "file_type": "code", - "source_file": ".husky/_/pre-rebase", - "source_location": "L1", - "metadata": { - "language": "bash", - "kind": "file" - }, - "_origin": "ast", - "id": "husky_pre_rebase", - "community": 30, - "norm_label": "pre-rebase" - }, - { - "label": "pre-rebase script", - "file_type": "code", - "source_file": ".husky/_/pre-rebase", - "source_location": "L1", - "metadata": { - "language": "bash", - "kind": "bash_entrypoint" - }, - "_origin": "ast", - "id": "husky_pre_rebase__entry", - "community": 30, - "norm_label": "pre-rebase script" - }, - { - "label": "prepare-commit-msg", - "file_type": "code", - "source_file": ".husky/_/prepare-commit-msg", - "source_location": "L1", - "metadata": { - "language": "bash", - "kind": "file" - }, - "_origin": "ast", - "id": "husky_prepare_commit_msg", - "community": 31, - "norm_label": "prepare-commit-msg" - }, - { - "label": "prepare-commit-msg script", - "file_type": "code", - "source_file": ".husky/_/prepare-commit-msg", - "source_location": "L1", - "metadata": { - "language": "bash", - "kind": "bash_entrypoint" - }, - "_origin": "ast", - "id": "husky_prepare_commit_msg__entry", - "community": 31, - "norm_label": "prepare-commit-msg script" - }, - { - "label": ".prettierrc.json", - "file_type": "code", - "source_file": ".prettierrc.json", - "source_location": "L1", - "_origin": "ast", - "id": "prettierrc", - "community": 12, - "norm_label": ".prettierrc.json" - }, - { - "label": "semi", - "file_type": "code", - "source_file": ".prettierrc.json", - "source_location": "L2", - "_origin": "ast", - "id": "prettierrc_semi", - "community": 12, - "norm_label": "semi" - }, - { - "label": "singleQuote", - "file_type": "code", - "source_file": ".prettierrc.json", - "source_location": "L3", - "_origin": "ast", - "id": "prettierrc_singlequote", - "community": 12, - "norm_label": "singlequote" - }, - { - "label": "trailingComma", - "file_type": "code", - "source_file": ".prettierrc.json", - "source_location": "L4", - "_origin": "ast", - "id": "prettierrc_trailingcomma", - "community": 12, - "norm_label": "trailingcomma" - }, - { - "label": "printWidth", - "file_type": "code", - "source_file": ".prettierrc.json", - "source_location": "L5", - "_origin": "ast", - "id": "prettierrc_printwidth", - "community": 12, - "norm_label": "printwidth" - }, - { - "label": "tabWidth", - "file_type": "code", - "source_file": ".prettierrc.json", - "source_location": "L6", - "_origin": "ast", - "id": "prettierrc_tabwidth", - "community": 12, - "norm_label": "tabwidth" - }, - { - "label": "arrowParens", - "file_type": "code", - "source_file": ".prettierrc.json", - "source_location": "L7", - "_origin": "ast", - "id": "prettierrc_arrowparens", - "community": 12, - "norm_label": "arrowparens" - }, - { - "label": "eslint.config.js", - "file_type": "code", - "source_file": "eslint.config.js", - "source_location": "L1", - "_origin": "ast", - "id": "eslint_config", - "community": 32, - "norm_label": "eslint.config.js" - }, - { - "label": "package.json", - "file_type": "code", - "source_file": "package.json", - "source_location": "L1", - "_origin": "ast", - "id": "package", - "community": 6, - "norm_label": "package.json" - }, - { - "label": "name", - "file_type": "code", - "source_file": "package.json", - "source_location": "L2", - "_origin": "ast", - "id": "package_name", - "community": 6, - "norm_label": "name" - }, - { - "label": "private", - "file_type": "code", - "source_file": "package.json", - "source_location": "L3", - "_origin": "ast", - "id": "package_private", - "community": 6, - "norm_label": "private" - }, - { - "label": "version", - "file_type": "code", - "source_file": "package.json", - "source_location": "L4", - "_origin": "ast", - "id": "package_version", - "community": 6, - "norm_label": "version" - }, - { - "label": "type", - "file_type": "code", - "source_file": "package.json", - "source_location": "L5", - "_origin": "ast", - "id": "package_type", - "community": 6, - "norm_label": "type" - }, - { - "label": "scripts", - "file_type": "code", - "source_file": "package.json", - "source_location": "L6", - "_origin": "ast", - "id": "package_scripts", - "community": 6, - "norm_label": "scripts" - }, - { - "label": "dev", - "file_type": "code", - "source_file": "package.json", - "source_location": "L7", - "_origin": "ast", - "id": "package_scripts_dev", - "community": 6, - "norm_label": "dev" - }, - { - "label": "build", - "file_type": "code", - "source_file": "package.json", - "source_location": "L8", - "_origin": "ast", - "id": "package_scripts_build", - "community": 6, - "norm_label": "build" - }, - { - "label": "test", - "file_type": "code", - "source_file": "package.json", - "source_location": "L9", - "_origin": "ast", - "id": "package_scripts_test", - "community": 6, - "norm_label": "test" - }, - { - "label": "test:watch", - "file_type": "code", - "source_file": "package.json", - "source_location": "L10", - "_origin": "ast", - "id": "package_scripts_test_watch", - "community": 6, - "norm_label": "test:watch" - }, - { - "label": "test:ui", - "file_type": "code", - "source_file": "package.json", - "source_location": "L11", - "_origin": "ast", - "id": "package_scripts_test_ui", - "community": 6, - "norm_label": "test:ui" - }, - { - "label": "test:coverage", - "file_type": "code", - "source_file": "package.json", - "source_location": "L12", - "_origin": "ast", - "id": "package_scripts_test_coverage", - "community": 6, - "norm_label": "test:coverage" - }, - { - "label": "lint", - "file_type": "code", - "source_file": "package.json", - "source_location": "L13", - "_origin": "ast", - "id": "package_scripts_lint", - "community": 6, - "norm_label": "lint" - }, - { - "label": "lint:fix", - "file_type": "code", - "source_file": "package.json", - "source_location": "L14", - "_origin": "ast", - "id": "package_scripts_lint_fix", - "community": 6, - "norm_label": "lint:fix" - }, - { - "label": "format", - "file_type": "code", - "source_file": "package.json", - "source_location": "L15", - "_origin": "ast", - "id": "package_scripts_format", - "community": 6, - "norm_label": "format" - }, - { - "label": "format:check", - "file_type": "code", - "source_file": "package.json", - "source_location": "L16", - "_origin": "ast", - "id": "package_scripts_format_check", - "community": 6, - "norm_label": "format:check" - }, - { - "label": "preview", - "file_type": "code", - "source_file": "package.json", - "source_location": "L17", - "_origin": "ast", - "id": "package_scripts_preview", - "community": 6, - "norm_label": "preview" - }, - { - "label": "prepare", - "file_type": "code", - "source_file": "package.json", - "source_location": "L18", - "_origin": "ast", - "id": "package_scripts_prepare", - "community": 6, - "norm_label": "prepare" - }, - { - "label": "dependencies", - "file_type": "code", - "source_file": "package.json", - "source_location": "L20", - "_origin": "ast", - "id": "package_dependencies", - "community": 6, - "norm_label": "dependencies" - }, - { - "label": "oidc-client-ts", - "file_type": "code", - "source_file": "package.json", - "source_location": "L21", - "_origin": "ast", - "id": "package_dependencies_oidc_client_ts", - "community": 6, - "norm_label": "oidc-client-ts" - }, - { - "label": "react", - "file_type": "code", - "source_file": "package.json", - "source_location": "L22", - "_origin": "ast", - "id": "package_dependencies_react", - "community": 6, - "norm_label": "react" - }, - { - "label": "react-dom", - "file_type": "code", - "source_file": "package.json", - "source_location": "L23", - "_origin": "ast", - "id": "package_dependencies_react_dom", - "community": 6, - "norm_label": "react-dom" - }, - { - "label": "react-router", - "file_type": "code", - "source_file": "package.json", - "source_location": "L24", - "_origin": "ast", - "id": "package_dependencies_react_router", - "community": 6, - "norm_label": "react-router" - }, - { - "label": "devDependencies", - "file_type": "code", - "source_file": "package.json", - "source_location": "L26", - "_origin": "ast", - "id": "package_devdependencies", - "community": 5, - "norm_label": "devdependencies" - }, - { - "label": "@eslint/js", - "file_type": "code", - "source_file": "package.json", - "source_location": "L27", - "_origin": "ast", - "id": "package_devdependencies_eslint_js", - "community": 5, - "norm_label": "@eslint/js" - }, - { - "label": "@tailwindcss/vite", - "file_type": "code", - "source_file": "package.json", - "source_location": "L28", - "_origin": "ast", - "id": "package_devdependencies_tailwindcss_vite", - "community": 5, - "norm_label": "@tailwindcss/vite" - }, - { - "label": "@testing-library/jest-dom", - "file_type": "code", - "source_file": "package.json", - "source_location": "L29", - "_origin": "ast", - "id": "package_devdependencies_testing_library_jest_dom", - "community": 5, - "norm_label": "@testing-library/jest-dom" - }, - { - "label": "@testing-library/react", - "file_type": "code", - "source_file": "package.json", - "source_location": "L30", - "_origin": "ast", - "id": "package_devdependencies_testing_library_react", - "community": 5, - "norm_label": "@testing-library/react" - }, - { - "label": "@testing-library/user-event", - "file_type": "code", - "source_file": "package.json", - "source_location": "L31", - "_origin": "ast", - "id": "package_devdependencies_testing_library_user_event", - "community": 5, - "norm_label": "@testing-library/user-event" - }, - { - "label": "@types/node", - "file_type": "code", - "source_file": "package.json", - "source_location": "L32", - "_origin": "ast", - "id": "package_devdependencies_types_node", - "community": 5, - "norm_label": "@types/node" - }, - { - "label": "@types/react", - "file_type": "code", - "source_file": "package.json", - "source_location": "L33", - "_origin": "ast", - "id": "package_devdependencies_types_react", - "community": 5, - "norm_label": "@types/react" - }, - { - "label": "@types/react-dom", - "file_type": "code", - "source_file": "package.json", - "source_location": "L34", - "_origin": "ast", - "id": "package_devdependencies_types_react_dom", - "community": 5, - "norm_label": "@types/react-dom" - }, - { - "label": "@vitejs/plugin-react", - "file_type": "code", - "source_file": "package.json", - "source_location": "L35", - "_origin": "ast", - "id": "package_devdependencies_vitejs_plugin_react", - "community": 5, - "norm_label": "@vitejs/plugin-react" - }, - { - "label": "@vitest/ui", - "file_type": "code", - "source_file": "package.json", - "source_location": "L36", - "_origin": "ast", - "id": "package_devdependencies_vitest_ui", - "community": 5, - "norm_label": "@vitest/ui" - }, - { - "label": "eslint", - "file_type": "code", - "source_file": "package.json", - "source_location": "L37", - "_origin": "ast", - "id": "package_devdependencies_eslint", - "community": 5, - "norm_label": "eslint" - }, - { - "label": "eslint-config-prettier", - "file_type": "code", - "source_file": "package.json", - "source_location": "L38", - "_origin": "ast", - "id": "package_devdependencies_eslint_config_prettier", - "community": 5, - "norm_label": "eslint-config-prettier" - }, - { - "label": "eslint-plugin-react-hooks", - "file_type": "code", - "source_file": "package.json", - "source_location": "L39", - "_origin": "ast", - "id": "package_devdependencies_eslint_plugin_react_hooks", - "community": 5, - "norm_label": "eslint-plugin-react-hooks" - }, - { - "label": "eslint-plugin-react-refresh", - "file_type": "code", - "source_file": "package.json", - "source_location": "L40", - "_origin": "ast", - "id": "package_devdependencies_eslint_plugin_react_refresh", - "community": 5, - "norm_label": "eslint-plugin-react-refresh" - }, - { - "label": "globals", - "file_type": "code", - "source_file": "package.json", - "source_location": "L41", - "_origin": "ast", - "id": "package_devdependencies_globals", - "community": 5, - "norm_label": "globals" - }, - { - "label": "husky", - "file_type": "code", - "source_file": "package.json", - "source_location": "L42", - "_origin": "ast", - "id": "package_devdependencies_husky", - "community": 5, - "norm_label": "husky" - }, - { - "label": "jsdom", - "file_type": "code", - "source_file": "package.json", - "source_location": "L43", - "_origin": "ast", - "id": "package_devdependencies_jsdom", - "community": 5, - "norm_label": "jsdom" - }, - { - "label": "lint-staged", - "file_type": "code", - "source_file": "package.json", - "source_location": "L44", - "_origin": "ast", - "id": "package_devdependencies_lint_staged", - "community": 5, - "norm_label": "lint-staged" - }, - { - "label": "msw", - "file_type": "code", - "source_file": "package.json", - "source_location": "L45", - "_origin": "ast", - "id": "package_devdependencies_msw", - "community": 5, - "norm_label": "msw" - }, - { - "label": "prettier", - "file_type": "code", - "source_file": "package.json", - "source_location": "L46", - "_origin": "ast", - "id": "package_devdependencies_prettier", - "community": 5, - "norm_label": "prettier" - }, - { - "label": "tailwindcss", - "file_type": "code", - "source_file": "package.json", - "source_location": "L47", - "_origin": "ast", - "id": "package_devdependencies_tailwindcss", - "community": 5, - "norm_label": "tailwindcss" - }, - { - "label": "typescript", - "file_type": "code", - "source_file": "package.json", - "source_location": "L48", - "_origin": "ast", - "id": "package_devdependencies_typescript", - "community": 5, - "norm_label": "typescript" - }, - { - "label": "typescript-eslint", - "file_type": "code", - "source_file": "package.json", - "source_location": "L49", - "_origin": "ast", - "id": "package_devdependencies_typescript_eslint", - "community": 5, - "norm_label": "typescript-eslint" - }, - { - "label": "vite", - "file_type": "code", - "source_file": "package.json", - "source_location": "L50", - "_origin": "ast", - "id": "package_devdependencies_vite", - "community": 5, - "norm_label": "vite" - }, - { - "label": "vitest", - "file_type": "code", - "source_file": "package.json", - "source_location": "L51", - "_origin": "ast", - "id": "package_devdependencies_vitest", - "community": 5, - "norm_label": "vitest" - }, - { - "label": "App.tsx", - "file_type": "code", - "source_file": "src/App.tsx", - "source_location": "L1", - "_origin": "ast", - "id": "src_app", - "community": 9, - "norm_label": "app.tsx" - }, - { - "label": "App()", - "file_type": "code", - "source_file": "src/App.tsx", - "source_location": "L5", - "_origin": "ast", - "id": "src_app_app", - "community": 9, - "norm_label": "app()" - }, - { - "label": "TenantContext.ts", - "file_type": "code", - "source_file": "src/application/context/TenantContext.ts", - "source_location": "L1", - "_origin": "ast", - "id": "src_application_context_tenantcontext", - "community": 4, - "norm_label": "tenantcontext.ts" - }, - { - "label": "TenantContext", - "file_type": "code", - "source_file": "src/application/context/TenantContext.ts", - "source_location": "L14", - "_origin": "ast", - "id": "src_application_context_tenantcontext_tenantcontext", - "community": 4, - "norm_label": "tenantcontext" - }, - { - "label": "toTenantContext()", - "file_type": "code", - "source_file": "src/application/context/TenantContext.ts", - "source_location": "L19", - "_origin": "ast", - "id": "src_application_context_tenantcontext_totenantcontext", - "community": 4, - "norm_label": "totenantcontext()" - }, - { - "label": "AuthRepository.ts", - "file_type": "code", - "source_file": "src/application/repositories/AuthRepository.ts", - "source_location": "L1", - "_origin": "ast", - "id": "src_application_repositories_authrepository", - "community": 4, - "norm_label": "authrepository.ts" - }, - { - "label": "AuthRepository", - "file_type": "code", - "source_file": "src/application/repositories/AuthRepository.ts", - "source_location": "L14", - "_origin": "ast", - "id": "src_application_repositories_authrepository_authrepository", - "community": 4, - "norm_label": "authrepository" - }, - { - "label": ".initiateLogin()", - "file_type": "code", - "source_file": "src/application/repositories/AuthRepository.ts", - "source_location": "L19", - "_origin": "ast", - "id": "src_application_repositories_authrepository_authrepository_initiatelogin", - "community": 4, - "norm_label": ".initiatelogin()" - }, - { - "label": ".handleCallback()", - "file_type": "code", - "source_file": "src/application/repositories/AuthRepository.ts", - "source_location": "L30", - "_origin": "ast", - "id": "src_application_repositories_authrepository_authrepository_handlecallback", - "community": 4, - "norm_label": ".handlecallback()" - }, - { - "label": ".getCurrentSession()", - "file_type": "code", - "source_file": "src/application/repositories/AuthRepository.ts", - "source_location": "L39", - "_origin": "ast", - "id": "src_application_repositories_authrepository_authrepository_getcurrentsession", - "community": 4, - "norm_label": ".getcurrentsession()" - }, - { - "label": ".logout()", - "file_type": "code", - "source_file": "src/application/repositories/AuthRepository.ts", - "source_location": "L46", - "_origin": "ast", - "id": "src_application_repositories_authrepository_authrepository_logout", - "community": 4, - "norm_label": ".logout()" - }, - { - "label": "createFakeAuthRepository.ts", - "file_type": "code", - "source_file": "src/application/test-helpers/createFakeAuthRepository.ts", - "source_location": "L1", - "_origin": "ast", - "id": "src_application_test_helpers_createfakeauthrepository", - "community": 4, - "norm_label": "createfakeauthrepository.ts" - }, - { - "label": "createFakeAuthRepository()", - "file_type": "code", - "source_file": "src/application/test-helpers/createFakeAuthRepository.ts", - "source_location": "L13", - "_origin": "ast", - "id": "src_application_test_helpers_createfakeauthrepository_createfakeauthrepository", - "community": 4, - "norm_label": "createfakeauthrepository()" - }, - { - "label": "GetCurrentSession.test.ts", - "file_type": "code", - "source_file": "src/application/use-cases/auth/GetCurrentSession.test.ts", - "source_location": "L1", - "_origin": "ast", - "id": "src_application_use_cases_auth_getcurrentsession_test", - "community": 3, - "norm_label": "getcurrentsession.test.ts" - }, - { - "label": "GetCurrentSession.ts", - "file_type": "code", - "source_file": "src/application/use-cases/auth/GetCurrentSession.ts", - "source_location": "L1", - "_origin": "ast", - "id": "src_application_use_cases_auth_getcurrentsession", - "community": 4, - "norm_label": "getcurrentsession.ts" - }, - { - "label": "GetCurrentSession", - "file_type": "code", - "source_file": "src/application/use-cases/auth/GetCurrentSession.ts", - "source_location": "L10", - "_origin": "ast", - "id": "src_application_use_cases_auth_getcurrentsession_getcurrentsession", - "community": 4, - "norm_label": "getcurrentsession" - }, - { - "label": ".constructor()", - "file_type": "code", - "source_file": "src/application/use-cases/auth/GetCurrentSession.ts", - "source_location": "L13", - "_origin": "ast", - "id": "src_application_use_cases_auth_getcurrentsession_getcurrentsession_constructor", - "community": 4, - "norm_label": ".constructor()" - }, - { - "label": ".execute()", - "file_type": "code", - "source_file": "src/application/use-cases/auth/GetCurrentSession.ts", - "source_location": "L17", - "_origin": "ast", - "id": "src_application_use_cases_auth_getcurrentsession_getcurrentsession_execute", - "community": 4, - "norm_label": ".execute()" - }, - { - "label": "HandleAuthCallback.test.ts", - "file_type": "code", - "source_file": "src/application/use-cases/auth/HandleAuthCallback.test.ts", - "source_location": "L1", - "_origin": "ast", - "id": "src_application_use_cases_auth_handleauthcallback_test", - "community": 3, - "norm_label": "handleauthcallback.test.ts" - }, - { - "label": "HandleAuthCallback.ts", - "file_type": "code", - "source_file": "src/application/use-cases/auth/HandleAuthCallback.ts", - "source_location": "L1", - "_origin": "ast", - "id": "src_application_use_cases_auth_handleauthcallback", - "community": 4, - "norm_label": "handleauthcallback.ts" - }, - { - "label": "HandleAuthCallback", - "file_type": "code", - "source_file": "src/application/use-cases/auth/HandleAuthCallback.ts", - "source_location": "L17", - "_origin": "ast", - "id": "src_application_use_cases_auth_handleauthcallback_handleauthcallback", - "community": 4, - "norm_label": "handleauthcallback" - }, - { - "label": ".constructor()", - "file_type": "code", - "source_file": "src/application/use-cases/auth/HandleAuthCallback.ts", - "source_location": "L20", - "_origin": "ast", - "id": "src_application_use_cases_auth_handleauthcallback_handleauthcallback_constructor", - "community": 4, - "norm_label": ".constructor()" - }, - { - "label": ".execute()", - "file_type": "code", - "source_file": "src/application/use-cases/auth/HandleAuthCallback.ts", - "source_location": "L24", - "_origin": "ast", - "id": "src_application_use_cases_auth_handleauthcallback_handleauthcallback_execute", - "community": 4, - "norm_label": ".execute()" - }, - { - "label": "InitiateLogin.test.ts", - "file_type": "code", - "source_file": "src/application/use-cases/auth/InitiateLogin.test.ts", - "source_location": "L1", - "_origin": "ast", - "id": "src_application_use_cases_auth_initiatelogin_test", - "community": 4, - "norm_label": "initiatelogin.test.ts" - }, - { - "label": "InitiateLogin.ts", - "file_type": "code", - "source_file": "src/application/use-cases/auth/InitiateLogin.ts", - "source_location": "L1", - "_origin": "ast", - "id": "src_application_use_cases_auth_initiatelogin", - "community": 4, - "norm_label": "initiatelogin.ts" - }, - { - "label": "InitiateLogin", - "file_type": "code", - "source_file": "src/application/use-cases/auth/InitiateLogin.ts", - "source_location": "L9", - "_origin": "ast", - "id": "src_application_use_cases_auth_initiatelogin_initiatelogin", - "community": 4, - "norm_label": "initiatelogin" - }, - { - "label": ".constructor()", - "file_type": "code", - "source_file": "src/application/use-cases/auth/InitiateLogin.ts", - "source_location": "L12", - "_origin": "ast", - "id": "src_application_use_cases_auth_initiatelogin_initiatelogin_constructor", - "community": 4, - "norm_label": ".constructor()" - }, - { - "label": ".execute()", - "file_type": "code", - "source_file": "src/application/use-cases/auth/InitiateLogin.ts", - "source_location": "L16", - "_origin": "ast", - "id": "src_application_use_cases_auth_initiatelogin_initiatelogin_execute", - "community": 4, - "norm_label": ".execute()" - }, - { - "label": "Logout.test.ts", - "file_type": "code", - "source_file": "src/application/use-cases/auth/Logout.test.ts", - "source_location": "L1", - "_origin": "ast", - "id": "src_application_use_cases_auth_logout_test", - "community": 4, - "norm_label": "logout.test.ts" - }, - { - "label": "Logout.ts", - "file_type": "code", - "source_file": "src/application/use-cases/auth/Logout.ts", - "source_location": "L1", - "_origin": "ast", - "id": "src_application_use_cases_auth_logout", - "community": 4, - "norm_label": "logout.ts" - }, - { - "label": "Logout", - "file_type": "code", - "source_file": "src/application/use-cases/auth/Logout.ts", - "source_location": "L10", - "_origin": "ast", - "id": "src_application_use_cases_auth_logout_logout", - "community": 4, - "norm_label": "logout" - }, - { - "label": ".constructor()", - "file_type": "code", - "source_file": "src/application/use-cases/auth/Logout.ts", - "source_location": "L13", - "_origin": "ast", - "id": "src_application_use_cases_auth_logout_logout_constructor", - "community": 4, - "norm_label": ".constructor()" - }, - { - "label": ".execute()", - "file_type": "code", - "source_file": "src/application/use-cases/auth/Logout.ts", - "source_location": "L17", - "_origin": "ast", - "id": "src_application_use_cases_auth_logout_logout_execute", - "community": 4, - "norm_label": ".execute()" - }, - { - "label": "container.ts", - "file_type": "code", - "source_file": "src/composition/container.ts", - "source_location": "L1", - "_origin": "ast", - "id": "src_composition_container", - "community": 4, - "norm_label": "container.ts" - }, - { - "label": "AppContainer", - "file_type": "code", - "source_file": "src/composition/container.ts", - "source_location": "L9", - "_origin": "ast", - "id": "src_composition_container_appcontainer", - "community": 4, - "norm_label": "appcontainer" - }, - { - "label": "createAppContainer()", - "file_type": "code", - "source_file": "src/composition/container.ts", - "source_location": "L30", - "_origin": "ast", - "id": "src_composition_container_createappcontainer", - "community": 9, - "norm_label": "createappcontainer()" - }, - { - "label": "Session.test.ts", - "file_type": "code", - "source_file": "src/domain/entities/Session.test.ts", - "source_location": "L1", - "_origin": "ast", - "id": "src_domain_entities_session_test", - "community": 3, - "norm_label": "session.test.ts" - }, - { - "label": "Session.ts", - "file_type": "code", - "source_file": "src/domain/entities/Session.ts", - "source_location": "L1", - "_origin": "ast", - "id": "src_domain_entities_session", - "community": 3, - "norm_label": "session.ts" - }, - { - "label": "CreateSessionInput", - "file_type": "code", - "source_file": "src/domain/entities/Session.ts", - "source_location": "L5", - "_origin": "ast", - "id": "src_domain_entities_session_createsessioninput", - "community": 3, - "norm_label": "createsessioninput" - }, - { - "label": "Session", - "file_type": "code", - "source_file": "src/domain/entities/Session.ts", - "source_location": "L21", - "_origin": "ast", - "id": "src_domain_entities_session_session", - "community": 3, - "norm_label": "session" - }, - { - "label": ".constructor()", - "file_type": "code", - "source_file": "src/domain/entities/Session.ts", - "source_location": "L26", - "_origin": "ast", - "id": "src_domain_entities_session_session_constructor", - "community": 3, - "norm_label": ".constructor()" - }, - { - "label": ".create()", - "file_type": "code", - "source_file": "src/domain/entities/Session.ts", - "source_location": "L32", - "_origin": "ast", - "id": "src_domain_entities_session_session_create", - "community": 3, - "norm_label": ".create()" - }, - { - "label": ".isExpiredAt()", - "file_type": "code", - "source_file": "src/domain/entities/Session.ts", - "source_location": "L45", - "_origin": "ast", - "id": "src_domain_entities_session_session_isexpiredat", - "community": 3, - "norm_label": ".isexpiredat()" - }, - { - "label": ".belongsToTenant()", - "file_type": "code", - "source_file": "src/domain/entities/Session.ts", - "source_location": "L49", - "_origin": "ast", - "id": "src_domain_entities_session_session_belongstotenant", - "community": 3, - "norm_label": ".belongstotenant()" - }, - { - "label": "User.test.ts", - "file_type": "code", - "source_file": "src/domain/entities/User.test.ts", - "source_location": "L1", - "_origin": "ast", - "id": "src_domain_entities_user_test", - "community": 3, - "norm_label": "user.test.ts" - }, - { - "label": "User.ts", - "file_type": "code", - "source_file": "src/domain/entities/User.ts", - "source_location": "L1", - "_origin": "ast", - "id": "src_domain_entities_user", - "community": 3, - "norm_label": "user.ts" - }, - { - "label": "CreateUserInput", - "file_type": "code", - "source_file": "src/domain/entities/User.ts", - "source_location": "L4", - "_origin": "ast", - "id": "src_domain_entities_user_createuserinput", - "community": 3, - "norm_label": "createuserinput" - }, - { - "label": "User", - "file_type": "code", - "source_file": "src/domain/entities/User.ts", - "source_location": "L20", - "_origin": "ast", - "id": "src_domain_entities_user_user", - "community": 3, - "norm_label": "user" - }, - { - "label": ".constructor()", - "file_type": "code", - "source_file": "src/domain/entities/User.ts", - "source_location": "L26", - "_origin": "ast", - "id": "src_domain_entities_user_user_constructor", - "community": 3, - "norm_label": ".constructor()" - }, - { - "label": ".create()", - "file_type": "code", - "source_file": "src/domain/entities/User.ts", - "source_location": "L37", - "_origin": "ast", - "id": "src_domain_entities_user_user_create", - "community": 3, - "norm_label": ".create()" - }, - { - "label": ".belongsToTenant()", - "file_type": "code", - "source_file": "src/domain/entities/User.ts", - "source_location": "L45", - "_origin": "ast", - "id": "src_domain_entities_user_user_belongstotenant", - "community": 3, - "norm_label": ".belongstotenant()" - }, - { - "label": "DomainError.ts", - "file_type": "code", - "source_file": "src/domain/errors/DomainError.ts", - "source_location": "L1", - "_origin": "ast", - "id": "src_domain_errors_domainerror", - "community": 10, - "norm_label": "domainerror.ts" - }, - { - "label": "DomainError", - "file_type": "code", - "source_file": "src/domain/errors/DomainError.ts", - "source_location": "L8", - "_origin": "ast", - "id": "src_domain_errors_domainerror_domainerror", - "community": 10, - "norm_label": "domainerror" - }, - { - "label": ".constructor()", - "file_type": "code", - "source_file": "src/domain/errors/DomainError.ts", - "source_location": "L9", - "_origin": "ast", - "id": "src_domain_errors_domainerror_domainerror_constructor", - "community": 10, - "norm_label": ".constructor()" - }, - { - "label": "InvalidSessionError.ts", - "file_type": "code", - "source_file": "src/domain/errors/InvalidSessionError.ts", - "source_location": "L1", - "_origin": "ast", - "id": "src_domain_errors_invalidsessionerror", - "community": 10, - "norm_label": "invalidsessionerror.ts" - }, - { - "label": "InvalidSessionError", - "file_type": "code", - "source_file": "src/domain/errors/InvalidSessionError.ts", - "source_location": "L3", - "_origin": "ast", - "id": "src_domain_errors_invalidsessionerror_invalidsessionerror", - "community": 10, - "norm_label": "invalidsessionerror" - }, - { - "label": "InvalidTenantError.ts", - "file_type": "code", - "source_file": "src/domain/errors/InvalidTenantError.ts", - "source_location": "L1", - "_origin": "ast", - "id": "src_domain_errors_invalidtenanterror", - "community": 10, - "norm_label": "invalidtenanterror.ts" - }, - { - "label": "InvalidTenantError", - "file_type": "code", - "source_file": "src/domain/errors/InvalidTenantError.ts", - "source_location": "L3", - "_origin": "ast", - "id": "src_domain_errors_invalidtenanterror_invalidtenanterror", - "community": 10, - "norm_label": "invalidtenanterror" - }, - { - "label": "InvalidUserError.ts", - "file_type": "code", - "source_file": "src/domain/errors/InvalidUserError.ts", - "source_location": "L1", - "_origin": "ast", - "id": "src_domain_errors_invalidusererror", - "community": 10, - "norm_label": "invalidusererror.ts" - }, - { - "label": "InvalidUserError", - "file_type": "code", - "source_file": "src/domain/errors/InvalidUserError.ts", - "source_location": "L3", - "_origin": "ast", - "id": "src_domain_errors_invalidusererror_invalidusererror", - "community": 10, - "norm_label": "invalidusererror" - }, - { - "label": "Tenant.test.ts", - "file_type": "code", - "source_file": "src/domain/value-objects/Tenant.test.ts", - "source_location": "L1", - "_origin": "ast", - "id": "src_domain_value_objects_tenant_test", - "community": 3, - "norm_label": "tenant.test.ts" - }, - { - "label": "Tenant.ts", - "file_type": "code", - "source_file": "src/domain/value-objects/Tenant.ts", - "source_location": "L1", - "_origin": "ast", - "id": "src_domain_value_objects_tenant", - "community": 3, - "norm_label": "tenant.ts" - }, - { - "label": "Tenant", - "file_type": "code", - "source_file": "src/domain/value-objects/Tenant.ts", - "source_location": "L9", - "_origin": "ast", - "id": "src_domain_value_objects_tenant_tenant", - "community": 3, - "norm_label": "tenant" - }, - { - "label": ".constructor()", - "file_type": "code", - "source_file": "src/domain/value-objects/Tenant.ts", - "source_location": "L12", - "_origin": "ast", - "id": "src_domain_value_objects_tenant_tenant_constructor", - "community": 3, - "norm_label": ".constructor()" - }, - { - "label": ".create()", - "file_type": "code", - "source_file": "src/domain/value-objects/Tenant.ts", - "source_location": "L16", - "_origin": "ast", - "id": "src_domain_value_objects_tenant_tenant_create", - "community": 3, - "norm_label": ".create()" - }, - { - "label": ".equals()", - "file_type": "code", - "source_file": "src/domain/value-objects/Tenant.ts", - "source_location": "L26", - "_origin": "ast", - "id": "src_domain_value_objects_tenant_tenant_equals", - "community": 3, - "norm_label": ".equals()" - }, - { - "label": "OidcAuthRepository.test.ts", - "file_type": "code", - "source_file": "src/infrastructure/auth/OidcAuthRepository.test.ts", - "source_location": "L1", - "_origin": "ast", - "id": "src_infrastructure_auth_oidcauthrepository_test", - "community": 11, - "norm_label": "oidcauthrepository.test.ts" - }, - { - "label": "createFakeOidcUser()", - "file_type": "code", - "source_file": "src/infrastructure/auth/OidcAuthRepository.test.ts", - "source_location": "L7", - "_origin": "ast", - "id": "src_infrastructure_auth_oidcauthrepository_test_createfakeoidcuser", - "community": 11, - "norm_label": "createfakeoidcuser()" - }, - { - "label": "FakeUserManager", - "file_type": "code", - "source_file": "src/infrastructure/auth/OidcAuthRepository.test.ts", - "source_location": "L23", - "_origin": "ast", - "id": "src_infrastructure_auth_oidcauthrepository_test_fakeusermanager", - "community": 11, - "norm_label": "fakeusermanager" - }, - { - "label": "createFakeUserManager()", - "file_type": "code", - "source_file": "src/infrastructure/auth/OidcAuthRepository.test.ts", - "source_location": "L32", - "_origin": "ast", - "id": "src_infrastructure_auth_oidcauthrepository_test_createfakeusermanager", - "community": 11, - "norm_label": "createfakeusermanager()" - }, - { - "label": "OidcAuthRepository.ts", - "file_type": "code", - "source_file": "src/infrastructure/auth/OidcAuthRepository.ts", - "source_location": "L1", - "_origin": "ast", - "id": "src_infrastructure_auth_oidcauthrepository", - "community": 4, - "norm_label": "oidcauthrepository.ts" - }, - { - "label": "OidcAuthRepository", - "file_type": "code", - "source_file": "src/infrastructure/auth/OidcAuthRepository.ts", - "source_location": "L16", - "_origin": "ast", - "id": "src_infrastructure_auth_oidcauthrepository_oidcauthrepository", - "community": 3, - "norm_label": "oidcauthrepository" - }, - { - "label": ".constructor()", - "file_type": "code", - "source_file": "src/infrastructure/auth/OidcAuthRepository.ts", - "source_location": "L19", - "_origin": "ast", - "id": "src_infrastructure_auth_oidcauthrepository_oidcauthrepository_constructor", - "community": 3, - "norm_label": ".constructor()" - }, - { - "label": ".initiateLogin()", - "file_type": "code", - "source_file": "src/infrastructure/auth/OidcAuthRepository.ts", - "source_location": "L23", - "_origin": "ast", - "id": "src_infrastructure_auth_oidcauthrepository_oidcauthrepository_initiatelogin", - "community": 3, - "norm_label": ".initiatelogin()" - }, - { - "label": ".handleCallback()", - "file_type": "code", - "source_file": "src/infrastructure/auth/OidcAuthRepository.ts", - "source_location": "L27", - "_origin": "ast", - "id": "src_infrastructure_auth_oidcauthrepository_oidcauthrepository_handlecallback", - "community": 3, - "norm_label": ".handlecallback()" - }, - { - "label": ".getCurrentSession()", - "file_type": "code", - "source_file": "src/infrastructure/auth/OidcAuthRepository.ts", - "source_location": "L33", - "_origin": "ast", - "id": "src_infrastructure_auth_oidcauthrepository_oidcauthrepository_getcurrentsession", - "community": 3, - "norm_label": ".getcurrentsession()" - }, - { - "label": ".logout()", - "file_type": "code", - "source_file": "src/infrastructure/auth/OidcAuthRepository.ts", - "source_location": "L65", - "_origin": "ast", - "id": "src_infrastructure_auth_oidcauthrepository_oidcauthrepository_logout", - "community": 3, - "norm_label": ".logout()" - }, - { - "label": "createUserManager.ts", - "file_type": "code", - "source_file": "src/infrastructure/config/createUserManager.ts", - "source_location": "L1", - "_origin": "ast", - "id": "src_infrastructure_config_createusermanager", - "community": 4, - "norm_label": "createusermanager.ts" - }, - { - "label": "createUserManager()", - "file_type": "code", - "source_file": "src/infrastructure/config/createUserManager.ts", - "source_location": "L16", - "_origin": "ast", - "id": "src_infrastructure_config_createusermanager_createusermanager", - "community": 4, - "norm_label": "createusermanager()" - }, - { - "label": "MissingTenantClaimError.ts", - "file_type": "code", - "source_file": "src/infrastructure/mappers/MissingTenantClaimError.ts", - "source_location": "L1", - "_origin": "ast", - "id": "src_infrastructure_mappers_missingtenantclaimerror", - "community": 11, - "norm_label": "missingtenantclaimerror.ts" - }, - { - "label": "MissingTenantClaimError", - "file_type": "code", - "source_file": "src/infrastructure/mappers/MissingTenantClaimError.ts", - "source_location": "L8", - "_origin": "ast", - "id": "src_infrastructure_mappers_missingtenantclaimerror_missingtenantclaimerror", - "community": 11, - "norm_label": "missingtenantclaimerror" - }, - { - "label": ".constructor()", - "file_type": "code", - "source_file": "src/infrastructure/mappers/MissingTenantClaimError.ts", - "source_location": "L9", - "_origin": "ast", - "id": "src_infrastructure_mappers_missingtenantclaimerror_missingtenantclaimerror_constructor", - "community": 11, - "norm_label": ".constructor()" - }, - { - "label": "oidcUserToSessionMapper.test.ts", - "file_type": "code", - "source_file": "src/infrastructure/mappers/oidcUserToSessionMapper.test.ts", - "source_location": "L1", - "_origin": "ast", - "id": "src_infrastructure_mappers_oidcusertosessionmapper_test", - "community": 11, - "norm_label": "oidcusertosessionmapper.test.ts" - }, - { - "label": "createFakeOidcUser()", - "file_type": "code", - "source_file": "src/infrastructure/mappers/oidcUserToSessionMapper.test.ts", - "source_location": "L6", - "_origin": "ast", - "id": "src_infrastructure_mappers_oidcusertosessionmapper_test_createfakeoidcuser", - "community": 11, - "norm_label": "createfakeoidcuser()" - }, - { - "label": "oidcUserToSessionMapper.ts", - "file_type": "code", - "source_file": "src/infrastructure/mappers/oidcUserToSessionMapper.ts", - "source_location": "L1", - "_origin": "ast", - "id": "src_infrastructure_mappers_oidcusertosessionmapper", - "community": 3, - "norm_label": "oidcusertosessionmapper.ts" - }, - { - "label": "mapOidcUserToSession()", - "file_type": "code", - "source_file": "src/infrastructure/mappers/oidcUserToSessionMapper.ts", - "source_location": "L14", - "_origin": "ast", - "id": "src_infrastructure_mappers_oidcusertosessionmapper_mapoidcusertosession", - "community": 3, - "norm_label": "mapoidcusertosession()" - }, - { - "label": "main.tsx", - "file_type": "code", - "source_file": "src/main.tsx", - "source_location": "L1", - "_origin": "ast", - "id": "src_main", - "community": 9, - "norm_label": "main.tsx" - }, - { - "label": "rootElement", - "file_type": "code", - "source_file": "src/main.tsx", - "source_location": "L7", - "_origin": "ast", - "id": "src_main_rootelement", - "community": 9, - "norm_label": "rootelement" - }, - { - "label": "useAppContainer.test.tsx", - "file_type": "code", - "source_file": "src/presentation/hooks/useAppContainer.test.tsx", - "source_location": "L1", - "_origin": "ast", - "id": "src_presentation_hooks_useappcontainer_test", - "community": 2, - "norm_label": "useappcontainer.test.tsx" - }, - { - "label": "useAppContainer.ts", - "file_type": "code", - "source_file": "src/presentation/hooks/useAppContainer.ts", - "source_location": "L1", - "_origin": "ast", - "id": "src_presentation_hooks_useappcontainer", - "community": 2, - "norm_label": "useappcontainer.ts" - }, - { - "label": "useAppContainer()", - "file_type": "code", - "source_file": "src/presentation/hooks/useAppContainer.ts", - "source_location": "L11", - "_origin": "ast", - "id": "src_presentation_hooks_useappcontainer_useappcontainer", - "community": 2, - "norm_label": "useappcontainer()" - }, - { - "label": "useAsync.test.tsx", - "file_type": "code", - "source_file": "src/presentation/hooks/useAsync.test.tsx", - "source_location": "L1", - "_origin": "ast", - "id": "src_presentation_hooks_useasync_test", - "community": 2, - "norm_label": "useasync.test.tsx" - }, - { - "label": "useAsync.ts", - "file_type": "code", - "source_file": "src/presentation/hooks/useAsync.ts", - "source_location": "L1", - "_origin": "ast", - "id": "src_presentation_hooks_useasync", - "community": 2, - "norm_label": "useasync.ts" - }, - { - "label": "AsyncStatus", - "file_type": "code", - "source_file": "src/presentation/hooks/useAsync.ts", - "source_location": "L3", - "_origin": "ast", - "id": "src_presentation_hooks_useasync_asyncstatus", - "community": 2, - "norm_label": "asyncstatus" - }, - { - "label": "UseAsyncResult", - "file_type": "code", - "source_file": "src/presentation/hooks/useAsync.ts", - "source_location": "L5", - "_origin": "ast", - "id": "src_presentation_hooks_useasync_useasyncresult", - "community": 2, - "norm_label": "useasyncresult" - }, - { - "label": "UseAsyncOptions", - "file_type": "code", - "source_file": "src/presentation/hooks/useAsync.ts", - "source_location": "L12", - "_origin": "ast", - "id": "src_presentation_hooks_useasync_useasyncoptions", - "community": 2, - "norm_label": "useasyncoptions" - }, - { - "label": "useAsync()", - "file_type": "code", - "source_file": "src/presentation/hooks/useAsync.ts", - "source_location": "L27", - "_origin": "ast", - "id": "src_presentation_hooks_useasync_useasync", - "community": 2, - "norm_label": "useasync()" - }, - { - "label": "useAuth.test.tsx", - "file_type": "code", - "source_file": "src/presentation/hooks/useAuth.test.tsx", - "source_location": "L1", - "_origin": "ast", - "id": "src_presentation_hooks_useauth_test", - "community": 3, - "norm_label": "useauth.test.tsx" - }, - { - "label": "FakeUseCases", - "file_type": "code", - "source_file": "src/presentation/hooks/useAuth.test.tsx", - "source_location": "L9", - "_origin": "ast", - "id": "src_presentation_hooks_useauth_test_fakeusecases", - "community": 3, - "norm_label": "fakeusecases" - }, - { - "label": "createFakeContainer()", - "file_type": "code", - "source_file": "src/presentation/hooks/useAuth.test.tsx", - "source_location": "L16", - "_origin": "ast", - "id": "src_presentation_hooks_useauth_test_createfakecontainer", - "community": 3, - "norm_label": "createfakecontainer()" - }, - { - "label": "renderUseAuth()", - "file_type": "code", - "source_file": "src/presentation/hooks/useAuth.test.tsx", - "source_location": "L34", - "_origin": "ast", - "id": "src_presentation_hooks_useauth_test_renderuseauth", - "community": 2, - "norm_label": "renderuseauth()" - }, - { - "label": "useAuth.ts", - "file_type": "code", - "source_file": "src/presentation/hooks/useAuth.ts", - "source_location": "L1", - "_origin": "ast", - "id": "src_presentation_hooks_useauth", - "community": 2, - "norm_label": "useauth.ts" - }, - { - "label": "AuthStatus", - "file_type": "code", - "source_file": "src/presentation/hooks/useAuth.ts", - "source_location": "L6", - "_origin": "ast", - "id": "src_presentation_hooks_useauth_authstatus", - "community": 2, - "norm_label": "authstatus" - }, - { - "label": "UseAuthResult", - "file_type": "code", - "source_file": "src/presentation/hooks/useAuth.ts", - "source_location": "L8", - "_origin": "ast", - "id": "src_presentation_hooks_useauth_useauthresult", - "community": 2, - "norm_label": "useauthresult" - }, - { - "label": "useAuth()", - "file_type": "code", - "source_file": "src/presentation/hooks/useAuth.ts", - "source_location": "L22", - "_origin": "ast", - "id": "src_presentation_hooks_useauth_useauth", - "community": 2, - "norm_label": "useauth()" - }, - { - "label": "AdminLayout.tsx", - "file_type": "code", - "source_file": "src/presentation/layouts/AdminLayout.tsx", - "source_location": "L1", - "_origin": "ast", - "id": "src_presentation_layouts_adminlayout", - "community": 2, - "norm_label": "adminlayout.tsx" - }, - { - "label": "NavItem", - "file_type": "code", - "source_file": "src/presentation/layouts/AdminLayout.tsx", - "source_location": "L5", - "_origin": "ast", - "id": "src_presentation_layouts_adminlayout_navitem", - "community": 2, - "norm_label": "navitem" - }, - { - "label": "NAV_ITEMS", - "file_type": "code", - "source_file": "src/presentation/layouts/AdminLayout.tsx", - "source_location": "L10", - "_origin": "ast", - "id": "src_presentation_layouts_adminlayout_nav_items", - "community": 2, - "norm_label": "nav_items" - }, - { - "label": "AdminLayout()", - "file_type": "code", - "source_file": "src/presentation/layouts/AdminLayout.tsx", - "source_location": "L19", - "_origin": "ast", - "id": "src_presentation_layouts_adminlayout_adminlayout", - "community": 2, - "norm_label": "adminlayout()" - }, - { - "label": "AppointmentsPage.tsx", - "file_type": "code", - "source_file": "src/presentation/pages/AppointmentsPage/AppointmentsPage.tsx", - "source_location": "L1", - "_origin": "ast", - "id": "src_presentation_pages_appointmentspage_appointmentspage", - "community": 2, - "norm_label": "appointmentspage.tsx" - }, - { - "label": "AppointmentsPage()", - "file_type": "code", - "source_file": "src/presentation/pages/AppointmentsPage/AppointmentsPage.tsx", - "source_location": "L3", - "_origin": "ast", - "id": "src_presentation_pages_appointmentspage_appointmentspage_appointmentspage", - "community": 2, - "norm_label": "appointmentspage()" - }, - { - "label": "CallbackPage.tsx", - "file_type": "code", - "source_file": "src/presentation/pages/CallbackPage/CallbackPage.tsx", - "source_location": "L1", - "_origin": "ast", - "id": "src_presentation_pages_callbackpage_callbackpage", - "community": 2, - "norm_label": "callbackpage.tsx" - }, - { - "label": "CallbackStatus", - "file_type": "code", - "source_file": "src/presentation/pages/CallbackPage/CallbackPage.tsx", - "source_location": "L5", - "_origin": "ast", - "id": "src_presentation_pages_callbackpage_callbackpage_callbackstatus", - "community": 2, - "norm_label": "callbackstatus" - }, - { - "label": "CallbackPage()", - "file_type": "code", - "source_file": "src/presentation/pages/CallbackPage/CallbackPage.tsx", - "source_location": "L15", - "_origin": "ast", - "id": "src_presentation_pages_callbackpage_callbackpage_callbackpage", - "community": 2, - "norm_label": "callbackpage()" - }, - { - "label": "ClientsPage.tsx", - "file_type": "code", - "source_file": "src/presentation/pages/ClientsPage/ClientsPage.tsx", - "source_location": "L1", - "_origin": "ast", - "id": "src_presentation_pages_clientspage_clientspage", - "community": 2, - "norm_label": "clientspage.tsx" - }, - { - "label": "ClientsPage()", - "file_type": "code", - "source_file": "src/presentation/pages/ClientsPage/ClientsPage.tsx", - "source_location": "L3", - "_origin": "ast", - "id": "src_presentation_pages_clientspage_clientspage_clientspage", - "community": 2, - "norm_label": "clientspage()" - }, - { - "label": "DashboardPage.tsx", - "file_type": "code", - "source_file": "src/presentation/pages/DashboardPage/DashboardPage.tsx", - "source_location": "L1", - "_origin": "ast", - "id": "src_presentation_pages_dashboardpage_dashboardpage", - "community": 2, - "norm_label": "dashboardpage.tsx" - }, - { - "label": "DashboardPage()", - "file_type": "code", - "source_file": "src/presentation/pages/DashboardPage/DashboardPage.tsx", - "source_location": "L3", - "_origin": "ast", - "id": "src_presentation_pages_dashboardpage_dashboardpage_dashboardpage", - "community": 2, - "norm_label": "dashboardpage()" - }, - { - "label": "InboxPage.tsx", - "file_type": "code", - "source_file": "src/presentation/pages/InboxPage/InboxPage.tsx", - "source_location": "L1", - "_origin": "ast", - "id": "src_presentation_pages_inboxpage_inboxpage", - "community": 2, - "norm_label": "inboxpage.tsx" - }, - { - "label": "InboxPage()", - "file_type": "code", - "source_file": "src/presentation/pages/InboxPage/InboxPage.tsx", - "source_location": "L3", - "_origin": "ast", - "id": "src_presentation_pages_inboxpage_inboxpage_inboxpage", - "community": 2, - "norm_label": "inboxpage()" - }, - { - "label": "LoginPage.test.tsx", - "file_type": "code", - "source_file": "src/presentation/pages/LoginPage/LoginPage.test.tsx", - "source_location": "L1", - "_origin": "ast", - "id": "src_presentation_pages_loginpage_loginpage_test", - "community": 2, - "norm_label": "loginpage.test.tsx" - }, - { - "label": "buildContainer()", - "file_type": "code", - "source_file": "src/presentation/pages/LoginPage/LoginPage.test.tsx", - "source_location": "L10", - "_origin": "ast", - "id": "src_presentation_pages_loginpage_loginpage_test_buildcontainer", - "community": 2, - "norm_label": "buildcontainer()" - }, - { - "label": "renderLoginPage()", - "file_type": "code", - "source_file": "src/presentation/pages/LoginPage/LoginPage.test.tsx", - "source_location": "L22", - "_origin": "ast", - "id": "src_presentation_pages_loginpage_loginpage_test_renderloginpage", - "community": 2, - "norm_label": "renderloginpage()" - }, - { - "label": "LoginPage.tsx", - "file_type": "code", - "source_file": "src/presentation/pages/LoginPage/LoginPage.tsx", - "source_location": "L1", - "_origin": "ast", - "id": "src_presentation_pages_loginpage_loginpage", - "community": 2, - "norm_label": "loginpage.tsx" - }, - { - "label": "LoginPage()", - "file_type": "code", - "source_file": "src/presentation/pages/LoginPage/LoginPage.tsx", - "source_location": "L10", - "_origin": "ast", - "id": "src_presentation_pages_loginpage_loginpage_loginpage", - "community": 2, - "norm_label": "loginpage()" - }, - { - "label": "ServicesPage.tsx", - "file_type": "code", - "source_file": "src/presentation/pages/ServicesPage/ServicesPage.tsx", - "source_location": "L1", - "_origin": "ast", - "id": "src_presentation_pages_servicespage_servicespage", - "community": 2, - "norm_label": "servicespage.tsx" - }, - { - "label": "ServicesPage()", - "file_type": "code", - "source_file": "src/presentation/pages/ServicesPage/ServicesPage.tsx", - "source_location": "L3", - "_origin": "ast", - "id": "src_presentation_pages_servicespage_servicespage_servicespage", - "community": 2, - "norm_label": "servicespage()" - }, - { - "label": "SettingsPage.tsx", - "file_type": "code", - "source_file": "src/presentation/pages/SettingsPage/SettingsPage.tsx", - "source_location": "L1", - "_origin": "ast", - "id": "src_presentation_pages_settingspage_settingspage", - "community": 2, - "norm_label": "settingspage.tsx" - }, - { - "label": "SettingsPage()", - "file_type": "code", - "source_file": "src/presentation/pages/SettingsPage/SettingsPage.tsx", - "source_location": "L3", - "_origin": "ast", - "id": "src_presentation_pages_settingspage_settingspage_settingspage", - "community": 2, - "norm_label": "settingspage()" - }, - { - "label": "AppContainerContext.ts", - "file_type": "code", - "source_file": "src/presentation/providers/AppContainerContext.ts", - "source_location": "L1", - "_origin": "ast", - "id": "src_presentation_providers_appcontainercontext", - "community": 2, - "norm_label": "appcontainercontext.ts" - }, - { - "label": "AppContainerContext", - "file_type": "code", - "source_file": "src/presentation/providers/AppContainerContext.ts", - "source_location": "L9", - "_origin": "ast", - "id": "src_presentation_providers_appcontainercontext_appcontainercontext", - "community": 2, - "norm_label": "appcontainercontext" - }, - { - "label": "AppProviders.tsx", - "file_type": "code", - "source_file": "src/presentation/providers/AppProviders.tsx", - "source_location": "L1", - "_origin": "ast", - "id": "src_presentation_providers_appproviders", - "community": 9, - "norm_label": "appproviders.tsx" - }, - { - "label": "AppProvidersProps", - "file_type": "code", - "source_file": "src/presentation/providers/AppProviders.tsx", - "source_location": "L5", - "_origin": "ast", - "id": "src_presentation_providers_appproviders_appprovidersprops", - "community": 9, - "norm_label": "appprovidersprops" - }, - { - "label": "AppProviders()", - "file_type": "code", - "source_file": "src/presentation/providers/AppProviders.tsx", - "source_location": "L16", - "_origin": "ast", - "id": "src_presentation_providers_appproviders_appproviders", - "community": 9, - "norm_label": "appproviders()" - }, - { - "label": "ProtectedRoute.test.tsx", - "file_type": "code", - "source_file": "src/presentation/routes/ProtectedRoute.test.tsx", - "source_location": "L1", - "_origin": "ast", - "id": "src_presentation_routes_protectedroute_test", - "community": 3, - "norm_label": "protectedroute.test.tsx" - }, - { - "label": "buildContainer()", - "file_type": "code", - "source_file": "src/presentation/routes/ProtectedRoute.test.tsx", - "source_location": "L11", - "_origin": "ast", - "id": "src_presentation_routes_protectedroute_test_buildcontainer", - "community": 3, - "norm_label": "buildcontainer()" - }, - { - "label": "renderWithRouter()", - "file_type": "code", - "source_file": "src/presentation/routes/ProtectedRoute.test.tsx", - "source_location": "L37", - "_origin": "ast", - "id": "src_presentation_routes_protectedroute_test_renderwithrouter", - "community": 3, - "norm_label": "renderwithrouter()" - }, - { - "label": "ProtectedRoute.tsx", - "file_type": "code", - "source_file": "src/presentation/routes/ProtectedRoute.tsx", - "source_location": "L1", - "_origin": "ast", - "id": "src_presentation_routes_protectedroute", - "community": 2, - "norm_label": "protectedroute.tsx" - }, - { - "label": "ProtectedRoute()", - "file_type": "code", - "source_file": "src/presentation/routes/ProtectedRoute.tsx", - "source_location": "L11", - "_origin": "ast", - "id": "src_presentation_routes_protectedroute_protectedroute", - "community": 2, - "norm_label": "protectedroute()" - }, - { - "label": "router.tsx", - "file_type": "code", - "source_file": "src/presentation/routes/router.tsx", - "source_location": "L1", - "_origin": "ast", - "id": "src_presentation_routes_router", - "community": 2, - "norm_label": "router.tsx" - }, - { - "label": "router", - "file_type": "code", - "source_file": "src/presentation/routes/router.tsx", - "source_location": "L13", - "_origin": "ast", - "id": "src_presentation_routes_router_router", - "community": 9, - "norm_label": "router" - }, - { - "label": "index.ts", - "file_type": "code", - "source_file": "src/test/mocks/handlers/index.ts", - "source_location": "L1", - "_origin": "ast", - "id": "src_test_mocks_handlers_index", - "community": 14, - "norm_label": "index.ts" - }, - { - "label": "handlers", - "file_type": "code", - "source_file": "src/test/mocks/handlers/index.ts", - "source_location": "L7", - "_origin": "ast", - "id": "src_test_mocks_handlers_index_handlers", - "community": 14, - "norm_label": "handlers" - }, - { - "label": "server.ts", - "file_type": "code", - "source_file": "src/test/mocks/server.ts", - "source_location": "L1", - "_origin": "ast", - "id": "src_test_mocks_server", - "community": 14, - "norm_label": "server.ts" - }, - { - "label": "server", - "file_type": "code", - "source_file": "src/test/mocks/server.ts", - "source_location": "L4", - "_origin": "ast", - "id": "src_test_mocks_server_server", - "community": 14, - "norm_label": "server" - }, - { - "label": "setup.ts", - "file_type": "code", - "source_file": "src/test/setup.ts", - "source_location": "L1", - "_origin": "ast", - "id": "src_test_setup", - "community": 14, - "norm_label": "setup.ts" - }, - { - "label": "vite-env.d.ts", - "file_type": "code", - "source_file": "src/vite-env.d.ts", - "source_location": "L1", - "_origin": "ast", - "id": "src_vite_env_d", - "community": 16, - "norm_label": "vite-env.d.ts" - }, - { - "label": "ImportMetaEnv", - "file_type": "code", - "source_file": "src/vite-env.d.ts", - "source_location": "L3", - "_origin": "ast", - "id": "src_vite_env_d_importmetaenv", - "community": 16, - "norm_label": "importmetaenv" - }, - { - "label": "ImportMeta", - "file_type": "code", - "source_file": "src/vite-env.d.ts", - "source_location": "L19", - "_origin": "ast", - "id": "src_vite_env_d_importmeta", - "community": 16, - "norm_label": "importmeta" - }, - { - "label": "tsconfig.app.json", - "file_type": "code", - "source_file": "tsconfig.app.json", - "source_location": "L1", - "_origin": "ast", - "id": "tsconfig_app", - "community": 7, - "norm_label": "tsconfig.app.json" - }, - { - "label": "compilerOptions", - "file_type": "code", - "source_file": "tsconfig.app.json", - "source_location": "L2", - "_origin": "ast", - "id": "tsconfig_app_compileroptions", - "community": 7, - "norm_label": "compileroptions" - }, - { - "label": "tsBuildInfoFile", - "file_type": "code", - "source_file": "tsconfig.app.json", - "source_location": "L3", - "_origin": "ast", - "id": "tsconfig_app_compileroptions_tsbuildinfofile", - "community": 7, - "norm_label": "tsbuildinfofile" - }, - { - "label": "target", - "file_type": "code", - "source_file": "tsconfig.app.json", - "source_location": "L4", - "_origin": "ast", - "id": "tsconfig_app_compileroptions_target", - "community": 7, - "norm_label": "target" - }, - { - "label": "lib", - "file_type": "code", - "source_file": "tsconfig.app.json", - "source_location": "L5", - "_origin": "ast", - "id": "tsconfig_app_compileroptions_lib", - "community": 7, - "norm_label": "lib" - }, - { - "label": "module", - "file_type": "code", - "source_file": "tsconfig.app.json", - "source_location": "L6", - "_origin": "ast", - "id": "tsconfig_app_compileroptions_module", - "community": 7, - "norm_label": "module" - }, - { - "label": "types", - "file_type": "code", - "source_file": "tsconfig.app.json", - "source_location": "L7", - "_origin": "ast", - "id": "tsconfig_app_compileroptions_types", - "community": 7, - "norm_label": "types" - }, - { - "label": "skipLibCheck", - "file_type": "code", - "source_file": "tsconfig.app.json", - "source_location": "L8", - "_origin": "ast", - "id": "tsconfig_app_compileroptions_skiplibcheck", - "community": 7, - "norm_label": "skiplibcheck" - }, - { - "label": "moduleResolution", - "file_type": "code", - "source_file": "tsconfig.app.json", - "source_location": "L11", - "_origin": "ast", - "id": "tsconfig_app_compileroptions_moduleresolution", - "community": 7, - "norm_label": "moduleresolution" - }, - { - "label": "allowImportingTsExtensions", - "file_type": "code", - "source_file": "tsconfig.app.json", - "source_location": "L12", - "_origin": "ast", - "id": "tsconfig_app_compileroptions_allowimportingtsextensions", - "community": 7, - "norm_label": "allowimportingtsextensions" - }, - { - "label": "verbatimModuleSyntax", - "file_type": "code", - "source_file": "tsconfig.app.json", - "source_location": "L13", - "_origin": "ast", - "id": "tsconfig_app_compileroptions_verbatimmodulesyntax", - "community": 7, - "norm_label": "verbatimmodulesyntax" - }, - { - "label": "moduleDetection", - "file_type": "code", - "source_file": "tsconfig.app.json", - "source_location": "L14", - "_origin": "ast", - "id": "tsconfig_app_compileroptions_moduledetection", - "community": 7, - "norm_label": "moduledetection" - }, - { - "label": "noEmit", - "file_type": "code", - "source_file": "tsconfig.app.json", - "source_location": "L15", - "_origin": "ast", - "id": "tsconfig_app_compileroptions_noemit", - "community": 7, - "norm_label": "noemit" - }, - { - "label": "jsx", - "file_type": "code", - "source_file": "tsconfig.app.json", - "source_location": "L16", - "_origin": "ast", - "id": "tsconfig_app_compileroptions_jsx", - "community": 7, - "norm_label": "jsx" - }, - { - "label": "strict", - "file_type": "code", - "source_file": "tsconfig.app.json", - "source_location": "L19", - "_origin": "ast", - "id": "tsconfig_app_compileroptions_strict", - "community": 7, - "norm_label": "strict" - }, - { - "label": "noUncheckedIndexedAccess", - "file_type": "code", - "source_file": "tsconfig.app.json", - "source_location": "L20", - "_origin": "ast", - "id": "tsconfig_app_compileroptions_nouncheckedindexedaccess", - "community": 7, - "norm_label": "nouncheckedindexedaccess" - }, - { - "label": "noImplicitOverride", - "file_type": "code", - "source_file": "tsconfig.app.json", - "source_location": "L21", - "_origin": "ast", - "id": "tsconfig_app_compileroptions_noimplicitoverride", - "community": 7, - "norm_label": "noimplicitoverride" - }, - { - "label": "exactOptionalPropertyTypes", - "file_type": "code", - "source_file": "tsconfig.app.json", - "source_location": "L22", - "_origin": "ast", - "id": "tsconfig_app_compileroptions_exactoptionalpropertytypes", - "community": 7, - "norm_label": "exactoptionalpropertytypes" - }, - { - "label": "noUnusedLocals", - "file_type": "code", - "source_file": "tsconfig.app.json", - "source_location": "L25", - "_origin": "ast", - "id": "tsconfig_app_compileroptions_nounusedlocals", - "community": 7, - "norm_label": "nounusedlocals" - }, - { - "label": "noUnusedParameters", - "file_type": "code", - "source_file": "tsconfig.app.json", - "source_location": "L26", - "_origin": "ast", - "id": "tsconfig_app_compileroptions_nounusedparameters", - "community": 7, - "norm_label": "nounusedparameters" - }, - { - "label": "erasableSyntaxOnly", - "file_type": "code", - "source_file": "tsconfig.app.json", - "source_location": "L27", - "_origin": "ast", - "id": "tsconfig_app_compileroptions_erasablesyntaxonly", - "community": 7, - "norm_label": "erasablesyntaxonly" - }, - { - "label": "noFallthroughCasesInSwitch", - "file_type": "code", - "source_file": "tsconfig.app.json", - "source_location": "L28", - "_origin": "ast", - "id": "tsconfig_app_compileroptions_nofallthroughcasesinswitch", - "community": 7, - "norm_label": "nofallthroughcasesinswitch" - }, - { - "label": "include", - "file_type": "code", - "source_file": "tsconfig.app.json", - "source_location": "L30", - "_origin": "ast", - "id": "tsconfig_app_include", - "community": 7, - "norm_label": "include" - }, - { - "label": "tsconfig.json", - "file_type": "code", - "source_file": "tsconfig.json", - "source_location": "L1", - "_origin": "ast", - "id": "tsconfig", - "community": 17, - "norm_label": "tsconfig.json" - }, - { - "label": "files", - "file_type": "code", - "source_file": "tsconfig.json", - "source_location": "L2", - "_origin": "ast", - "id": "tsconfig_files", - "community": 17, - "norm_label": "files" - }, - { - "label": "references", - "file_type": "code", - "source_file": "tsconfig.json", - "source_location": "L3", - "_origin": "ast", - "id": "tsconfig_references", - "community": 17, - "norm_label": "references" - }, - { - "label": "tsconfig.node.json", - "file_type": "code", - "source_file": "tsconfig.node.json", - "source_location": "L1", - "_origin": "ast", - "id": "tsconfig_node", - "community": 8, - "norm_label": "tsconfig.node.json" - }, - { - "label": "compilerOptions", - "file_type": "code", - "source_file": "tsconfig.node.json", - "source_location": "L2", - "_origin": "ast", - "id": "tsconfig_node_compileroptions", - "community": 8, - "norm_label": "compileroptions" - }, - { - "label": "tsBuildInfoFile", - "file_type": "code", - "source_file": "tsconfig.node.json", - "source_location": "L3", - "_origin": "ast", - "id": "tsconfig_node_compileroptions_tsbuildinfofile", - "community": 8, - "norm_label": "tsbuildinfofile" - }, - { - "label": "target", - "file_type": "code", - "source_file": "tsconfig.node.json", - "source_location": "L4", - "_origin": "ast", - "id": "tsconfig_node_compileroptions_target", - "community": 8, - "norm_label": "target" - }, - { - "label": "lib", - "file_type": "code", - "source_file": "tsconfig.node.json", - "source_location": "L5", - "_origin": "ast", - "id": "tsconfig_node_compileroptions_lib", - "community": 8, - "norm_label": "lib" - }, - { - "label": "types", - "file_type": "code", - "source_file": "tsconfig.node.json", - "source_location": "L6", - "_origin": "ast", - "id": "tsconfig_node_compileroptions_types", - "community": 8, - "norm_label": "types" - }, - { - "label": "skipLibCheck", - "file_type": "code", - "source_file": "tsconfig.node.json", - "source_location": "L7", - "_origin": "ast", - "id": "tsconfig_node_compileroptions_skiplibcheck", - "community": 8, - "norm_label": "skiplibcheck" - }, - { - "label": "module", - "file_type": "code", - "source_file": "tsconfig.node.json", - "source_location": "L10", - "_origin": "ast", - "id": "tsconfig_node_compileroptions_module", - "community": 8, - "norm_label": "module" - }, - { - "label": "allowImportingTsExtensions", - "file_type": "code", - "source_file": "tsconfig.node.json", - "source_location": "L11", - "_origin": "ast", - "id": "tsconfig_node_compileroptions_allowimportingtsextensions", - "community": 8, - "norm_label": "allowimportingtsextensions" - }, - { - "label": "verbatimModuleSyntax", - "file_type": "code", - "source_file": "tsconfig.node.json", - "source_location": "L12", - "_origin": "ast", - "id": "tsconfig_node_compileroptions_verbatimmodulesyntax", - "community": 8, - "norm_label": "verbatimmodulesyntax" - }, - { - "label": "moduleDetection", - "file_type": "code", - "source_file": "tsconfig.node.json", - "source_location": "L13", - "_origin": "ast", - "id": "tsconfig_node_compileroptions_moduledetection", - "community": 8, - "norm_label": "moduledetection" - }, - { - "label": "noEmit", - "file_type": "code", - "source_file": "tsconfig.node.json", - "source_location": "L14", - "_origin": "ast", - "id": "tsconfig_node_compileroptions_noemit", - "community": 8, - "norm_label": "noemit" - }, - { - "label": "noUnusedLocals", - "file_type": "code", - "source_file": "tsconfig.node.json", - "source_location": "L17", - "_origin": "ast", - "id": "tsconfig_node_compileroptions_nounusedlocals", - "community": 8, - "norm_label": "nounusedlocals" - }, - { - "label": "noUnusedParameters", - "file_type": "code", - "source_file": "tsconfig.node.json", - "source_location": "L18", - "_origin": "ast", - "id": "tsconfig_node_compileroptions_nounusedparameters", - "community": 8, - "norm_label": "nounusedparameters" - }, - { - "label": "erasableSyntaxOnly", - "file_type": "code", - "source_file": "tsconfig.node.json", - "source_location": "L19", - "_origin": "ast", - "id": "tsconfig_node_compileroptions_erasablesyntaxonly", - "community": 8, - "norm_label": "erasablesyntaxonly" - }, - { - "label": "noFallthroughCasesInSwitch", - "file_type": "code", - "source_file": "tsconfig.node.json", - "source_location": "L20", - "_origin": "ast", - "id": "tsconfig_node_compileroptions_nofallthroughcasesinswitch", - "community": 8, - "norm_label": "nofallthroughcasesinswitch" - }, - { - "label": "include", - "file_type": "code", - "source_file": "tsconfig.node.json", - "source_location": "L22", - "_origin": "ast", - "id": "tsconfig_node_include", - "community": 8, - "norm_label": "include" - }, - { - "label": "vite.config.ts", - "file_type": "code", - "source_file": "vite.config.ts", - "source_location": "L1", - "_origin": "ast", - "id": "vite_config", - "community": 34, - "norm_label": "vite.config.ts" - }, - { - "label": "vitest.config.ts", - "file_type": "code", - "source_file": "vitest.config.ts", - "source_location": "L1", - "_origin": "ast", - "id": "vitest_config", - "community": 35, - "norm_label": "vitest.config.ts" - }, - { - "label": "Admin API Contract Skill", - "file_type": "document", - "source_file": ".skills/admin-api-contract/SKILL.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "id": "skills_admin_api_contract_skill_admin_api_contract", - "community": 0, - "norm_label": "admin api contract skill" - }, - { - "label": "ServiceDto Interface Pattern", - "file_type": "code", - "source_file": ".skills/admin-api-contract/SKILL.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "id": "skills_admin_api_contract_skill_servicedto", - "community": 0, - "norm_label": "servicedto interface pattern" - }, - { - "label": "Tenant Scoping Mechanism Decision Table", - "file_type": "rationale", - "source_file": ".skills/admin-api-contract/SKILL.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "rationale": "JWT claim is most likely mechanism given IdentityServer setup; no extra work needed if so, since token already proves tenant identity.", - "id": "skills_admin_api_contract_skill_tenant_scoping_mechanism", - "community": 0, - "norm_label": "tenant scoping mechanism decision table" - }, - { - "label": "Common Field Translation Patterns (API to Domain)", - "file_type": "concept", - "source_file": ".skills/admin-api-contract/SKILL.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "id": "skills_admin_api_contract_skill_field_translation_patterns", - "community": 0, - "norm_label": "common field translation patterns (api to domain)" - }, - { - "label": "Admin Feature Vertical Skill", - "file_type": "document", - "source_file": ".skills/admin-feature-vertical/SKILL.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "id": "skills_admin_feature_vertical_skill_admin_feature_vertical", - "community": 0, - "norm_label": "admin feature vertical skill" - }, - { - "label": "Feature Vertical Slice Pattern", - "file_type": "rationale", - "source_file": ".skills/admin-feature-vertical/SKILL.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "rationale": "A full slice from domain entity through use cases, infrastructure repository, presentation hook, and page component ensures consistent architecture per feature.", - "id": "skills_admin_feature_vertical_skill_feature_vertical_slice", - "community": 0, - "norm_label": "feature vertical slice pattern" - }, - { - "label": "HttpClient Interface", - "file_type": "code", - "source_file": ".skills/admin-feature-vertical/SKILL.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "id": "skills_admin_feature_vertical_skill_httpclient", - "community": 0, - "norm_label": "httpclient interface" - }, - { - "label": "AuthenticatedHttpClient Implementation", - "file_type": "code", - "source_file": ".skills/admin-feature-vertical/SKILL.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "id": "skills_admin_feature_vertical_skill_authenticatedhttpclient", - "community": 0, - "norm_label": "authenticatedhttpclient implementation" - }, - { - "label": "Feature Vertical Commit Checklist", - "file_type": "concept", - "source_file": ".skills/admin-feature-vertical/SKILL.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "id": "skills_admin_feature_vertical_skill_commit_checklist", - "community": 0, - "norm_label": "feature vertical commit checklist" - }, - { - "label": "ListServices Use Case Example", - "file_type": "code", - "source_file": ".skills/admin-feature-vertical/SKILL.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "id": "skills_admin_feature_vertical_skill_listservices", - "community": 0, - "norm_label": "listservices use case example" - }, - { - "label": "Admin TDD Conventions Skill", - "file_type": "document", - "source_file": ".skills/admin-tdd-conventions/SKILL.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "id": "skills_admin_tdd_conventions_skill_admin_tdd_conventions", - "community": 1, - "norm_label": "admin tdd conventions skill" - }, - { - "label": "Mock Strategy Per Layer Table", - "file_type": "rationale", - "source_file": ".skills/admin-tdd-conventions/SKILL.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "rationale": "Never mix mock strategies: use fakes for use cases, MSW for infrastructure boundary, fake container for presentation, to keep tests aligned with what layer they verify.", - "id": "skills_admin_tdd_conventions_skill_mock_strategy_table", - "community": 1, - "norm_label": "mock strategy per layer table" - }, - { - "label": "createFakeServiceRepository Pattern", - "file_type": "code", - "source_file": ".skills/admin-tdd-conventions/SKILL.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "id": "skills_admin_tdd_conventions_skill_createfakeservicerepository", - "community": 1, - "norm_label": "createfakeservicerepository pattern" - }, - { - "label": "buildFakeContainer Pattern", - "file_type": "code", - "source_file": ".skills/admin-tdd-conventions/SKILL.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "id": "skills_admin_tdd_conventions_skill_buildfakecontainer", - "community": 1, - "norm_label": "buildfakecontainer pattern" - }, - { - "label": "erasableSyntaxOnly Test Gotcha", - "file_type": "rationale", - "source_file": ".skills/admin-tdd-conventions/SKILL.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "rationale": "Applies to all classes including test helpers and fakes: no constructor parameter shorthand allowed even though vitest alone would pass.", - "id": "skills_admin_tdd_conventions_skill_erasablesyntaxonly_gotcha", - "community": 1, - "norm_label": "erasablesyntaxonly test gotcha" - }, - { - "label": "exactOptionalPropertyTypes Conditional Spread Gotcha", - "file_type": "rationale", - "source_file": ".skills/admin-tdd-conventions/SKILL.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "rationale": "Optional fields must be conditionally spread rather than assigned directly to satisfy strict optional property typing.", - "id": "skills_admin_tdd_conventions_skill_exactoptionalpropertytypes_gotcha", - "community": 1, - "norm_label": "exactoptionalpropertytypes conditional spread gotcha" - }, - { - "label": "Never-Resolving Promise Test Pattern", - "file_type": "code", - "source_file": ".skills/admin-tdd-conventions/SKILL.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "id": "skills_admin_tdd_conventions_skill_never_resolving_promise", - "community": 1, - "norm_label": "never-resolving promise test pattern" - }, - { - "label": "renderHook Generic Types Convention", - "file_type": "concept", - "source_file": ".skills/admin-tdd-conventions/SKILL.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "id": "skills_admin_tdd_conventions_skill_renderhook_generics", - "community": 1, - "norm_label": "renderhook generic types convention" - }, - { - "label": "MSW Handler Conventions", - "file_type": "concept", - "source_file": ".skills/admin-tdd-conventions/SKILL.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "id": "skills_admin_tdd_conventions_skill_msw_handler_conventions", - "community": 1, - "norm_label": "msw handler conventions" - }, - { - "label": "onUnhandledRequest: error Config", - "file_type": "rationale", - "source_file": ".skills/admin-tdd-conventions/SKILL.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "rationale": "Any unhandled request fails loudly, catching missing handlers rather than silently hanging tests.", - "id": "skills_admin_tdd_conventions_skill_onunhandledrequest_error", - "community": 1, - "norm_label": "onunhandledrequest: error config" - }, - { - "label": "react-hooks/set-state-in-effect Suppression", - "file_type": "rationale", - "source_file": ".skills/admin-tdd-conventions/SKILL.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "rationale": "useAsync's void execute() call inside useEffect triggers this rule as a false positive since the rule traces async call graphs; suppression is intentional and documented, must not be removed or reused elsewhere.", - "id": "skills_admin_tdd_conventions_skill_set_state_in_effect_suppression", - "community": 1, - "norm_label": "react-hooks/set-state-in-effect suppression" - }, - { - "label": "Admin Panel AI Assistant Instructions", - "file_type": "document", - "source_file": "CLAUDE.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "id": "claude_md_admin_panel_instructions", - "community": 1, - "norm_label": "admin panel ai assistant instructions" - }, - { - "label": "erasableSyntaxOnly Constraint", - "file_type": "rationale", - "source_file": "CLAUDE.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "rationale": "No constructor parameter property shorthand; explicit field declaration and assignment in constructor body required.", - "id": "claude_md_erasablesyntaxonly", - "community": 1, - "norm_label": "erasablesyntaxonly constraint" - }, - { - "label": "exactOptionalPropertyTypes Constraint", - "file_type": "rationale", - "source_file": "CLAUDE.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "rationale": "Requires explicit undefined checks before assigning optional fields, never direct assignment of possibly-undefined values.", - "id": "claude_md_exactoptionalpropertytypes", - "community": 1, - "norm_label": "exactoptionalpropertytypes constraint" - }, - { - "label": "noUncheckedIndexedAccess Constraint", - "file_type": "rationale", - "source_file": "CLAUDE.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "rationale": "Index access returns T | undefined so all index accesses must be guarded.", - "id": "claude_md_nouncheckedindexedaccess", - "community": 1, - "norm_label": "nouncheckedindexedaccess constraint" - }, - { - "label": "Clean Architecture Boundary Constraint", - "file_type": "rationale", - "source_file": "CLAUDE.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "rationale": "domain/ and application/ must never import React, react-router, or infrastructure/presentation; ESLint enforces this boundary.", - "id": "claude_md_architecture_constraint", - "community": 1, - "norm_label": "clean architecture boundary constraint" - }, - { - "label": "composition/container.ts Composition Root", - "file_type": "code", - "source_file": "CLAUDE.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "id": "claude_md_composition_container", - "community": 1, - "norm_label": "composition/container.ts composition root" - }, - { - "label": "TenantContext First Param Rule", - "file_type": "rationale", - "source_file": "CLAUDE.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "rationale": "Every repository interface method takes TenantContext as first param to structurally enforce tenant scoping in the application layer.", - "id": "claude_md_tenantcontext_param_rule", - "community": 0, - "norm_label": "tenantcontext first param rule" - }, - { - "label": "Admin Panel Tech Stack", - "file_type": "concept", - "source_file": "CLAUDE.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "id": "claude_md_tech_stack", - "community": 1, - "norm_label": "admin panel tech stack" - }, - { - "label": "Admin Panel Design Language", - "file_type": "concept", - "source_file": "CLAUDE.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "id": "claude_md_design_language", - "community": 1, - "norm_label": "admin panel design language" - }, - { - "label": "Current Project State Summary", - "file_type": "document", - "source_file": "CLAUDE.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "id": "claude_md_current_state", - "community": 1, - "norm_label": "current project state summary" - }, - { - "label": "React + TypeScript + Vite Template README", - "file_type": "document", - "source_file": "README.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "id": "readme_react_typescript_vite_template", - "community": 1, - "norm_label": "react + typescript + vite template readme" - }, - { - "label": "@vitejs/plugin-react (Oxc)", - "file_type": "concept", - "source_file": "README.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "id": "readme_vite_plugin_react", - "community": 1, - "norm_label": "@vitejs/plugin-react (oxc)" - }, - { - "label": "@vitejs/plugin-react-swc (SWC)", - "file_type": "concept", - "source_file": "README.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "id": "readme_vite_plugin_react_swc", - "community": 1, - "norm_label": "@vitejs/plugin-react-swc (swc)" - }, - { - "label": "React Compiler (not enabled)", - "file_type": "concept", - "source_file": "README.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "id": "readme_react_compiler", - "community": 1, - "norm_label": "react compiler (not enabled)" - }, - { - "label": "Oxlint Type-Aware Configuration", - "file_type": "concept", - "source_file": "README.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "id": "readme_oxlint_config", - "community": 1, - "norm_label": "oxlint type-aware configuration" - }, - { - "label": "API Integration Guide", - "file_type": "document", - "source_file": "docs/API.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "id": "docs_api_api_integration_guide", - "community": 0, - "norm_label": "api integration guide" - }, - { - "label": "VITE_API_BASE_URL Config", - "file_type": "concept", - "source_file": "docs/API.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "id": "docs_api_base_url", - "community": 0, - "norm_label": "vite_api_base_url config" - }, - { - "label": "Bearer Token Authentication Flow", - "file_type": "concept", - "source_file": "docs/API.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "id": "docs_api_authentication", - "community": 0, - "norm_label": "bearer token authentication flow" - }, - { - "label": "UnauthenticatedError", - "file_type": "code", - "source_file": "docs/API.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "id": "docs_api_unauthenticatederror", - "community": 0, - "norm_label": "unauthenticatederror" - }, - { - "label": "Tenant Scoping via JWT Claim", - "file_type": "rationale", - "source_file": "docs/API.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "rationale": "The tenant_id claim inside the JWT access token is read by the backend directly, so AuthenticatedHttpClient does not need a separate tenant header; TenantContext param remains for structural enforcement in the application layer.", - "id": "docs_api_tenant_scoping", - "community": 0, - "norm_label": "tenant scoping via jwt claim" - }, - { - "label": "API Error Shape (Placeholder)", - "file_type": "concept", - "source_file": "docs/API.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "id": "docs_api_error_shape", - "community": 0, - "norm_label": "api error shape (placeholder)" - }, - { - "label": "ApiError Class", - "file_type": "code", - "source_file": "docs/API.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "id": "docs_api_apierror_class", - "community": 0, - "norm_label": "apierror class" - }, - { - "label": "Pagination Strategy (TBD)", - "file_type": "concept", - "source_file": "docs/API.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "id": "docs_api_pagination", - "community": 0, - "norm_label": "pagination strategy (tbd)" - }, - { - "label": "Resource Endpoints Placeholder Section", - "file_type": "document", - "source_file": "docs/API.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "id": "docs_api_resource_endpoints", - "community": 0, - "norm_label": "resource endpoints placeholder section" - }, - { - "label": "How to Add a New Resource Workflow", - "file_type": "concept", - "source_file": "docs/API.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "id": "docs_api_how_to_add_resource", - "community": 0, - "norm_label": "how to add a new resource workflow" - }, - { - "label": "Project Decisions Log", - "file_type": "document", - "source_file": "docs/DECISIONS.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "id": "docs_decisions_project_decisions_log", - "community": 1, - "norm_label": "project decisions log" - }, - { - "label": "erasableSyntaxOnly Decision", - "file_type": "rationale", - "source_file": "docs/DECISIONS.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "rationale": "Forces explicit field declarations instead of constructor parameter shorthand, aligning with Clean Code principles at the cost of verbosity, applied project-wide.", - "id": "docs_decisions_erasablesyntaxonly", - "community": 1, - "norm_label": "erasablesyntaxonly decision" - }, - { - "label": "exactOptionalPropertyTypes Decision", - "file_type": "rationale", - "source_file": "docs/DECISIONS.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "rationale": "Kept despite friction because domain models auth session/tenant context where field-absent vs explicitly-undefined are meaningfully different states; caught real bugs during Auth build.", - "id": "docs_decisions_exactoptionalpropertytypes", - "community": 1, - "norm_label": "exactoptionalpropertytypes decision" - }, - { - "label": "noUncheckedIndexedAccess Decision", - "file_type": "rationale", - "source_file": "docs/DECISIONS.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "rationale": "Motivated by oidcUser.profile['tenant_id'] resolving to unknown not string; runtime guard typeof x === 'string' is the only safe approach.", - "id": "docs_decisions_nouncheckedindexedaccess", - "community": 1, - "norm_label": "nouncheckedindexedaccess decision" - }, - { - "label": "oidc-client-ts Library Decision", - "file_type": "rationale", - "source_file": "docs/DECISIONS.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "rationale": "Framework-agnostic, maintained standard for Auth Code + PKCE flows with full TypeScript types.", - "id": "docs_decisions_oidc_client_ts", - "community": 0, - "norm_label": "oidc-client-ts library decision" - }, - { - "label": "automaticSilentRenew: false Decision", - "file_type": "rationale", - "source_file": "docs/DECISIONS.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "rationale": "Event-driven background renewal is invisible to callers who cannot observe failure or trigger logout; explicit renewal in getCurrentSession() gives full control.", - "id": "docs_decisions_automaticsilentrenew_false", - "community": 1, - "norm_label": "automaticsilentrenew: false decision" - }, - { - "label": "HandleAuthCallback Propagates Errors Unwrapped Decision", - "file_type": "rationale", - "source_file": "docs/DECISIONS.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "rationale": "Exact error shapes from oidc-client-ts on various failure modes were unknown at build time; wrapping prematurely would discard information the presentation layer might need. Revisit when IdentityServer backend exists and real error shapes observed.", - "id": "docs_decisions_handleauthcallback_unwrapped_errors", - "community": 1, - "norm_label": "handleauthcallback propagates errors unwrapped decision" - }, - { - "label": "tenant_id Claim Name Decision", - "file_type": "rationale", - "source_file": "docs/DECISIONS.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "rationale": "Used as claim name in IdentityServer tokens; only referenced in oidcUserToSessionMapper.ts; revisit when real token can be decoded and claim name confirmed.", - "id": "docs_decisions_tenant_id_claim_name", - "community": 0, - "norm_label": "tenant_id claim name decision" - }, - { - "label": "email and name on User Entity Decision", - "file_type": "rationale", - "source_file": "docs/DECISIONS.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "rationale": "Included as optional fields marked as unverified assumptions since actual IdentityServer configuration not confirmed.", - "id": "docs_decisions_email_name_user_entity", - "community": 1, - "norm_label": "email and name on user entity decision" - }, - { - "label": "No Server-State Library Decision", - "file_type": "rationale", - "source_file": "docs/DECISIONS.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "rationale": "Explicit project constraint from the brief; most state is server data scoped by tenant with no complex shared client state or advanced caching requirements in v1.", - "id": "docs_decisions_no_server_state_library", - "community": 1, - "norm_label": "no server-state library decision" - }, - { - "label": "useAsync immediate Flag Decision", - "file_type": "rationale", - "source_file": "docs/DECISIONS.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "rationale": "Defaults to true for fetch-on-mount pattern; immediate:false covers action-style mutation calls triggered by user interaction.", - "id": "docs_decisions_useasync_immediate_flag", - "community": 1, - "norm_label": "useasync immediate flag decision" - }, - { - "label": "set-state-in-effect Suppression Decision", - "file_type": "rationale", - "source_file": "docs/DECISIONS.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "rationale": "Documented false positive per react/react#34743; the rule traces async call graphs and flags setState after await though those calls are genuinely async.", - "id": "docs_decisions_set_state_in_effect_suppression", - "community": 1, - "norm_label": "set-state-in-effect suppression decision" - }, - { - "label": "MSW onUnhandledRequest error Decision", - "file_type": "rationale", - "source_file": "docs/DECISIONS.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "rationale": "Silent failures like hanging promises are worse than loud ones; unhandled requests must fail immediately.", - "id": "docs_decisions_msw_onunhandledrequest_error", - "community": 1, - "norm_label": "msw onunhandledrequest error decision" - }, - { - "label": "No Real Network Calls in Tests Decision", - "file_type": "rationale", - "source_file": "docs/DECISIONS.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "rationale": "UserManager does its own internal fetch management so MSW (which intercepts fetch) can't mock it; a hand-written fake createFakeUserManager() is used instead.", - "id": "docs_decisions_no_real_network_calls_in_tests", - "community": 1, - "norm_label": "no real network calls in tests decision" - }, - { - "label": "Context-based DI (not module-level singletons) Decision", - "file_type": "rationale", - "source_file": "docs/DECISIONS.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "rationale": "Singleton modules make testing harder via shared state between tests; context-based DI lets each test provide its own fake container without module-level mocking.", - "id": "docs_decisions_context_based_di", - "community": 1, - "norm_label": "context-based di (not module-level singletons) decision" - }, - { - "label": "ProtectedRoute Handles Loading State Explicitly Decision", - "file_type": "rationale", - "source_file": "docs/DECISIONS.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "rationale": "Prevents a race condition where an authenticated user gets briefly redirected to login before getCurrentSession() resolves.", - "id": "docs_decisions_protectedroute_loading_state", - "community": 1, - "norm_label": "protectedroute handles loading state explicitly decision" - }, - { - "label": "Presentation Design Language Decision", - "file_type": "rationale", - "source_file": "docs/DECISIONS.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "rationale": "Slate gray primary, white surfaces, teal accent chosen to feel calm, professional, trustworthy for healthcare/wellness business owners rather than startup-flashy.", - "id": "docs_decisions_design_language", - "community": 1, - "norm_label": "presentation design language decision" - }, - { - "label": "Domain Glossary", - "file_type": "document", - "source_file": "docs/DOMAIN.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "id": "docs_domain_domain_glossary", - "community": 0, - "norm_label": "domain glossary" - }, - { - "label": "Business (Tenant)", - "file_type": "concept", - "source_file": "docs/DOMAIN.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "id": "docs_domain_business_tenant", - "community": 0, - "norm_label": "business (tenant)" - }, - { - "label": "User", - "file_type": "concept", - "source_file": "docs/DOMAIN.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "id": "docs_domain_user", - "community": 0, - "norm_label": "user" - }, - { - "label": "Session", - "file_type": "concept", - "source_file": "docs/DOMAIN.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "id": "docs_domain_session", - "community": 0, - "norm_label": "session" - }, - { - "label": "Appointment", - "file_type": "concept", - "source_file": "docs/DOMAIN.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "id": "docs_domain_appointment", - "community": 0, - "norm_label": "appointment" - }, - { - "label": "AppointmentStatus", - "file_type": "concept", - "source_file": "docs/DOMAIN.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "id": "docs_domain_appointmentstatus", - "community": 0, - "norm_label": "appointmentstatus" - }, - { - "label": "AppointmentSource", - "file_type": "concept", - "source_file": "docs/DOMAIN.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "id": "docs_domain_appointmentsource", - "community": 0, - "norm_label": "appointmentsource" - }, - { - "label": "Service", - "file_type": "concept", - "source_file": "docs/DOMAIN.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "id": "docs_domain_service", - "community": 0, - "norm_label": "service" - }, - { - "label": "Client", - "file_type": "concept", - "source_file": "docs/DOMAIN.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "id": "docs_domain_client", - "community": 0, - "norm_label": "client" - }, - { - "label": "Conversation", - "file_type": "concept", - "source_file": "docs/DOMAIN.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "id": "docs_domain_conversation", - "community": 0, - "norm_label": "conversation" - }, - { - "label": "ConversationStatus", - "file_type": "concept", - "source_file": "docs/DOMAIN.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "id": "docs_domain_conversationstatus", - "community": 0, - "norm_label": "conversationstatus" - }, - { - "label": "Message", - "file_type": "concept", - "source_file": "docs/DOMAIN.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "id": "docs_domain_message", - "community": 0, - "norm_label": "message" - }, - { - "label": "Inbox", - "file_type": "concept", - "source_file": "docs/DOMAIN.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "id": "docs_domain_inbox", - "community": 0, - "norm_label": "inbox" - }, - { - "label": "Business Settings", - "file_type": "concept", - "source_file": "docs/DOMAIN.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "id": "docs_domain_business_settings", - "community": 0, - "norm_label": "business settings" - }, - { - "label": "Out of Scope in v1", - "file_type": "concept", - "source_file": "docs/DOMAIN.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "id": "docs_domain_out_of_scope_v1", - "community": 0, - "norm_label": "out of scope in v1" - }, - { - "label": "Feature Status Tracker", - "file_type": "document", - "source_file": "docs/STATUS.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "id": "docs_status_feature_status", - "community": 0, - "norm_label": "feature status tracker" - }, - { - "label": "HttpClient Interface + AuthenticatedHttpClient (stub status)", - "file_type": "code", - "source_file": "docs/STATUS.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "id": "docs_status_httpclient_stub", - "community": 0, - "norm_label": "httpclient interface + authenticatedhttpclient (stub status)" - }, - { - "label": "Auth Vertical (done status)", - "file_type": "concept", - "source_file": "docs/STATUS.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "id": "docs_status_auth_vertical", - "community": 0, - "norm_label": "auth vertical (done status)" - }, - { - "label": "Services Vertical (stub status)", - "file_type": "concept", - "source_file": "docs/STATUS.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "id": "docs_status_services_vertical", - "community": 0, - "norm_label": "services vertical (stub status)" - }, - { - "label": "Clients Vertical (stub status)", - "file_type": "concept", - "source_file": "docs/STATUS.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "id": "docs_status_clients_vertical", - "community": 0, - "norm_label": "clients vertical (stub status)" - }, - { - "label": "Appointments Vertical (stub status)", - "file_type": "concept", - "source_file": "docs/STATUS.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "id": "docs_status_appointments_vertical", - "community": 0, - "norm_label": "appointments vertical (stub status)" - }, - { - "label": "Dashboard Vertical (stub status)", - "file_type": "concept", - "source_file": "docs/STATUS.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "id": "docs_status_dashboard_vertical", - "community": 0, - "norm_label": "dashboard vertical (stub status)" - }, - { - "label": "Inbox (Conversations) Vertical (stub status)", - "file_type": "concept", - "source_file": "docs/STATUS.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "id": "docs_status_inbox_vertical", - "community": 0, - "norm_label": "inbox (conversations) vertical (stub status)" - }, - { - "label": "Settings Vertical (stub status)", - "file_type": "concept", - "source_file": "docs/STATUS.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "id": "docs_status_settings_vertical", - "community": 0, - "norm_label": "settings vertical (stub status)" - }, - { - "label": "Recommended Build Order", - "file_type": "rationale", - "source_file": "docs/STATUS.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "rationale": "HttpClient unblocks all REST features; Services first as simplest CRUD with no dependencies; Appointments depends on Services; Inbox depends on Clients; Dashboard depends on Appointments and Inbox; Settings independent.", - "id": "docs_status_recommended_build_order", - "community": 0, - "norm_label": "recommended build order" - }, - { - "label": "ADR 001: Clean Architecture Layer Structure", - "file_type": "rationale", - "source_file": "docs/adr/001-clean-architecture-layers.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "rationale": "Strict inward-only dependency rule (domain <- application <- infrastructure + composition + presentation) keeps domain and use case logic framework-agnostic and testable in isolation; swapping IdentityServer, HTTP client, or router requires changes only in outer layers.", - "id": "docs_adr_001_clean_architecture_layers", - "community": 1, - "norm_label": "adr 001: clean architecture layer structure" - }, - { - "label": "ADR 002: No Server-State Library", - "file_type": "rationale", - "source_file": "docs/adr/002-no-server-state-library.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "rationale": "Explicit project constraint; most state is server data scoped by tenant with no complex shared client state, optimistic updates, or advanced caching needs in v1; migrate to TanStack Query if caching requirements grow.", - "id": "docs_adr_002_no_server_state_library", - "community": 1, - "norm_label": "adr 002: no server-state library" - }, - { - "label": "ADR 003: Manual Dependency Injection, No Container Library", - "file_type": "rationale", - "source_file": "docs/adr/003-manual-di-no-container-library.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "rationale": "Dependency graph is simple and static; a DI library would add decorators conflicting with erasableSyntaxOnly and unneeded indirection at this scale; reconsider beyond ~10 repositories.", - "id": "docs_adr_003_manual_di_no_container_library", - "community": 1, - "norm_label": "adr 003: manual dependency injection, no container library" - }, - { - "label": "ADR 004: Explicit Silent Token Renewal, Not Event-Driven", - "file_type": "rationale", - "source_file": "docs/adr/004-explicit-silent-renewal.md", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "rationale": "automaticSilentRenew:false because event-driven background renewal fires invisibly with no clean hook to force logout on failure; explicit renewal inside getCurrentSession() gives full control over retry, session clearing, and redirect.", - "id": "docs_adr_004_explicit_silent_renewal", - "community": 1, - "norm_label": "adr 004: explicit silent token renewal, not event-driven" - }, - { - "label": "index.html Vite Entry Point", - "file_type": "code", - "source_file": "index.html", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "id": "index_html_entry_point", - "community": 1, - "norm_label": "index.html vite entry point" - }, - { - "label": "Favicon Icon (Admin Panel Logo)", - "file_type": "image", - "source_file": "public/favicon.svg", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "id": "public_favicon_icon", - "community": 33, - "norm_label": "favicon icon (admin panel logo)" - }, - { - "label": "Public Icon Sprite (SVG Symbols)", - "file_type": "image", - "source_file": "public/icons.svg", - "source_location": null, - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "id": "public_icons_icon_set", - "community": 13, - "norm_label": "public icon sprite (svg symbols)" - }, - { - "label": "Bluesky Icon Symbol", - "file_type": "image", - "source_file": "public/icons.svg", - "source_location": "symbol#bluesky-icon", - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "id": "public_icons_bluesky_icon", - "community": 13, - "norm_label": "bluesky icon symbol" - }, - { - "label": "Discord Icon Symbol", - "file_type": "image", - "source_file": "public/icons.svg", - "source_location": "symbol#discord-icon", - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "id": "public_icons_discord_icon", - "community": 13, - "norm_label": "discord icon symbol" - }, - { - "label": "Documentation Icon Symbol", - "file_type": "image", - "source_file": "public/icons.svg", - "source_location": "symbol#documentation-icon", - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "id": "public_icons_documentation_icon", - "community": 13, - "norm_label": "documentation icon symbol" - }, - { - "label": "GitHub Icon Symbol", - "file_type": "image", - "source_file": "public/icons.svg", - "source_location": "symbol#github-icon", - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "id": "public_icons_github_icon", - "community": 13, - "norm_label": "github icon symbol" - }, - { - "label": "Social/People Icon Symbol", - "file_type": "image", - "source_file": "public/icons.svg", - "source_location": "symbol#social-icon", - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "id": "public_icons_social_icon", - "community": 13, - "norm_label": "social/people icon symbol" - }, - { - "label": "X (Twitter) Icon Symbol", - "file_type": "image", - "source_file": "public/icons.svg", - "source_location": "symbol#x-icon", - "source_url": null, - "captured_at": null, - "author": null, - "contributor": null, - "id": "public_icons_x_icon", - "community": 13, - "norm_label": "x (twitter) icon symbol" - } - ], - "links": [ - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": ".husky/_/applypatch-msg", - "source_location": "L1", - "weight": 1.0, - "source": "husky_applypatch_msg", - "target": "husky_applypatch_msg__entry", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": ".husky/_/commit-msg", - "source_location": "L1", - "weight": 1.0, - "source": "husky_commit_msg", - "target": "husky_commit_msg__entry", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": ".husky/_/h", - "source_location": "L1", - "weight": 1.0, - "source": "husky_h", - "target": "husky_h__entry", - "confidence_score": 1.0 - }, - { - "relation": "defines", - "confidence": "EXTRACTED", - "source_file": ".husky/_/h", - "source_location": "L16", - "weight": 1.0, - "source": "husky_h", - "target": "husky_h_path", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": ".husky/_/husky.sh", - "source_location": "L1", - "weight": 1.0, - "source": "husky_husky", - "target": "husky_husky_sh__entry", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "confidence": "EXTRACTED", - "source_file": "package.json", - "source_location": "L42", - "weight": 1.0, - "context": "import", - "source": "package_devdependencies_husky", - "target": "husky_husky", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": ".husky/_/post-applypatch", - "source_location": "L1", - "weight": 1.0, - "source": "husky_post_applypatch", - "target": "husky_post_applypatch__entry", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": ".husky/_/post-checkout", - "source_location": "L1", - "weight": 1.0, - "source": "husky_post_checkout", - "target": "husky_post_checkout__entry", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": ".husky/_/post-commit", - "source_location": "L1", - "weight": 1.0, - "source": "husky_post_commit", - "target": "husky_post_commit__entry", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": ".husky/_/post-merge", - "source_location": "L1", - "weight": 1.0, - "source": "husky_post_merge", - "target": "husky_post_merge__entry", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": ".husky/_/post-rewrite", - "source_location": "L1", - "weight": 1.0, - "source": "husky_post_rewrite", - "target": "husky_post_rewrite__entry", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": ".husky/_/pre-applypatch", - "source_location": "L1", - "weight": 1.0, - "source": "husky_pre_applypatch", - "target": "husky_pre_applypatch__entry", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": ".husky/_/pre-auto-gc", - "source_location": "L1", - "weight": 1.0, - "source": "husky_pre_auto_gc", - "target": "husky_pre_auto_gc__entry", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": ".husky/_/pre-commit", - "source_location": "L1", - "weight": 1.0, - "source": "husky_pre_commit", - "target": "husky_pre_commit__entry", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": ".husky/_/pre-merge-commit", - "source_location": "L1", - "weight": 1.0, - "source": "husky_pre_merge_commit", - "target": "husky_pre_merge_commit__entry", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": ".husky/_/pre-push", - "source_location": "L1", - "weight": 1.0, - "source": "husky_pre_push", - "target": "husky_pre_push__entry", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": ".husky/_/pre-rebase", - "source_location": "L1", - "weight": 1.0, - "source": "husky_pre_rebase", - "target": "husky_pre_rebase__entry", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": ".husky/_/prepare-commit-msg", - "source_location": "L1", - "weight": 1.0, - "source": "husky_prepare_commit_msg", - "target": "husky_prepare_commit_msg__entry", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": ".prettierrc.json", - "source_location": "L7", - "weight": 1.0, - "source": "prettierrc", - "target": "prettierrc_arrowparens", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": ".prettierrc.json", - "source_location": "L5", - "weight": 1.0, - "source": "prettierrc", - "target": "prettierrc_printwidth", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": ".prettierrc.json", - "source_location": "L2", - "weight": 1.0, - "source": "prettierrc", - "target": "prettierrc_semi", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": ".prettierrc.json", - "source_location": "L3", - "weight": 1.0, - "source": "prettierrc", - "target": "prettierrc_singlequote", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": ".prettierrc.json", - "source_location": "L6", - "weight": 1.0, - "source": "prettierrc", - "target": "prettierrc_tabwidth", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": ".prettierrc.json", - "source_location": "L4", - "weight": 1.0, - "source": "prettierrc", - "target": "prettierrc_trailingcomma", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "package.json", - "source_location": "L20", - "weight": 1.0, - "source": "package", - "target": "package_dependencies", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "package.json", - "source_location": "L26", - "weight": 1.0, - "source": "package", - "target": "package_devdependencies", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "package.json", - "source_location": "L2", - "weight": 1.0, - "source": "package", - "target": "package_name", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "package.json", - "source_location": "L3", - "weight": 1.0, - "source": "package", - "target": "package_private", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "package.json", - "source_location": "L6", - "weight": 1.0, - "source": "package", - "target": "package_scripts", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "package.json", - "source_location": "L5", - "weight": 1.0, - "source": "package", - "target": "package_type", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "package.json", - "source_location": "L4", - "weight": 1.0, - "source": "package", - "target": "package_version", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "package.json", - "source_location": "L8", - "weight": 1.0, - "source": "package_scripts", - "target": "package_scripts_build", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "package.json", - "source_location": "L7", - "weight": 1.0, - "source": "package_scripts", - "target": "package_scripts_dev", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "package.json", - "source_location": "L15", - "weight": 1.0, - "source": "package_scripts", - "target": "package_scripts_format", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "package.json", - "source_location": "L16", - "weight": 1.0, - "source": "package_scripts", - "target": "package_scripts_format_check", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "package.json", - "source_location": "L13", - "weight": 1.0, - "source": "package_scripts", - "target": "package_scripts_lint", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "package.json", - "source_location": "L14", - "weight": 1.0, - "source": "package_scripts", - "target": "package_scripts_lint_fix", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "package.json", - "source_location": "L18", - "weight": 1.0, - "source": "package_scripts", - "target": "package_scripts_prepare", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "package.json", - "source_location": "L17", - "weight": 1.0, - "source": "package_scripts", - "target": "package_scripts_preview", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "package.json", - "source_location": "L9", - "weight": 1.0, - "source": "package_scripts", - "target": "package_scripts_test", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "package.json", - "source_location": "L12", - "weight": 1.0, - "source": "package_scripts", - "target": "package_scripts_test_coverage", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "package.json", - "source_location": "L11", - "weight": 1.0, - "source": "package_scripts", - "target": "package_scripts_test_ui", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "package.json", - "source_location": "L10", - "weight": 1.0, - "source": "package_scripts", - "target": "package_scripts_test_watch", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "package.json", - "source_location": "L21", - "weight": 1.0, - "source": "package_dependencies", - "target": "package_dependencies_oidc_client_ts", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "package.json", - "source_location": "L22", - "weight": 1.0, - "source": "package_dependencies", - "target": "package_dependencies_react", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "package.json", - "source_location": "L23", - "weight": 1.0, - "source": "package_dependencies", - "target": "package_dependencies_react_dom", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "package.json", - "source_location": "L24", - "weight": 1.0, - "source": "package_dependencies", - "target": "package_dependencies_react_router", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "package.json", - "source_location": "L37", - "weight": 1.0, - "source": "package_devdependencies", - "target": "package_devdependencies_eslint", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "package.json", - "source_location": "L38", - "weight": 1.0, - "source": "package_devdependencies", - "target": "package_devdependencies_eslint_config_prettier", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "package.json", - "source_location": "L27", - "weight": 1.0, - "source": "package_devdependencies", - "target": "package_devdependencies_eslint_js", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "package.json", - "source_location": "L39", - "weight": 1.0, - "source": "package_devdependencies", - "target": "package_devdependencies_eslint_plugin_react_hooks", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "package.json", - "source_location": "L40", - "weight": 1.0, - "source": "package_devdependencies", - "target": "package_devdependencies_eslint_plugin_react_refresh", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "package.json", - "source_location": "L41", - "weight": 1.0, - "source": "package_devdependencies", - "target": "package_devdependencies_globals", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "package.json", - "source_location": "L42", - "weight": 1.0, - "source": "package_devdependencies", - "target": "package_devdependencies_husky", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "package.json", - "source_location": "L43", - "weight": 1.0, - "source": "package_devdependencies", - "target": "package_devdependencies_jsdom", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "package.json", - "source_location": "L44", - "weight": 1.0, - "source": "package_devdependencies", - "target": "package_devdependencies_lint_staged", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "package.json", - "source_location": "L45", - "weight": 1.0, - "source": "package_devdependencies", - "target": "package_devdependencies_msw", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "package.json", - "source_location": "L46", - "weight": 1.0, - "source": "package_devdependencies", - "target": "package_devdependencies_prettier", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "package.json", - "source_location": "L47", - "weight": 1.0, - "source": "package_devdependencies", - "target": "package_devdependencies_tailwindcss", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "package.json", - "source_location": "L28", - "weight": 1.0, - "source": "package_devdependencies", - "target": "package_devdependencies_tailwindcss_vite", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "package.json", - "source_location": "L29", - "weight": 1.0, - "source": "package_devdependencies", - "target": "package_devdependencies_testing_library_jest_dom", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "package.json", - "source_location": "L30", - "weight": 1.0, - "source": "package_devdependencies", - "target": "package_devdependencies_testing_library_react", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "package.json", - "source_location": "L31", - "weight": 1.0, - "source": "package_devdependencies", - "target": "package_devdependencies_testing_library_user_event", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "package.json", - "source_location": "L32", - "weight": 1.0, - "source": "package_devdependencies", - "target": "package_devdependencies_types_node", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "package.json", - "source_location": "L33", - "weight": 1.0, - "source": "package_devdependencies", - "target": "package_devdependencies_types_react", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "package.json", - "source_location": "L34", - "weight": 1.0, - "source": "package_devdependencies", - "target": "package_devdependencies_types_react_dom", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "package.json", - "source_location": "L48", - "weight": 1.0, - "source": "package_devdependencies", - "target": "package_devdependencies_typescript", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "package.json", - "source_location": "L49", - "weight": 1.0, - "source": "package_devdependencies", - "target": "package_devdependencies_typescript_eslint", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "package.json", - "source_location": "L50", - "weight": 1.0, - "source": "package_devdependencies", - "target": "package_devdependencies_vite", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "package.json", - "source_location": "L35", - "weight": 1.0, - "source": "package_devdependencies", - "target": "package_devdependencies_vitejs_plugin_react", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "package.json", - "source_location": "L51", - "weight": 1.0, - "source": "package_devdependencies", - "target": "package_devdependencies_vitest", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "package.json", - "source_location": "L36", - "weight": 1.0, - "source": "package_devdependencies", - "target": "package_devdependencies_vitest_ui", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "src/App.tsx", - "source_location": "L5", - "weight": 1.0, - "source": "src_app", - "target": "src_app_app", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/App.tsx", - "source_location": "L3", - "weight": 1.0, - "source": "src_app", - "target": "src_presentation_routes_router", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/App.tsx", - "source_location": "L3", - "weight": 1.0, - "source": "src_app", - "target": "src_presentation_routes_router_router", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/main.tsx", - "source_location": "L4", - "weight": 1.0, - "source": "src_main", - "target": "src_app", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/main.tsx", - "source_location": "L4", - "weight": 1.0, - "source": "src_main", - "target": "src_app_app", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "src/application/context/TenantContext.ts", - "source_location": "L14", - "weight": 1.0, - "source": "src_application_context_tenantcontext", - "target": "src_application_context_tenantcontext_tenantcontext", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "src/application/context/TenantContext.ts", - "source_location": "L19", - "weight": 1.0, - "source": "src_application_context_tenantcontext", - "target": "src_application_context_tenantcontext_totenantcontext", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/application/context/TenantContext.ts", - "source_location": "L2", - "weight": 1.0, - "source": "src_application_context_tenantcontext", - "target": "src_domain_entities_user", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/application/context/TenantContext.ts", - "source_location": "L2", - "weight": 1.0, - "source": "src_application_context_tenantcontext", - "target": "src_domain_entities_user_user", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/application/context/TenantContext.ts", - "source_location": "L1", - "weight": 1.0, - "source": "src_application_context_tenantcontext", - "target": "src_domain_value_objects_tenant", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/application/context/TenantContext.ts", - "source_location": "L1", - "weight": 1.0, - "source": "src_application_context_tenantcontext", - "target": "src_domain_value_objects_tenant_tenant", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/application/use-cases/auth/GetCurrentSession.ts", - "source_location": "L2", - "weight": 1.0, - "source": "src_application_use_cases_auth_getcurrentsession", - "target": "src_application_context_tenantcontext", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/application/use-cases/auth/HandleAuthCallback.ts", - "source_location": "L2", - "weight": 1.0, - "source": "src_application_use_cases_auth_handleauthcallback", - "target": "src_application_context_tenantcontext", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/hooks/useAuth.ts", - "source_location": "L4", - "weight": 1.0, - "source": "src_presentation_hooks_useauth", - "target": "src_application_context_tenantcontext", - "confidence_score": 1.0 - }, - { - "relation": "references", - "context": "field", - "confidence": "EXTRACTED", - "source_file": "src/application/context/TenantContext.ts", - "source_location": "L16", - "weight": 1.0, - "source": "src_application_context_tenantcontext_tenantcontext", - "target": "src_domain_entities_user_user", - "confidence_score": 1.0 - }, - { - "relation": "references", - "context": "field", - "confidence": "EXTRACTED", - "source_file": "src/application/context/TenantContext.ts", - "source_location": "L15", - "weight": 1.0, - "source": "src_application_context_tenantcontext_tenantcontext", - "target": "src_domain_value_objects_tenant_tenant", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/application/use-cases/auth/GetCurrentSession.ts", - "source_location": "L2", - "weight": 1.0, - "source": "src_application_use_cases_auth_getcurrentsession", - "target": "src_application_context_tenantcontext_tenantcontext", - "confidence_score": 1.0 - }, - { - "relation": "references", - "context": "generic_arg", - "confidence": "EXTRACTED", - "source_file": "src/application/use-cases/auth/GetCurrentSession.ts", - "source_location": "L17", - "weight": 1.0, - "source": "src_application_use_cases_auth_getcurrentsession_getcurrentsession_execute", - "target": "src_application_context_tenantcontext_tenantcontext", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/application/use-cases/auth/HandleAuthCallback.ts", - "source_location": "L2", - "weight": 1.0, - "source": "src_application_use_cases_auth_handleauthcallback", - "target": "src_application_context_tenantcontext_tenantcontext", - "confidence_score": 1.0 - }, - { - "relation": "references", - "context": "generic_arg", - "confidence": "EXTRACTED", - "source_file": "src/application/use-cases/auth/HandleAuthCallback.ts", - "source_location": "L24", - "weight": 1.0, - "source": "src_application_use_cases_auth_handleauthcallback_handleauthcallback_execute", - "target": "src_application_context_tenantcontext_tenantcontext", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/hooks/useAuth.ts", - "source_location": "L4", - "weight": 1.0, - "source": "src_presentation_hooks_useauth", - "target": "src_application_context_tenantcontext_tenantcontext", - "confidence_score": 1.0 - }, - { - "relation": "references", - "context": "field", - "confidence": "EXTRACTED", - "source_file": "src/presentation/hooks/useAuth.ts", - "source_location": "L10", - "weight": 1.0, - "source": "src_presentation_hooks_useauth_useauthresult", - "target": "src_application_context_tenantcontext_tenantcontext", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/application/use-cases/auth/GetCurrentSession.ts", - "source_location": "L2", - "weight": 1.0, - "source": "src_application_use_cases_auth_getcurrentsession", - "target": "src_application_context_tenantcontext_totenantcontext", - "confidence_score": 1.0 - }, - { - "relation": "calls", - "context": "call", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "src/application/use-cases/auth/GetCurrentSession.ts", - "source_location": "L24", - "weight": 1.0, - "source": "src_application_use_cases_auth_getcurrentsession_getcurrentsession_execute", - "target": "src_application_context_tenantcontext_totenantcontext" - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/application/use-cases/auth/HandleAuthCallback.ts", - "source_location": "L2", - "weight": 1.0, - "source": "src_application_use_cases_auth_handleauthcallback", - "target": "src_application_context_tenantcontext_totenantcontext", - "confidence_score": 1.0 - }, - { - "relation": "calls", - "context": "call", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "src/application/use-cases/auth/HandleAuthCallback.ts", - "source_location": "L27", - "weight": 1.0, - "source": "src_application_use_cases_auth_handleauthcallback_handleauthcallback_execute", - "target": "src_application_context_tenantcontext_totenantcontext" - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "src/application/repositories/AuthRepository.ts", - "source_location": "L14", - "weight": 1.0, - "source": "src_application_repositories_authrepository", - "target": "src_application_repositories_authrepository_authrepository", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/application/repositories/AuthRepository.ts", - "source_location": "L1", - "weight": 1.0, - "source": "src_application_repositories_authrepository", - "target": "src_domain_entities_session", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/application/repositories/AuthRepository.ts", - "source_location": "L1", - "weight": 1.0, - "source": "src_application_repositories_authrepository", - "target": "src_domain_entities_session_session", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/application/test-helpers/createFakeAuthRepository.ts", - "source_location": "L1", - "weight": 1.0, - "source": "src_application_test_helpers_createfakeauthrepository", - "target": "src_application_repositories_authrepository", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/application/use-cases/auth/GetCurrentSession.ts", - "source_location": "L1", - "weight": 1.0, - "source": "src_application_use_cases_auth_getcurrentsession", - "target": "src_application_repositories_authrepository", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/application/use-cases/auth/HandleAuthCallback.ts", - "source_location": "L1", - "weight": 1.0, - "source": "src_application_use_cases_auth_handleauthcallback", - "target": "src_application_repositories_authrepository", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/application/use-cases/auth/InitiateLogin.ts", - "source_location": "L1", - "weight": 1.0, - "source": "src_application_use_cases_auth_initiatelogin", - "target": "src_application_repositories_authrepository", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/application/use-cases/auth/Logout.ts", - "source_location": "L1", - "weight": 1.0, - "source": "src_application_use_cases_auth_logout", - "target": "src_application_repositories_authrepository", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/composition/container.ts", - "source_location": "L3", - "weight": 1.0, - "source": "src_composition_container", - "target": "src_application_repositories_authrepository", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/infrastructure/auth/OidcAuthRepository.ts", - "source_location": "L2", - "weight": 1.0, - "source": "src_infrastructure_auth_oidcauthrepository", - "target": "src_application_repositories_authrepository", - "confidence_score": 1.0 - }, - { - "relation": "method", - "confidence": "EXTRACTED", - "source_file": "src/application/repositories/AuthRepository.ts", - "source_location": "L39", - "weight": 1.0, - "source": "src_application_repositories_authrepository_authrepository", - "target": "src_application_repositories_authrepository_authrepository_getcurrentsession", - "confidence_score": 1.0 - }, - { - "relation": "method", - "confidence": "EXTRACTED", - "source_file": "src/application/repositories/AuthRepository.ts", - "source_location": "L30", - "weight": 1.0, - "source": "src_application_repositories_authrepository_authrepository", - "target": "src_application_repositories_authrepository_authrepository_handlecallback", - "confidence_score": 1.0 - }, - { - "relation": "method", - "confidence": "EXTRACTED", - "source_file": "src/application/repositories/AuthRepository.ts", - "source_location": "L19", - "weight": 1.0, - "source": "src_application_repositories_authrepository_authrepository", - "target": "src_application_repositories_authrepository_authrepository_initiatelogin", - "confidence_score": 1.0 - }, - { - "relation": "method", - "confidence": "EXTRACTED", - "source_file": "src/application/repositories/AuthRepository.ts", - "source_location": "L46", - "weight": 1.0, - "source": "src_application_repositories_authrepository_authrepository", - "target": "src_application_repositories_authrepository_authrepository_logout", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/application/test-helpers/createFakeAuthRepository.ts", - "source_location": "L1", - "weight": 1.0, - "source": "src_application_test_helpers_createfakeauthrepository", - "target": "src_application_repositories_authrepository_authrepository", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/application/use-cases/auth/GetCurrentSession.ts", - "source_location": "L1", - "weight": 1.0, - "source": "src_application_use_cases_auth_getcurrentsession", - "target": "src_application_repositories_authrepository_authrepository", - "confidence_score": 1.0 - }, - { - "relation": "references", - "context": "field", - "confidence": "EXTRACTED", - "source_file": "src/application/use-cases/auth/GetCurrentSession.ts", - "source_location": "L11", - "weight": 1.0, - "source": "src_application_use_cases_auth_getcurrentsession_getcurrentsession", - "target": "src_application_repositories_authrepository_authrepository", - "confidence_score": 1.0 - }, - { - "relation": "references", - "context": "parameter_type", - "confidence": "EXTRACTED", - "source_file": "src/application/use-cases/auth/GetCurrentSession.ts", - "source_location": "L13", - "weight": 1.0, - "source": "src_application_use_cases_auth_getcurrentsession_getcurrentsession_constructor", - "target": "src_application_repositories_authrepository_authrepository", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/application/use-cases/auth/HandleAuthCallback.ts", - "source_location": "L1", - "weight": 1.0, - "source": "src_application_use_cases_auth_handleauthcallback", - "target": "src_application_repositories_authrepository_authrepository", - "confidence_score": 1.0 - }, - { - "relation": "references", - "context": "field", - "confidence": "EXTRACTED", - "source_file": "src/application/use-cases/auth/HandleAuthCallback.ts", - "source_location": "L18", - "weight": 1.0, - "source": "src_application_use_cases_auth_handleauthcallback_handleauthcallback", - "target": "src_application_repositories_authrepository_authrepository", - "confidence_score": 1.0 - }, - { - "relation": "references", - "context": "parameter_type", - "confidence": "EXTRACTED", - "source_file": "src/application/use-cases/auth/HandleAuthCallback.ts", - "source_location": "L20", - "weight": 1.0, - "source": "src_application_use_cases_auth_handleauthcallback_handleauthcallback_constructor", - "target": "src_application_repositories_authrepository_authrepository", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/application/use-cases/auth/InitiateLogin.ts", - "source_location": "L1", - "weight": 1.0, - "source": "src_application_use_cases_auth_initiatelogin", - "target": "src_application_repositories_authrepository_authrepository", - "confidence_score": 1.0 - }, - { - "relation": "references", - "context": "field", - "confidence": "EXTRACTED", - "source_file": "src/application/use-cases/auth/InitiateLogin.ts", - "source_location": "L10", - "weight": 1.0, - "source": "src_application_use_cases_auth_initiatelogin_initiatelogin", - "target": "src_application_repositories_authrepository_authrepository", - "confidence_score": 1.0 - }, - { - "relation": "references", - "context": "parameter_type", - "confidence": "EXTRACTED", - "source_file": "src/application/use-cases/auth/InitiateLogin.ts", - "source_location": "L12", - "weight": 1.0, - "source": "src_application_use_cases_auth_initiatelogin_initiatelogin_constructor", - "target": "src_application_repositories_authrepository_authrepository", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/application/use-cases/auth/Logout.ts", - "source_location": "L1", - "weight": 1.0, - "source": "src_application_use_cases_auth_logout", - "target": "src_application_repositories_authrepository_authrepository", - "confidence_score": 1.0 - }, - { - "relation": "references", - "context": "field", - "confidence": "EXTRACTED", - "source_file": "src/application/use-cases/auth/Logout.ts", - "source_location": "L11", - "weight": 1.0, - "source": "src_application_use_cases_auth_logout_logout", - "target": "src_application_repositories_authrepository_authrepository", - "confidence_score": 1.0 - }, - { - "relation": "references", - "context": "parameter_type", - "confidence": "EXTRACTED", - "source_file": "src/application/use-cases/auth/Logout.ts", - "source_location": "L13", - "weight": 1.0, - "source": "src_application_use_cases_auth_logout_logout_constructor", - "target": "src_application_repositories_authrepository_authrepository", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/composition/container.ts", - "source_location": "L3", - "weight": 1.0, - "source": "src_composition_container", - "target": "src_application_repositories_authrepository_authrepository", - "confidence_score": 1.0 - }, - { - "relation": "references", - "context": "field", - "confidence": "EXTRACTED", - "source_file": "src/composition/container.ts", - "source_location": "L10", - "weight": 1.0, - "source": "src_composition_container_appcontainer", - "target": "src_application_repositories_authrepository_authrepository", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/infrastructure/auth/OidcAuthRepository.ts", - "source_location": "L2", - "weight": 1.0, - "source": "src_infrastructure_auth_oidcauthrepository", - "target": "src_application_repositories_authrepository_authrepository", - "confidence_score": 1.0 - }, - { - "relation": "implements", - "context": "type", - "confidence": "EXTRACTED", - "source_file": "src/infrastructure/auth/OidcAuthRepository.ts", - "source_location": "L16", - "weight": 1.0, - "source": "src_infrastructure_auth_oidcauthrepository_oidcauthrepository", - "target": "src_application_repositories_authrepository_authrepository", - "confidence_score": 1.0 - }, - { - "relation": "calls", - "context": "call", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "src/application/use-cases/auth/InitiateLogin.ts", - "source_location": "L17", - "weight": 1.0, - "source": "src_application_use_cases_auth_initiatelogin_initiatelogin_execute", - "target": "src_application_repositories_authrepository_authrepository_initiatelogin" - }, - { - "relation": "references", - "context": "generic_arg", - "confidence": "EXTRACTED", - "source_file": "src/application/repositories/AuthRepository.ts", - "source_location": "L30", - "weight": 1.0, - "source": "src_application_repositories_authrepository_authrepository_handlecallback", - "target": "src_domain_entities_session_session", - "confidence_score": 1.0 - }, - { - "relation": "calls", - "context": "call", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "src/application/use-cases/auth/HandleAuthCallback.ts", - "source_location": "L25", - "weight": 1.0, - "source": "src_application_use_cases_auth_handleauthcallback_handleauthcallback_execute", - "target": "src_application_repositories_authrepository_authrepository_handlecallback" - }, - { - "relation": "references", - "context": "generic_arg", - "confidence": "EXTRACTED", - "source_file": "src/application/repositories/AuthRepository.ts", - "source_location": "L39", - "weight": 1.0, - "source": "src_application_repositories_authrepository_authrepository_getcurrentsession", - "target": "src_domain_entities_session_session", - "confidence_score": 1.0 - }, - { - "relation": "calls", - "context": "call", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "src/application/use-cases/auth/GetCurrentSession.ts", - "source_location": "L18", - "weight": 1.0, - "source": "src_application_use_cases_auth_getcurrentsession_getcurrentsession_execute", - "target": "src_application_repositories_authrepository_authrepository_getcurrentsession" - }, - { - "relation": "calls", - "context": "call", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "src/application/use-cases/auth/Logout.ts", - "source_location": "L18", - "weight": 1.0, - "source": "src_application_use_cases_auth_logout_logout_execute", - "target": "src_application_repositories_authrepository_authrepository_logout" - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "src/application/test-helpers/createFakeAuthRepository.ts", - "source_location": "L13", - "weight": 1.0, - "source": "src_application_test_helpers_createfakeauthrepository", - "target": "src_application_test_helpers_createfakeauthrepository_createfakeauthrepository", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/application/use-cases/auth/GetCurrentSession.test.ts", - "source_location": "L3", - "weight": 1.0, - "source": "src_application_use_cases_auth_getcurrentsession_test", - "target": "src_application_test_helpers_createfakeauthrepository", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/application/use-cases/auth/HandleAuthCallback.test.ts", - "source_location": "L3", - "weight": 1.0, - "source": "src_application_use_cases_auth_handleauthcallback_test", - "target": "src_application_test_helpers_createfakeauthrepository", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/application/use-cases/auth/InitiateLogin.test.ts", - "source_location": "L3", - "weight": 1.0, - "source": "src_application_use_cases_auth_initiatelogin_test", - "target": "src_application_test_helpers_createfakeauthrepository", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/application/use-cases/auth/Logout.test.ts", - "source_location": "L3", - "weight": 1.0, - "source": "src_application_use_cases_auth_logout_test", - "target": "src_application_test_helpers_createfakeauthrepository", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/application/use-cases/auth/GetCurrentSession.test.ts", - "source_location": "L3", - "weight": 1.0, - "source": "src_application_use_cases_auth_getcurrentsession_test", - "target": "src_application_test_helpers_createfakeauthrepository_createfakeauthrepository", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/application/use-cases/auth/HandleAuthCallback.test.ts", - "source_location": "L3", - "weight": 1.0, - "source": "src_application_use_cases_auth_handleauthcallback_test", - "target": "src_application_test_helpers_createfakeauthrepository_createfakeauthrepository", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/application/use-cases/auth/InitiateLogin.test.ts", - "source_location": "L3", - "weight": 1.0, - "source": "src_application_use_cases_auth_initiatelogin_test", - "target": "src_application_test_helpers_createfakeauthrepository_createfakeauthrepository", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/application/use-cases/auth/Logout.test.ts", - "source_location": "L3", - "weight": 1.0, - "source": "src_application_use_cases_auth_logout_test", - "target": "src_application_test_helpers_createfakeauthrepository_createfakeauthrepository", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/application/use-cases/auth/GetCurrentSession.test.ts", - "source_location": "L2", - "weight": 1.0, - "source": "src_application_use_cases_auth_getcurrentsession_test", - "target": "src_application_use_cases_auth_getcurrentsession", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/application/use-cases/auth/GetCurrentSession.test.ts", - "source_location": "L2", - "weight": 1.0, - "source": "src_application_use_cases_auth_getcurrentsession_test", - "target": "src_application_use_cases_auth_getcurrentsession_getcurrentsession", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/application/use-cases/auth/GetCurrentSession.test.ts", - "source_location": "L4", - "weight": 1.0, - "source": "src_application_use_cases_auth_getcurrentsession_test", - "target": "src_domain_entities_session", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/application/use-cases/auth/GetCurrentSession.test.ts", - "source_location": "L4", - "weight": 1.0, - "source": "src_application_use_cases_auth_getcurrentsession_test", - "target": "src_domain_entities_session_session", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/application/use-cases/auth/GetCurrentSession.test.ts", - "source_location": "L5", - "weight": 1.0, - "source": "src_application_use_cases_auth_getcurrentsession_test", - "target": "src_domain_entities_user", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/application/use-cases/auth/GetCurrentSession.test.ts", - "source_location": "L5", - "weight": 1.0, - "source": "src_application_use_cases_auth_getcurrentsession_test", - "target": "src_domain_entities_user_user", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/application/use-cases/auth/GetCurrentSession.test.ts", - "source_location": "L6", - "weight": 1.0, - "source": "src_application_use_cases_auth_getcurrentsession_test", - "target": "src_domain_value_objects_tenant", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/application/use-cases/auth/GetCurrentSession.test.ts", - "source_location": "L6", - "weight": 1.0, - "source": "src_application_use_cases_auth_getcurrentsession_test", - "target": "src_domain_value_objects_tenant_tenant", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "src/application/use-cases/auth/GetCurrentSession.ts", - "source_location": "L10", - "weight": 1.0, - "source": "src_application_use_cases_auth_getcurrentsession", - "target": "src_application_use_cases_auth_getcurrentsession_getcurrentsession", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/composition/container.ts", - "source_location": "L6", - "weight": 1.0, - "source": "src_composition_container", - "target": "src_application_use_cases_auth_getcurrentsession", - "confidence_score": 1.0 - }, - { - "relation": "method", - "confidence": "EXTRACTED", - "source_file": "src/application/use-cases/auth/GetCurrentSession.ts", - "source_location": "L13", - "weight": 1.0, - "source": "src_application_use_cases_auth_getcurrentsession_getcurrentsession", - "target": "src_application_use_cases_auth_getcurrentsession_getcurrentsession_constructor", - "confidence_score": 1.0 - }, - { - "relation": "method", - "confidence": "EXTRACTED", - "source_file": "src/application/use-cases/auth/GetCurrentSession.ts", - "source_location": "L17", - "weight": 1.0, - "source": "src_application_use_cases_auth_getcurrentsession_getcurrentsession", - "target": "src_application_use_cases_auth_getcurrentsession_getcurrentsession_execute", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/composition/container.ts", - "source_location": "L6", - "weight": 1.0, - "source": "src_composition_container", - "target": "src_application_use_cases_auth_getcurrentsession_getcurrentsession", - "confidence_score": 1.0 - }, - { - "relation": "references", - "context": "field", - "confidence": "EXTRACTED", - "source_file": "src/composition/container.ts", - "source_location": "L11", - "weight": 1.0, - "source": "src_composition_container_appcontainer", - "target": "src_application_use_cases_auth_getcurrentsession_getcurrentsession", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/application/use-cases/auth/HandleAuthCallback.test.ts", - "source_location": "L2", - "weight": 1.0, - "source": "src_application_use_cases_auth_handleauthcallback_test", - "target": "src_application_use_cases_auth_handleauthcallback", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/application/use-cases/auth/HandleAuthCallback.test.ts", - "source_location": "L2", - "weight": 1.0, - "source": "src_application_use_cases_auth_handleauthcallback_test", - "target": "src_application_use_cases_auth_handleauthcallback_handleauthcallback", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/application/use-cases/auth/HandleAuthCallback.test.ts", - "source_location": "L4", - "weight": 1.0, - "source": "src_application_use_cases_auth_handleauthcallback_test", - "target": "src_domain_entities_session", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/application/use-cases/auth/HandleAuthCallback.test.ts", - "source_location": "L4", - "weight": 1.0, - "source": "src_application_use_cases_auth_handleauthcallback_test", - "target": "src_domain_entities_session_session", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/application/use-cases/auth/HandleAuthCallback.test.ts", - "source_location": "L5", - "weight": 1.0, - "source": "src_application_use_cases_auth_handleauthcallback_test", - "target": "src_domain_entities_user", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/application/use-cases/auth/HandleAuthCallback.test.ts", - "source_location": "L5", - "weight": 1.0, - "source": "src_application_use_cases_auth_handleauthcallback_test", - "target": "src_domain_entities_user_user", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/application/use-cases/auth/HandleAuthCallback.test.ts", - "source_location": "L6", - "weight": 1.0, - "source": "src_application_use_cases_auth_handleauthcallback_test", - "target": "src_domain_value_objects_tenant", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/application/use-cases/auth/HandleAuthCallback.test.ts", - "source_location": "L6", - "weight": 1.0, - "source": "src_application_use_cases_auth_handleauthcallback_test", - "target": "src_domain_value_objects_tenant_tenant", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "src/application/use-cases/auth/HandleAuthCallback.ts", - "source_location": "L17", - "weight": 1.0, - "source": "src_application_use_cases_auth_handleauthcallback", - "target": "src_application_use_cases_auth_handleauthcallback_handleauthcallback", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/composition/container.ts", - "source_location": "L5", - "weight": 1.0, - "source": "src_composition_container", - "target": "src_application_use_cases_auth_handleauthcallback", - "confidence_score": 1.0 - }, - { - "relation": "method", - "confidence": "EXTRACTED", - "source_file": "src/application/use-cases/auth/HandleAuthCallback.ts", - "source_location": "L20", - "weight": 1.0, - "source": "src_application_use_cases_auth_handleauthcallback_handleauthcallback", - "target": "src_application_use_cases_auth_handleauthcallback_handleauthcallback_constructor", - "confidence_score": 1.0 - }, - { - "relation": "method", - "confidence": "EXTRACTED", - "source_file": "src/application/use-cases/auth/HandleAuthCallback.ts", - "source_location": "L24", - "weight": 1.0, - "source": "src_application_use_cases_auth_handleauthcallback_handleauthcallback", - "target": "src_application_use_cases_auth_handleauthcallback_handleauthcallback_execute", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/composition/container.ts", - "source_location": "L5", - "weight": 1.0, - "source": "src_composition_container", - "target": "src_application_use_cases_auth_handleauthcallback_handleauthcallback", - "confidence_score": 1.0 - }, - { - "relation": "references", - "context": "field", - "confidence": "EXTRACTED", - "source_file": "src/composition/container.ts", - "source_location": "L11", - "weight": 1.0, - "source": "src_composition_container_appcontainer", - "target": "src_application_use_cases_auth_handleauthcallback_handleauthcallback", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/application/use-cases/auth/InitiateLogin.test.ts", - "source_location": "L2", - "weight": 1.0, - "source": "src_application_use_cases_auth_initiatelogin_test", - "target": "src_application_use_cases_auth_initiatelogin", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/application/use-cases/auth/InitiateLogin.test.ts", - "source_location": "L2", - "weight": 1.0, - "source": "src_application_use_cases_auth_initiatelogin_test", - "target": "src_application_use_cases_auth_initiatelogin_initiatelogin", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "src/application/use-cases/auth/InitiateLogin.ts", - "source_location": "L9", - "weight": 1.0, - "source": "src_application_use_cases_auth_initiatelogin", - "target": "src_application_use_cases_auth_initiatelogin_initiatelogin", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/composition/container.ts", - "source_location": "L4", - "weight": 1.0, - "source": "src_composition_container", - "target": "src_application_use_cases_auth_initiatelogin", - "confidence_score": 1.0 - }, - { - "relation": "method", - "confidence": "EXTRACTED", - "source_file": "src/application/use-cases/auth/InitiateLogin.ts", - "source_location": "L12", - "weight": 1.0, - "source": "src_application_use_cases_auth_initiatelogin_initiatelogin", - "target": "src_application_use_cases_auth_initiatelogin_initiatelogin_constructor", - "confidence_score": 1.0 - }, - { - "relation": "method", - "confidence": "EXTRACTED", - "source_file": "src/application/use-cases/auth/InitiateLogin.ts", - "source_location": "L16", - "weight": 1.0, - "source": "src_application_use_cases_auth_initiatelogin_initiatelogin", - "target": "src_application_use_cases_auth_initiatelogin_initiatelogin_execute", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/composition/container.ts", - "source_location": "L4", - "weight": 1.0, - "source": "src_composition_container", - "target": "src_application_use_cases_auth_initiatelogin_initiatelogin", - "confidence_score": 1.0 - }, - { - "relation": "references", - "context": "field", - "confidence": "EXTRACTED", - "source_file": "src/composition/container.ts", - "source_location": "L11", - "weight": 1.0, - "source": "src_composition_container_appcontainer", - "target": "src_application_use_cases_auth_initiatelogin_initiatelogin", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/application/use-cases/auth/Logout.test.ts", - "source_location": "L2", - "weight": 1.0, - "source": "src_application_use_cases_auth_logout_test", - "target": "src_application_use_cases_auth_logout", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/application/use-cases/auth/Logout.test.ts", - "source_location": "L2", - "weight": 1.0, - "source": "src_application_use_cases_auth_logout_test", - "target": "src_application_use_cases_auth_logout_logout", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "src/application/use-cases/auth/Logout.ts", - "source_location": "L10", - "weight": 1.0, - "source": "src_application_use_cases_auth_logout", - "target": "src_application_use_cases_auth_logout_logout", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/composition/container.ts", - "source_location": "L7", - "weight": 1.0, - "source": "src_composition_container", - "target": "src_application_use_cases_auth_logout", - "confidence_score": 1.0 - }, - { - "relation": "method", - "confidence": "EXTRACTED", - "source_file": "src/application/use-cases/auth/Logout.ts", - "source_location": "L13", - "weight": 1.0, - "source": "src_application_use_cases_auth_logout_logout", - "target": "src_application_use_cases_auth_logout_logout_constructor", - "confidence_score": 1.0 - }, - { - "relation": "method", - "confidence": "EXTRACTED", - "source_file": "src/application/use-cases/auth/Logout.ts", - "source_location": "L17", - "weight": 1.0, - "source": "src_application_use_cases_auth_logout_logout", - "target": "src_application_use_cases_auth_logout_logout_execute", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/composition/container.ts", - "source_location": "L7", - "weight": 1.0, - "source": "src_composition_container", - "target": "src_application_use_cases_auth_logout_logout", - "confidence_score": 1.0 - }, - { - "relation": "references", - "context": "field", - "confidence": "EXTRACTED", - "source_file": "src/composition/container.ts", - "source_location": "L11", - "weight": 1.0, - "source": "src_composition_container_appcontainer", - "target": "src_application_use_cases_auth_logout_logout", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "src/composition/container.ts", - "source_location": "L9", - "weight": 1.0, - "source": "src_composition_container", - "target": "src_composition_container_appcontainer", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "src/composition/container.ts", - "source_location": "L30", - "weight": 1.0, - "source": "src_composition_container", - "target": "src_composition_container_createappcontainer", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/composition/container.ts", - "source_location": "L2", - "weight": 1.0, - "source": "src_composition_container", - "target": "src_infrastructure_auth_oidcauthrepository", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/composition/container.ts", - "source_location": "L2", - "weight": 1.0, - "source": "src_composition_container", - "target": "src_infrastructure_auth_oidcauthrepository_oidcauthrepository", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/composition/container.ts", - "source_location": "L1", - "weight": 1.0, - "source": "src_composition_container", - "target": "src_infrastructure_config_createusermanager", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/composition/container.ts", - "source_location": "L1", - "weight": 1.0, - "source": "src_composition_container", - "target": "src_infrastructure_config_createusermanager_createusermanager", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/hooks/useAppContainer.ts", - "source_location": "L3", - "weight": 1.0, - "source": "src_presentation_hooks_useappcontainer", - "target": "src_composition_container", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/hooks/useAppContainer.test.tsx", - "source_location": "L5", - "weight": 1.0, - "source": "src_presentation_hooks_useappcontainer_test", - "target": "src_composition_container", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/hooks/useAuth.test.tsx", - "source_location": "L5", - "weight": 1.0, - "source": "src_presentation_hooks_useauth_test", - "target": "src_composition_container", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/pages/LoginPage/LoginPage.test.tsx", - "source_location": "L7", - "weight": 1.0, - "source": "src_presentation_pages_loginpage_loginpage_test", - "target": "src_composition_container", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/providers/AppContainerContext.ts", - "source_location": "L2", - "weight": 1.0, - "source": "src_presentation_providers_appcontainercontext", - "target": "src_composition_container", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/providers/AppProviders.tsx", - "source_location": "L3", - "weight": 1.0, - "source": "src_presentation_providers_appproviders", - "target": "src_composition_container", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/routes/ProtectedRoute.test.tsx", - "source_location": "L6", - "weight": 1.0, - "source": "src_presentation_routes_protectedroute_test", - "target": "src_composition_container", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/hooks/useAppContainer.ts", - "source_location": "L3", - "weight": 1.0, - "source": "src_presentation_hooks_useappcontainer", - "target": "src_composition_container_appcontainer", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/hooks/useAppContainer.test.tsx", - "source_location": "L5", - "weight": 1.0, - "source": "src_presentation_hooks_useappcontainer_test", - "target": "src_composition_container_appcontainer", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/hooks/useAuth.test.tsx", - "source_location": "L5", - "weight": 1.0, - "source": "src_presentation_hooks_useauth_test", - "target": "src_composition_container_appcontainer", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/pages/LoginPage/LoginPage.test.tsx", - "source_location": "L7", - "weight": 1.0, - "source": "src_presentation_pages_loginpage_loginpage_test", - "target": "src_composition_container_appcontainer", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/providers/AppContainerContext.ts", - "source_location": "L2", - "weight": 1.0, - "source": "src_presentation_providers_appcontainercontext", - "target": "src_composition_container_appcontainer", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/routes/ProtectedRoute.test.tsx", - "source_location": "L6", - "weight": 1.0, - "source": "src_presentation_routes_protectedroute_test", - "target": "src_composition_container_appcontainer", - "confidence_score": 1.0 - }, - { - "relation": "calls", - "context": "call", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "src/composition/container.ts", - "source_location": "L31", - "weight": 1.0, - "source": "src_composition_container_createappcontainer", - "target": "src_infrastructure_config_createusermanager_createusermanager" - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/providers/AppProviders.tsx", - "source_location": "L3", - "weight": 1.0, - "source": "src_presentation_providers_appproviders", - "target": "src_composition_container_createappcontainer", - "confidence_score": 1.0 - }, - { - "relation": "calls", - "context": "call", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "src/presentation/providers/AppProviders.tsx", - "source_location": "L17", - "weight": 1.0, - "source": "src_presentation_providers_appproviders_appproviders", - "target": "src_composition_container_createappcontainer" - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/domain/entities/Session.test.ts", - "source_location": "L2", - "weight": 1.0, - "source": "src_domain_entities_session_test", - "target": "src_domain_entities_session", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/domain/entities/Session.test.ts", - "source_location": "L2", - "weight": 1.0, - "source": "src_domain_entities_session_test", - "target": "src_domain_entities_session_session", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/domain/entities/Session.test.ts", - "source_location": "L3", - "weight": 1.0, - "source": "src_domain_entities_session_test", - "target": "src_domain_entities_user", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/domain/entities/Session.test.ts", - "source_location": "L3", - "weight": 1.0, - "source": "src_domain_entities_session_test", - "target": "src_domain_entities_user_user", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/domain/entities/Session.test.ts", - "source_location": "L4", - "weight": 1.0, - "source": "src_domain_entities_session_test", - "target": "src_domain_value_objects_tenant", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/domain/entities/Session.test.ts", - "source_location": "L4", - "weight": 1.0, - "source": "src_domain_entities_session_test", - "target": "src_domain_value_objects_tenant_tenant", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "src/domain/entities/Session.ts", - "source_location": "L5", - "weight": 1.0, - "source": "src_domain_entities_session", - "target": "src_domain_entities_session_createsessioninput", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "src/domain/entities/Session.ts", - "source_location": "L21", - "weight": 1.0, - "source": "src_domain_entities_session", - "target": "src_domain_entities_session_session", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/domain/entities/Session.ts", - "source_location": "L1", - "weight": 1.0, - "source": "src_domain_entities_session", - "target": "src_domain_entities_user", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/domain/entities/Session.ts", - "source_location": "L1", - "weight": 1.0, - "source": "src_domain_entities_session", - "target": "src_domain_entities_user_user", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/domain/entities/Session.ts", - "source_location": "L3", - "weight": 1.0, - "source": "src_domain_entities_session", - "target": "src_domain_errors_invalidsessionerror", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/domain/entities/Session.ts", - "source_location": "L3", - "weight": 1.0, - "source": "src_domain_entities_session", - "target": "src_domain_errors_invalidsessionerror_invalidsessionerror", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/domain/entities/Session.ts", - "source_location": "L2", - "weight": 1.0, - "source": "src_domain_entities_session", - "target": "src_domain_value_objects_tenant", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/domain/entities/Session.ts", - "source_location": "L2", - "weight": 1.0, - "source": "src_domain_entities_session", - "target": "src_domain_value_objects_tenant_tenant", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/infrastructure/auth/OidcAuthRepository.ts", - "source_location": "L3", - "weight": 1.0, - "source": "src_infrastructure_auth_oidcauthrepository", - "target": "src_domain_entities_session", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/infrastructure/mappers/oidcUserToSessionMapper.ts", - "source_location": "L2", - "weight": 1.0, - "source": "src_infrastructure_mappers_oidcusertosessionmapper", - "target": "src_domain_entities_session", - "confidence_score": 1.0 - }, - { - "relation": "references", - "context": "field", - "confidence": "EXTRACTED", - "source_file": "src/domain/entities/Session.ts", - "source_location": "L6", - "weight": 1.0, - "source": "src_domain_entities_session_createsessioninput", - "target": "src_domain_entities_user_user", - "confidence_score": 1.0 - }, - { - "relation": "method", - "confidence": "EXTRACTED", - "source_file": "src/domain/entities/Session.ts", - "source_location": "L49", - "weight": 1.0, - "source": "src_domain_entities_session_session", - "target": "src_domain_entities_session_session_belongstotenant", - "confidence_score": 1.0 - }, - { - "relation": "method", - "confidence": "EXTRACTED", - "source_file": "src/domain/entities/Session.ts", - "source_location": "L26", - "weight": 1.0, - "source": "src_domain_entities_session_session", - "target": "src_domain_entities_session_session_constructor", - "confidence_score": 1.0 - }, - { - "relation": "method", - "confidence": "EXTRACTED", - "source_file": "src/domain/entities/Session.ts", - "source_location": "L32", - "weight": 1.0, - "source": "src_domain_entities_session_session", - "target": "src_domain_entities_session_session_create", - "confidence_score": 1.0 - }, - { - "relation": "method", - "confidence": "EXTRACTED", - "source_file": "src/domain/entities/Session.ts", - "source_location": "L45", - "weight": 1.0, - "source": "src_domain_entities_session_session", - "target": "src_domain_entities_session_session_isexpiredat", - "confidence_score": 1.0 - }, - { - "relation": "references", - "context": "field", - "confidence": "EXTRACTED", - "source_file": "src/domain/entities/Session.ts", - "source_location": "L22", - "weight": 1.0, - "source": "src_domain_entities_session_session", - "target": "src_domain_entities_user_user", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/infrastructure/auth/OidcAuthRepository.ts", - "source_location": "L3", - "weight": 1.0, - "source": "src_infrastructure_auth_oidcauthrepository", - "target": "src_domain_entities_session_session", - "confidence_score": 1.0 - }, - { - "relation": "references", - "context": "generic_arg", - "confidence": "EXTRACTED", - "source_file": "src/infrastructure/auth/OidcAuthRepository.ts", - "source_location": "L33", - "weight": 1.0, - "source": "src_infrastructure_auth_oidcauthrepository_oidcauthrepository_getcurrentsession", - "target": "src_domain_entities_session_session", - "confidence_score": 1.0 - }, - { - "relation": "references", - "context": "generic_arg", - "confidence": "EXTRACTED", - "source_file": "src/infrastructure/auth/OidcAuthRepository.ts", - "source_location": "L27", - "weight": 1.0, - "source": "src_infrastructure_auth_oidcauthrepository_oidcauthrepository_handlecallback", - "target": "src_domain_entities_session_session", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/infrastructure/mappers/oidcUserToSessionMapper.ts", - "source_location": "L2", - "weight": 1.0, - "source": "src_infrastructure_mappers_oidcusertosessionmapper", - "target": "src_domain_entities_session_session", - "confidence_score": 1.0 - }, - { - "relation": "references", - "context": "parameter_type", - "confidence": "EXTRACTED", - "source_file": "src/domain/entities/Session.ts", - "source_location": "L26", - "weight": 1.0, - "source": "src_domain_entities_session_session_constructor", - "target": "src_domain_entities_user_user", - "confidence_score": 1.0 - }, - { - "relation": "calls", - "context": "call", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "src/infrastructure/mappers/oidcUserToSessionMapper.ts", - "source_location": "L33", - "weight": 1.0, - "source": "src_infrastructure_mappers_oidcusertosessionmapper_mapoidcusertosession", - "target": "src_domain_entities_session_session_create" - }, - { - "relation": "calls", - "context": "call", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "src/domain/entities/Session.ts", - "source_location": "L50", - "weight": 1.0, - "source": "src_domain_entities_session_session_belongstotenant", - "target": "src_domain_entities_user_user_belongstotenant" - }, - { - "relation": "references", - "context": "parameter_type", - "confidence": "EXTRACTED", - "source_file": "src/domain/entities/Session.ts", - "source_location": "L49", - "weight": 1.0, - "source": "src_domain_entities_session_session_belongstotenant", - "target": "src_domain_value_objects_tenant_tenant", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/domain/entities/User.test.ts", - "source_location": "L2", - "weight": 1.0, - "source": "src_domain_entities_user_test", - "target": "src_domain_entities_user", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/domain/entities/User.test.ts", - "source_location": "L2", - "weight": 1.0, - "source": "src_domain_entities_user_test", - "target": "src_domain_entities_user_user", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/domain/entities/User.test.ts", - "source_location": "L3", - "weight": 1.0, - "source": "src_domain_entities_user_test", - "target": "src_domain_value_objects_tenant", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/domain/entities/User.test.ts", - "source_location": "L3", - "weight": 1.0, - "source": "src_domain_entities_user_test", - "target": "src_domain_value_objects_tenant_tenant", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "src/domain/entities/User.ts", - "source_location": "L4", - "weight": 1.0, - "source": "src_domain_entities_user", - "target": "src_domain_entities_user_createuserinput", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "src/domain/entities/User.ts", - "source_location": "L20", - "weight": 1.0, - "source": "src_domain_entities_user", - "target": "src_domain_entities_user_user", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/domain/entities/User.ts", - "source_location": "L2", - "weight": 1.0, - "source": "src_domain_entities_user", - "target": "src_domain_errors_invalidusererror", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/domain/entities/User.ts", - "source_location": "L2", - "weight": 1.0, - "source": "src_domain_entities_user", - "target": "src_domain_errors_invalidusererror_invalidusererror", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/domain/entities/User.ts", - "source_location": "L1", - "weight": 1.0, - "source": "src_domain_entities_user", - "target": "src_domain_value_objects_tenant", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/domain/entities/User.ts", - "source_location": "L1", - "weight": 1.0, - "source": "src_domain_entities_user", - "target": "src_domain_value_objects_tenant_tenant", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/infrastructure/mappers/oidcUserToSessionMapper.ts", - "source_location": "L3", - "weight": 1.0, - "source": "src_infrastructure_mappers_oidcusertosessionmapper", - "target": "src_domain_entities_user", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/hooks/useAuth.test.tsx", - "source_location": "L6", - "weight": 1.0, - "source": "src_presentation_hooks_useauth_test", - "target": "src_domain_entities_user", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/routes/ProtectedRoute.test.tsx", - "source_location": "L8", - "weight": 1.0, - "source": "src_presentation_routes_protectedroute_test", - "target": "src_domain_entities_user", - "confidence_score": 1.0 - }, - { - "relation": "references", - "context": "field", - "confidence": "EXTRACTED", - "source_file": "src/domain/entities/User.ts", - "source_location": "L6", - "weight": 1.0, - "source": "src_domain_entities_user_createuserinput", - "target": "src_domain_value_objects_tenant_tenant", - "confidence_score": 1.0 - }, - { - "relation": "method", - "confidence": "EXTRACTED", - "source_file": "src/domain/entities/User.ts", - "source_location": "L45", - "weight": 1.0, - "source": "src_domain_entities_user_user", - "target": "src_domain_entities_user_user_belongstotenant", - "confidence_score": 1.0 - }, - { - "relation": "method", - "confidence": "EXTRACTED", - "source_file": "src/domain/entities/User.ts", - "source_location": "L26", - "weight": 1.0, - "source": "src_domain_entities_user_user", - "target": "src_domain_entities_user_user_constructor", - "confidence_score": 1.0 - }, - { - "relation": "method", - "confidence": "EXTRACTED", - "source_file": "src/domain/entities/User.ts", - "source_location": "L37", - "weight": 1.0, - "source": "src_domain_entities_user_user", - "target": "src_domain_entities_user_user_create", - "confidence_score": 1.0 - }, - { - "relation": "references", - "context": "field", - "confidence": "EXTRACTED", - "source_file": "src/domain/entities/User.ts", - "source_location": "L22", - "weight": 1.0, - "source": "src_domain_entities_user_user", - "target": "src_domain_value_objects_tenant_tenant", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/infrastructure/mappers/oidcUserToSessionMapper.ts", - "source_location": "L3", - "weight": 1.0, - "source": "src_infrastructure_mappers_oidcusertosessionmapper", - "target": "src_domain_entities_user_user", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/hooks/useAuth.test.tsx", - "source_location": "L6", - "weight": 1.0, - "source": "src_presentation_hooks_useauth_test", - "target": "src_domain_entities_user_user", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/routes/ProtectedRoute.test.tsx", - "source_location": "L8", - "weight": 1.0, - "source": "src_presentation_routes_protectedroute_test", - "target": "src_domain_entities_user_user", - "confidence_score": 1.0 - }, - { - "relation": "references", - "context": "parameter_type", - "confidence": "EXTRACTED", - "source_file": "src/domain/entities/User.ts", - "source_location": "L26", - "weight": 1.0, - "source": "src_domain_entities_user_user_constructor", - "target": "src_domain_value_objects_tenant_tenant", - "confidence_score": 1.0 - }, - { - "relation": "calls", - "context": "call", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "src/infrastructure/mappers/oidcUserToSessionMapper.ts", - "source_location": "L26", - "weight": 1.0, - "source": "src_infrastructure_mappers_oidcusertosessionmapper_mapoidcusertosession", - "target": "src_domain_entities_user_user_create" - }, - { - "relation": "calls", - "context": "call", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "src/presentation/routes/ProtectedRoute.test.tsx", - "source_location": "L15", - "weight": 1.0, - "source": "src_presentation_routes_protectedroute_test_buildcontainer", - "target": "src_domain_entities_user_user_create" - }, - { - "relation": "references", - "context": "parameter_type", - "confidence": "EXTRACTED", - "source_file": "src/domain/entities/User.ts", - "source_location": "L45", - "weight": 1.0, - "source": "src_domain_entities_user_user_belongstotenant", - "target": "src_domain_value_objects_tenant_tenant", - "confidence_score": 1.0 - }, - { - "relation": "calls", - "context": "call", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "src/domain/entities/User.ts", - "source_location": "L46", - "weight": 1.0, - "source": "src_domain_entities_user_user_belongstotenant", - "target": "src_domain_value_objects_tenant_tenant_equals" - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "src/domain/errors/DomainError.ts", - "source_location": "L8", - "weight": 1.0, - "source": "src_domain_errors_domainerror", - "target": "src_domain_errors_domainerror_domainerror", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/domain/errors/InvalidSessionError.ts", - "source_location": "L1", - "weight": 1.0, - "source": "src_domain_errors_invalidsessionerror", - "target": "src_domain_errors_domainerror", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/domain/errors/InvalidTenantError.ts", - "source_location": "L1", - "weight": 1.0, - "source": "src_domain_errors_invalidtenanterror", - "target": "src_domain_errors_domainerror", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/domain/errors/InvalidUserError.ts", - "source_location": "L1", - "weight": 1.0, - "source": "src_domain_errors_invalidusererror", - "target": "src_domain_errors_domainerror", - "confidence_score": 1.0 - }, - { - "relation": "method", - "confidence": "EXTRACTED", - "source_file": "src/domain/errors/DomainError.ts", - "source_location": "L9", - "weight": 1.0, - "source": "src_domain_errors_domainerror_domainerror", - "target": "src_domain_errors_domainerror_domainerror_constructor", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/domain/errors/InvalidSessionError.ts", - "source_location": "L1", - "weight": 1.0, - "source": "src_domain_errors_invalidsessionerror", - "target": "src_domain_errors_domainerror_domainerror", - "confidence_score": 1.0 - }, - { - "relation": "inherits", - "context": "type", - "confidence": "EXTRACTED", - "source_file": "src/domain/errors/InvalidSessionError.ts", - "source_location": "L3", - "weight": 1.0, - "source": "src_domain_errors_invalidsessionerror_invalidsessionerror", - "target": "src_domain_errors_domainerror_domainerror", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/domain/errors/InvalidTenantError.ts", - "source_location": "L1", - "weight": 1.0, - "source": "src_domain_errors_invalidtenanterror", - "target": "src_domain_errors_domainerror_domainerror", - "confidence_score": 1.0 - }, - { - "relation": "inherits", - "context": "type", - "confidence": "EXTRACTED", - "source_file": "src/domain/errors/InvalidTenantError.ts", - "source_location": "L3", - "weight": 1.0, - "source": "src_domain_errors_invalidtenanterror_invalidtenanterror", - "target": "src_domain_errors_domainerror_domainerror", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/domain/errors/InvalidUserError.ts", - "source_location": "L1", - "weight": 1.0, - "source": "src_domain_errors_invalidusererror", - "target": "src_domain_errors_domainerror_domainerror", - "confidence_score": 1.0 - }, - { - "relation": "inherits", - "context": "type", - "confidence": "EXTRACTED", - "source_file": "src/domain/errors/InvalidUserError.ts", - "source_location": "L3", - "weight": 1.0, - "source": "src_domain_errors_invalidusererror_invalidusererror", - "target": "src_domain_errors_domainerror_domainerror", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "src/domain/errors/InvalidSessionError.ts", - "source_location": "L3", - "weight": 1.0, - "source": "src_domain_errors_invalidsessionerror", - "target": "src_domain_errors_invalidsessionerror_invalidsessionerror", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "src/domain/errors/InvalidTenantError.ts", - "source_location": "L3", - "weight": 1.0, - "source": "src_domain_errors_invalidtenanterror", - "target": "src_domain_errors_invalidtenanterror_invalidtenanterror", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/domain/value-objects/Tenant.ts", - "source_location": "L1", - "weight": 1.0, - "source": "src_domain_value_objects_tenant", - "target": "src_domain_errors_invalidtenanterror", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/domain/value-objects/Tenant.ts", - "source_location": "L1", - "weight": 1.0, - "source": "src_domain_value_objects_tenant", - "target": "src_domain_errors_invalidtenanterror_invalidtenanterror", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "src/domain/errors/InvalidUserError.ts", - "source_location": "L3", - "weight": 1.0, - "source": "src_domain_errors_invalidusererror", - "target": "src_domain_errors_invalidusererror_invalidusererror", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/domain/value-objects/Tenant.test.ts", - "source_location": "L2", - "weight": 1.0, - "source": "src_domain_value_objects_tenant_test", - "target": "src_domain_value_objects_tenant", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/domain/value-objects/Tenant.test.ts", - "source_location": "L2", - "weight": 1.0, - "source": "src_domain_value_objects_tenant_test", - "target": "src_domain_value_objects_tenant_tenant", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "src/domain/value-objects/Tenant.ts", - "source_location": "L9", - "weight": 1.0, - "source": "src_domain_value_objects_tenant", - "target": "src_domain_value_objects_tenant_tenant", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/infrastructure/mappers/oidcUserToSessionMapper.ts", - "source_location": "L4", - "weight": 1.0, - "source": "src_infrastructure_mappers_oidcusertosessionmapper", - "target": "src_domain_value_objects_tenant", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/hooks/useAuth.test.tsx", - "source_location": "L7", - "weight": 1.0, - "source": "src_presentation_hooks_useauth_test", - "target": "src_domain_value_objects_tenant", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/routes/ProtectedRoute.test.tsx", - "source_location": "L7", - "weight": 1.0, - "source": "src_presentation_routes_protectedroute_test", - "target": "src_domain_value_objects_tenant", - "confidence_score": 1.0 - }, - { - "relation": "method", - "confidence": "EXTRACTED", - "source_file": "src/domain/value-objects/Tenant.ts", - "source_location": "L12", - "weight": 1.0, - "source": "src_domain_value_objects_tenant_tenant", - "target": "src_domain_value_objects_tenant_tenant_constructor", - "confidence_score": 1.0 - }, - { - "relation": "method", - "confidence": "EXTRACTED", - "source_file": "src/domain/value-objects/Tenant.ts", - "source_location": "L16", - "weight": 1.0, - "source": "src_domain_value_objects_tenant_tenant", - "target": "src_domain_value_objects_tenant_tenant_create", - "confidence_score": 1.0 - }, - { - "relation": "method", - "confidence": "EXTRACTED", - "source_file": "src/domain/value-objects/Tenant.ts", - "source_location": "L26", - "weight": 1.0, - "source": "src_domain_value_objects_tenant_tenant", - "target": "src_domain_value_objects_tenant_tenant_equals", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/infrastructure/mappers/oidcUserToSessionMapper.ts", - "source_location": "L4", - "weight": 1.0, - "source": "src_infrastructure_mappers_oidcusertosessionmapper", - "target": "src_domain_value_objects_tenant_tenant", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/hooks/useAuth.test.tsx", - "source_location": "L7", - "weight": 1.0, - "source": "src_presentation_hooks_useauth_test", - "target": "src_domain_value_objects_tenant_tenant", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/routes/ProtectedRoute.test.tsx", - "source_location": "L7", - "weight": 1.0, - "source": "src_presentation_routes_protectedroute_test", - "target": "src_domain_value_objects_tenant_tenant", - "confidence_score": 1.0 - }, - { - "relation": "calls", - "context": "call", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "src/infrastructure/mappers/oidcUserToSessionMapper.ts", - "source_location": "L25", - "weight": 1.0, - "source": "src_infrastructure_mappers_oidcusertosessionmapper_mapoidcusertosession", - "target": "src_domain_value_objects_tenant_tenant_create" - }, - { - "relation": "calls", - "context": "call", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "src/presentation/routes/ProtectedRoute.test.tsx", - "source_location": "L14", - "weight": 1.0, - "source": "src_presentation_routes_protectedroute_test_buildcontainer", - "target": "src_domain_value_objects_tenant_tenant_create" - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/infrastructure/auth/OidcAuthRepository.test.ts", - "source_location": "L4", - "weight": 1.0, - "source": "src_infrastructure_auth_oidcauthrepository_test", - "target": "src_infrastructure_auth_oidcauthrepository", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/infrastructure/auth/OidcAuthRepository.test.ts", - "source_location": "L4", - "weight": 1.0, - "source": "src_infrastructure_auth_oidcauthrepository_test", - "target": "src_infrastructure_auth_oidcauthrepository_oidcauthrepository", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "src/infrastructure/auth/OidcAuthRepository.test.ts", - "source_location": "L7", - "weight": 1.0, - "source": "src_infrastructure_auth_oidcauthrepository_test", - "target": "src_infrastructure_auth_oidcauthrepository_test_createfakeoidcuser", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "src/infrastructure/auth/OidcAuthRepository.test.ts", - "source_location": "L32", - "weight": 1.0, - "source": "src_infrastructure_auth_oidcauthrepository_test", - "target": "src_infrastructure_auth_oidcauthrepository_test_createfakeusermanager", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "src/infrastructure/auth/OidcAuthRepository.test.ts", - "source_location": "L23", - "weight": 1.0, - "source": "src_infrastructure_auth_oidcauthrepository_test", - "target": "src_infrastructure_auth_oidcauthrepository_test_fakeusermanager", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/infrastructure/auth/OidcAuthRepository.test.ts", - "source_location": "L5", - "weight": 1.0, - "source": "src_infrastructure_auth_oidcauthrepository_test", - "target": "src_infrastructure_mappers_missingtenantclaimerror", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/infrastructure/auth/OidcAuthRepository.test.ts", - "source_location": "L5", - "weight": 1.0, - "source": "src_infrastructure_auth_oidcauthrepository_test", - "target": "src_infrastructure_mappers_missingtenantclaimerror_missingtenantclaimerror", - "confidence_score": 1.0 - }, - { - "relation": "calls", - "context": "call", - "confidence": "EXTRACTED", - "source_file": "src/infrastructure/auth/OidcAuthRepository.test.ts", - "source_location": "L35", - "weight": 1.0, - "source": "src_infrastructure_auth_oidcauthrepository_test_createfakeusermanager", - "target": "src_infrastructure_auth_oidcauthrepository_test_createfakeoidcuser", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "src/infrastructure/auth/OidcAuthRepository.ts", - "source_location": "L16", - "weight": 1.0, - "source": "src_infrastructure_auth_oidcauthrepository", - "target": "src_infrastructure_auth_oidcauthrepository_oidcauthrepository", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/infrastructure/auth/OidcAuthRepository.ts", - "source_location": "L4", - "weight": 1.0, - "source": "src_infrastructure_auth_oidcauthrepository", - "target": "src_infrastructure_mappers_oidcusertosessionmapper", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/infrastructure/auth/OidcAuthRepository.ts", - "source_location": "L4", - "weight": 1.0, - "source": "src_infrastructure_auth_oidcauthrepository", - "target": "src_infrastructure_mappers_oidcusertosessionmapper_mapoidcusertosession", - "confidence_score": 1.0 - }, - { - "relation": "method", - "confidence": "EXTRACTED", - "source_file": "src/infrastructure/auth/OidcAuthRepository.ts", - "source_location": "L19", - "weight": 1.0, - "source": "src_infrastructure_auth_oidcauthrepository_oidcauthrepository", - "target": "src_infrastructure_auth_oidcauthrepository_oidcauthrepository_constructor", - "confidence_score": 1.0 - }, - { - "relation": "method", - "confidence": "EXTRACTED", - "source_file": "src/infrastructure/auth/OidcAuthRepository.ts", - "source_location": "L33", - "weight": 1.0, - "source": "src_infrastructure_auth_oidcauthrepository_oidcauthrepository", - "target": "src_infrastructure_auth_oidcauthrepository_oidcauthrepository_getcurrentsession", - "confidence_score": 1.0 - }, - { - "relation": "method", - "confidence": "EXTRACTED", - "source_file": "src/infrastructure/auth/OidcAuthRepository.ts", - "source_location": "L27", - "weight": 1.0, - "source": "src_infrastructure_auth_oidcauthrepository_oidcauthrepository", - "target": "src_infrastructure_auth_oidcauthrepository_oidcauthrepository_handlecallback", - "confidence_score": 1.0 - }, - { - "relation": "method", - "confidence": "EXTRACTED", - "source_file": "src/infrastructure/auth/OidcAuthRepository.ts", - "source_location": "L23", - "weight": 1.0, - "source": "src_infrastructure_auth_oidcauthrepository_oidcauthrepository", - "target": "src_infrastructure_auth_oidcauthrepository_oidcauthrepository_initiatelogin", - "confidence_score": 1.0 - }, - { - "relation": "method", - "confidence": "EXTRACTED", - "source_file": "src/infrastructure/auth/OidcAuthRepository.ts", - "source_location": "L65", - "weight": 1.0, - "source": "src_infrastructure_auth_oidcauthrepository_oidcauthrepository", - "target": "src_infrastructure_auth_oidcauthrepository_oidcauthrepository_logout", - "confidence_score": 1.0 - }, - { - "relation": "calls", - "context": "call", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "src/infrastructure/auth/OidcAuthRepository.ts", - "source_location": "L30", - "weight": 1.0, - "source": "src_infrastructure_auth_oidcauthrepository_oidcauthrepository_handlecallback", - "target": "src_infrastructure_mappers_oidcusertosessionmapper_mapoidcusertosession" - }, - { - "relation": "calls", - "context": "call", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "src/infrastructure/auth/OidcAuthRepository.ts", - "source_location": "L40", - "weight": 1.0, - "source": "src_infrastructure_auth_oidcauthrepository_oidcauthrepository_getcurrentsession", - "target": "src_infrastructure_mappers_oidcusertosessionmapper_mapoidcusertosession" - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "src/infrastructure/config/createUserManager.ts", - "source_location": "L16", - "weight": 1.0, - "source": "src_infrastructure_config_createusermanager", - "target": "src_infrastructure_config_createusermanager_createusermanager", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "src/infrastructure/mappers/MissingTenantClaimError.ts", - "source_location": "L8", - "weight": 1.0, - "source": "src_infrastructure_mappers_missingtenantclaimerror", - "target": "src_infrastructure_mappers_missingtenantclaimerror_missingtenantclaimerror", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/infrastructure/mappers/oidcUserToSessionMapper.ts", - "source_location": "L5", - "weight": 1.0, - "source": "src_infrastructure_mappers_oidcusertosessionmapper", - "target": "src_infrastructure_mappers_missingtenantclaimerror", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/infrastructure/mappers/oidcUserToSessionMapper.test.ts", - "source_location": "L4", - "weight": 1.0, - "source": "src_infrastructure_mappers_oidcusertosessionmapper_test", - "target": "src_infrastructure_mappers_missingtenantclaimerror", - "confidence_score": 1.0 - }, - { - "relation": "method", - "confidence": "EXTRACTED", - "source_file": "src/infrastructure/mappers/MissingTenantClaimError.ts", - "source_location": "L9", - "weight": 1.0, - "source": "src_infrastructure_mappers_missingtenantclaimerror_missingtenantclaimerror", - "target": "src_infrastructure_mappers_missingtenantclaimerror_missingtenantclaimerror_constructor", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/infrastructure/mappers/oidcUserToSessionMapper.ts", - "source_location": "L5", - "weight": 1.0, - "source": "src_infrastructure_mappers_oidcusertosessionmapper", - "target": "src_infrastructure_mappers_missingtenantclaimerror_missingtenantclaimerror", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/infrastructure/mappers/oidcUserToSessionMapper.test.ts", - "source_location": "L4", - "weight": 1.0, - "source": "src_infrastructure_mappers_oidcusertosessionmapper_test", - "target": "src_infrastructure_mappers_missingtenantclaimerror_missingtenantclaimerror", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/infrastructure/mappers/oidcUserToSessionMapper.test.ts", - "source_location": "L3", - "weight": 1.0, - "source": "src_infrastructure_mappers_oidcusertosessionmapper_test", - "target": "src_infrastructure_mappers_oidcusertosessionmapper", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/infrastructure/mappers/oidcUserToSessionMapper.test.ts", - "source_location": "L3", - "weight": 1.0, - "source": "src_infrastructure_mappers_oidcusertosessionmapper_test", - "target": "src_infrastructure_mappers_oidcusertosessionmapper_mapoidcusertosession", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "src/infrastructure/mappers/oidcUserToSessionMapper.test.ts", - "source_location": "L6", - "weight": 1.0, - "source": "src_infrastructure_mappers_oidcusertosessionmapper_test", - "target": "src_infrastructure_mappers_oidcusertosessionmapper_test_createfakeoidcuser", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "src/infrastructure/mappers/oidcUserToSessionMapper.ts", - "source_location": "L14", - "weight": 1.0, - "source": "src_infrastructure_mappers_oidcusertosessionmapper", - "target": "src_infrastructure_mappers_oidcusertosessionmapper_mapoidcusertosession", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "src/main.tsx", - "source_location": "L7", - "weight": 1.0, - "source": "src_main", - "target": "src_main_rootelement", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/main.tsx", - "source_location": "L5", - "weight": 1.0, - "source": "src_main", - "target": "src_presentation_providers_appproviders", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/main.tsx", - "source_location": "L5", - "weight": 1.0, - "source": "src_main", - "target": "src_presentation_providers_appproviders_appproviders", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/hooks/useAppContainer.test.tsx", - "source_location": "L3", - "weight": 1.0, - "source": "src_presentation_hooks_useappcontainer_test", - "target": "src_presentation_hooks_useappcontainer", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/hooks/useAppContainer.test.tsx", - "source_location": "L3", - "weight": 1.0, - "source": "src_presentation_hooks_useappcontainer_test", - "target": "src_presentation_hooks_useappcontainer_useappcontainer", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/hooks/useAppContainer.test.tsx", - "source_location": "L4", - "weight": 1.0, - "source": "src_presentation_hooks_useappcontainer_test", - "target": "src_presentation_providers_appcontainercontext", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/hooks/useAppContainer.test.tsx", - "source_location": "L4", - "weight": 1.0, - "source": "src_presentation_hooks_useappcontainer_test", - "target": "src_presentation_providers_appcontainercontext_appcontainercontext", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "src/presentation/hooks/useAppContainer.ts", - "source_location": "L11", - "weight": 1.0, - "source": "src_presentation_hooks_useappcontainer", - "target": "src_presentation_hooks_useappcontainer_useappcontainer", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/hooks/useAppContainer.ts", - "source_location": "L2", - "weight": 1.0, - "source": "src_presentation_hooks_useappcontainer", - "target": "src_presentation_providers_appcontainercontext", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/hooks/useAppContainer.ts", - "source_location": "L2", - "weight": 1.0, - "source": "src_presentation_hooks_useappcontainer", - "target": "src_presentation_providers_appcontainercontext_appcontainercontext", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/hooks/useAuth.ts", - "source_location": "L2", - "weight": 1.0, - "source": "src_presentation_hooks_useauth", - "target": "src_presentation_hooks_useappcontainer", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/pages/CallbackPage/CallbackPage.tsx", - "source_location": "L3", - "weight": 1.0, - "source": "src_presentation_pages_callbackpage_callbackpage", - "target": "src_presentation_hooks_useappcontainer", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/hooks/useAuth.ts", - "source_location": "L2", - "weight": 1.0, - "source": "src_presentation_hooks_useauth", - "target": "src_presentation_hooks_useappcontainer_useappcontainer", - "confidence_score": 1.0 - }, - { - "relation": "calls", - "context": "call", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "src/presentation/hooks/useAuth.ts", - "source_location": "L23", - "weight": 1.0, - "source": "src_presentation_hooks_useauth_useauth", - "target": "src_presentation_hooks_useappcontainer_useappcontainer" - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/pages/CallbackPage/CallbackPage.tsx", - "source_location": "L3", - "weight": 1.0, - "source": "src_presentation_pages_callbackpage_callbackpage", - "target": "src_presentation_hooks_useappcontainer_useappcontainer", - "confidence_score": 1.0 - }, - { - "relation": "calls", - "context": "call", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "src/presentation/pages/CallbackPage/CallbackPage.tsx", - "source_location": "L16", - "weight": 1.0, - "source": "src_presentation_pages_callbackpage_callbackpage_callbackpage", - "target": "src_presentation_hooks_useappcontainer_useappcontainer" - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/hooks/useAsync.test.tsx", - "source_location": "L3", - "weight": 1.0, - "source": "src_presentation_hooks_useasync_test", - "target": "src_presentation_hooks_useasync", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/hooks/useAsync.test.tsx", - "source_location": "L3", - "weight": 1.0, - "source": "src_presentation_hooks_useasync_test", - "target": "src_presentation_hooks_useasync_useasync", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "src/presentation/hooks/useAsync.ts", - "source_location": "L3", - "weight": 1.0, - "source": "src_presentation_hooks_useasync", - "target": "src_presentation_hooks_useasync_asyncstatus", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "src/presentation/hooks/useAsync.ts", - "source_location": "L27", - "weight": 1.0, - "source": "src_presentation_hooks_useasync", - "target": "src_presentation_hooks_useasync_useasync", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "src/presentation/hooks/useAsync.ts", - "source_location": "L12", - "weight": 1.0, - "source": "src_presentation_hooks_useasync", - "target": "src_presentation_hooks_useasync_useasyncoptions", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "src/presentation/hooks/useAsync.ts", - "source_location": "L5", - "weight": 1.0, - "source": "src_presentation_hooks_useasync", - "target": "src_presentation_hooks_useasync_useasyncresult", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/hooks/useAuth.ts", - "source_location": "L3", - "weight": 1.0, - "source": "src_presentation_hooks_useauth", - "target": "src_presentation_hooks_useasync", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/hooks/useAuth.ts", - "source_location": "L3", - "weight": 1.0, - "source": "src_presentation_hooks_useauth", - "target": "src_presentation_hooks_useasync_useasync", - "confidence_score": 1.0 - }, - { - "relation": "calls", - "context": "call", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "src/presentation/hooks/useAuth.ts", - "source_location": "L32", - "weight": 1.0, - "source": "src_presentation_hooks_useauth_useauth", - "target": "src_presentation_hooks_useasync_useasync" - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/hooks/useAuth.test.tsx", - "source_location": "L3", - "weight": 1.0, - "source": "src_presentation_hooks_useauth_test", - "target": "src_presentation_hooks_useauth", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "src/presentation/hooks/useAuth.test.tsx", - "source_location": "L16", - "weight": 1.0, - "source": "src_presentation_hooks_useauth_test", - "target": "src_presentation_hooks_useauth_test_createfakecontainer", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "src/presentation/hooks/useAuth.test.tsx", - "source_location": "L9", - "weight": 1.0, - "source": "src_presentation_hooks_useauth_test", - "target": "src_presentation_hooks_useauth_test_fakeusecases", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "src/presentation/hooks/useAuth.test.tsx", - "source_location": "L34", - "weight": 1.0, - "source": "src_presentation_hooks_useauth_test", - "target": "src_presentation_hooks_useauth_test_renderuseauth", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/hooks/useAuth.test.tsx", - "source_location": "L3", - "weight": 1.0, - "source": "src_presentation_hooks_useauth_test", - "target": "src_presentation_hooks_useauth_useauth", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/hooks/useAuth.test.tsx", - "source_location": "L3", - "weight": 1.0, - "source": "src_presentation_hooks_useauth_test", - "target": "src_presentation_hooks_useauth_useauthresult", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/hooks/useAuth.test.tsx", - "source_location": "L4", - "weight": 1.0, - "source": "src_presentation_hooks_useauth_test", - "target": "src_presentation_providers_appcontainercontext", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/hooks/useAuth.test.tsx", - "source_location": "L4", - "weight": 1.0, - "source": "src_presentation_hooks_useauth_test", - "target": "src_presentation_providers_appcontainercontext_appcontainercontext", - "confidence_score": 1.0 - }, - { - "relation": "calls", - "context": "call", - "confidence": "EXTRACTED", - "source_file": "src/presentation/hooks/useAuth.test.tsx", - "source_location": "L35", - "weight": 1.0, - "source": "src_presentation_hooks_useauth_test_renderuseauth", - "target": "src_presentation_hooks_useauth_useauth", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "src/presentation/hooks/useAuth.ts", - "source_location": "L6", - "weight": 1.0, - "source": "src_presentation_hooks_useauth", - "target": "src_presentation_hooks_useauth_authstatus", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "src/presentation/hooks/useAuth.ts", - "source_location": "L22", - "weight": 1.0, - "source": "src_presentation_hooks_useauth", - "target": "src_presentation_hooks_useauth_useauth", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "src/presentation/hooks/useAuth.ts", - "source_location": "L8", - "weight": 1.0, - "source": "src_presentation_hooks_useauth", - "target": "src_presentation_hooks_useauth_useauthresult", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/layouts/AdminLayout.tsx", - "source_location": "L3", - "weight": 1.0, - "source": "src_presentation_layouts_adminlayout", - "target": "src_presentation_hooks_useauth", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/pages/LoginPage/LoginPage.tsx", - "source_location": "L2", - "weight": 1.0, - "source": "src_presentation_pages_loginpage_loginpage", - "target": "src_presentation_hooks_useauth", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/routes/ProtectedRoute.tsx", - "source_location": "L3", - "weight": 1.0, - "source": "src_presentation_routes_protectedroute", - "target": "src_presentation_hooks_useauth", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/layouts/AdminLayout.tsx", - "source_location": "L3", - "weight": 1.0, - "source": "src_presentation_layouts_adminlayout", - "target": "src_presentation_hooks_useauth_useauth", - "confidence_score": 1.0 - }, - { - "relation": "calls", - "context": "call", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "src/presentation/layouts/AdminLayout.tsx", - "source_location": "L20", - "weight": 1.0, - "source": "src_presentation_layouts_adminlayout_adminlayout", - "target": "src_presentation_hooks_useauth_useauth" - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/pages/LoginPage/LoginPage.tsx", - "source_location": "L2", - "weight": 1.0, - "source": "src_presentation_pages_loginpage_loginpage", - "target": "src_presentation_hooks_useauth_useauth", - "confidence_score": 1.0 - }, - { - "relation": "calls", - "context": "call", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "src/presentation/pages/LoginPage/LoginPage.tsx", - "source_location": "L11", - "weight": 1.0, - "source": "src_presentation_pages_loginpage_loginpage_loginpage", - "target": "src_presentation_hooks_useauth_useauth" - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/routes/ProtectedRoute.tsx", - "source_location": "L3", - "weight": 1.0, - "source": "src_presentation_routes_protectedroute", - "target": "src_presentation_hooks_useauth_useauth", - "confidence_score": 1.0 - }, - { - "relation": "calls", - "context": "call", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "src/presentation/routes/ProtectedRoute.tsx", - "source_location": "L12", - "weight": 1.0, - "source": "src_presentation_routes_protectedroute_protectedroute", - "target": "src_presentation_hooks_useauth_useauth" - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "src/presentation/layouts/AdminLayout.tsx", - "source_location": "L19", - "weight": 1.0, - "source": "src_presentation_layouts_adminlayout", - "target": "src_presentation_layouts_adminlayout_adminlayout", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "src/presentation/layouts/AdminLayout.tsx", - "source_location": "L10", - "weight": 1.0, - "source": "src_presentation_layouts_adminlayout", - "target": "src_presentation_layouts_adminlayout_nav_items", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "src/presentation/layouts/AdminLayout.tsx", - "source_location": "L5", - "weight": 1.0, - "source": "src_presentation_layouts_adminlayout", - "target": "src_presentation_layouts_adminlayout_navitem", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/routes/router.tsx", - "source_location": "L3", - "weight": 1.0, - "source": "src_presentation_routes_router", - "target": "src_presentation_layouts_adminlayout", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/routes/router.tsx", - "source_location": "L3", - "weight": 1.0, - "source": "src_presentation_routes_router", - "target": "src_presentation_layouts_adminlayout_adminlayout", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "src/presentation/pages/AppointmentsPage/AppointmentsPage.tsx", - "source_location": "L3", - "weight": 1.0, - "source": "src_presentation_pages_appointmentspage_appointmentspage", - "target": "src_presentation_pages_appointmentspage_appointmentspage_appointmentspage", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/routes/router.tsx", - "source_location": "L7", - "weight": 1.0, - "source": "src_presentation_routes_router", - "target": "src_presentation_pages_appointmentspage_appointmentspage", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/routes/router.tsx", - "source_location": "L7", - "weight": 1.0, - "source": "src_presentation_routes_router", - "target": "src_presentation_pages_appointmentspage_appointmentspage_appointmentspage", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "src/presentation/pages/CallbackPage/CallbackPage.tsx", - "source_location": "L15", - "weight": 1.0, - "source": "src_presentation_pages_callbackpage_callbackpage", - "target": "src_presentation_pages_callbackpage_callbackpage_callbackpage", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "src/presentation/pages/CallbackPage/CallbackPage.tsx", - "source_location": "L5", - "weight": 1.0, - "source": "src_presentation_pages_callbackpage_callbackpage", - "target": "src_presentation_pages_callbackpage_callbackpage_callbackstatus", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/routes/router.tsx", - "source_location": "L5", - "weight": 1.0, - "source": "src_presentation_routes_router", - "target": "src_presentation_pages_callbackpage_callbackpage", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/routes/router.tsx", - "source_location": "L5", - "weight": 1.0, - "source": "src_presentation_routes_router", - "target": "src_presentation_pages_callbackpage_callbackpage_callbackpage", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "src/presentation/pages/ClientsPage/ClientsPage.tsx", - "source_location": "L3", - "weight": 1.0, - "source": "src_presentation_pages_clientspage_clientspage", - "target": "src_presentation_pages_clientspage_clientspage_clientspage", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/routes/router.tsx", - "source_location": "L9", - "weight": 1.0, - "source": "src_presentation_routes_router", - "target": "src_presentation_pages_clientspage_clientspage", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/routes/router.tsx", - "source_location": "L9", - "weight": 1.0, - "source": "src_presentation_routes_router", - "target": "src_presentation_pages_clientspage_clientspage_clientspage", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "src/presentation/pages/DashboardPage/DashboardPage.tsx", - "source_location": "L3", - "weight": 1.0, - "source": "src_presentation_pages_dashboardpage_dashboardpage", - "target": "src_presentation_pages_dashboardpage_dashboardpage_dashboardpage", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/routes/router.tsx", - "source_location": "L6", - "weight": 1.0, - "source": "src_presentation_routes_router", - "target": "src_presentation_pages_dashboardpage_dashboardpage", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/routes/router.tsx", - "source_location": "L6", - "weight": 1.0, - "source": "src_presentation_routes_router", - "target": "src_presentation_pages_dashboardpage_dashboardpage_dashboardpage", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "src/presentation/pages/InboxPage/InboxPage.tsx", - "source_location": "L3", - "weight": 1.0, - "source": "src_presentation_pages_inboxpage_inboxpage", - "target": "src_presentation_pages_inboxpage_inboxpage_inboxpage", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/routes/router.tsx", - "source_location": "L10", - "weight": 1.0, - "source": "src_presentation_routes_router", - "target": "src_presentation_pages_inboxpage_inboxpage", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/routes/router.tsx", - "source_location": "L10", - "weight": 1.0, - "source": "src_presentation_routes_router", - "target": "src_presentation_pages_inboxpage_inboxpage_inboxpage", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/pages/LoginPage/LoginPage.test.tsx", - "source_location": "L5", - "weight": 1.0, - "source": "src_presentation_pages_loginpage_loginpage_test", - "target": "src_presentation_pages_loginpage_loginpage", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/pages/LoginPage/LoginPage.test.tsx", - "source_location": "L5", - "weight": 1.0, - "source": "src_presentation_pages_loginpage_loginpage_test", - "target": "src_presentation_pages_loginpage_loginpage_loginpage", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "src/presentation/pages/LoginPage/LoginPage.test.tsx", - "source_location": "L10", - "weight": 1.0, - "source": "src_presentation_pages_loginpage_loginpage_test", - "target": "src_presentation_pages_loginpage_loginpage_test_buildcontainer", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "src/presentation/pages/LoginPage/LoginPage.test.tsx", - "source_location": "L22", - "weight": 1.0, - "source": "src_presentation_pages_loginpage_loginpage_test", - "target": "src_presentation_pages_loginpage_loginpage_test_renderloginpage", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/pages/LoginPage/LoginPage.test.tsx", - "source_location": "L6", - "weight": 1.0, - "source": "src_presentation_pages_loginpage_loginpage_test", - "target": "src_presentation_providers_appcontainercontext", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/pages/LoginPage/LoginPage.test.tsx", - "source_location": "L6", - "weight": 1.0, - "source": "src_presentation_pages_loginpage_loginpage_test", - "target": "src_presentation_providers_appcontainercontext_appcontainercontext", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "src/presentation/pages/LoginPage/LoginPage.tsx", - "source_location": "L10", - "weight": 1.0, - "source": "src_presentation_pages_loginpage_loginpage", - "target": "src_presentation_pages_loginpage_loginpage_loginpage", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/routes/router.tsx", - "source_location": "L4", - "weight": 1.0, - "source": "src_presentation_routes_router", - "target": "src_presentation_pages_loginpage_loginpage", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/routes/router.tsx", - "source_location": "L4", - "weight": 1.0, - "source": "src_presentation_routes_router", - "target": "src_presentation_pages_loginpage_loginpage_loginpage", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "src/presentation/pages/ServicesPage/ServicesPage.tsx", - "source_location": "L3", - "weight": 1.0, - "source": "src_presentation_pages_servicespage_servicespage", - "target": "src_presentation_pages_servicespage_servicespage_servicespage", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/routes/router.tsx", - "source_location": "L8", - "weight": 1.0, - "source": "src_presentation_routes_router", - "target": "src_presentation_pages_servicespage_servicespage", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/routes/router.tsx", - "source_location": "L8", - "weight": 1.0, - "source": "src_presentation_routes_router", - "target": "src_presentation_pages_servicespage_servicespage_servicespage", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "src/presentation/pages/SettingsPage/SettingsPage.tsx", - "source_location": "L3", - "weight": 1.0, - "source": "src_presentation_pages_settingspage_settingspage", - "target": "src_presentation_pages_settingspage_settingspage_settingspage", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/routes/router.tsx", - "source_location": "L11", - "weight": 1.0, - "source": "src_presentation_routes_router", - "target": "src_presentation_pages_settingspage_settingspage", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/routes/router.tsx", - "source_location": "L11", - "weight": 1.0, - "source": "src_presentation_routes_router", - "target": "src_presentation_pages_settingspage_settingspage_settingspage", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "src/presentation/providers/AppContainerContext.ts", - "source_location": "L9", - "weight": 1.0, - "source": "src_presentation_providers_appcontainercontext", - "target": "src_presentation_providers_appcontainercontext_appcontainercontext", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/providers/AppProviders.tsx", - "source_location": "L2", - "weight": 1.0, - "source": "src_presentation_providers_appproviders", - "target": "src_presentation_providers_appcontainercontext", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/routes/ProtectedRoute.test.tsx", - "source_location": "L5", - "weight": 1.0, - "source": "src_presentation_routes_protectedroute_test", - "target": "src_presentation_providers_appcontainercontext", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/providers/AppProviders.tsx", - "source_location": "L2", - "weight": 1.0, - "source": "src_presentation_providers_appproviders", - "target": "src_presentation_providers_appcontainercontext_appcontainercontext", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/routes/ProtectedRoute.test.tsx", - "source_location": "L5", - "weight": 1.0, - "source": "src_presentation_routes_protectedroute_test", - "target": "src_presentation_providers_appcontainercontext_appcontainercontext", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "src/presentation/providers/AppProviders.tsx", - "source_location": "L16", - "weight": 1.0, - "source": "src_presentation_providers_appproviders", - "target": "src_presentation_providers_appproviders_appproviders", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "src/presentation/providers/AppProviders.tsx", - "source_location": "L5", - "weight": 1.0, - "source": "src_presentation_providers_appproviders", - "target": "src_presentation_providers_appproviders_appprovidersprops", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/routes/ProtectedRoute.test.tsx", - "source_location": "L4", - "weight": 1.0, - "source": "src_presentation_routes_protectedroute_test", - "target": "src_presentation_routes_protectedroute", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/routes/ProtectedRoute.test.tsx", - "source_location": "L4", - "weight": 1.0, - "source": "src_presentation_routes_protectedroute_test", - "target": "src_presentation_routes_protectedroute_protectedroute", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "src/presentation/routes/ProtectedRoute.test.tsx", - "source_location": "L11", - "weight": 1.0, - "source": "src_presentation_routes_protectedroute_test", - "target": "src_presentation_routes_protectedroute_test_buildcontainer", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "src/presentation/routes/ProtectedRoute.test.tsx", - "source_location": "L37", - "weight": 1.0, - "source": "src_presentation_routes_protectedroute_test", - "target": "src_presentation_routes_protectedroute_test_renderwithrouter", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "src/presentation/routes/ProtectedRoute.tsx", - "source_location": "L11", - "weight": 1.0, - "source": "src_presentation_routes_protectedroute", - "target": "src_presentation_routes_protectedroute_protectedroute", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/routes/router.tsx", - "source_location": "L2", - "weight": 1.0, - "source": "src_presentation_routes_router", - "target": "src_presentation_routes_protectedroute", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/presentation/routes/router.tsx", - "source_location": "L2", - "weight": 1.0, - "source": "src_presentation_routes_router", - "target": "src_presentation_routes_protectedroute_protectedroute", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "src/presentation/routes/router.tsx", - "source_location": "L13", - "weight": 1.0, - "source": "src_presentation_routes_router", - "target": "src_presentation_routes_router_router", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "src/test/mocks/handlers/index.ts", - "source_location": "L7", - "weight": 1.0, - "source": "src_test_mocks_handlers_index", - "target": "src_test_mocks_handlers_index_handlers", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/test/mocks/server.ts", - "source_location": "L2", - "weight": 1.0, - "source": "src_test_mocks_server", - "target": "src_test_mocks_handlers_index", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/test/mocks/server.ts", - "source_location": "L2", - "weight": 1.0, - "source": "src_test_mocks_server", - "target": "src_test_mocks_handlers_index_handlers", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "src/test/mocks/server.ts", - "source_location": "L4", - "weight": 1.0, - "source": "src_test_mocks_server", - "target": "src_test_mocks_server_server", - "confidence_score": 1.0 - }, - { - "relation": "imports_from", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/test/setup.ts", - "source_location": "L4", - "weight": 1.0, - "source": "src_test_setup", - "target": "src_test_mocks_server", - "confidence_score": 1.0 - }, - { - "relation": "imports", - "context": "import", - "confidence": "EXTRACTED", - "source_file": "src/test/setup.ts", - "source_location": "L4", - "weight": 1.0, - "source": "src_test_setup", - "target": "src_test_mocks_server_server", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "src/vite-env.d.ts", - "source_location": "L19", - "weight": 1.0, - "source": "src_vite_env_d", - "target": "src_vite_env_d_importmeta", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "src/vite-env.d.ts", - "source_location": "L3", - "weight": 1.0, - "source": "src_vite_env_d", - "target": "src_vite_env_d_importmetaenv", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "tsconfig.app.json", - "source_location": "L2", - "weight": 1.0, - "source": "tsconfig_app", - "target": "tsconfig_app_compileroptions", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "tsconfig.app.json", - "source_location": "L30", - "weight": 1.0, - "source": "tsconfig_app", - "target": "tsconfig_app_include", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "tsconfig.app.json", - "source_location": "L12", - "weight": 1.0, - "source": "tsconfig_app_compileroptions", - "target": "tsconfig_app_compileroptions_allowimportingtsextensions", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "tsconfig.app.json", - "source_location": "L27", - "weight": 1.0, - "source": "tsconfig_app_compileroptions", - "target": "tsconfig_app_compileroptions_erasablesyntaxonly", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "tsconfig.app.json", - "source_location": "L22", - "weight": 1.0, - "source": "tsconfig_app_compileroptions", - "target": "tsconfig_app_compileroptions_exactoptionalpropertytypes", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "tsconfig.app.json", - "source_location": "L16", - "weight": 1.0, - "source": "tsconfig_app_compileroptions", - "target": "tsconfig_app_compileroptions_jsx", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "tsconfig.app.json", - "source_location": "L5", - "weight": 1.0, - "source": "tsconfig_app_compileroptions", - "target": "tsconfig_app_compileroptions_lib", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "tsconfig.app.json", - "source_location": "L6", - "weight": 1.0, - "source": "tsconfig_app_compileroptions", - "target": "tsconfig_app_compileroptions_module", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "tsconfig.app.json", - "source_location": "L14", - "weight": 1.0, - "source": "tsconfig_app_compileroptions", - "target": "tsconfig_app_compileroptions_moduledetection", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "tsconfig.app.json", - "source_location": "L11", - "weight": 1.0, - "source": "tsconfig_app_compileroptions", - "target": "tsconfig_app_compileroptions_moduleresolution", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "tsconfig.app.json", - "source_location": "L15", - "weight": 1.0, - "source": "tsconfig_app_compileroptions", - "target": "tsconfig_app_compileroptions_noemit", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "tsconfig.app.json", - "source_location": "L28", - "weight": 1.0, - "source": "tsconfig_app_compileroptions", - "target": "tsconfig_app_compileroptions_nofallthroughcasesinswitch", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "tsconfig.app.json", - "source_location": "L21", - "weight": 1.0, - "source": "tsconfig_app_compileroptions", - "target": "tsconfig_app_compileroptions_noimplicitoverride", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "tsconfig.app.json", - "source_location": "L20", - "weight": 1.0, - "source": "tsconfig_app_compileroptions", - "target": "tsconfig_app_compileroptions_nouncheckedindexedaccess", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "tsconfig.app.json", - "source_location": "L25", - "weight": 1.0, - "source": "tsconfig_app_compileroptions", - "target": "tsconfig_app_compileroptions_nounusedlocals", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "tsconfig.app.json", - "source_location": "L26", - "weight": 1.0, - "source": "tsconfig_app_compileroptions", - "target": "tsconfig_app_compileroptions_nounusedparameters", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "tsconfig.app.json", - "source_location": "L8", - "weight": 1.0, - "source": "tsconfig_app_compileroptions", - "target": "tsconfig_app_compileroptions_skiplibcheck", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "tsconfig.app.json", - "source_location": "L19", - "weight": 1.0, - "source": "tsconfig_app_compileroptions", - "target": "tsconfig_app_compileroptions_strict", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "tsconfig.app.json", - "source_location": "L4", - "weight": 1.0, - "source": "tsconfig_app_compileroptions", - "target": "tsconfig_app_compileroptions_target", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "tsconfig.app.json", - "source_location": "L3", - "weight": 1.0, - "source": "tsconfig_app_compileroptions", - "target": "tsconfig_app_compileroptions_tsbuildinfofile", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "tsconfig.app.json", - "source_location": "L7", - "weight": 1.0, - "source": "tsconfig_app_compileroptions", - "target": "tsconfig_app_compileroptions_types", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "tsconfig.app.json", - "source_location": "L13", - "weight": 1.0, - "source": "tsconfig_app_compileroptions", - "target": "tsconfig_app_compileroptions_verbatimmodulesyntax", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "tsconfig.json", - "source_location": "L2", - "weight": 1.0, - "source": "tsconfig", - "target": "tsconfig_files", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "tsconfig.json", - "source_location": "L3", - "weight": 1.0, - "source": "tsconfig", - "target": "tsconfig_references", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "tsconfig.node.json", - "source_location": "L2", - "weight": 1.0, - "source": "tsconfig_node", - "target": "tsconfig_node_compileroptions", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "tsconfig.node.json", - "source_location": "L22", - "weight": 1.0, - "source": "tsconfig_node", - "target": "tsconfig_node_include", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "tsconfig.node.json", - "source_location": "L11", - "weight": 1.0, - "source": "tsconfig_node_compileroptions", - "target": "tsconfig_node_compileroptions_allowimportingtsextensions", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "tsconfig.node.json", - "source_location": "L19", - "weight": 1.0, - "source": "tsconfig_node_compileroptions", - "target": "tsconfig_node_compileroptions_erasablesyntaxonly", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "tsconfig.node.json", - "source_location": "L5", - "weight": 1.0, - "source": "tsconfig_node_compileroptions", - "target": "tsconfig_node_compileroptions_lib", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "tsconfig.node.json", - "source_location": "L10", - "weight": 1.0, - "source": "tsconfig_node_compileroptions", - "target": "tsconfig_node_compileroptions_module", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "tsconfig.node.json", - "source_location": "L13", - "weight": 1.0, - "source": "tsconfig_node_compileroptions", - "target": "tsconfig_node_compileroptions_moduledetection", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "tsconfig.node.json", - "source_location": "L14", - "weight": 1.0, - "source": "tsconfig_node_compileroptions", - "target": "tsconfig_node_compileroptions_noemit", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "tsconfig.node.json", - "source_location": "L20", - "weight": 1.0, - "source": "tsconfig_node_compileroptions", - "target": "tsconfig_node_compileroptions_nofallthroughcasesinswitch", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "tsconfig.node.json", - "source_location": "L17", - "weight": 1.0, - "source": "tsconfig_node_compileroptions", - "target": "tsconfig_node_compileroptions_nounusedlocals", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "tsconfig.node.json", - "source_location": "L18", - "weight": 1.0, - "source": "tsconfig_node_compileroptions", - "target": "tsconfig_node_compileroptions_nounusedparameters", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "tsconfig.node.json", - "source_location": "L7", - "weight": 1.0, - "source": "tsconfig_node_compileroptions", - "target": "tsconfig_node_compileroptions_skiplibcheck", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "tsconfig.node.json", - "source_location": "L4", - "weight": 1.0, - "source": "tsconfig_node_compileroptions", - "target": "tsconfig_node_compileroptions_target", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "tsconfig.node.json", - "source_location": "L3", - "weight": 1.0, - "source": "tsconfig_node_compileroptions", - "target": "tsconfig_node_compileroptions_tsbuildinfofile", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "tsconfig.node.json", - "source_location": "L6", - "weight": 1.0, - "source": "tsconfig_node_compileroptions", - "target": "tsconfig_node_compileroptions_types", - "confidence_score": 1.0 - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "source_file": "tsconfig.node.json", - "source_location": "L12", - "weight": 1.0, - "source": "tsconfig_node_compileroptions", - "target": "tsconfig_node_compileroptions_verbatimmodulesyntax", - "confidence_score": 1.0 - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "CLAUDE.md", - "source_location": null, - "weight": 1.0, - "source": "claude_md_admin_panel_instructions", - "target": "skills_admin_api_contract_skill_admin_api_contract" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "docs/API.md", - "source_location": null, - "weight": 1.0, - "source": "docs_api_how_to_add_resource", - "target": "skills_admin_api_contract_skill_admin_api_contract" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": ".skills/admin-api-contract/SKILL.md", - "source_location": null, - "weight": 1.0, - "source": "skills_admin_api_contract_skill_admin_api_contract", - "target": "skills_admin_api_contract_skill_field_translation_patterns" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": ".skills/admin-api-contract/SKILL.md", - "source_location": null, - "weight": 1.0, - "source": "skills_admin_api_contract_skill_admin_api_contract", - "target": "skills_admin_api_contract_skill_servicedto" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": ".skills/admin-api-contract/SKILL.md", - "source_location": null, - "weight": 1.0, - "source": "skills_admin_api_contract_skill_admin_api_contract", - "target": "skills_admin_api_contract_skill_tenant_scoping_mechanism" - }, - { - "relation": "semantically_similar_to", - "confidence": "INFERRED", - "confidence_score": 0.75, - "source_file": ".skills/admin-api-contract/SKILL.md", - "source_location": null, - "weight": 1.0, - "source": "skills_admin_api_contract_skill_admin_api_contract", - "target": "skills_admin_feature_vertical_skill_admin_feature_vertical" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": ".skills/admin-feature-vertical/SKILL.md", - "source_location": null, - "weight": 1.0, - "source": "skills_admin_feature_vertical_skill_feature_vertical_slice", - "target": "skills_admin_api_contract_skill_admin_api_contract" - }, - { - "relation": "shares_data_with", - "confidence": "INFERRED", - "confidence_score": 0.95, - "source_file": ".skills/admin-api-contract/SKILL.md", - "source_location": null, - "weight": 1.0, - "source": "skills_admin_api_contract_skill_servicedto", - "target": "docs_domain_service" - }, - { - "relation": "conceptually_related_to", - "confidence": "INFERRED", - "confidence_score": 0.85, - "source_file": ".skills/admin-api-contract/SKILL.md", - "source_location": null, - "weight": 1.0, - "source": "skills_admin_api_contract_skill_tenant_scoping_mechanism", - "target": "claude_md_tenantcontext_param_rule" - }, - { - "relation": "conceptually_related_to", - "confidence": "INFERRED", - "confidence_score": 0.95, - "source_file": ".skills/admin-api-contract/SKILL.md", - "source_location": null, - "weight": 1.0, - "source": "skills_admin_api_contract_skill_tenant_scoping_mechanism", - "target": "docs_api_tenant_scoping" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "CLAUDE.md", - "source_location": null, - "weight": 1.0, - "source": "claude_md_admin_panel_instructions", - "target": "skills_admin_feature_vertical_skill_admin_feature_vertical" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": ".skills/admin-feature-vertical/SKILL.md", - "source_location": null, - "weight": 1.0, - "source": "skills_admin_feature_vertical_skill_admin_feature_vertical", - "target": "skills_admin_feature_vertical_skill_commit_checklist" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": ".skills/admin-feature-vertical/SKILL.md", - "source_location": null, - "weight": 1.0, - "source": "skills_admin_feature_vertical_skill_admin_feature_vertical", - "target": "skills_admin_feature_vertical_skill_feature_vertical_slice" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": ".skills/admin-feature-vertical/SKILL.md", - "source_location": null, - "weight": 1.0, - "source": "skills_admin_feature_vertical_skill_admin_feature_vertical", - "target": "skills_admin_feature_vertical_skill_httpclient" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": ".skills/admin-feature-vertical/SKILL.md", - "source_location": null, - "weight": 1.0, - "source": "skills_admin_feature_vertical_skill_admin_feature_vertical", - "target": "skills_admin_feature_vertical_skill_listservices" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": ".skills/admin-feature-vertical/SKILL.md", - "source_location": null, - "weight": 1.0, - "source": "skills_admin_feature_vertical_skill_feature_vertical_slice", - "target": "skills_admin_tdd_conventions_skill_admin_tdd_conventions" - }, - { - "relation": "conceptually_related_to", - "confidence": "INFERRED", - "confidence_score": 0.95, - "source_file": "docs/STATUS.md", - "source_location": null, - "weight": 1.0, - "source": "docs_status_httpclient_stub", - "target": "skills_admin_feature_vertical_skill_httpclient" - }, - { - "relation": "implements", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": ".skills/admin-feature-vertical/SKILL.md", - "source_location": null, - "weight": 1.0, - "source": "skills_admin_feature_vertical_skill_httpclient", - "target": "skills_admin_feature_vertical_skill_authenticatedhttpclient" - }, - { - "relation": "conceptually_related_to", - "confidence": "INFERRED", - "confidence_score": 0.85, - "source_file": ".skills/admin-feature-vertical/SKILL.md", - "source_location": null, - "weight": 1.0, - "source": "skills_admin_feature_vertical_skill_authenticatedhttpclient", - "target": "docs_api_apierror_class" - }, - { - "relation": "conceptually_related_to", - "confidence": "INFERRED", - "confidence_score": 0.95, - "source_file": ".skills/admin-feature-vertical/SKILL.md", - "source_location": null, - "weight": 1.0, - "source": "skills_admin_feature_vertical_skill_authenticatedhttpclient", - "target": "docs_api_authentication" - }, - { - "relation": "shares_data_with", - "confidence": "INFERRED", - "confidence_score": 0.85, - "source_file": ".skills/admin-feature-vertical/SKILL.md", - "source_location": null, - "weight": 1.0, - "source": "skills_admin_feature_vertical_skill_listservices", - "target": "docs_domain_service" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "CLAUDE.md", - "source_location": null, - "weight": 1.0, - "source": "claude_md_admin_panel_instructions", - "target": "skills_admin_tdd_conventions_skill_admin_tdd_conventions" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": ".skills/admin-tdd-conventions/SKILL.md", - "source_location": null, - "weight": 1.0, - "source": "skills_admin_tdd_conventions_skill_admin_tdd_conventions", - "target": "skills_admin_tdd_conventions_skill_buildfakecontainer" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": ".skills/admin-tdd-conventions/SKILL.md", - "source_location": null, - "weight": 1.0, - "source": "skills_admin_tdd_conventions_skill_admin_tdd_conventions", - "target": "skills_admin_tdd_conventions_skill_createfakeservicerepository" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": ".skills/admin-tdd-conventions/SKILL.md", - "source_location": null, - "weight": 1.0, - "source": "skills_admin_tdd_conventions_skill_admin_tdd_conventions", - "target": "skills_admin_tdd_conventions_skill_erasablesyntaxonly_gotcha" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": ".skills/admin-tdd-conventions/SKILL.md", - "source_location": null, - "weight": 1.0, - "source": "skills_admin_tdd_conventions_skill_admin_tdd_conventions", - "target": "skills_admin_tdd_conventions_skill_exactoptionalpropertytypes_gotcha" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": ".skills/admin-tdd-conventions/SKILL.md", - "source_location": null, - "weight": 1.0, - "source": "skills_admin_tdd_conventions_skill_admin_tdd_conventions", - "target": "skills_admin_tdd_conventions_skill_mock_strategy_table" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": ".skills/admin-tdd-conventions/SKILL.md", - "source_location": null, - "weight": 1.0, - "source": "skills_admin_tdd_conventions_skill_admin_tdd_conventions", - "target": "skills_admin_tdd_conventions_skill_msw_handler_conventions" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": ".skills/admin-tdd-conventions/SKILL.md", - "source_location": null, - "weight": 1.0, - "source": "skills_admin_tdd_conventions_skill_admin_tdd_conventions", - "target": "skills_admin_tdd_conventions_skill_never_resolving_promise" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": ".skills/admin-tdd-conventions/SKILL.md", - "source_location": null, - "weight": 1.0, - "source": "skills_admin_tdd_conventions_skill_admin_tdd_conventions", - "target": "skills_admin_tdd_conventions_skill_renderhook_generics" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": ".skills/admin-tdd-conventions/SKILL.md", - "source_location": null, - "weight": 1.0, - "source": "skills_admin_tdd_conventions_skill_admin_tdd_conventions", - "target": "skills_admin_tdd_conventions_skill_set_state_in_effect_suppression" - }, - { - "relation": "conceptually_related_to", - "confidence": "INFERRED", - "confidence_score": 0.85, - "source_file": ".skills/admin-tdd-conventions/SKILL.md", - "source_location": null, - "weight": 1.0, - "source": "skills_admin_tdd_conventions_skill_mock_strategy_table", - "target": "skills_admin_tdd_conventions_skill_buildfakecontainer" - }, - { - "relation": "conceptually_related_to", - "confidence": "INFERRED", - "confidence_score": 0.85, - "source_file": ".skills/admin-tdd-conventions/SKILL.md", - "source_location": null, - "weight": 1.0, - "source": "skills_admin_tdd_conventions_skill_mock_strategy_table", - "target": "skills_admin_tdd_conventions_skill_createfakeservicerepository" - }, - { - "relation": "conceptually_related_to", - "confidence": "INFERRED", - "confidence_score": 0.85, - "source_file": ".skills/admin-tdd-conventions/SKILL.md", - "source_location": null, - "weight": 1.0, - "source": "skills_admin_tdd_conventions_skill_mock_strategy_table", - "target": "skills_admin_tdd_conventions_skill_msw_handler_conventions" - }, - { - "relation": "conceptually_related_to", - "confidence": "INFERRED", - "confidence_score": 0.95, - "source_file": ".skills/admin-tdd-conventions/SKILL.md", - "source_location": null, - "weight": 1.0, - "source": "skills_admin_tdd_conventions_skill_erasablesyntaxonly_gotcha", - "target": "docs_decisions_erasablesyntaxonly" - }, - { - "relation": "conceptually_related_to", - "confidence": "INFERRED", - "confidence_score": 0.95, - "source_file": ".skills/admin-tdd-conventions/SKILL.md", - "source_location": null, - "weight": 1.0, - "source": "skills_admin_tdd_conventions_skill_exactoptionalpropertytypes_gotcha", - "target": "docs_decisions_exactoptionalpropertytypes" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": ".skills/admin-tdd-conventions/SKILL.md", - "source_location": null, - "weight": 1.0, - "source": "skills_admin_tdd_conventions_skill_msw_handler_conventions", - "target": "skills_admin_tdd_conventions_skill_onunhandledrequest_error" - }, - { - "relation": "conceptually_related_to", - "confidence": "INFERRED", - "confidence_score": 0.95, - "source_file": ".skills/admin-tdd-conventions/SKILL.md", - "source_location": null, - "weight": 1.0, - "source": "skills_admin_tdd_conventions_skill_onunhandledrequest_error", - "target": "docs_decisions_msw_onunhandledrequest_error" - }, - { - "relation": "conceptually_related_to", - "confidence": "INFERRED", - "confidence_score": 0.95, - "source_file": ".skills/admin-tdd-conventions/SKILL.md", - "source_location": null, - "weight": 1.0, - "source": "skills_admin_tdd_conventions_skill_set_state_in_effect_suppression", - "target": "docs_decisions_set_state_in_effect_suppression" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "CLAUDE.md", - "source_location": null, - "weight": 1.0, - "source": "claude_md_admin_panel_instructions", - "target": "claude_md_architecture_constraint" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "CLAUDE.md", - "source_location": null, - "weight": 1.0, - "source": "claude_md_admin_panel_instructions", - "target": "claude_md_current_state" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "CLAUDE.md", - "source_location": null, - "weight": 1.0, - "source": "claude_md_admin_panel_instructions", - "target": "claude_md_design_language" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "CLAUDE.md", - "source_location": null, - "weight": 1.0, - "source": "claude_md_admin_panel_instructions", - "target": "claude_md_erasablesyntaxonly" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "CLAUDE.md", - "source_location": null, - "weight": 1.0, - "source": "claude_md_admin_panel_instructions", - "target": "claude_md_exactoptionalpropertytypes" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "CLAUDE.md", - "source_location": null, - "weight": 1.0, - "source": "claude_md_admin_panel_instructions", - "target": "claude_md_nouncheckedindexedaccess" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "CLAUDE.md", - "source_location": null, - "weight": 1.0, - "source": "claude_md_admin_panel_instructions", - "target": "claude_md_tech_stack" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "CLAUDE.md", - "source_location": null, - "weight": 1.0, - "source": "claude_md_admin_panel_instructions", - "target": "claude_md_tenantcontext_param_rule" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "CLAUDE.md", - "source_location": null, - "weight": 1.0, - "source": "claude_md_admin_panel_instructions", - "target": "docs_adr_001_clean_architecture_layers" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "CLAUDE.md", - "source_location": null, - "weight": 1.0, - "source": "claude_md_admin_panel_instructions", - "target": "docs_adr_002_no_server_state_library" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "CLAUDE.md", - "source_location": null, - "weight": 1.0, - "source": "claude_md_admin_panel_instructions", - "target": "docs_adr_003_manual_di_no_container_library" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "CLAUDE.md", - "source_location": null, - "weight": 1.0, - "source": "claude_md_admin_panel_instructions", - "target": "docs_adr_004_explicit_silent_renewal" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "CLAUDE.md", - "source_location": null, - "weight": 1.0, - "source": "claude_md_admin_panel_instructions", - "target": "docs_api_api_integration_guide" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "CLAUDE.md", - "source_location": null, - "weight": 1.0, - "source": "claude_md_admin_panel_instructions", - "target": "docs_decisions_project_decisions_log" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "CLAUDE.md", - "source_location": null, - "weight": 1.0, - "source": "claude_md_admin_panel_instructions", - "target": "docs_domain_domain_glossary" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "CLAUDE.md", - "source_location": null, - "weight": 1.0, - "source": "claude_md_admin_panel_instructions", - "target": "docs_status_feature_status" - }, - { - "relation": "conceptually_related_to", - "confidence": "INFERRED", - "confidence_score": 0.95, - "source_file": "CLAUDE.md", - "source_location": null, - "weight": 1.0, - "source": "claude_md_erasablesyntaxonly", - "target": "docs_decisions_erasablesyntaxonly" - }, - { - "relation": "conceptually_related_to", - "confidence": "INFERRED", - "confidence_score": 0.95, - "source_file": "CLAUDE.md", - "source_location": null, - "weight": 1.0, - "source": "claude_md_exactoptionalpropertytypes", - "target": "docs_decisions_exactoptionalpropertytypes" - }, - { - "relation": "conceptually_related_to", - "confidence": "INFERRED", - "confidence_score": 0.95, - "source_file": "CLAUDE.md", - "source_location": null, - "weight": 1.0, - "source": "claude_md_nouncheckedindexedaccess", - "target": "docs_decisions_nouncheckedindexedaccess" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "CLAUDE.md", - "source_location": null, - "weight": 1.0, - "source": "claude_md_architecture_constraint", - "target": "claude_md_composition_container" - }, - { - "relation": "conceptually_related_to", - "confidence": "INFERRED", - "confidence_score": 0.95, - "source_file": "CLAUDE.md", - "source_location": null, - "weight": 1.0, - "source": "claude_md_architecture_constraint", - "target": "docs_adr_001_clean_architecture_layers" - }, - { - "relation": "conceptually_related_to", - "confidence": "INFERRED", - "confidence_score": 0.65, - "source_file": "README.md", - "source_location": null, - "weight": 1.0, - "source": "readme_react_typescript_vite_template", - "target": "claude_md_tech_stack" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "CLAUDE.md", - "source_location": null, - "weight": 1.0, - "source": "claude_md_design_language", - "target": "docs_decisions_design_language" - }, - { - "relation": "conceptually_related_to", - "confidence": "INFERRED", - "confidence_score": 0.75, - "source_file": "index.html", - "source_location": null, - "weight": 1.0, - "source": "index_html_entry_point", - "target": "readme_react_typescript_vite_template" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "README.md", - "source_location": null, - "weight": 1.0, - "source": "readme_react_typescript_vite_template", - "target": "readme_oxlint_config" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "README.md", - "source_location": null, - "weight": 1.0, - "source": "readme_react_typescript_vite_template", - "target": "readme_react_compiler" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "README.md", - "source_location": null, - "weight": 1.0, - "source": "readme_react_typescript_vite_template", - "target": "readme_vite_plugin_react" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "README.md", - "source_location": null, - "weight": 1.0, - "source": "readme_react_typescript_vite_template", - "target": "readme_vite_plugin_react_swc" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "docs/API.md", - "source_location": null, - "weight": 1.0, - "source": "docs_api_api_integration_guide", - "target": "docs_api_authentication" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "docs/API.md", - "source_location": null, - "weight": 1.0, - "source": "docs_api_api_integration_guide", - "target": "docs_api_base_url" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "docs/API.md", - "source_location": null, - "weight": 1.0, - "source": "docs_api_api_integration_guide", - "target": "docs_api_error_shape" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "docs/API.md", - "source_location": null, - "weight": 1.0, - "source": "docs_api_api_integration_guide", - "target": "docs_api_how_to_add_resource" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "docs/API.md", - "source_location": null, - "weight": 1.0, - "source": "docs_api_api_integration_guide", - "target": "docs_api_pagination" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "docs/API.md", - "source_location": null, - "weight": 1.0, - "source": "docs_api_api_integration_guide", - "target": "docs_api_resource_endpoints" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "docs/API.md", - "source_location": null, - "weight": 1.0, - "source": "docs_api_api_integration_guide", - "target": "docs_api_tenant_scoping" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "docs/API.md", - "source_location": null, - "weight": 1.0, - "source": "docs_api_authentication", - "target": "docs_api_unauthenticatederror" - }, - { - "relation": "conceptually_related_to", - "confidence": "INFERRED", - "confidence_score": 0.85, - "source_file": "docs/API.md", - "source_location": null, - "weight": 1.0, - "source": "docs_api_authentication", - "target": "docs_decisions_oidc_client_ts" - }, - { - "relation": "conceptually_related_to", - "confidence": "INFERRED", - "confidence_score": 0.85, - "source_file": "docs/API.md", - "source_location": null, - "weight": 1.0, - "source": "docs_api_tenant_scoping", - "target": "docs_decisions_tenant_id_claim_name" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "docs/API.md", - "source_location": null, - "weight": 1.0, - "source": "docs_api_error_shape", - "target": "docs_api_apierror_class" - }, - { - "relation": "conceptually_related_to", - "confidence": "INFERRED", - "confidence_score": 0.75, - "source_file": "docs/API.md", - "source_location": null, - "weight": 1.0, - "source": "docs_api_resource_endpoints", - "target": "docs_domain_appointment" - }, - { - "relation": "conceptually_related_to", - "confidence": "INFERRED", - "confidence_score": 0.75, - "source_file": "docs/API.md", - "source_location": null, - "weight": 1.0, - "source": "docs_api_resource_endpoints", - "target": "docs_domain_business_settings" - }, - { - "relation": "conceptually_related_to", - "confidence": "INFERRED", - "confidence_score": 0.75, - "source_file": "docs/API.md", - "source_location": null, - "weight": 1.0, - "source": "docs_api_resource_endpoints", - "target": "docs_domain_client" - }, - { - "relation": "conceptually_related_to", - "confidence": "INFERRED", - "confidence_score": 0.75, - "source_file": "docs/API.md", - "source_location": null, - "weight": 1.0, - "source": "docs_api_resource_endpoints", - "target": "docs_domain_conversation" - }, - { - "relation": "conceptually_related_to", - "confidence": "INFERRED", - "confidence_score": 0.75, - "source_file": "docs/API.md", - "source_location": null, - "weight": 1.0, - "source": "docs_api_resource_endpoints", - "target": "docs_domain_service" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "docs/DECISIONS.md", - "source_location": null, - "weight": 1.0, - "source": "docs_decisions_project_decisions_log", - "target": "docs_decisions_automaticsilentrenew_false" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "docs/DECISIONS.md", - "source_location": null, - "weight": 1.0, - "source": "docs_decisions_project_decisions_log", - "target": "docs_decisions_context_based_di" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "docs/DECISIONS.md", - "source_location": null, - "weight": 1.0, - "source": "docs_decisions_project_decisions_log", - "target": "docs_decisions_design_language" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "docs/DECISIONS.md", - "source_location": null, - "weight": 1.0, - "source": "docs_decisions_project_decisions_log", - "target": "docs_decisions_email_name_user_entity" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "docs/DECISIONS.md", - "source_location": null, - "weight": 1.0, - "source": "docs_decisions_project_decisions_log", - "target": "docs_decisions_erasablesyntaxonly" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "docs/DECISIONS.md", - "source_location": null, - "weight": 1.0, - "source": "docs_decisions_project_decisions_log", - "target": "docs_decisions_exactoptionalpropertytypes" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "docs/DECISIONS.md", - "source_location": null, - "weight": 1.0, - "source": "docs_decisions_project_decisions_log", - "target": "docs_decisions_handleauthcallback_unwrapped_errors" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "docs/DECISIONS.md", - "source_location": null, - "weight": 1.0, - "source": "docs_decisions_project_decisions_log", - "target": "docs_decisions_msw_onunhandledrequest_error" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "docs/DECISIONS.md", - "source_location": null, - "weight": 1.0, - "source": "docs_decisions_project_decisions_log", - "target": "docs_decisions_no_real_network_calls_in_tests" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "docs/DECISIONS.md", - "source_location": null, - "weight": 1.0, - "source": "docs_decisions_project_decisions_log", - "target": "docs_decisions_no_server_state_library" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "docs/DECISIONS.md", - "source_location": null, - "weight": 1.0, - "source": "docs_decisions_project_decisions_log", - "target": "docs_decisions_nouncheckedindexedaccess" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "docs/DECISIONS.md", - "source_location": null, - "weight": 1.0, - "source": "docs_decisions_project_decisions_log", - "target": "docs_decisions_oidc_client_ts" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "docs/DECISIONS.md", - "source_location": null, - "weight": 1.0, - "source": "docs_decisions_project_decisions_log", - "target": "docs_decisions_protectedroute_loading_state" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "docs/DECISIONS.md", - "source_location": null, - "weight": 1.0, - "source": "docs_decisions_project_decisions_log", - "target": "docs_decisions_set_state_in_effect_suppression" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "docs/DECISIONS.md", - "source_location": null, - "weight": 1.0, - "source": "docs_decisions_project_decisions_log", - "target": "docs_decisions_tenant_id_claim_name" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "docs/DECISIONS.md", - "source_location": null, - "weight": 1.0, - "source": "docs_decisions_project_decisions_log", - "target": "docs_decisions_useasync_immediate_flag" - }, - { - "relation": "conceptually_related_to", - "confidence": "INFERRED", - "confidence_score": 0.95, - "source_file": "docs/DECISIONS.md", - "source_location": null, - "weight": 1.0, - "source": "docs_decisions_automaticsilentrenew_false", - "target": "docs_adr_004_explicit_silent_renewal" - }, - { - "relation": "conceptually_related_to", - "confidence": "INFERRED", - "confidence_score": 0.85, - "source_file": "docs/DECISIONS.md", - "source_location": null, - "weight": 1.0, - "source": "docs_decisions_tenant_id_claim_name", - "target": "docs_domain_user" - }, - { - "relation": "shares_data_with", - "confidence": "INFERRED", - "confidence_score": 0.95, - "source_file": "docs/DECISIONS.md", - "source_location": null, - "weight": 1.0, - "source": "docs_decisions_email_name_user_entity", - "target": "docs_domain_user" - }, - { - "relation": "conceptually_related_to", - "confidence": "INFERRED", - "confidence_score": 0.95, - "source_file": "docs/DECISIONS.md", - "source_location": null, - "weight": 1.0, - "source": "docs_decisions_no_server_state_library", - "target": "docs_adr_002_no_server_state_library" - }, - { - "relation": "conceptually_related_to", - "confidence": "INFERRED", - "confidence_score": 0.85, - "source_file": "docs/DECISIONS.md", - "source_location": null, - "weight": 1.0, - "source": "docs_decisions_context_based_di", - "target": "docs_adr_003_manual_di_no_container_library" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "docs/DOMAIN.md", - "source_location": null, - "weight": 1.0, - "source": "docs_domain_domain_glossary", - "target": "docs_domain_appointment" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "docs/DOMAIN.md", - "source_location": null, - "weight": 1.0, - "source": "docs_domain_domain_glossary", - "target": "docs_domain_business_settings" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "docs/DOMAIN.md", - "source_location": null, - "weight": 1.0, - "source": "docs_domain_domain_glossary", - "target": "docs_domain_business_tenant" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "docs/DOMAIN.md", - "source_location": null, - "weight": 1.0, - "source": "docs_domain_domain_glossary", - "target": "docs_domain_client" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "docs/DOMAIN.md", - "source_location": null, - "weight": 1.0, - "source": "docs_domain_domain_glossary", - "target": "docs_domain_conversation" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "docs/DOMAIN.md", - "source_location": null, - "weight": 1.0, - "source": "docs_domain_domain_glossary", - "target": "docs_domain_out_of_scope_v1" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "docs/DOMAIN.md", - "source_location": null, - "weight": 1.0, - "source": "docs_domain_domain_glossary", - "target": "docs_domain_service" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "docs/DOMAIN.md", - "source_location": null, - "weight": 1.0, - "source": "docs_domain_domain_glossary", - "target": "docs_domain_session" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "docs/DOMAIN.md", - "source_location": null, - "weight": 1.0, - "source": "docs_domain_domain_glossary", - "target": "docs_domain_user" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "docs/DOMAIN.md", - "source_location": null, - "weight": 1.0, - "source": "docs_domain_business_settings", - "target": "docs_domain_business_tenant" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "docs/DOMAIN.md", - "source_location": null, - "weight": 1.0, - "source": "docs_domain_client", - "target": "docs_domain_business_tenant" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "docs/DOMAIN.md", - "source_location": null, - "weight": 1.0, - "source": "docs_domain_service", - "target": "docs_domain_business_tenant" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "docs/DOMAIN.md", - "source_location": null, - "weight": 1.0, - "source": "docs_domain_user", - "target": "docs_domain_business_tenant" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "docs/DOMAIN.md", - "source_location": null, - "weight": 1.0, - "source": "docs_domain_session", - "target": "docs_domain_user" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "docs/DOMAIN.md", - "source_location": null, - "weight": 1.0, - "source": "docs_domain_appointment", - "target": "docs_domain_appointmentsource" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "docs/DOMAIN.md", - "source_location": null, - "weight": 1.0, - "source": "docs_domain_appointment", - "target": "docs_domain_appointmentstatus" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "docs/DOMAIN.md", - "source_location": null, - "weight": 1.0, - "source": "docs_domain_appointment", - "target": "docs_domain_client" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "docs/DOMAIN.md", - "source_location": null, - "weight": 1.0, - "source": "docs_domain_appointment", - "target": "docs_domain_service" - }, - { - "relation": "shares_data_with", - "confidence": "INFERRED", - "confidence_score": 0.95, - "source_file": "docs/STATUS.md", - "source_location": null, - "weight": 1.0, - "source": "docs_status_appointments_vertical", - "target": "docs_domain_appointment" - }, - { - "relation": "shares_data_with", - "confidence": "INFERRED", - "confidence_score": 0.95, - "source_file": "docs/STATUS.md", - "source_location": null, - "weight": 1.0, - "source": "docs_status_services_vertical", - "target": "docs_domain_service" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "docs/DOMAIN.md", - "source_location": null, - "weight": 1.0, - "source": "docs_domain_conversation", - "target": "docs_domain_client" - }, - { - "relation": "shares_data_with", - "confidence": "INFERRED", - "confidence_score": 0.95, - "source_file": "docs/STATUS.md", - "source_location": null, - "weight": 1.0, - "source": "docs_status_clients_vertical", - "target": "docs_domain_client" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "docs/DOMAIN.md", - "source_location": null, - "weight": 1.0, - "source": "docs_domain_conversation", - "target": "docs_domain_conversationstatus" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "docs/DOMAIN.md", - "source_location": null, - "weight": 1.0, - "source": "docs_domain_conversation", - "target": "docs_domain_inbox" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "docs/DOMAIN.md", - "source_location": null, - "weight": 1.0, - "source": "docs_domain_conversation", - "target": "docs_domain_message" - }, - { - "relation": "shares_data_with", - "confidence": "INFERRED", - "confidence_score": 0.95, - "source_file": "docs/STATUS.md", - "source_location": null, - "weight": 1.0, - "source": "docs_status_inbox_vertical", - "target": "docs_domain_conversation" - }, - { - "relation": "shares_data_with", - "confidence": "INFERRED", - "confidence_score": 0.95, - "source_file": "docs/STATUS.md", - "source_location": null, - "weight": 1.0, - "source": "docs_status_settings_vertical", - "target": "docs_domain_business_settings" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "docs/STATUS.md", - "source_location": null, - "weight": 1.0, - "source": "docs_status_feature_status", - "target": "docs_status_appointments_vertical" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "docs/STATUS.md", - "source_location": null, - "weight": 1.0, - "source": "docs_status_feature_status", - "target": "docs_status_auth_vertical" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "docs/STATUS.md", - "source_location": null, - "weight": 1.0, - "source": "docs_status_feature_status", - "target": "docs_status_clients_vertical" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "docs/STATUS.md", - "source_location": null, - "weight": 1.0, - "source": "docs_status_feature_status", - "target": "docs_status_dashboard_vertical" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "docs/STATUS.md", - "source_location": null, - "weight": 1.0, - "source": "docs_status_feature_status", - "target": "docs_status_httpclient_stub" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "docs/STATUS.md", - "source_location": null, - "weight": 1.0, - "source": "docs_status_feature_status", - "target": "docs_status_inbox_vertical" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "docs/STATUS.md", - "source_location": null, - "weight": 1.0, - "source": "docs_status_feature_status", - "target": "docs_status_recommended_build_order" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "docs/STATUS.md", - "source_location": null, - "weight": 1.0, - "source": "docs_status_feature_status", - "target": "docs_status_services_vertical" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "docs/STATUS.md", - "source_location": null, - "weight": 1.0, - "source": "docs_status_feature_status", - "target": "docs_status_settings_vertical" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "docs/STATUS.md", - "source_location": null, - "weight": 1.0, - "source": "docs_status_recommended_build_order", - "target": "docs_status_httpclient_stub" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "docs/STATUS.md", - "source_location": null, - "weight": 1.0, - "source": "docs_status_recommended_build_order", - "target": "docs_status_services_vertical" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "docs/STATUS.md", - "source_location": null, - "weight": 1.0, - "source": "docs_status_recommended_build_order", - "target": "docs_status_clients_vertical" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "docs/STATUS.md", - "source_location": null, - "weight": 1.0, - "source": "docs_status_recommended_build_order", - "target": "docs_status_appointments_vertical" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "docs/STATUS.md", - "source_location": null, - "weight": 1.0, - "source": "docs_status_recommended_build_order", - "target": "docs_status_dashboard_vertical" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "docs/STATUS.md", - "source_location": null, - "weight": 1.0, - "source": "docs_status_recommended_build_order", - "target": "docs_status_inbox_vertical" - }, - { - "relation": "references", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "docs/STATUS.md", - "source_location": null, - "weight": 1.0, - "source": "docs_status_recommended_build_order", - "target": "docs_status_settings_vertical" - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "public/icons.svg", - "source_location": "symbol#bluesky-icon", - "weight": 1.0, - "source": "public_icons_icon_set", - "target": "public_icons_bluesky_icon" - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "public/icons.svg", - "source_location": "symbol#discord-icon", - "weight": 1.0, - "source": "public_icons_icon_set", - "target": "public_icons_discord_icon" - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "public/icons.svg", - "source_location": "symbol#documentation-icon", - "weight": 1.0, - "source": "public_icons_icon_set", - "target": "public_icons_documentation_icon" - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "public/icons.svg", - "source_location": "symbol#github-icon", - "weight": 1.0, - "source": "public_icons_icon_set", - "target": "public_icons_github_icon" - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "public/icons.svg", - "source_location": "symbol#social-icon", - "weight": 1.0, - "source": "public_icons_icon_set", - "target": "public_icons_social_icon" - }, - { - "relation": "contains", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "public/icons.svg", - "source_location": "symbol#x-icon", - "weight": 1.0, - "source": "public_icons_icon_set", - "target": "public_icons_x_icon" - }, - { - "relation": "conceptually_related_to", - "confidence": "INFERRED", - "confidence_score": 0.85, - "source_file": "public/icons.svg", - "source_location": null, - "weight": 1.0, - "source": "public_icons_bluesky_icon", - "target": "public_icons_x_icon" - }, - { - "relation": "conceptually_related_to", - "confidence": "INFERRED", - "confidence_score": 0.75, - "source_file": "public/icons.svg", - "source_location": null, - "weight": 1.0, - "source": "public_icons_social_icon", - "target": "public_icons_bluesky_icon" - }, - { - "relation": "conceptually_related_to", - "confidence": "INFERRED", - "confidence_score": 0.65, - "source_file": "public/icons.svg", - "source_location": null, - "weight": 1.0, - "source": "public_icons_discord_icon", - "target": "public_icons_github_icon" - } - ], - "hyperedges": [ - { - "id": "hyperedge_typescript_strictness_constraints", - "label": "TypeScript Strict Mode Constraints Group", - "nodes": [ - "claude_md_erasablesyntaxonly", - "claude_md_exactoptionalpropertytypes", - "claude_md_nouncheckedindexedaccess", - "docs_decisions_erasablesyntaxonly", - "docs_decisions_exactoptionalpropertytypes", - "docs_decisions_nouncheckedindexedaccess", - "skills_admin_tdd_conventions_skill_erasablesyntaxonly_gotcha", - "skills_admin_tdd_conventions_skill_exactoptionalpropertytypes_gotcha" - ], - "relation": "form", - "confidence": "INFERRED", - "confidence_score": 0.85, - "source_file": "CLAUDE.md" - }, - { - "id": "hyperedge_domain_entities", - "label": "Core Tenant-Scoped Domain Entities", - "nodes": [ - "docs_domain_business_tenant", - "docs_domain_user", - "docs_domain_service", - "docs_domain_client", - "docs_domain_appointment", - "docs_domain_conversation", - "docs_domain_business_settings" - ], - "relation": "participate_in", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "docs/DOMAIN.md" - }, - { - "id": "hyperedge_feature_vertical_build_order", - "label": "Feature Vertical Build Order Chain", - "nodes": [ - "docs_status_httpclient_stub", - "docs_status_services_vertical", - "docs_status_clients_vertical", - "docs_status_appointments_vertical", - "docs_status_inbox_vertical", - "docs_status_dashboard_vertical", - "docs_status_settings_vertical" - ], - "relation": "form", - "confidence": "EXTRACTED", - "confidence_score": 1.0, - "source_file": "docs/STATUS.md" - } - ] -} \ No newline at end of file diff --git a/apps/admin-frontend/graphify-out/manifest.json b/apps/admin-frontend/graphify-out/manifest.json deleted file mode 100644 index d155bf2..0000000 --- a/apps/admin-frontend/graphify-out/manifest.json +++ /dev/null @@ -1,482 +0,0 @@ -{ - ".husky/_/applypatch-msg": { - "mtime": 1782683991.0, - "ast_hash": "0b71c2e7d39daa010985d529f1320b37", - "semantic_hash": "0b71c2e7d39daa010985d529f1320b37" - }, - ".husky/_/commit-msg": { - "mtime": 1782683991.0, - "ast_hash": "0b71c2e7d39daa010985d529f1320b37", - "semantic_hash": "0b71c2e7d39daa010985d529f1320b37" - }, - ".husky/_/h": { - "mtime": 1782683991.0, - "ast_hash": "15239a01c5554e56ff745570fb583a91", - "semantic_hash": "15239a01c5554e56ff745570fb583a91" - }, - ".husky/_/husky.sh": { - "mtime": 1782683991.0, - "ast_hash": "13871d2ab13e8423790ecb2c01c78c18", - "semantic_hash": "13871d2ab13e8423790ecb2c01c78c18" - }, - ".husky/_/post-applypatch": { - "mtime": 1782683991.0, - "ast_hash": "0b71c2e7d39daa010985d529f1320b37", - "semantic_hash": "0b71c2e7d39daa010985d529f1320b37" - }, - ".husky/_/post-checkout": { - "mtime": 1782683991.0, - "ast_hash": "0b71c2e7d39daa010985d529f1320b37", - "semantic_hash": "0b71c2e7d39daa010985d529f1320b37" - }, - ".husky/_/post-commit": { - "mtime": 1782683991.0, - "ast_hash": "0b71c2e7d39daa010985d529f1320b37", - "semantic_hash": "0b71c2e7d39daa010985d529f1320b37" - }, - ".husky/_/post-merge": { - "mtime": 1782683991.0, - "ast_hash": "0b71c2e7d39daa010985d529f1320b37", - "semantic_hash": "0b71c2e7d39daa010985d529f1320b37" - }, - ".husky/_/post-rewrite": { - "mtime": 1782683991.0, - "ast_hash": "0b71c2e7d39daa010985d529f1320b37", - "semantic_hash": "0b71c2e7d39daa010985d529f1320b37" - }, - ".husky/_/pre-applypatch": { - "mtime": 1782683991.0, - "ast_hash": "0b71c2e7d39daa010985d529f1320b37", - "semantic_hash": "0b71c2e7d39daa010985d529f1320b37" - }, - ".husky/_/pre-auto-gc": { - "mtime": 1782683991.0, - "ast_hash": "0b71c2e7d39daa010985d529f1320b37", - "semantic_hash": "0b71c2e7d39daa010985d529f1320b37" - }, - ".husky/_/pre-commit": { - "mtime": 1782683991.0, - "ast_hash": "0b71c2e7d39daa010985d529f1320b37", - "semantic_hash": "0b71c2e7d39daa010985d529f1320b37" - }, - ".husky/_/pre-merge-commit": { - "mtime": 1782683991.0, - "ast_hash": "0b71c2e7d39daa010985d529f1320b37", - "semantic_hash": "0b71c2e7d39daa010985d529f1320b37" - }, - ".husky/_/pre-push": { - "mtime": 1782683991.0, - "ast_hash": "0b71c2e7d39daa010985d529f1320b37", - "semantic_hash": "0b71c2e7d39daa010985d529f1320b37" - }, - ".husky/_/pre-rebase": { - "mtime": 1782683991.0, - "ast_hash": "0b71c2e7d39daa010985d529f1320b37", - "semantic_hash": "0b71c2e7d39daa010985d529f1320b37" - }, - ".husky/_/prepare-commit-msg": { - "mtime": 1782683991.0, - "ast_hash": "0b71c2e7d39daa010985d529f1320b37", - "semantic_hash": "0b71c2e7d39daa010985d529f1320b37" - }, - ".lintstagedrc.json": { - "mtime": 1782684003.0, - "ast_hash": "9434a817c08daa77c5782e0dc0b5b3c3", - "semantic_hash": "9434a817c08daa77c5782e0dc0b5b3c3" - }, - ".prettierrc.json": { - "mtime": 1782683750.0, - "ast_hash": "363b7847889cc7e26b1c0e2f253e699b", - "semantic_hash": "363b7847889cc7e26b1c0e2f253e699b" - }, - "eslint.config.js": { - "mtime": 1782683821.0, - "ast_hash": "301afd40a38f233353fe189f8aca721b", - "semantic_hash": "301afd40a38f233353fe189f8aca721b" - }, - "package.json": { - "mtime": 1782936536.0, - "ast_hash": "d7ea0692002851fc14d5e2e1b444cafd", - "semantic_hash": "d7ea0692002851fc14d5e2e1b444cafd" - }, - "src/App.tsx": { - "mtime": 1782936735.0, - "ast_hash": "32fab2919d80bff8a3f8e9c3e5d7b00f", - "semantic_hash": "32fab2919d80bff8a3f8e9c3e5d7b00f" - }, - "src/application/context/TenantContext.ts": { - "mtime": 1782684797.0, - "ast_hash": "38a21a0f1f75d94d22917e4a149fa9bc", - "semantic_hash": "38a21a0f1f75d94d22917e4a149fa9bc" - }, - "src/application/repositories/AuthRepository.ts": { - "mtime": 1782684807.0, - "ast_hash": "709542d8b8736115a79467a505aa7e45", - "semantic_hash": "709542d8b8736115a79467a505aa7e45" - }, - "src/application/test-helpers/createFakeAuthRepository.ts": { - "mtime": 1782685065.0, - "ast_hash": "327725193b57a4f1e97bb9a723675f44", - "semantic_hash": "327725193b57a4f1e97bb9a723675f44" - }, - "src/application/use-cases/auth/GetCurrentSession.test.ts": { - "mtime": 1782685000.0, - "ast_hash": "25ad6da8a5934497dd12ddfdcd5901be", - "semantic_hash": "25ad6da8a5934497dd12ddfdcd5901be" - }, - "src/application/use-cases/auth/GetCurrentSession.ts": { - "mtime": 1782684869.0, - "ast_hash": "ba2f4c2dfe93fac74907b3c88a27d14c", - "semantic_hash": "ba2f4c2dfe93fac74907b3c88a27d14c" - }, - "src/application/use-cases/auth/HandleAuthCallback.test.ts": { - "mtime": 1782685016.0, - "ast_hash": "ec22840721629cedbfbc7e6776edff8e", - "semantic_hash": "ec22840721629cedbfbc7e6776edff8e" - }, - "src/application/use-cases/auth/HandleAuthCallback.ts": { - "mtime": 1782684968.0, - "ast_hash": "d40496132ff3f53ab5f0baf9c26855dc", - "semantic_hash": "d40496132ff3f53ab5f0baf9c26855dc" - }, - "src/application/use-cases/auth/InitiateLogin.test.ts": { - "mtime": 1782685010.0, - "ast_hash": "3463fdb2cb88e0654251047433d31824", - "semantic_hash": "3463fdb2cb88e0654251047433d31824" - }, - "src/application/use-cases/auth/InitiateLogin.ts": { - "mtime": 1782684923.0, - "ast_hash": "9eaa37f7722a2451152bbdf483cca4ae", - "semantic_hash": "9eaa37f7722a2451152bbdf483cca4ae" - }, - "src/application/use-cases/auth/Logout.test.ts": { - "mtime": 1782685005.0, - "ast_hash": "665be12e0b6f95475dcdc1f06370f564", - "semantic_hash": "665be12e0b6f95475dcdc1f06370f564" - }, - "src/application/use-cases/auth/Logout.ts": { - "mtime": 1782684911.0, - "ast_hash": "7ee20e7a86894c7af0545ee83cfbb670", - "semantic_hash": "7ee20e7a86894c7af0545ee83cfbb670" - }, - "src/composition/container.ts": { - "mtime": 1782737003.0, - "ast_hash": "9b29efd199951ac4c997fafb8b46d704", - "semantic_hash": "9b29efd199951ac4c997fafb8b46d704" - }, - "src/domain/entities/Session.test.ts": { - "mtime": 1782684512.0, - "ast_hash": "6329c34fcc62f8d9542b5200b7cf853e", - "semantic_hash": "6329c34fcc62f8d9542b5200b7cf853e" - }, - "src/domain/entities/Session.ts": { - "mtime": 1782684577.0, - "ast_hash": "c412c201686637306ab160364008e125", - "semantic_hash": "c412c201686637306ab160364008e125" - }, - "src/domain/entities/User.test.ts": { - "mtime": 1782684475.0, - "ast_hash": "a2e3f5e05b94a73076d0dc6a856069ad", - "semantic_hash": "a2e3f5e05b94a73076d0dc6a856069ad" - }, - "src/domain/entities/User.ts": { - "mtime": 1782684591.0, - "ast_hash": "136e04154e92065cb3539720cc53f461", - "semantic_hash": "136e04154e92065cb3539720cc53f461" - }, - "src/domain/errors/DomainError.ts": { - "mtime": 1782684449.0, - "ast_hash": "1afdeacc111a03a46d5decbe52b5a6f3", - "semantic_hash": "1afdeacc111a03a46d5decbe52b5a6f3" - }, - "src/domain/errors/InvalidSessionError.ts": { - "mtime": 1782684532.0, - "ast_hash": "11cd2106547ebe98938e3aef60011d53", - "semantic_hash": "11cd2106547ebe98938e3aef60011d53" - }, - "src/domain/errors/InvalidTenantError.ts": { - "mtime": 1782684452.0, - "ast_hash": "954d86e0f1b1def0416b42384d452e69", - "semantic_hash": "954d86e0f1b1def0416b42384d452e69" - }, - "src/domain/errors/InvalidUserError.ts": { - "mtime": 1782684493.0, - "ast_hash": "389f1a1a33a534e7af13ecee77212334", - "semantic_hash": "389f1a1a33a534e7af13ecee77212334" - }, - "src/domain/value-objects/Tenant.test.ts": { - "mtime": 1782684429.0, - "ast_hash": "51a1f3127466f5de68104097d592d6da", - "semantic_hash": "51a1f3127466f5de68104097d592d6da" - }, - "src/domain/value-objects/Tenant.ts": { - "mtime": 1782684567.0, - "ast_hash": "e4d26174455e7cc4460d9400e45b8507", - "semantic_hash": "e4d26174455e7cc4460d9400e45b8507" - }, - "src/infrastructure/auth/OidcAuthRepository.test.ts": { - "mtime": 1782736787.0, - "ast_hash": "1f5e936f81991dafe0d7193711274a12", - "semantic_hash": "1f5e936f81991dafe0d7193711274a12" - }, - "src/infrastructure/auth/OidcAuthRepository.ts": { - "mtime": 1782736668.0, - "ast_hash": "3d616898373af056561410aceb5ce6b0", - "semantic_hash": "3d616898373af056561410aceb5ce6b0" - }, - "src/infrastructure/config/createUserManager.ts": { - "mtime": 1782736735.0, - "ast_hash": "85c927331593043472bedf4296317ec5", - "semantic_hash": "85c927331593043472bedf4296317ec5" - }, - "src/infrastructure/mappers/MissingTenantClaimError.ts": { - "mtime": 1782685297.0, - "ast_hash": "2657f219956e06ce780f845d11078101", - "semantic_hash": "2657f219956e06ce780f845d11078101" - }, - "src/infrastructure/mappers/oidcUserToSessionMapper.test.ts": { - "mtime": 1782685374.0, - "ast_hash": "6e7a8d023b1e29143425ec2426abc7af", - "semantic_hash": "6e7a8d023b1e29143425ec2426abc7af" - }, - "src/infrastructure/mappers/oidcUserToSessionMapper.ts": { - "mtime": 1782736787.0, - "ast_hash": "6391314a2341a98b5f701cb28fab5680", - "semantic_hash": "6391314a2341a98b5f701cb28fab5680" - }, - "src/main.tsx": { - "mtime": 1782936742.0, - "ast_hash": "ab56db6bac2f32acd77824254f460763", - "semantic_hash": "ab56db6bac2f32acd77824254f460763" - }, - "src/presentation/hooks/useAppContainer.test.tsx": { - "mtime": 1782771852.0, - "ast_hash": "65f3ec0228a54ffec92c8f3e080350eb", - "semantic_hash": "65f3ec0228a54ffec92c8f3e080350eb" - }, - "src/presentation/hooks/useAppContainer.ts": { - "mtime": 1782737049.0, - "ast_hash": "ad11e0685431a8dc1ac2ec9f69bbd4c4", - "semantic_hash": "ad11e0685431a8dc1ac2ec9f69bbd4c4" - }, - "src/presentation/hooks/useAsync.test.tsx": { - "mtime": 1782737092.0, - "ast_hash": "d3e56adebacb6aa9ae01c742aec8e3a9", - "semantic_hash": "d3e56adebacb6aa9ae01c742aec8e3a9" - }, - "src/presentation/hooks/useAsync.ts": { - "mtime": 1782771619.0, - "ast_hash": "94786f2c81a152ea13c199a5fc189db4", - "semantic_hash": "94786f2c81a152ea13c199a5fc189db4" - }, - "src/presentation/hooks/useAuth.test.tsx": { - "mtime": 1782771800.0, - "ast_hash": "53645781f381ecb4d683991b6c252fdb", - "semantic_hash": "53645781f381ecb4d683991b6c252fdb" - }, - "src/presentation/hooks/useAuth.ts": { - "mtime": 1782771788.0, - "ast_hash": "5c39f573e7fbbc3dd8134885c70c8650", - "semantic_hash": "5c39f573e7fbbc3dd8134885c70c8650" - }, - "src/presentation/layouts/AdminLayout.tsx": { - "mtime": 1782936601.0, - "ast_hash": "e3b131b45867d94590833d2aba13773c", - "semantic_hash": "e3b131b45867d94590833d2aba13773c" - }, - "src/presentation/pages/AppointmentsPage/AppointmentsPage.tsx": { - "mtime": 1782936578.0, - "ast_hash": "2c72c9c08527f1a5498f3303ae78dbdc", - "semantic_hash": "2c72c9c08527f1a5498f3303ae78dbdc" - }, - "src/presentation/pages/CallbackPage/CallbackPage.tsx": { - "mtime": 1782936760.0, - "ast_hash": "585f29dd7c77bb54620b521b1f3550f1", - "semantic_hash": "585f29dd7c77bb54620b521b1f3550f1" - }, - "src/presentation/pages/ClientsPage/ClientsPage.tsx": { - "mtime": 1782936578.0, - "ast_hash": "154fedeaaebc4be85549ccf6e28a2dc5", - "semantic_hash": "154fedeaaebc4be85549ccf6e28a2dc5" - }, - "src/presentation/pages/DashboardPage/DashboardPage.tsx": { - "mtime": 1782936578.0, - "ast_hash": "3c0a0ccb7d52fd7999f46f556eb4e835", - "semantic_hash": "3c0a0ccb7d52fd7999f46f556eb4e835" - }, - "src/presentation/pages/InboxPage/InboxPage.tsx": { - "mtime": 1782936578.0, - "ast_hash": "dfabdfcc3e25372ef98e9ec3c3bc78bc", - "semantic_hash": "dfabdfcc3e25372ef98e9ec3c3bc78bc" - }, - "src/presentation/pages/LoginPage/LoginPage.test.tsx": { - "mtime": 1782936919.0, - "ast_hash": "f62398f72e7866386df534a57430c6c8", - "semantic_hash": "f62398f72e7866386df534a57430c6c8" - }, - "src/presentation/pages/LoginPage/LoginPage.tsx": { - "mtime": 1782936674.0, - "ast_hash": "710903e14f9b69f5dad6672130aaa42c", - "semantic_hash": "710903e14f9b69f5dad6672130aaa42c" - }, - "src/presentation/pages/ServicesPage/ServicesPage.tsx": { - "mtime": 1782936578.0, - "ast_hash": "c7f6582a2a76f9b5c5ce958f917d0d19", - "semantic_hash": "c7f6582a2a76f9b5c5ce958f917d0d19" - }, - "src/presentation/pages/SettingsPage/SettingsPage.tsx": { - "mtime": 1782936578.0, - "ast_hash": "2e003f9cd5c5a440c4d4040df702bf5d", - "semantic_hash": "2e003f9cd5c5a440c4d4040df702bf5d" - }, - "src/presentation/providers/AppContainerContext.ts": { - "mtime": 1782737012.0, - "ast_hash": "80d2d5e3ba183007cd8df4cd235c620c", - "semantic_hash": "80d2d5e3ba183007cd8df4cd235c620c" - }, - "src/presentation/providers/AppProviders.tsx": { - "mtime": 1782737065.0, - "ast_hash": "1dbcb29fc35f0f2ad1ae9f842256d63b", - "semantic_hash": "1dbcb29fc35f0f2ad1ae9f842256d63b" - }, - "src/presentation/routes/ProtectedRoute.test.tsx": { - "mtime": 1782936925.0, - "ast_hash": "31ec0cde42913575c21fc13bffd22375", - "semantic_hash": "31ec0cde42913575c21fc13bffd22375" - }, - "src/presentation/routes/ProtectedRoute.tsx": { - "mtime": 1782936635.0, - "ast_hash": "c049694da1844b5f599b5426941d9599", - "semantic_hash": "c049694da1844b5f599b5426941d9599" - }, - "src/presentation/routes/router.tsx": { - "mtime": 1782936731.0, - "ast_hash": "2557c48a934ddecd19a8e755130ab222", - "semantic_hash": "2557c48a934ddecd19a8e755130ab222" - }, - "src/test/mocks/handlers/index.ts": { - "mtime": 1782683914.0, - "ast_hash": "0e106aec453cee0366c338ba34214289", - "semantic_hash": "0e106aec453cee0366c338ba34214289" - }, - "src/test/mocks/server.ts": { - "mtime": 1782683918.0, - "ast_hash": "c56f70fb3f61bdf5e1b441a577a4f69b", - "semantic_hash": "c56f70fb3f61bdf5e1b441a577a4f69b" - }, - "src/test/setup.ts": { - "mtime": 1782684263.0, - "ast_hash": "1ffcdb9e7370cf1067ec61f3f6c5d15a", - "semantic_hash": "1ffcdb9e7370cf1067ec61f3f6c5d15a" - }, - "src/vite-env.d.ts": { - "mtime": 1782685231.0, - "ast_hash": "b4fdbcc16b8b6436b4cad4675a595b83", - "semantic_hash": "b4fdbcc16b8b6436b4cad4675a595b83" - }, - "tsconfig.app.json": { - "mtime": 1782683697.0, - "ast_hash": "830f432578bc44f51b6a334a929e44af", - "semantic_hash": "830f432578bc44f51b6a334a929e44af" - }, - "tsconfig.json": { - "mtime": 1782683822.0, - "ast_hash": "cebcd0aef9a53b9c724ac53266f03437", - "semantic_hash": "cebcd0aef9a53b9c724ac53266f03437" - }, - "tsconfig.node.json": { - "mtime": 1782683962.0, - "ast_hash": "a4867d26540f102cb63042afd6f19e6d", - "semantic_hash": "a4867d26540f102cb63042afd6f19e6d" - }, - "vite.config.ts": { - "mtime": 1782936545.0, - "ast_hash": "5b641ab5f85eff4b0b9acec3417ca9b5", - "semantic_hash": "5b641ab5f85eff4b0b9acec3417ca9b5" - }, - "vitest.config.ts": { - "mtime": 1782683875.0, - "ast_hash": "f50a4c1d5e75a5a1d094f040ce68ab06", - "semantic_hash": "f50a4c1d5e75a5a1d094f040ce68ab06" - }, - ".skills/admin-api-contract/SKILL.md": { - "mtime": 1783167398.0, - "ast_hash": "d262f50a1c4745fd551067c7775bfff0", - "semantic_hash": "d262f50a1c4745fd551067c7775bfff0" - }, - ".skills/admin-feature-vertical/SKILL.md": { - "mtime": 1783167398.0, - "ast_hash": "b7d31f1f0a5554760b7e87c8a5ece20d", - "semantic_hash": "b7d31f1f0a5554760b7e87c8a5ece20d" - }, - ".skills/admin-tdd-conventions/SKILL.md": { - "mtime": 1783167399.0, - "ast_hash": "733adc1d21d47e47c6fcfc14920bd7e1", - "semantic_hash": "733adc1d21d47e47c6fcfc14920bd7e1" - }, - "CLAUDE.md": { - "mtime": 1783167796.0, - "ast_hash": "189775c44c16c9e2d29a03ec8ff15d40", - "semantic_hash": "189775c44c16c9e2d29a03ec8ff15d40" - }, - "README.md": { - "mtime": 1782683576.0, - "ast_hash": "10495bfcc6c568eca4dab6dd5df795d3", - "semantic_hash": "10495bfcc6c568eca4dab6dd5df795d3" - }, - "docs/API.md": { - "mtime": 1783167796.0, - "ast_hash": "e160db90d1447b3f2c1ef64c25b348bc", - "semantic_hash": "e160db90d1447b3f2c1ef64c25b348bc" - }, - "docs/DECISIONS.md": { - "mtime": 1783167796.0, - "ast_hash": "8bc03156694f0c415dc76cc2b5431563", - "semantic_hash": "8bc03156694f0c415dc76cc2b5431563" - }, - "docs/DOMAIN.md": { - "mtime": 1783167796.0, - "ast_hash": "fcc210fc5c8f845a5741d10e09b2f640", - "semantic_hash": "fcc210fc5c8f845a5741d10e09b2f640" - }, - "docs/STATUS.md": { - "mtime": 1783167796.0, - "ast_hash": "44ffe5d79e961a28cee79c6f35f3a758", - "semantic_hash": "44ffe5d79e961a28cee79c6f35f3a758" - }, - "docs/adr/001-clean-architecture-layers.md": { - "mtime": 1783167796.0, - "ast_hash": "57dddb8bfb50f61d9291cf370e2bf4dc", - "semantic_hash": "57dddb8bfb50f61d9291cf370e2bf4dc" - }, - "docs/adr/002-no-server-state-library.md": { - "mtime": 1783167796.0, - "ast_hash": "f6a66e0c88f6a9b85957ff552c094265", - "semantic_hash": "f6a66e0c88f6a9b85957ff552c094265" - }, - "docs/adr/003-manual-di-no-container-library.md": { - "mtime": 1783167796.0, - "ast_hash": "2f32f48a584fad8b1b43329cde9ab7a3", - "semantic_hash": "2f32f48a584fad8b1b43329cde9ab7a3" - }, - "docs/adr/004-explicit-silent-renewal.md": { - "mtime": 1783167796.0, - "ast_hash": "b977779ace9bafb119919a675cef17f0", - "semantic_hash": "b977779ace9bafb119919a675cef17f0" - }, - "index.html": { - "mtime": 1782683576.0, - "ast_hash": "e55a171b95e40c405f453a2017a16afe", - "semantic_hash": "e55a171b95e40c405f453a2017a16afe" - }, - "public/favicon.svg": { - "mtime": 1782683576.0, - "ast_hash": "7e840862161341271697daa99a40d76b", - "semantic_hash": "7e840862161341271697daa99a40d76b" - }, - "public/icons.svg": { - "mtime": 1782683576.0, - "ast_hash": "3b4fcfcf393eca4d264dca4a4663bc37", - "semantic_hash": "3b4fcfcf393eca4d264dca4a4663bc37" - } -} \ No newline at end of file diff --git a/backend/.skills/backend-new-microservice/SKILL.md b/backend/.skills/backend-new-microservice/SKILL.md deleted file mode 100644 index 34bd894..0000000 --- a/backend/.skills/backend-new-microservice/SKILL.md +++ /dev/null @@ -1,376 +0,0 @@ ---- -name: backend-new-microservice -description: > - Use this skill when creating a brand-new backend service under - backend/services/. Trigger on "new service", "new microservice", or - when a feature clearly belongs to a business context no existing - service owns. Encodes the project layout, solution wiring, shared - Postgres schema convention, auth wiring, Aspire, and CI expectations. ---- - -# New Backend Microservice - -## First: does this need a new service? - -Services here are **context-aggregated** (docs/adr/0001): one service per -explicit business context, and a service may own several related -capabilities. If the new capability belongs to a context an existing -service already owns, add a use case there instead (see -`backend-use-case` skill). Create a new service only for a genuinely new -context (e.g. notifications/email, billing). - -## Steps - -1. **Copy the five-project layout** of `services/services-service/` - (Domain, Application, Infrastructure, Api, Tests) with your service's - name. Both `services-service` and - `identity-service` are real, fully-built services — mirror either's - patterns for the _content_ of each project (rich domain entities, - use cases, EF Core repositories, thin controllers). - -2. **Add to the solution**: - `dotnet sln backend/AdminBackend.slnx add ` - -3. **Wire references** (Domain: none — not even `Admin.SharedKernel` - (backend/CLAUDE.md's zero-reference rule); Application → Domain + - `shared/Admin.SharedKernel` (CQRS/Result, docs/adr/0005) + - `FluentValidation.DependencyInjectionExtensions`; Infrastructure → - Application + `shared/Admin.Identity.Client` (ICurrentUserAccessor for - the audit interceptor) + `shared/Admin.SharedKernel.EntityFrameworkCore` - (`RepositoryBase`, docs/adr/0006); Api → Application + - Infrastructure + `shared/Admin.Identity.Client` + `ServiceDefaults` + - `Asp.Versioning.Mvc`; Tests → Application + Domain, plus - `coverlet.msbuild`, `AwesomeAssertions`, `NSubstitute`, `xunit`, - `Microsoft.NET.Test.Sdk`, `xunit.runner.visualstudio` (global - `` + `` - too) — copy the ItemGroup from an existing Tests csproj; the 80% - coverage gate from `backend/Directory.Build.props`/`.targets` applies - to any `*.Tests` project automatically, with `Admin.SharedKernel` - already excluded). There is no `.IntegrationTests` project — - CI runs unit tests only, no database, no Docker (docs/adr/0015). - Verify endpoints/persistence/auth manually instead (`dotnet run` + a - real HTTP client) before merging. - -4. **Auth**: in `Program.cs`, call - `AddIdentityServiceAuthentication(builder.Configuration, "")` - from `Admin.Identity.Client`; register the audience as a scope in - identity-service's `Program.cs` + `DatabaseSeeder`. Also add - `options.Filters.Add()` to `AddControllers` so - every tenant-scoped resource controller is protected by default - (docs/adr/0006) — mark any genuinely tenant-free action - `[IgnoreTenant]`. Read tenant id via `ITenantAccessor.TenantId` (the - filter already validated it — no `TryGetTenantId`/`Forbid()` needed in - the action). - -5. **CQRS/Application wiring**: in `Program.cs`, call - `builder.Services.AddSharedKernel()` then your service's own - `AddXApplication()` extension (copy - `ServicesService.Application/DependencyInjection.cs` - assembly-scans - for command/query handlers and FluentValidation validators, so new - slices need no registration). See `backend-use-case` skill for how to - build the first vertical slice. - -6. **API versioning**: in `Program.cs`, add - `builder.Services.AddApiVersioning(options => { options.DefaultApiVersion -= new ApiVersion(1, 0); options.AssumeDefaultVersionWhenUnspecified = -true; options.ReportApiVersions = true; }).AddMvc();`. Every business - controller gets `[ApiVersion("1.0")]` + - `[Route("api/v{version:apiVersion}/...")]`. - -7. **Persistence**: one shared Postgres instance, one schema per service. - In `OnModelCreating`: `modelBuilder.HasDefaultSchema("")` - (pattern: `IdentityDataContext`). Connection string key: - `ConnectionStrings__Default` pointing at the shared `postgres` service. - Define your own `IUnitOfWork` shape in `Application/Abstractions/` - matching what this service's writes actually need (docs/adr/0005) — - don't assume either existing service's shape fits. Add - `{Service}.Domain/Common/BaseEntity.cs` (copy verbatim from either - existing service — audit fields + soft delete, docs/adr/0006) for - every aggregate root to inherit, and copy - `AuditableEntitySaveChangesInterceptor` into - `Infrastructure/Persistence/Interceptors/` (wired in step 4's - `AddWidgetServiceInfrastructure` above). If this service has - tenant-owned entities, also add `{Service}.Domain/Common/ITenantOwned.cs` - (copy verbatim), `ICurrentTenantProvider` in `Application/Abstractions/` - - its `Infrastructure/Security/CurrentTenantProvider.cs` implementation - (registered as scoped), give the `DbContext` an optional - `ICurrentTenantProvider?` constructor parameter (defaults to `null` so - `dotnet ef` design-time tooling still works) that it uses to capture - the current tenant id, and expose it as a **public `CurrentTenantId` - property** (`_currentTenantId ?? Guid.Empty`) — the query filter has to - read this property off the live `DbContext` instance at query time, it - must never be baked in as a snapshotted `Guid` value (EF Core caches - the compiled model per `DbContext` _type_, so a baked-in constant - would leak across every request regardless of the real caller — there - is no automated regression test for this, `{Service}.Tests` - deliberately has no EF Core dependency, docs/adr/0015; verify - manually with two different tenants' tokens against a running - instance). After - `ApplyConfigurationsFromAssembly` in `OnModelCreating`, call - `builder.ApplyAuditableConventions(this, typeof(BaseEntity), -typeof(ITenantOwned))` (`Admin.SharedKernel.EntityFrameworkCore`) — - applies the soft-delete filter/index to every `BaseEntity`, and the - tenant filter/index to every `ITenantOwned` entity, automatically. No - entity configuration or repository method needs an explicit tenant id - (docs/adr/0006). - -8. **No exceptions for expected outcomes** (docs/adr/0014): add - `{Service}.Domain/Common/DomainResult.cs` + `DomainError.cs` (copy - verbatim — mirrors `Admin.SharedKernel.Result`/`Error` but with zero - external dependencies) and `{Service}.Application/Abstractions/DomainErrorMapper.cs` - (`DomainError.ToApplicationError()` → `Error.Validation(code, message)`, - copy verbatim). Every entity factory/`Update` method returns - `DomainResult`/`DomainResult` instead of throwing — see - `backend-use-case` skill step 1. Register only - `builder.Services.AddExceptionHandler(); -builder.Services.AddProblemDetails();` in `Program.cs`, then - `app.UseExceptionHandler();` early in the pipeline (before - `MapControllers`) — there is no per-service `BusinessExceptionHandler` - to register; `Admin.SharedKernel.GenericExceptionHandler` (shared, no - per-service copy) is the only exception handler, reserved for - genuinely unexpected failures. Command handlers that construct/mutate - a domain entity check `domainResult.IsFailure` and map it — never - wrap it in try/catch — see `backend-use-case` skill step 3. - -9. **Observability**: `builder.AddServiceDefaults()` + - `app.MapDefaultEndpoints()` (health checks + OpenTelemetry come free). - -10. **Aspire**: add the project to `backend/AppHost/AppHost.cs`, give it a - stable local HTTP endpoint, reference its database connection and upstream - services, and use `WaitFor` to make startup dependencies explicit. Do not - add a Dockerfile or Compose service; Aspire is the single local - application orchestrator (docs/adr/0029). - -11. **CI**: nothing to do — `backend-ci.yml` builds/tests the whole - solution and the coverage gate applies automatically. The API-contract - job starts AppHost, so the new resource joins the runtime graph when its - API surface affects that smoke. - -12. **Docs**: add the service to `docs/MONOREPO.md`'s tree and note its - context in `docs/VISION.md`. - ---- - -## Copy-paste templates - -A fictional **WidgetService** — rename throughout for your real service. -Swap `WidgetService`/`Widgets`/`widget-service` for your service/context/ -kebab-case name. - -### Program.cs (services-service's exact shape — copy, then adjust) - -```csharp -using Admin.Identity.Client; -using Admin.SharedKernel; -using Asp.Versioning; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc.Authorization; -using WidgetService.Application; -using WidgetService.Infrastructure; - -var builder = WebApplication.CreateBuilder(args); - -builder.AddServiceDefaults(); - -builder.Services.AddControllers(options => -{ - // Fail-closed: auth required unless [AllowAnonymous]; a verified X-Tenant-Id header required unless [IgnoreTenant]. - options.Filters.Add(new AuthorizeFilter()); - options.Filters.Add(); -}); -builder.Services.AddOpenApi(); - -builder.Services.AddExceptionHandler(); -builder.Services.AddProblemDetails(); - -builder.Services - .AddApiVersioning(options => - { - options.DefaultApiVersion = new ApiVersion(1, 0); - options.AssumeDefaultVersionWhenUnspecified = true; - options.ReportApiVersions = true; - }) - .AddMvc(); - -builder.Services.AddIdentityServiceAuthentication(builder.Configuration, audience: "widget-service-api"); - -builder.Services.AddSharedKernel(); -builder.Services.AddWidgetServiceApplication(); -builder.Services.AddWidgetServiceInfrastructure(builder.Configuration); - -// Only if this service migrates its own schema on startup - copy ServicesService.Api/Setup/DatabaseMigrator.cs: -// builder.Services.AddHostedService(); - -var spaOrigin = builder.Configuration["Cors:SpaOrigin"] ?? "http://localhost:5173"; -builder.Services.AddCors(options => -{ - options.AddPolicy("spa", policy => policy - .WithOrigins(spaOrigin) - .AllowAnyHeader() - .AllowAnyMethod()); -}); - -var app = builder.Build(); - -app.UseExceptionHandler(); - -if (app.Environment.IsDevelopment()) -{ - app.MapOpenApi(); -} - -app.UseHttpsRedirection(); -app.UseCors("spa"); -app.UseAuthentication(); -app.UseAuthorization(); - -app.MapControllers(); -app.MapDefaultEndpoints(); - -app.Run(); -``` - -### Application/DependencyInjection.cs - -```csharp -using System.Reflection; -using Admin.SharedKernel; -using FluentValidation; -using Microsoft.Extensions.DependencyInjection; - -namespace WidgetService.Application; - -public static class DependencyInjection -{ - public static IServiceCollection AddWidgetServiceApplication(this IServiceCollection services) - { - var assembly = Assembly.GetExecutingAssembly(); - services.AddValidatorsFromAssembly(assembly); - services.AddHandlersFromAssembly(assembly); - return services; - } -} -``` - -### Infrastructure/DependencyInjection.cs (single-DbContext shape — copy services-service's) - -```csharp -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using WidgetService.Application.Abstractions; -using WidgetService.Infrastructure.Persistence; -using WidgetService.Infrastructure.Persistence.Interceptors; -using WidgetService.Infrastructure.Repositories; - -namespace WidgetService.Infrastructure; - -public static class DependencyInjection -{ - public static IServiceCollection AddWidgetServiceInfrastructure( - this IServiceCollection services, IConfiguration configuration) - { - var connectionString = configuration.GetConnectionString("Default") - ?? throw new InvalidOperationException("Missing 'ConnectionStrings:Default' configuration."); - - services.AddSingleton(TimeProvider.System); - services.AddScoped(); - - services.AddDbContext((serviceProvider, options) => - options - .UseNpgsql(connectionString) - .AddInterceptors(serviceProvider.GetRequiredService())); - - services.AddScoped(); - services.AddScoped(); // shape to this service's need - see backend-use-case skill - - return services; - } -} -``` - -`WidgetRepository` extends `Admin.SharedKernel.EntityFrameworkCore.RepositoryBase` -(docs/adr/0006) — see `backend-use-case` skill step 5. - -`ICurrentUserAccessor` (needed by the interceptor above) is registered -by `AddIdentityServiceAuthentication` already if this service is a -JwtBearer resource server (step 4) — nothing extra to do. If this -service validates tokens a different way (like identity-service, the -OIDC provider itself), register `services.AddHttpContextAccessor(); -services.AddScoped();` -directly. - -### csproj ItemGroups (Application project — the part that differs from a plain class library) - -```xml - - - - - - - - -``` - -### csproj ItemGroups (Infrastructure project) - -```xml - - - - - -``` - -### csproj ItemGroups (Api project) - -```xml - - - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - - - - - -``` - -### csproj ItemGroups (Tests project) - -```xml - - - - - - - - - - - - - - - - - - - - -``` - -If a version pin above looks stale by the time you use it, `dotnet add -package ` without `--version` picks up the current latest — don't -hand-copy an outdated number just to match this file exactly. diff --git a/backend/.skills/backend-use-case/SKILL.md b/backend/.skills/backend-use-case/SKILL.md deleted file mode 100644 index f34a410..0000000 --- a/backend/.skills/backend-use-case/SKILL.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -name: backend-use-case -description: > - OBSOLETE — superseded by agent-skills/agenza-backend-use-case. Do not - use this file; read the canonical skill instead. ---- - -# Backend Use Case (obsolete — moved) - -This skill's content moved to -[`agent-skills/agenza-backend-use-case/SKILL.md`](../../../agent-skills/agenza-backend-use-case/SKILL.md) -(distributed to `.claude/skills/agenza-backend-use-case/` and -`.agents/skills/agenza-backend-use-case/` by -`scripts/sync_agent_skills.py`) as part of the cross-tool governance -migration (see `docs/AGENT-GOVERNANCE.md`). - -The migration also fixed a real drift in this file's old copy-paste -templates: they still showed `MustAsync` validator rules taking a -repository dependency (the docs/adr/0010 shape), which docs/adr/0012 -reverted — validators in this codebase take no repository dependencies, -and existence/uniqueness checks live in the handler. The canonical skill's -templates now match the actual `Tags` feature code exactly. Read the -canonical skill, not this file. diff --git a/backend/AGENTS.md b/backend/AGENTS.md index 37ce767..79099fb 100644 --- a/backend/AGENTS.md +++ b/backend/AGENTS.md @@ -1,424 +1,103 @@ -# Backend (.NET microservices) — Agent Instructions +# Backend — agent instructions -Read [../AGENTS.md](../AGENTS.md) first for repo-wide rules (question -policy, tenant scoping, exception policy, rule persistence). This file -covers what's specific to `backend/`. +Read [../AGENTS.md](../AGENTS.md) first. This file contains durable rules for +`backend/`; current package versions and project membership come from the +solution, project files, and `Directory.Packages.props`. Decision history is +routed through [../docs/adr/README.md](../docs/adr/README.md). -## What this is +## Read by task -A small set of **context-aggregated services** (see docs/adr/0001): each -service owns one explicit business context end-to-end. Not nano-services — -a service may aggregate several related capabilities (identity-service -owns authentication AND tenant provisioning; services-service owns the -business's offerings, starting with Tags). Both `identity-service` and -`services-service` are real, fully-built services — copy either's -patterns for a new service. +| Task | Read | +| --- | --- | +| Command, query, entity, repository, endpoint | `.agents/skills/agenza-backend-use-case` | +| Brand-new business-context service | `.agents/skills/agenza-backend-new-service` | +| Exception/error-flow audit | `.agents/skills/agenza-exception-flow-audit` | +| Tenant-isolation audit | `.agents/skills/agenza-tenant-isolation-review` | +| Migration or schema change | `.agents/skills/agenza-migration-safety` | +| API contract drift | `.agents/skills/agenza-api-contract-review` | +| CI and coverage behavior | `docs/QUALITY.md` | +| Rationale or superseded decisions | `docs/adr/README.md`, then only routed ADRs | -Each service is built as **CQRS + vertical slices inside Clean -Architecture layers, with a Result pattern instead of exceptions for -business errors** (see docs/adr/0005 for the full rationale, including -why MediatR/FluentAssertions specifically are NOT used here). +Inspect the live service and tests before relying on an example in prose. -## Read these before doing any work - -| Resource | When to read | -| --------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | -| `README.md` | Solution layout, commands | -| `agent-skills/agenza-backend-use-case` | Adding any command/query / business logic — canonical, portable | -| `.skills/backend-new-microservice/SKILL.md` | Creating a new service | -| `agent-skills/agenza-exception-flow-audit` | Auditing throw/try/catch/Exception usage | -| `agent-skills/agenza-tenant-isolation-review` | Auditing tenant scoping | -| `agent-skills/agenza-migration-safety` | Any EF Core migration or schema change | -| `../docs/QUALITY.md` | What CI gates, before pushing | -| `../docs/adr/0005-...md` | CQRS/vertical-slice/Result convention rationale | -| `../docs/adr/` | Cross-cutting decisions with rationale | -| `../docs/adr/0006-...md` | Tenant header/automatic scoping, BaseEntity/soft delete, GUID v7, generic repository, NSubstitute, business exceptions | -| `../docs/adr/0007-...md` | Controllers bind commands directly (no per-endpoint body record), Command→Domain mapping extension methods | -| `../docs/adr/0008-...md` | Automatic tenant assignment on save (AssignTenant + interceptor) | -| `../docs/adr/0009-...md` | TenantOwnedEntity base class (BaseEntity + ITenantOwned combined) | -| `../docs/adr/0012-...md` | Cross-aggregate checks live in handlers, not validators — validators take no repository dependencies | -| `../docs/adr/0014-...md` | Result pattern end-to-end — Domain/persistence no longer throw for expected outcomes | -| `../docs/adr/0015-...md` | Historical removal of the old broad/flaky integration suites | -| `../docs/adr/0017-...md` | Schema-scoped `__EFMigrationsHistory` per service — read before touching either service's migrations or `DependencyInjection.cs` | -| `../docs/adr/0018-...md` | `Admin.SharedKernel` vs `Admin.SharedKernel.AspNetCore` split — read before adding to either | -| `../docs/adr/0019-...md` | `ServicesService.PersistenceTests` — narrow EF InMemory coverage for tenant assignment/scoping, outside the *.Tests boundary and its coverage gate | -| `../docs/adr/0023-...md` | Historical bounded runtime-test decision, superseded by ADR 0026 | -| `../docs/adr/0024-...md` | Per-service database roles and composite tenant relationships | -| `../docs/adr/0025-...md` | Historical explicit/serialized bootstrap decision, narrowed by ADR 0027 | -| `../docs/adr/0026-...md` | Removal of the dedicated runtime-test project; retained runtime smokes and known gaps | -| `../docs/adr/0027-...md` | Single-instance demo bootstrap without a distributed advisory lock | -| `../docs/adr/0028-...md` | One-time EF migration baseline reset before the first deployment | -| `../docs/adr/0029-...md` | Aspire as the single local application orchestrator | - -## Critical constraints (non-negotiable) - -### Layering (enforced by project references — never add a reference that violates this) +## Architecture ```text -Domain zero project references, zero NuGet framework deps -Application → Domain, Admin.SharedKernel. Ports live in Abstractions/ -Infrastructure → Application, Admin.Identity.Client, Admin.SharedKernel.EntityFrameworkCore -Api → Application + Infrastructure + Admin.SharedKernel.AspNetCore. Controllers stay thin -Tests → Application + Domain (unit tests) -PersistenceTests → Infrastructure (narrow Docker-free EF tenant mechanisms only, docs/adr/0019) +Domain no project references or framework dependencies +Application -> Domain + Admin.SharedKernel; ports in Abstractions/ +Infrastructure -> Application + infrastructure-specific shared packages +Api -> Application + Infrastructure + Admin.SharedKernel.AspNetCore +Tests -> Domain/Application unit boundaries +PersistenceTests -> Infrastructure security behavior where needed ``` -`backend/shared/Admin.SharedKernel` is cross-cutting CQRS/Result -infrastructure (like `Admin.Identity.Client` is for auth) — every -service's Application layer references it. It is NOT a place for -business logic, and it takes no ASP.NET Core dependency (no -`FrameworkReference`, just `Microsoft.Extensions.DependencyInjection.Abstractions` + -FluentValidation) so that Application, which references it, stays -framework-agnostic. The MVC-specific half — `ResultExtensions.ToActionResult` -and `GenericExceptionHandler` — lives in the sibling -`backend/shared/Admin.SharedKernel.AspNetCore` instead (docs/adr/0018); -only `.Api` projects reference it. `backend/shared/Admin.SharedKernel.EntityFrameworkCore` -is a separate project (generic `RepositoryBase`, docs/adr/0006) -because it needs EF Core — Infrastructure-only, Application must never -see it. - -### Comments — minimal, by default zero - -Default to no comments, including XML `` docblocks (none of -these projects generate Swagger/API docs from them, so they're pure -comments, not tooling input). Trust identifiers and structure — a class, -method, or property name should carry its own meaning. Add a short -inline comment only for something a careful reviewer would still get -wrong without it: a security-relevant default (fail-closed auth), a -protocol/library quirk (OpenIddict claim remapping, a docker-network -issuer mismatch), or a non-obvious ordering/transaction constraint. One -line, not a paragraph — rationale for _why_ a pattern was chosen (CQRS -vs. MediatR, Result vs. exceptions, schema-per-service) belongs in -`docs/adr/`, not repeated in every file that uses the pattern. - -### Rich domain model — no anemic entities - -- Entities validate their invariants in a `private` constructor plus a - `public static DomainResult Create(...)` factory, and return - `DomainResult`/`DomainResult` on violation instead of throwing — see - `Tenant` in identity-service (name required) and `Tag`/`Category`/ - `Service`/`TagColor` in services-service (name/description length, - duration range/ordering, price, discount range, palette membership), - and `DomainResult`/`DomainError` (`{Service}.Domain/Common/`) — this is - still defense-in-depth on top of FluentValidation, not a replacement - for it (docs/adr/0012, docs/adr/0014): a handler that reaches Domain - with invalid data means a validator/domain mismatch bug, but per the - repo-wide no-exceptions-for-expected-outcomes directive (docs/adr/0014) - that bug now surfaces as a `DomainResult.Failure` mapped to - `Error.Validation` (400), not a thrown exception. `Update` methods - return plain `DomainResult` (no value to return); both `Create` and - `Update` validate every new value into a local before assigning/ - returning, so a later validation failure can never leave the entity - partially mutated. -- No public setters. `private set` + behavior methods that keep the - entity valid. A `private` parameterless constructor exists only for EF. -- New value concepts with rules (email, time range, money) become value - objects in Domain, not raw strings/decimals passed around. -- Business rules live in Domain/Application — never in controllers, - never in EF configurations. -- Every aggregate root inherits `BaseEntity` (`{Service}.Domain/Common/` - — one copy per service, Domain can't reference a shared assembly). - Gives `Id`, `CreatedAt`/`CreatedBy`, `UpdatedAt`/`UpdatedBy`, - `DeletedAt`/`DeletedBy`, `IsDeleted`, set only via `MarkCreated`/ - `MarkUpdated`/`MarkDeleted` — never public setters. Delete is a real - soft delete: each service's `AuditableEntitySaveChangesInterceptor` - turns a tracked `Deleted` entry into `Modified` and calls - `MarkDeleted`. The query filter that hides soft-deleted rows from - ordinary reads, and a supporting `DeletedAt` index, are applied - **automatically** to every `BaseEntity` type by - `Admin.SharedKernel.EntityFrameworkCore`'s `ApplyAuditableConventions` - (called once from each `DbContext.OnModelCreating`) — don't add - `HasQueryFilter` by hand in an `IEntityTypeConfiguration` (see - docs/adr/0006). -- New entity ids come from `Guid.CreateVersion7()` directly (UUID v7), - not `Guid.NewGuid()`. -- A domain `DomainError` (`Code` + `Message`) maps to - `Admin.SharedKernel.Error` via an explicit, tested per-service - `DomainErrorMapper.ToApplicationError()` extension - (`{Service}.Application/Abstractions/DomainErrorMapper.cs`) — always - `Error.Validation(code, message)` (400), never a raw - `Exception`/`ArgumentException` or an HTTP type leaking into Domain. - `Code` values are the same stable strings the old `BusinessException` - subtypes used (`"Tag.Invalid"`, `"Service.Invalid"`, etc.). -- A tenant-owned aggregate root inherits `TenantOwnedEntity` - (`{Service}.Domain/Common/TenantOwnedEntity.cs` — `BaseEntity` + - `ITenantOwned` combined, see `Tag`/`Service`) instead of - `BaseEntity` directly, and needs no `ITenantOwned`/`AssignTenant` - boilerplate of its own. Its constructor never takes a `tenantId` - parameter at all — `TenantId` starts `Guid.Empty` and only - `AssignTenant(Guid tenantId)` (inherited, not overridden) can set it. - Unlike entity validation, `AssignTenant` still **throws** a plain - `InvalidOperationException` on an empty guid (not a `DomainResult`) — - `TenantHeaderFilter` already rejects any request with a missing/ - mismatched tenant header with 403 before any handler/interceptor runs, - so this can only happen via an internal bug, never directly from a - request (docs/adr/0014); it is not a business outcome, so it is exempt - from the no-exceptions-for-expected-outcomes rule below. - `AuditableEntitySaveChangesInterceptor` calls `AssignTenant` - automatically for a newly added entity whose `TenantId` is still - `Guid.Empty` — mirrors `MarkCreated` exactly, just for a - security-relevant field instead of an audit one. A mapping extension - (`ToModel()`, see CQRS section below) is therefore parameterless too — - it never threads a tenant id through. - -### Tenant scoping (repo-wide non-negotiable) - -- Resource services validate JWTs via `shared/Admin.Identity.Client`'s - `AddIdentityServiceAuthentication(...)` — do not hand-roll JwtBearer. -- The client sends the tenant id in the `X-Tenant-Id` header on every - request (admin-frontend's `AuthenticatedHttpClient` attaches it - automatically). It is **never trusted on its own**: `Admin.Identity.Client`'s - `TenantHeaderFilter` (a global `IAsyncActionFilter`, wired into - `AddControllers(options => options.Filters.Add())`) - rejects the request with 403 before any action runs unless the header - equals the token's `tenant_id` claim. **Every action requires a - validated tenant by default** — opt out with `[IgnoreTenant]` (class or - method) for actions that genuinely aren't tenant-scoped (M2M - provisioning, OIDC protocol endpoints). Once the filter has run, read - `ITenantAccessor.TenantId` directly (the throwing property) — don't - repeat the check in the action. -- A tenant-owned entity inherits `TenantOwnedEntity`, which implements - `ITenantOwned` (`{Service}.Domain/Common/ITenantOwned.cs`, - `Guid TenantId { get; }` + `void AssignTenant(Guid tenantId)`) once for - every tenant-scoped aggregate in the service — don't implement the - interface directly on the entity. Its `DbContext` exposes a public - `CurrentTenantId` property (sourced from - `ICurrentTenantProvider`) and passes `this` + `typeof(ITenantOwned)` to - `ApplyAuditableConventions` — the query filter must read - `CurrentTenantId` off the live instance, never a value snapshotted at - model-build time (EF Core caches the compiled model per `DbContext` - _type_, so a baked-in constant would leak across every request — see - docs/adr/0006 for the incident this caught). Repository methods, - commands, and queries for that entity never take an explicit - `tenantId` parameter (see `ITagRepository`/`CreateTagCommand`). -- **New-entity tenant assignment is automatic, not handler code** - (docs/adr/0008): a mapping extension constructs with `Guid.Empty` - (`command.ToModel()`, no tenant parameter needed) and - `AuditableEntitySaveChangesInterceptor` calls `AssignTenant` on save, - sourcing the tenant from `ICurrentTenantProvider` itself — the - interceptor throws rather than persisting a tenant-less row if none is - available. A cross-tenant read/write is still a security bug, not a - code-style issue — the automatic filter and automatic assignment are - defense in depth on top of `TenantHeaderFilter`, not a replacement for - it. -- See docs/adr/0006 for why the header filter is wired into - services-service's `Program.cs` only, not identity-service's, and for - the automatic tenant-scoping mechanism in full. - -### Identity login feedback - -- The identity-service credential page classifies expected sign-in outcomes - into stable `AUTH_*` codes and actionable pt-BR text: invalid credentials, - temporary lockout, account not allowed, and two-factor required. Keep - invalid e-mail and invalid password under the same code to avoid account - enumeration. -- Every visible authentication failure states what happened, what the user - can do next, and which code plus attempt time to include when requesting - help. Never expose raw protocol/exception details, ask for a password, or - replace this with a generic “contacte o administrador” message - (docs/adr/0020). -- The credential page preserves visual continuity with the admin frontend: - accept only `light` or `dark` from the OIDC authorization request, apply - the theme before loading the stylesheet, expose an accessible toggle, and - persist the identity-origin preference. An explicit identity-page - preference wins on later visits; otherwise, use the valid frontend request - and then the operating-system theme. -- Every Razor form that performs a `POST` explicitly declares - `asp-antiforgery="true"`. Do not rely on implicit token generation: - adding a manual HTML `action` can bypass the Form Tag Helper and turn every - valid submission into a 400 response. The architecture guard enforces this - for every backend `.cshtml` file. - -### CQRS + vertical slices - -- One folder per feature under `Application//`, one subfolder - per operation: `Application/Tags/CreateTag/{CreateTagCommand, -CreateTagCommandHandler, CreateTagCommandValidator}.cs`. A DTO shared - by more than one operation in the feature sits at the feature root - (`Application/Tags/TagResponse.cs`). -- Commands mutate (`ICommand` when there's nothing to return, - `ICommand` otherwise); queries read (`IQuery`). - Each has exactly one handler (`ICommandHandler<...>` / - `IQueryHandler<...>`), returning `Result` / `Result` - (`Admin.SharedKernel`) — never throwing for an expected business - outcome. -- Controllers depend on `IDispatcher` (constructor-injected), - `await _dispatcher.Send(...)` / `.Query(...)`, and map the `Result` - with `result.ToActionResult(this, value => Ok(value))` (or `Created`/ - `NoContent`/etc.) — never a concrete handler type, never a try/catch - per exception type. -- **Bind the command/query itself as the action parameter — no - per-endpoint `...Body` record** (docs/adr/0007). `[ApiController]` - already infers `[FromBody]` for a complex-type parameter with no - explicit binding source; a route id binds into its own `Guid id` - parameter independently and gets merged in with a `with` expression - right before dispatching (`command with { TagId = id }`) since the - client's JSON body never carries it. See `TagsController` for the - pattern. -- **Command → Domain mapping lives in an extension method next to the - command**, not inlined in the handler (docs/adr/0007): - `{Operation}CommandExtensions.ToModel(...)` for construction, - `.ApplyTo(entity)` for mutation. `Handle(...)` calls it and reads as - orchestration only. See `CreateTagCommandExtensions`/ - `UpdateTagCommandExtensions`. -- Register nothing by hand: each service's `Application/DependencyInjection.cs` - (`AddXApplication()`) scans its own assembly for handlers and - FluentValidation validators. A new slice just needs its files created. - -### Result pattern — exceptions are not conventional control flow (docs/adr/0014) - -No layer uses exceptions for an _expected_ outcome — input validation, -domain invariants, not-found, conflict/duplicate, in-use, tenant -authorization. Every layer's failure signature is explicit in its return -type. Exceptions are reserved for genuinely unexpected/unrecoverable -failures: missing startup configuration, framework/programmer-error -guards, an unrecognized database error, transactional rollback cleanup. - -- **Domain** (entity constructors/methods, value object factories): - returns `DomainResult`/`DomainResult` (`{Service}.Domain/Common/`, - one pair per service — Domain has zero project references, so it can't - depend on `Admin.SharedKernel`'s `Result`), never throws for a - validation failure. `identity-service`'s `Tenant` and services-service's - `Tag`/`Category`/`Service`/`TagColor` all follow this. See "Rich domain - model" above for the `Create`/`Update` shape. -- **Application handlers have no `try/catch` for business flow.** A - handler calls `Entity.Create(...)`/`.Update(...)`, checks - `domainResult.IsFailure`, and maps the failure via - `DomainErrorMapper.ToApplicationError()` — no exception ever - propagates out of `Handle(...)` for an expected outcome. Cross-aggregate - rules that need a repository round-trip (uniqueness, existence, in-use) - live in the handler itself and return `Result.Failure` directly - (docs/adr/0012); role/scope checks return `Forbid()`. - `IUnitOfWork.SaveChangesAsync` (services-service) returns - `PersistenceResult` instead of throwing when a database - unique-constraint race loses to a concurrent request — the handler - checks `saveResult.IsFailure` and maps it via a per-entity - `{Entity}PersistenceErrorMapper` (feature root, e.g. - `Tags/TagPersistenceErrorMapper.cs`) to `Error.Conflict`, same outcome - as the pre-emptive `NameExistsAsync` check. Delete handlers check this - result too, even though nothing used to be caught there — discarding it - would silently swallow a real conflict and report success. - `identity-service`'s `IUnitOfWork.ExecuteInTransactionAsync` is already - `Result`-aware; its `try/catch` exists only for transactional rollback - on a genuinely unexpected failure, not to convert a business outcome — - see "UnitOfWork" below. -- **`Admin.SharedKernel.AspNetCore.GenericExceptionHandler`** (`IExceptionHandler`, - registered via `AddExceptionHandler()` + `app.UseExceptionHandler()` - in each `Program.cs`) is the _only_ global exception handler in either - service — it logs at Error level via `ILogger` and returns a generic - 500 Problem Details with no exception details in the body. There is no - `BusinessExceptionHandler` anymore: nothing throws a business exception - for it to catch. **Never reintroduce one** — see - `agent-skills/agenza-exception-flow-audit` and - `scripts/architecture_guard.py`, which both fail on - `BusinessExceptionHandler`/`DuplicateEntityException` reappearing. - -### FluentValidation - -- One `CommandValidator : AbstractValidator` per - command that takes user input, checking **only** cheap, synchronous shape - rules: required, length, format, numeric range, precision/scale - (`.PrecisionScale(...)`), enum/palette membership, and cross-field - comparisons within the same command (e.g. `min <= duration <= max`). - **Validators take no repository dependencies** (docs/adr/0012, - reverting docs/adr/0010) — a validator that needs a repository to do - its job is a sign the check belongs in the handler instead. This means - no `MustAsync`/`CustomAsync` rule ever calls a repository — if you see - one, it's the reverted pattern; delete it and move the check into the - handler (see `CreateTagCommandHandler`'s duplicate-name pre-check for - the current shape). -- Cross-aggregate rules that need a repository round-trip — existence - (Category/Tag/Service by id), uniqueness (duplicate name), in-use - (Category/Tag referenced by a Service) — live in the **handler**, as - plain `if (...) return Result.Failure(Error.NotFound(...)/Conflict(...))` - checks before any persistence. See `CreateCategoryCommandHandler`/ - `UpdateCategoryCommandHandler`/`DeleteCategoryCommandHandler` for the - simple case, and `CreateServiceCommandHandler`/`UpdateServiceCommandHandler` - plus `Application/Services/ServiceRelationshipLoader.cs` for a - multi-dependency case — the loader fetches Category/Tags exactly once and - the handler reuses the same instances for both construction and the - response, instead of fetching them again to build it. -- Runs automatically: the dispatcher resolves `IValidator` (if - one is registered) and validates before calling the handler. A - validation failure never reaches the handler. -- **Structured field errors, not one joined string:** `Dispatcher.ValidateAsync` - groups every FluentValidation failure by `PropertyName` into - `Error.FieldErrors` (`IReadOnlyDictionary>`, - each `FieldError` a `Code`+`Message` pair) instead of concatenating every - message into one string. `ResultExtensions.ToActionResult` renders that as - a structured `ProblemDetails` (`code` + a per-field `errors` map) so the - front-end can map an error to the exact field without parsing free text — - see docs/adr/0012. -- A validator with no `MustAsync`/`CustomAsync` rule at all can be tested - with the synchronous `Validate(...)` again — none of the six - Tag/Category/Service validators need `ValidateAsync` for this reason - anymore, though using it is still harmless. - -### UnitOfWork - -- Defined **per service** in `Application/Abstractions/IUnitOfWork.cs` — - not shared, because different services genuinely need different - shapes (see docs/adr/0005). Match the shape to what the service's - writes actually need: - - Only ever writing through one `DbContext`? A - `Task> SaveChangesAsync(CancellationToken)` is - enough (services-service) — `PersistenceResult`/`PersistenceError` - (`Application/Abstractions/`) let Infrastructure report a recognized - unique-constraint violation without throwing or referencing Npgsql - from Application (docs/adr/0014). Repositories only stage changes - (`Add`/`Remove`, no internal commit) — the handler commits explicitly - and checks the returned result. - - Writing through more than one abstraction that each commit on their - own (e.g. an EF repository AND `UserManager`)? Wrap both in an - explicit transaction: `ExecuteInTransactionAsync(Func<..., -Task>>, ...)`, Result-aware so a handler's - `Result.Failure` rolls back exactly like an exception would - (identity-service). - -### API versioning - -- Every business controller: `[ApiVersion("1.0")]` + - `[Route("api/v{version:apiVersion}/...")]` (or `internal/v{version:apiVersion}/...` - for M2M-only routes). `Asp.Versioning.Mvc`, wired via - `AddApiVersioning(...).AddMvc()` in `Program.cs`. -- OpenIddict's own protocol endpoints (`/connect/*`, - `/.well-known/...`) are **never** versioned — those paths are fixed - by the OIDC spec. - -### Tests - -- xUnit + **AwesomeAssertions** (`result.Should()....`) — not - FluentAssertions (v8+ requires a paid license; AwesomeAssertions is - the actively-maintained free fork, see docs/adr/0005). Global - `using AwesomeAssertions;` is set per test csproj. -- Unit tests (`.Tests`) target **handlers**, not controllers: - **NSubstitute** mocks for `Abstractions/` interfaces - (`Substitute.For()`, `.Returns(...)`, `.Received(n)`/ - `.DidNotReceive()` — not hand-written fakes, see docs/adr/0006), - asserting on the returned `Result` - (`result.IsSuccess`/`result.Error.Type`/`result.Value`). The 80% - line-coverage gate over Domain + Application is configured in - `Directory.Build.props`/`.targets` and applies automatically — - `Admin.SharedKernel` is excluded from every service's gate since it - has its own (`Admin.SharedKernel.Tests`). -- ADR 0015 still prevents restoring a broad, flaky endpoint suite. - `ServicesService.PersistenceTests` covers automatic tenant assignment - and query filtering with EF InMemory (docs/adr/0019). - There is no Testcontainers/`WebApplicationFactory` project. The Aspire - API-contract job applies migrations to a fresh PostgreSQL database and - exercises real OIDC authentication, scope denial, tenant fail-closed - behavior, and authorized provisioning (docs/adr/0026). -- New endpoint = a unit test per new handler/validator. Add a runtime - test tier only when concrete failure evidence justifies its maintenance - cost, and record the boundary and exit criteria in an ADR. - -## Both must pass before every commit +- Services are context-aggregated, not one microservice per entity. A feature + that fits an existing context stays in that service as vertical slices. +- Business slices live under `Application///`. Handlers and + validators are assembly-scanned; do not register each one manually or add + MediatR. +- Controllers bind commands/queries, dispatch, and map `Result` to HTTP. They do + not own business rules or persistence. +- `Admin.SharedKernel` is framework-agnostic CQRS/Result infrastructure. + ASP.NET Core and EF helpers stay in their dedicated sibling packages. + +## Domain and error flow + +- Aggregate roots inherit the service-local `BaseEntity`; tenant-owned roots + inherit `TenantOwnedEntity`. Audit and tenant fields have no public setters. +- Entities enforce permanent invariants through private construction and + behavior methods returning `DomainResult`. Validate all new values before + mutating state so a failure cannot leave a partial update. +- FluentValidation checks command shape only. Validators are synchronous and + never inject repositories or query the database. +- Existence, uniqueness, in-use, and other current-state checks belong in the + handler and return `Result.Failure`. +- A recognized database conflict becomes `PersistenceResult.Failure` at the + infrastructure boundary and is mapped explicitly by Application. +- Exceptions are reserved for unexpected technical failure, programming + violations, rollback/resource cleanup, or technical-exception-to-result + conversion at an infrastructure boundary. Expected outcomes never throw. + +## Tenant isolation and persistence + +- Resource APIs use `Admin.Identity.Client` and `TenantHeaderFilter`. The + `X-Tenant-Id` header is verified against the authenticated claim; client input + alone is never trusted. `[IgnoreTenant]` requires a genuinely tenant-free, + reviewed endpoint. +- Repositories do not accept arbitrary tenant ids. EF query filters read the + live `DbContext.CurrentTenantId`; do not capture a tenant constant while the + model is built and do not add hand-written tenant filters per entity. +- The save interceptor assigns the current tenant to new `ITenantOwned` + entities and fails closed if no valid tenant exists. Handlers do not assign + `TenantId`. +- Apply shared auditable conventions once in `OnModelCreating`. Uniqueness and + relationships involving tenant-owned data include the tenant boundary. +- Each service owns its schema, migrations history, database role, and writes. + Services never share tables or write another service's schema. +- A migration uses `.agents/skills/agenza-migration-safety`; never edit an + applied migration or silently destroy/transform existing data. + +## Testing and packages + +- Unit tests use xUnit, AwesomeAssertions, and NSubstitute, asserting returned + `Result` behavior at Domain/Application boundaries. +- Inspect the current `*PersistenceTests` projects before assessing tenant EF + coverage. Add or extend the narrow persistence tier when a change affects + assignment, filters, tenant indexes, or tenant-aware relationships. +- Do not restore broad Testcontainers/`WebApplicationFactory` suites without an + ADR supported by concrete failure evidence. Runtime OIDC/contract smokes + remain a separate CI boundary. +- Package versions are centralized in `backend/Directory.Packages.props`. + Project files use versionless `PackageReference` entries. +- Comments default to zero. Keep one only for a non-obvious security default, + concurrency/transaction constraint, provider quirk, or unavoidable + suppression. Rationale belongs in an ADR. + +## Required gates ```bash dotnet build backend/AdminBackend.slnx -dotnet test backend/AdminBackend.slnx # unit coverage + EF tenant persistence tier +dotnet test backend/AdminBackend.slnx ``` -Also run the repo-wide governance checks from [../AGENTS.md](../AGENTS.md) -(`scripts/architecture_guard.py` in particular scans this directory for the -reverted exception patterns above). +Also run the repo-wide governance commands from [../AGENTS.md](../AGENTS.md). diff --git a/backend/CLAUDE.md b/backend/CLAUDE.md index 36a9d91..43c994c 100644 --- a/backend/CLAUDE.md +++ b/backend/CLAUDE.md @@ -1,6 +1 @@ @AGENTS.md - -See the repo-root [CLAUDE.md](../CLAUDE.md) for Claude Code-specific -integration notes (skills, subagents, governance gates) — they apply here -unchanged; this file exists only so Claude Code loads `backend/AGENTS.md` -automatically while working under `backend/`. diff --git a/backend/README.md b/backend/README.md index 983e3ca..7c8eeb8 100644 --- a/backend/README.md +++ b/backend/README.md @@ -20,7 +20,7 @@ services// Both `identity-service` and `services-service` are real, fully-built services — mirror either's patterns for a new service's project structure. Conventions and how-to guides live in [CLAUDE.md](CLAUDE.md) -and [.skills/](.skills/). +and the canonical workflows under [../.agents/skills/](../.agents/skills/). There is also `shared/Admin.Identity.Client` — the JWT-validation + `ITenantAccessor` library every resource service references instead of diff --git a/docs/AGENT-GOVERNANCE.md b/docs/AGENT-GOVERNANCE.md index e5c1cdc..42b0b04 100644 --- a/docs/AGENT-GOVERNANCE.md +++ b/docs/AGENT-GOVERNANCE.md @@ -1,231 +1,69 @@ # AI agent governance -How this repository keeps every AI coding agent — Claude Code, OpenAI -Codex, or anything else that reads `AGENTS.md` — working from the same -architectural rules, the same skills, and the same completion criteria, -whether it's running locally or in CI, on Windows, Linux, or macOS. - -## Why this exists - -This codebase has already made, tried, and in a few cases reverted real -architectural decisions (see docs/adr/0010 → docs/adr/0012, and -docs/adr/0005/0006/0012 → docs/adr/0014). An agent that only reads a stale -skill or an out-of-date `CLAUDE.md` can reintroduce a bug that was already -found and fixed — this happened for real in this repo: `backend/.skills/backend-use-case/SKILL.md`'s -copy-paste templates kept teaching a `MustAsync`-validator-with-a-repository -pattern for over a cycle after docs/adr/0012 reverted it, because the -prose above the templates was updated and the templates weren't. This -framework exists to make that class of drift structurally harder, and to -make sure it isn't every tool's problem to solve separately. - -## Architecture +The repository keeps one portable instruction source per responsibility and +uses progressive disclosure so tools load task-specific workflows only when +needed. -``` -agent-skills/ <- the ONLY editable skill source - ├── sync (content-hash) → .agents/skills/ (OpenAI Codex) - └── sync (content-hash) → .claude/skills/ (Claude Code) +## Instruction stack -AGENTS.md (root, backend/, apps/admin-frontend/) <- canonical, durable rules - └── imported by CLAUDE.md (root, backend/, apps/admin-frontend/) via `@AGENTS.md` +```text +AGENTS.md durable repo-wide rules and routing +├── backend/AGENTS.md backend-only rules +├── apps/admin-frontend/AGENTS.md frontend-only rules +├── .agents/skills/* portable task workflows and references +├── docs/adr/README.md accepted/superseded decision routing +└── living docs, config, code current state and executable truth -guards (all three run the same checks, nothing is agent-specific) - ├── Claude Code Stop hook → scripts/claude_stop_guard.py - ├── Codex / any agent → the "Mandatory commands" section of AGENTS.md - └── CI → .github/workflows/agent-governance.yml +.agents/skills/ --sync--> .claude/skills/ ``` -### `AGENTS.md` is the canonical instruction source - -Root `AGENTS.md` holds only rules that are durable and apply everywhere: -the question policy, the repo-wide non-negotiables (tenant scoping, Clean -Architecture, no shared mutable state, the exception policy), testing/ -documentation/rule-persistence policy, mandatory commands, and completion -criteria. It deliberately stays short — area-specific detail lives in -`backend/AGENTS.md` and `apps/admin-frontend/AGENTS.md`, which are read -second, only by an agent actually working in that area. - -### `CLAUDE.md` imports it, never restates it - -Every `CLAUDE.md` in this repo (root, `backend/`, `apps/admin-frontend/`) -is a thin file: a `@AGENTS.md` import line plus, at the root only, Claude -Code-specific integration notes (which skills/subagents to prefer, which -governance commands to run before finishing a turn). None of them repeat -canonical rules — if you find yourself wanting to add a rule to a -`CLAUDE.md`, it almost certainly belongs in the matching `AGENTS.md` -instead, so Codex (and any future tool) gets it too. - -### `agent-skills/` is the single editable skill source - -Every skill lives once, at `agent-skills//SKILL.md`, with portable -frontmatter (`name` + `description` only — no `allowed-tools`, `context`, -`agent`, `hooks`, or model-specific fields). `scripts/sync_agent_skills.py` -copies it, verbatim, into `.agents/skills//` (where Codex looks) and -`.claude/skills//` (where Claude Code looks). **Never hand-edit -either distribution directory** — edit `agent-skills/`, then run the sync -script; a hand-edit in a distribution directory is exactly the "divergent -copy" `scripts/check_agent_governance.py` is designed to catch. - -Three skills predate this framework and still live outside `agent-skills/` -on purpose, because they don't duplicate one of the nine canonical -skills: `backend/.skills/backend-new-microservice/SKILL.md` and -`apps/admin-frontend/.skills/admin-api-contract`/`admin-tdd-conventions`. -They're referenced directly from `backend/AGENTS.md`/ -`apps/admin-frontend/AGENTS.md`. Two others — `backend/.skills/backend-use-case` -and `apps/admin-frontend/.skills/admin-feature-vertical` — were true -duplicates of canonical skills and are now redirect stubs pointing at -`agent-skills/agenza-backend-use-case` and `agent-skills/agenza-frontend-feature` -respectively. - -## The nine canonical skills - -| Skill | Purpose | -| --- | --- | -| `agenza-backend-use-case` | Build/change a .NET command, query, entity, or endpoint | -| `agenza-frontend-feature` | Build/change a React feature vertical | -| `agenza-exception-flow-audit` | Classify every throw/try/catch in `backend/` against docs/adr/0012/0014 | -| `agenza-architecture-review` | General architecture audit across the monorepo | -| `evolve-modular-architecture` | Make evidence-led module, topology, migration, ADR, and fitness-function decisions | -| `agenza-rule-persistence` | Turn a one-off correction into a durable, cross-file rule | -| `agenza-api-contract-review` | Audit backend/frontend contract drift | -| `agenza-tenant-isolation-review` | Audit multi-tenancy end to end | -| `agenza-migration-safety` | Audit/author an EF Core migration safely | - -## Guards and how they compose - -Three scripts, each usable standalone and by everything else: - -- **`scripts/sync_agent_skills.py`** — `--check` verifies `.agents/skills/` - and `.claude/skills/` are byte-identical (by content hash, not mtime) to - `agent-skills/`; without `--check` it makes them so. No symlinks (works - identically on Windows/Linux/macOS and in a CI checkout). -- **`scripts/check_agent_governance.py`** — verifies the governance - *meta-files* are internally consistent: `AGENTS.md`/`CLAUDE.md` present - and importing correctly, skill frontmatter valid and portable, skills - in sync, no `.codex/skills` distribution directory, every `docs/adr/NNNN` - and `scripts/*.py` reference in a governance file actually resolving, - every documented `npm run` command actually existing in - `apps/admin-frontend/package.json`. -- **`scripts/architecture_guard.py`** — scans actual application source - (backend C#, frontend TS/TSX) plus the fenced code blocks inside every - Markdown instruction/skill file for the specific reverted patterns this - repo has already hit once: `DuplicateEntityException`, - `BusinessExceptionHandler`, `ValidateAndThrow`, a repository dependency - or `MustAsync`/`CustomAsync` rule in a validator, a domain entity - throwing instead of returning `DomainResult`, `any` in frontend source, - a cross-feature-page import, and a coverage-exclude entry outside the - small documented allowlist. `--inventory` lists every finding - (including informational, non-blocking ones) without failing; the - default mode fails only on blocking findings. The allowlist for - exceptions to these checks is intentionally tiny and lives at the top of - the script itself, one entry per reviewed exception. - -Wired into three places, all running the same logic: - -- **Claude Code Stop hook** (`.claude/settings.json` → `scripts/claude_stop_guard.py`): - runs the three scripts above, in order, stopping at the first stage with - problems. Blocks the turn from ending (exit code 2) with the failures - fed back to Claude. Reads `stop_hook_active` from its stdin payload and - unconditionally allows the turn to end on any retry, so a governance - problem an agent can't resolve alone can never hang the session — - Claude Code's own ~8-attempt block cap is a second, independent safety - net on top of that. -- **Any other agent (Codex included)**: the "Mandatory commands" section - of `AGENTS.md` documents the same three commands directly — no - tool-specific hook mechanism needed, since Codex has none of its own to - wire this into. -- **CI** (`.github/workflows/agent-governance.yml`): runs the same three - commands on every PR/push, independent of whether any agent tool is - installed at all. This is the backstop that doesn't trust any agent's - local hook to have actually run. - -## How to create a new skill - -1. `mkdir agent-skills/` and write `agent-skills//SKILL.md` - with portable frontmatter (`name` matching the directory, a specific - `description` naming concrete trigger phrases). -2. Run `python scripts/sync_agent_skills.py` to distribute it. -3. Run `python scripts/check_agent_governance.py` to verify the - frontmatter and sync are both valid. -4. Reference it from the relevant `AGENTS.md`'s skill table and, if it - should be a first-class Claude Code reviewer, add a - `.claude/agents/*.md` subagent that points at it (see the four - existing ones for the pattern — they consult the shared skill, they - don't restate it). - -## How to update a rule - -Don't just fix the code. Follow `agenza-rule-persistence`'s cycle: fix the -code, update the right `AGENTS.md`, update the skill (prose *and* any -copy-paste template — see "Why this exists" above for what happens when -only the prose gets updated), add/update an ADR if it's a genuine -architectural decision, add a regression test, add or update a guard in -`scripts/architecture_guard.py` if the pattern is mechanically detectable, -and confirm it runs in CI. Then run -`python scripts/check_agent_governance.py` — it catches several of the -most common ways a "persisted" rule quietly isn't (skill out of sync, -dangling ADR reference, missing `@AGENTS.md` import). - -## How to test a skill - -There's no runtime to "execute" a skill against — validate it the way -`scripts/tests/` validates the governance scripts themselves: write a -regression test in `scripts/tests/` if the skill's rule is mechanically -checkable (add it to `architecture_guard.py` first, then test the guard), -and otherwise dry-run the skill against a realistic prompt and check the -output against the skill's own stated checklist/commit checklist. - -## How to fix divergences +Codex and GitHub Copilot consume `AGENTS.md` and `.agents/skills/`. Claude Code +loads the same `AGENTS.md` files through import-only `CLAUDE.md` files and uses +the synced `.claude/skills/` distribution. `.github/copilot-instructions.md` is +a thin compatibility bridge, not another rule source. -```bash -python scripts/sync_agent_skills.py --check # see exactly what's missing/divergent/extra -python scripts/sync_agent_skills.py # fix it -python scripts/check_agent_governance.py # confirm structural consistency -python scripts/architecture_guard.py --inventory # see every content-level finding, blocking or not -python scripts/architecture_guard.py # confirm no blocking finding remains -``` +## Ownership + +- `AGENTS.md`: durable constraints, routing, and completion gates. No versions, + test counts, feature inventories, or copied code. +- `.agents/skills/`: one portable workflow per task class. Put conditional + detail in directly linked `references/` and prefer live code over templates. +- `docs/STATUS.md`: current implementation progress for the owning app. +- ADRs: rationale and history; indexes identify superseded decisions before an + agent opens them. +- Code, config, generated contracts, and tests: executable truth. + +Do not create `agent-skills/`, `prompts/`, `.claude/agents/`, repo-local +`.skills/`, `.codex/skills/`, or standalone `.agent.md` instruction layers. +Machine-local state such as `.claude/settings.local.json` stays ignored. -## How to run the commands locally +## Distribution and guards -All four governance scripts are plain, dependency-free Python 3 (stdlib -only) and run identically on Windows, Linux, and macOS: +Edit `.agents/skills/`, then run: ```bash -python scripts/sync_agent_skills.py [--check] -python scripts/check_agent_governance.py -python scripts/architecture_guard.py [--inventory] -python scripts/claude_stop_guard.py # normally only invoked by the Stop hook itself +python scripts/sync_agent_skills.py +python scripts/sync_agent_skills.py --check ``` -Full stack gates remain as documented in the root/area `AGENTS.md` files -and `docs/QUALITY.md` — the governance scripts are a fast, deterministic -layer in addition to those, never a replacement for build/test/lint/ -coverage. - -## How to use the templates - -`prompts/agent-task-template.md` (generic) and the three specialized -variants (`backend-feature-template.md`, `frontend-feature-template.md`, -`architecture-review-template.md`) are plain Markdown with no tool-specific -syntax — copy one, fill in every section, and send it to whichever agent -you're using. They name skills in plain language (`Skills to use: -agenza-backend-use-case`) instead of a tool-specific invocation syntax, -since both Claude Code and Codex discover a skill from its description -once told to look for it. - -## Tool-specific limitations - -- **Claude Code**: discovers skills automatically from `.claude/skills/` - descriptions; the Stop hook only runs inside a Claude Code session - (`.claude/settings.json`). Subagents (`.claude/agents/`) are Claude Code - only — Codex has no equivalent concept, so their instructions must never - be the only place a rule lives (they only point at `agent-skills/` and - `AGENTS.md`). -- **OpenAI Codex**: reads `AGENTS.md` files and `.agents/skills/` - directly; it has no hook mechanism, so it relies entirely on the - "Mandatory commands" section of `AGENTS.md` being followed and on CI as - the backstop. Skill *discovery* by description-matching is Codex's own - behavior and not something this repo configures. -- **Both**: neither tool is required for `scripts/*.py` or CI to work — - every guard runs as plain Python, and - `.github/workflows/agent-governance.yml` has no dependency on either - tool being installed. +`scripts/check_agent_governance.py` validates instruction entry points, +portable skill frontmatter, the Copilot bridge, Claude imports, skill sync, +references, commands, and forbidden legacy layers. `scripts/architecture_guard.py` +checks mechanically recognizable application and documentation regressions. +The agent-governance GitHub Actions workflow runs both guards and their tests. + +## Changing a durable rule + +Use `.agents/skills/agenza-rule-persistence`: fix the concrete instance, +update the owning `AGENTS.md` and skill, amend or add an ADR when architectural, +add a regression test and guard where mechanical enforcement is possible, +confirm CI executes them, and remove obsolete teaching everywhere. + +## Adding a skill + +1. Confirm the workflow is reusable rather than a one-off prompt. +2. Add `.agents/skills//SKILL.md` with portable `name` and `description` + frontmatter only. +3. Keep the body concise and route conditional detail to one-level references. +4. Validate the skill, sync Claude's distribution, run governance tests, and + update routing only where another agent genuinely needs to discover it. diff --git a/docs/HARDENING_REPORT.md b/docs/HARDENING_REPORT.md deleted file mode 100644 index 3299010..0000000 --- a/docs/HARDENING_REPORT.md +++ /dev/null @@ -1,327 +0,0 @@ -# Hardening and architectural finalization — final report - -> **Historical, point-in-time report — not a living reference.** The -> `*.IntegrationTests` projects this report counts in its test totals -> (`IdentityService.IntegrationTests`, `ServicesService.IntegrationTests`) -> were later deleted outright by docs/adr/0015 (CI runs unit tests only, -> no Docker/Postgres) and do not exist in the current repo. A narrow -> exception, `ServicesService.PersistenceTests` (EF InMemory only, no -> Docker), was added later still by docs/adr/0019. For the current test -> layout and gates, see `docs/QUALITY.md` and `backend/AGENTS.md`, not -> this report. - -Date: 2026-07-21 -Scope: `backend/services/services-service` (Categories/Services/Tags verticals), `backend/services/identity-service`, `backend/shared/*`, `apps/admin-frontend`. - -This report documents a single hardening pass covering the 27-point mandate: dangling ADR references, structured API errors, tenant-isolation UI correctness, inline-creation correctness, duplicate-id validation, atomic domain updates, DB constraint mapping, normalized queries, migration data-safety, NuGet centralization, Vitest/ESLint hygiene, accessibility, OpenAPI codegen, and final validation. - ---- - -## 1. Executive summary - -All 21 substantive work items from the mandate are implemented, tested, and verified against the current repository state (not assumed). One item (§12, tenant-scoped composite FK constraints) required a business-rule decision that only the project owner could make; it was asked as a single batched question and answered — see §4. Everything else proceeded without further questions, per the mandate's own instruction to only ask when a decision could change business rules, contracts, data, auth, tenant isolation, or an incompatible DB change. - -Final state, verified in this session: - -- **Frontend**: `npm run format:check`, `npm run lint` (0 errors, 14 pre-existing warnings), `npm run build`, and `npm run test:coverage` (284/284 tests, 88.0% statement coverage) all pass. -- **Backend**: `dotnet build -c Release` (0 errors), `dotnet test -c Release` — 296/296 tests pass (179 ServicesService.Tests, 15 IdentityService.Tests, 25 Admin.SharedKernel.Tests, 8 IdentityService.IntegrationTests, 69 ServicesService.IntegrationTests), coverage 86–99% across Domain/Application projects, all above the 80% gate. -- One integration test is confirmed transiently flaky under concurrent Testcontainers load (not a regression) — see §19. -- Two known, disclosed limitations remain: the local Node version (22.18.0) is below the mandate's requested ≥22.22.1, and `packages/*` in the root workspace glob is left as-is (deliberate, per `docs/VISION.md`) — see §22. - ---- - -## 2. Confirmed baseline (verified against the repo before changing anything) - -Three read-only exploration passes (backend, frontend, docs/CI/deploy) verified every claim in the mandate against actual code before any change was made. Key confirmed findings: - -- ADR `0013` did not exist; every one of 14 source references to `docs/adr/0013` actually described the content of `docs/adr/0012` (revert of cross-aggregate checks to handlers/domain). -- `UnitOfWork.cs` converted *any* Postgres `23505` into one generic `DuplicateEntityException`, no constraint inspection — every handler reported `".DuplicateName"` unconditionally, even for a `Code` collision. -- `ServiceRelationshipLoader.LoadAsync` compared `tags.Count != tagIds.Count` — duplicate ids in the input made a fully-valid tag list look like a 404. -- `Service.Update`/`Tag.Update` mutated fields before validating later ones (non-atomic; a late validation failure left partial state). `Category.Update` was already atomic. -- `NameExistsAsync` in all three repositories used `x.Name.ToLower() == normalized` instead of the `NameNormalized` shadow column. -- Migration `20260721121859_AddCaseInsensitiveUniquenessAndCategoryLimits.cs` (dated the day of this session, unreleased) did a bare `AlterColumn` shrinking Category 100→60 / Service 100→80 with no pre-flight check. -- Both `DatabaseMigrator` (services-service) and `DatabaseSeeder` (identity-service) called `Database.MigrateAsync()` unconditionally on every host start, no config flag existed — `docs/MONOREPO.md` already tracked this as a known gap. -- No `Directory.Packages.props` existed; confirmed version drift across 5 test projects (`Microsoft.NET.Test.Sdk` 18.7.0 vs 17.14.1, `xunit.runner.visualstudio` 3.1.5 vs 3.1.4, `coverlet.msbuild` 10.0.1 vs 6.0.4). -- `code` was only emitted for validation errors (`Error.FieldErrors`); Conflict/NotFound/Forbidden results and `BusinessException`s never exposed a dedicated `code` field. -- `useAsync.ts` had a request-id guard for stale responses but never cleared `data` on a genuine reset; no tenant-switcher UI exists today (single tenant per session), so this was forward-looking hardening, not a fix for an observed production bug. -- `AuthenticatedHttpClient`'s local `ProblemDetails` interface only modeled `type/title/status/detail` — not the `code`/`errors` extensions the backend actually sends. -- No form called React Hook Form's `setError`; every mutation error funneled into one global `StatusMessage` string. -- `useCategories`/`useTags`/`useServices`' `createX` functions awaited the follow-up refetch as part of the same promise — a refetch failure threw out of the whole create call even though the POST had already succeeded. -- `serviceFormSchema.tagIds` had no duplicate check. -- `TextAreaField`'s character counter read `String(value ?? '').length`, but every real usage spread RHF's `register()`, whose return value never includes `value` — the counter rendered `0/N` forever in production usage. -- `StatusMessage` had two tones (`muted`/`error`) and no ARIA attributes. -- `CreatableSingleSelect`/`CreatableMultiSelect` had combobox/listbox/option ARIA roles but zero keyboard handlers. -- No bundle-size measurement or documentation existed anywhere in the repo — the mandate's assumed "438/78/102 KB" baseline does not correspond to anything on disk (see §21). -- Two concrete `act()`-warning sources: `AdminLayout.test.tsx` and `LoginPage.test.tsx`, each with one synchronous first test. - ---- - -## 3. Problems found beyond the mandate's own list - -- `serviceMapper.ts`'s `ServiceDto` type used a homomorphic mapped type (`{ [K in NumericServiceFields]: number }`) that ESLint's `@typescript-eslint/consistent-indexed-object-style` flags as an error once the file was exercised by the full lint run — found and fixed during final validation (see §23), converted to `Record`. -- The OpenAPI contract drift-check script (`scripts/checkGeneratedApiTypes.mjs`) used `execFileSync('npx', ...)` without `shell: true`, which fails on Windows (`ENOENT`) because `npx` resolves to a `.cmd` shim — found and fixed during this session's continuation (see §14). -- Both service Dockerfiles broke under Central Package Management because their layer-caching pattern copied `.csproj` files before `Directory.Packages.props`, and CPM requires the props file present for `dotnet restore` to resolve any version — found and fixed (see §17). -- The generated OpenAPI document initially had zero response-body schemas (ASP.NET Core's reflection-based generator can't infer response types from untyped `Task` signatures) — found and fixed by adding `[ProducesResponseType]` to every controller action (see §14). -- The end-to-end wiring from a structured API error to a highlighted form field (`setError`/`setFocus`) had zero test coverage at the page level before this continuation — found and fixed by adding 6 new page-level tests (see §6). - ---- - -## 4. Questions asked and answered - -One question was batched and asked, per the mandate's instruction to only interrupt for genuine business-rule decisions: - -> **§12 — tenant-scoped composite FK constraints.** Should `Service.CategoryId` and the `ServiceTags` join table get tenant-aware composite foreign keys (`(TenantId, CategoryId) → Categories(TenantId, Id)`), closing the theoretical gap where a row inserted outside the normal application flow could reference another tenant's Category/Tag? -> -> **Answer (user, verbatim):** "Ambas devem pertencer ao mesmo tenentId, não tem necessiade de adicionar mais uma constraint" — both records already belong to the same tenant by business rule; no additional constraint is needed. - -Applied as: no schema change, `Id`-only FKs kept exactly as they were, isolation reaffirmed as an application-layer guarantee (query filters + `ServiceRelationshipLoader`), documented in `docs/adr/0013-tenant-scoped-relationships-enforced-at-the-application-layer.md`, and proven end-to-end with new integration tests in `ServicesEndpointTests.cs` (cross-tenant `CategoryId`/`TagIds` return 404, not a leaked cross-tenant read). - -No other questions were needed — every other mandate item had a single, code-verifiable correct answer once the current state was actually read. - ---- - -## 5. Files changed - -122 files touched: 108 modified, 14 new. Full diff: `git diff --stat` / `git status --short` from the repo root. Highlights by area: - -- **Backend Domain/Application**: `Service.cs`, `Tag.cs` (atomic `Update`), `ServiceRelationshipLoader.cs`, `CreateServiceCommandValidator.cs`/`UpdateServiceCommandValidator.cs` (duplicate TagIds), `DuplicateEntityException.cs` (constraint name), 6 command handlers (constraint-aware duplicate mapping + `ILogger`). -- **Backend Infrastructure**: `UnitOfWork.cs`, `CategoryRepository.cs`/`ServiceRepository.cs`/`TagRepository.cs` (`NameNormalized` queries), the case-insensitive-uniqueness migration (data-safety guards), `DatabaseMigrator.cs`/`DatabaseSeeder.cs` (startup flag), both Dockerfiles (CPM fix). -- **Backend Api**: `ResultExtensions.cs`, both `BusinessExceptionHandler.cs` (structured `code`), 3 controllers (`[ProducesResponseType]`). -- **Backend shared**: new `backend/Directory.Packages.props`, `Version=` stripped from 16 `.csproj` files. -- **Backend tests**: unit tests for every handler/validator/domain change above; new `MigrationDataSafetyTests.cs`, `MigrationsRunOnStartupTests.cs`; expanded `CategoriesEndpointTests.cs`/`ServicesEndpointTests.cs`/`TagsEndpointTests.cs`. -- **Frontend infrastructure**: new `ProblemDetails.ts`, `ApiError.ts` (typed `details`), `AuthenticatedHttpClient.ts`, 3 mappers rewired to the generated OpenAPI types, new `src/infrastructure/generated/services-api.d.ts`. -- **Frontend forms/hooks**: new `serverFormError.ts`, `fieldMaps.ts`; `CategoryForm.tsx`/`TagForm.tsx`/`ServiceForm.tsx` (server-error wiring); `useAsync.ts` (`resetKey`, `mutate`); `useCategories.ts`/`useTags.ts`/`useServices.ts` (optimistic insert); `useCreateInline.ts`. -- **Frontend components**: `TextAreaField.tsx` (`currentLength` prop), `StatusMessage.tsx` (tones/ARIA), `CreatableSingleSelect.tsx`/`CreatableMultiSelect.tsx` (rebuilt on `cmdk`), new `command.tsx`/`input-group.tsx` (shadcn-generated). -- **Frontend tests**: 6 new page-level structured-error tests (this continuation), plus all tests listed in the prior summary for hooks/components/forms. -- **Docs/CI**: new `docs/adr/0013-...md`, `docs/MONOREPO.md` (migration flag), `apps/admin-frontend/docs/STATUS.md` (bundle-size baseline, this continuation), `apps/admin-frontend/docs/API.md` (error shape), `.github/workflows/frontend-ci.yml` (contract-drift jobs), this file. - ---- - -## 6. Structured-error implementation (§5, §10) - -**Backend**: `ResultExtensions.ToProblemResult` always sets `Extensions["code"]` — both the validation branch (`FieldErrors` → `errors` map) and the generic `Problem(...)` branch. `BusinessExceptionHandler` (both services) keeps `Title = Code` (unchanged, to avoid breaking existing assertions) and additionally sets `Extensions["code"]`. - -**Frontend**: `src/infrastructure/http/ProblemDetails.ts` defines `ProblemDetails`/`FieldError` and a safe runtime parser (`parseProblemDetails`, no `any`, no shape-guessing). `ApiError.details` is now typed `ProblemDetails | undefined`. `src/presentation/forms/serverFormError.ts`'s `mapApiErrorToForm` differentiates a validation `errors` map (per-field, via `fieldMap`) from a Conflict/NotFound/Forbidden `code` (via `codeFieldMap`, e.g. `Category.DuplicateName` → `name`) from an unmapped/unexpected error (global `StatusMessage`). Every form's submit handler applies this via `setError`/`setFocus` in a `useEffect`. - -**New end-to-end evidence (this continuation)**: `CategoriesPage.test.tsx`, `TagsPage.test.tsx`, and `ServicesPage.test.tsx` each got 2 new tests exercising the full page → `mapApiErrorToForm` → RHF `setError`/`setFocus` → rendered `role="alert"` chain — one for a validation `errors` map, one for a conflict `code`. All 6 pass (`npm run test --workspace=apps/admin-frontend`, 284/284). - ---- - -## 7. Tenant-isolation changes (§6) - -`useAsync(asyncFn, { resetKey })`: when `resetKey` changes between renders (compared via `useState`, not a ref read/write during render — the earlier ref-based draft tripped the `react-hooks/refs` lint rule and was replaced with React's own "derive from previous render" state pattern), `data`/`error` clear and `status` resets to `loading` synchronously, before the new fetch resolves. A same-tenant refetch (filter/page change, post-mutation refresh) does not go through this path. Wired via `resetKey: tenantContext?.tenant.id` in `useCategories`/`useTags`/`useServices`. - -Test (`useAsync.test.tsx`) follows the mandate's exact script: load tenant A, switch to tenant B mid-flight, assert zero tenant-A data visible, resolve the stale tenant-A promise late, assert it's ignored (the pre-existing request-id guard covers the ignore half; the new test proves the immediate-clear half). - -Since the app has no tenant-switcher UI today (single tenant per session, confirmed in `User.ts`), this is forward-looking hardening built generically, not a fix for an observed bug. - ---- - -## 8. Inline-creation changes (§7) - -`useCategories`/`useTags`/`useServices`'s `createX` functions no longer await the follow-up refetch as part of the create promise: `useCases.createX(...)` resolves, the result is inserted into local state immediately via `mutate()`, the caller's promise resolves right away, and `execute()` (the refetch) fires in the background — its failure surfaces as a non-blocking `StatusMessage` warning ("não foi possível atualizar a lista de..."), never as "creation failed." - -Bug caught by this fix: `CategoriesPage.test.tsx`'s pre-existing refetch-failure test mocked `createCategory` to return the *same* object already in the list, which combined with the optimistic insert produced a literal duplicate array entry (React duplicate-key warning). Fixed by using a distinct create-response object and asserting the new item is genuinely visible. - ---- - -## 9. Duplicate-ID validation (§8) - -- `ServiceRelationshipLoader.LoadAsync`: `tags.Count != tagIds.Count` → `tags.Count != tagIds.Distinct().Count()`. -- `CreateServiceCommandValidator`/`UpdateServiceCommandValidator`: new `RuleFor(c => c.TagIds).Must(ids => ids == null || ids.Distinct().Count() == ids.Count)`, message says "duplicado", not "não encontrado". -- `ServiceForm.schema.ts`: Zod `.refine` on `tagIds` mirrors the same duplicate check client-side. -- Test matrix (loader, validator, Zod) covers: none/one duplicate/one missing/duplicate+missing/empty/multiple valid distinct — backend in `ServiceRelationshipLoaderTests.cs` + `CreateServiceCommandValidatorTests.cs`/`UpdateServiceCommandValidatorTests.cs`, frontend in `ServiceForm.test.tsx`. - ---- - -## 10. Invariants and atomicity (§9) - -`Service.Update`/`Tag.Update` rewritten to validate every new value into locals first, then assign all fields in one block after every validation has succeeded — mirrors the constructor's own per-field validation order, just reordered so no partial mutation happens before the last check passes. `Category.Update` needed no change (already single-field, already atomic). - -Tests (`ServiceTests.cs`/`TagTests.cs`): `Update_WhenValidationFailsOnALaterField_LeavesEveryFieldUnchanged` — snapshots every field, forces a late-field failure (valid name, over-length description), asserts the entity is unchanged after the caught exception. - ---- - -## 11. Mapped constraints (§10 backend half) - -`DuplicateEntityException.ConstraintName` (captured from `PostgresException.ConstraintName` in `UnitOfWork.IsUniqueViolation`) lets every handler switch on the actual index name instead of assuming "duplicate name": - -| Constraint | Mapped to | -|---|---| -| `IX_Services_TenantId_NameNormalized` | `Service.DuplicateName` | -| `IX_Services_TenantId_Code` | `Service.DuplicateCode` | -| `IX_Categories_TenantId_NameNormalized` | `Category.DuplicateName` | -| `IX_Tags_TenantId_NameNormalized` | `Tag.DuplicateName` | -| unrecognized/null | generic safe `Error.Conflict`, raw constraint name logged via `ILogger`, never guessed | - -Tests per handler cover the known-constraint path, the `Service.DuplicateCode` path, and the unrecognized-constraint fallback. - ---- - -## 12. Normalized queries (§11) - -`CategoryRepository`/`ServiceRepository`/`TagRepository.NameExistsAsync` now query `EF.Property(x, "NameNormalized") == normalized` (the shadow column already populated on every write) instead of `x.Name.ToLower() == normalized`. Input is normalized with `.ToLowerInvariant()` before the query. Integration tests cover case differences, leading/trailing whitespace, cross-tenant isolation, soft-deleted rows, and self-exclusion on update. - ---- - -## 13. Migrations created/edited (§13) - -`20260721121859_AddCaseInsensitiveUniquenessAndCategoryLimits.cs` (dated this session, unreleased anywhere) — edited in place, not superseded: added a `migrationBuilder.Sql("DO $$ BEGIN IF EXISTS (...) THEN RAISE EXCEPTION ... END IF; END $$;")` guard before each `AlterColumn` (Category 100→60, Service 100→80), matching the house style already established in `20260720235529_AddCategoryForeignKeyToService.cs`. Fails loudly with a clear message if incompatible data exists; never truncates or deletes. `MigrationDataSafetyTests.cs` (new, Testcontainers-based) proves both the failure path (over-length existing data) and the success path. - -The 60/80 limits themselves were pre-approved and not reopened. - ---- - -## 14. Generated contracts — OpenAPI DTO-only codegen (§17) - -Scope decision (self-resolving from the existing architecture, not a new question): DTOs-only, not a full generated client — `AuthenticatedHttpClient` already owns OIDC auth injection, the `X-Tenant-Id` header, and error parsing; a generated client would collide with all three. - -- `npm run generate:api-types` runs `openapi-typescript` against the live services-service `/openapi/v1.json`, writing `src/infrastructure/generated/services-api.d.ts` (types only, header-commented as generated, added to `.prettierignore` and ESLint's `ignores` — never hand-edited, never linted against app-code rules). -- Backend fix required first: ASP.NET Core's reflection-based OpenAPI generator can't infer response types from untyped `Task` — added `[ProducesResponseType]` to every List/Create/Update/Delete action across `CategoriesController`/`ServicesController`/`TagsController`, verified by rebuilding and re-fetching the OpenAPI doc (response schemas present where they were previously empty `{"description": "OK"}`). -- `categoryMapper.ts`/`tagMapper.ts`/`serviceMapper.ts` now derive their DTO types from `components['schemas'][...]`; `ServiceDto`/`PagedServiceDto` narrow the generator's `number | string` union (a known ASP.NET Core OpenAPI quirk for value types, not a real runtime behavior) back to `number` via `Omit<...> & Record`. -- `npm run generate:api-types:check` (`scripts/checkGeneratedApiTypes.mjs`) regenerates into a temp file and diffs against the committed one, failing non-zero if they differ. **Fixed in this continuation**: the script's `execFileSync('npx', [...])` failed on Windows (`ENOENT`, `npx` resolves to a `.cmd` shim `execFileSync` can't invoke without a shell) — added `shell: process.platform === 'win32'`. Re-verified: `npm run generate:api-types:check --workspace=apps/admin-frontend` passes against the live services-service instance (`src/infrastructure/generated/services-api.d.ts matches the live OpenAPI contract.`). -- CI: `.github/workflows/frontend-ci.yml` gained `api-contract-changes` (path-filter detect) and `api-contract-check` jobs. -- `packages/*` in the root workspace glob left exactly as-is — confirmed as a deliberate, documented placeholder in `docs/VISION.md` (recreate `packages/shared-types` only once a second Node app exists), not an oversight. - ---- - -## 15. Accessibility changes (§14, §15, §16) - -- **`TextAreaField`**: added `currentLength?: number`, computed by callers via RHF's `useWatch({ control, name })` (re-renders only on that field's own changes) instead of deriving from a `value` prop that RHF's uncontrolled `register()` never populates. Wired into `TagForm`/`ServiceForm` for `description`. -- **`StatusMessage`**: tone union extended to `muted | error | success | warning | info | loading`; `error` → `role="alert"`, the rest → `aria-live="polite"`; uses existing semantic tokens, no new colors. -- **`CreatableSingleSelect`/`CreatableMultiSelect`**: rebuilt on shadcn's `Command` primitive (`cmdk`, new dependency — justified as the ecosystem-standard answer to ARIA-combobox-with-keyboard-nav, strictly less code/risk than hand-rolling roving tabindex + `aria-activedescendant`). Full keyboard support verified by tests: Arrow/Home/End/Enter/Escape/Tab, focus management, `aria-expanded`/`aria-controls`/`aria-activedescendant`, filtered-list and empty-state behavior, keyboard removal in the multi-select. - ---- - -## 16. Vitest fixes (§19) - -`AdminLayout.test.tsx` and `LoginPage.test.tsx`'s first test in each file rendered a tree whose `useAuth()`/`useAsync()` resolved a mocked promise on a later microtask the (synchronous) test body never awaited. Fixed by `await screen.findByText(...)`, matching the pattern every other test in the same files already used correctly. No timeout increases, no worker-count changes, no warnings hidden. - -A deeper bug surfaced and fixed during the `useAsync` tenant-switch test's own development (not a production bug): the test's inline `asyncFn` arrow wasn't memoized, so `execute`'s `useCallback([asyncFn])` changed identity every render, re-firing the mount effect — an infinite fetch loop that only manifested because the mock's `Promise.resolve(...)` resolved synchronously enough to mask it as a hang. Fixed by wrapping the test's `asyncFn` in `useCallback(..., [tenantId])`, exactly matching how production hooks are already memoized. - ---- - -## 17. Lint fixes (§20) - -Final state: `npm run lint --workspace=apps/admin-frontend` → **0 errors, 14 warnings**, all pre-existing: -- 9× `react-refresh/only-export-components` (router.tsx's route-table exports, button.tsx's `buttonVariants` export) — architectural, not a real bug; fixing would mean restructuring shadcn-generated files or the route table for no correctness gain. -- 5× `@typescript-eslint/explicit-function-return-type` in `CreatableMultiSelect.test.tsx`/`CreatableSingleSelect.test.tsx` — test helper functions, harmless. - -One error found and fixed during this continuation's final validation: `serviceMapper.ts`'s `ServiceDto` used a homomorphic mapped type flagged by `@typescript-eslint/consistent-indexed-object-style`; converted to `Record` (semantically identical, the rule's preferred form). `src/infrastructure/generated/services-api.d.ts` (12 errors, all `consistent-indexed-object-style` on the generator's own index-signature output) is now excluded from ESLint entirely via the top-level `ignores` array, alongside `dist`/`coverage` — consistent with "never hand-edit generated code." - -No architectural-boundary, hooks, or real-bug-catching rule was disabled anywhere. - ---- - -## 18. Dependencies added/removed - -**Added**: -- `cmdk@^1.1.1` (runtime) — Creatable-select accessibility rebuild, §15. -- `openapi-typescript@^7.13.0` (dev, `--legacy-peer-deps` — its peer range targets TS5, not yet updated for this project's TS7; it doesn't invoke the project's TS compiler API at runtime, confirmed safe) — OpenAPI codegen, §14. - -**Removed**: none. - -**Backend**: no NuGet packages added or removed — `Directory.Packages.props` only centralizes existing versions (picking the newer of each drifted pair: `Microsoft.NET.Test.Sdk` 18.7.0, `xunit.runner.visualstudio` 3.1.5, `coverlet.msbuild` 10.0.1, `xunit` 2.9.3 — already consistent). `Version=` attributes stripped from 16 `.csproj` files. - ---- - -## 19. Test/build/format/lint results — both stacks (final, post-change) - -**Frontend** (`apps/admin-frontend`, Node 22.18.0 — see §22): -``` -npm run format:check → All matched files use Prettier code style! -npm run lint → 0 errors, 14 warnings (all pre-existing, see §17) -npm run build → tsc -b && vite build — success, 0 errors -npm run test:coverage → 57/57 test files, 284/284 tests passed -``` - -**Backend** (`dotnet build backend/AdminBackend.slnx -c Release`): -``` -0 errors, 47 warnings (all pre-existing: NU1507 package-source mapping advisory, -NU1903 known vulnerability in transitive System.Security.Cryptography.Xml, -CS0618 obsolete Testcontainers PostgreSqlBuilder() constructor — none introduced -by this pass, none touch code this pass changed) -``` - -**Backend** (`dotnet test backend/AdminBackend.slnx -c Release --no-build`): -``` -IdentityService.Tests 15/15 passed -ServicesService.Tests 179/179 passed -Admin.SharedKernel.Tests 25/25 passed -IdentityService.IntegrationTests 8/8 passed -ServicesService.IntegrationTests 68/69 passed (1 transiently flaky — see below) -``` - -**Flaky test**: `MigrationDataSafetyTests.Migrating_with_a_service_name_over_the_new_80_char_limit_fails_loudly_instead_of_silently_altering_it` failed once in the full concurrent run with `Npgsql.NpgsqlException: Exception while reading from stream` (an SSL-handshake race under concurrent Testcontainers container startup — this test spins up its own Postgres container independent of the 3 already running via docker-compose). Re-ran in isolation (`dotnet test ... --filter "FullyQualifiedName~MigrationDataSafetyTests"`): **2/2 passed**. Confirmed transient infrastructure flakiness, not a logic bug — same failure mode was observed and documented earlier in this same session. - -Total: **296/296 backend tests pass** (69/69 counting the isolated re-run), **284/284 frontend tests pass**. - ---- - -## 20. Coverage (final) - -**Frontend** (`vitest run --coverage`, gate 80%): -``` -All files: 88.03% statements, 81.73% branches, 81.77% functions, 88.27% lines -``` -All above gate. Lowest-covered non-trivial files: `useServices.ts` (78.78% lines, mostly pagination edge branches), `ServicesPage.tsx` (77.1% lines, mostly filter-combination branches) — both well-tested on their primary paths, gaps are secondary branch combinations. - -**Backend** (coverlet, gate 80% on Domain+Application): -``` -IdentityService.Application 90.69% line / 100% branch / 87.5% method -IdentityService.Domain 86.48% line / 100% branch / 89.47% method -Admin.SharedKernel 95.07% line / 88% branch / 92.85% method -ServicesService.Application 98.75% line / 98.71% branch / 97.32% method -ServicesService.Domain 90.55% line / 86.36% branch / 91.52% method -``` -All above gate. `*.IntegrationTests` are exempt from the line-coverage gate per `docs/QUALITY.md`. - ---- - -## 21. Bundle size — before/after - -No bundle-size measurement or documentation existed anywhere in the repo prior to this session — see `apps/admin-frontend/docs/STATUS.md`'s new "Bundle size baseline" section for the full writeup. The mandate's assumed baseline (438/78/102 KB) does not correspond to any figure previously recorded in this repo; treat the numbers below as the first recorded baseline, not a confirmation. - -``` -index-*.js (main entry) 447.82 kB raw / 137.38 kB gzip -ServicesPage-*.js 93.51 kB raw / 29.58 kB gzip -table-*.js (shared table) 103.84 kB raw / 30.78 kB gzip -index-*.css 63.32 kB raw / 10.85 kB gzip -``` -Unchanged across every rebuild performed in this session (including after the `cmdk` dependency addition and the OpenAPI-generated-types rewiring), confirming no bundle regression was introduced by any change in this pass. No pathological duplication was found (no repeated Radix/shadcn tree across chunks), so no bundle-splitting work was undertaken against this baseline. - ---- - -## 22. Remaining risks and known limitations - -- **Node version**: this session ran Node 22.18.0, not the mandate's requested ≥22.22.1. An `nvm install 22.22.1` was attempted but never confirmed complete; work proceeded on 22.18.0. No Node-version-specific behavior was observed in any test/build/lint run, but this should be corrected before the next real CI/deploy run on a machine where it matters. -- **Testcontainers flakiness**: `MigrationDataSafetyTests` showed one transient SSL-handshake-race failure under concurrent container load (§19). This is an existing Testcontainers/Docker-Desktop-on-Windows characteristic, not something introduced or fixed by this pass — worth a retry policy in CI if it recurs there, but not addressed here since it wasn't asked for and doesn't indicate a code defect. -- **`packages/*` workspace glob**: still resolves to nothing (no `packages/` directory exists). Confirmed deliberate per `docs/VISION.md` — recreate `packages/shared-types` only once a second Node app exists. Not a defect. -- **ADR 0013 residual FK gap**: by design (§4/§7/§13) — a future write path to `Services`/`Categories`/`Tags`/`ServiceTags` that bypasses `ServicesDataContext` (bulk import, direct SQL, a second service) would reopen the cross-tenant question the ADR explicitly flags for revisit. -- **Migration-on-startup replica safety**: `Migrations:RunOnStartup` is now configurable (§13/docs/MONOREPO.md), but the *safe-under-multiple-replicas* execution mechanism (e.g. a dedicated migration job, a leader-election lock) is still deferred — there is no k8s/CD topology yet for it to attach to, matching what `docs/MONOREPO.md` already documented as a known, non-blocking gap. -- **Two NuGet advisories** (`NU1903` on `System.Security.Cryptography.Xml` 10.0.7, transitive via identity-service's integration test dependencies) are pre-existing and out of this pass's scope — flagged here for visibility, not fixed, since bumping a transitive dependency wasn't part of the mandate and risks an unreviewed behavior change. - ---- - -## 23. Work not done and why - -Everything in the mandate's 21 substantive items (§4–§21, excluding the already-answered §12 question) was implemented. Nothing was descoped. The only work added *beyond* the original mandate, done in this session's continuation because it was found missing during final validation rather than requested outright: - -- 6 new page-level tests proving the structured-error → form-field mapping works end-to-end through the real component tree (§6) — the mandate explicitly asked for "testes ponta a ponta para o mapeamento" and no test previously exercised this path above the `serverFormError.ts` unit level. -- The bundle-size documentation write-up itself (§21) — numbers were already captured earlier in the session but never written to `docs/STATUS.md` until this continuation. -- Fixing the Windows-specific bug in the drift-check script (§14) and the `consistent-indexed-object-style` lint error (§17) — both discovered only when actually running the final validation commands, exactly the kind of thing the mandate's "never claim something works without demonstrated evidence" instruction exists to catch. - ---- - -## 24. Evidence index - -Every claim above has a corresponding command run in this session: -- `npm run format:check / lint / build / test:coverage --workspace=apps/admin-frontend` -- `dotnet build backend/AdminBackend.slnx -c Release` -- `dotnet test backend/AdminBackend.slnx -c Release --no-build` -- `dotnet test .../ServicesService.IntegrationTests -c Release --no-build --filter "FullyQualifiedName~MigrationDataSafetyTests"` (isolated re-run) -- `npm run generate:api-types:check --workspace=apps/admin-frontend` -- `grep -rn "0013" ...` (confirmed only the new, legitimate ADR reference remains) -- `git status --short` / `git diff --stat` (122 files touched: 108 modified, 14 new) - -No claim in this report is asserted without one of the above having been executed and its output read in full during this session. diff --git a/docs/MONOREPO.md b/docs/MONOREPO.md index 4407a24..d88fdd6 100644 --- a/docs/MONOREPO.md +++ b/docs/MONOREPO.md @@ -3,9 +3,9 @@ ``` admin/ ├── apps/ -│ └── admin-frontend/ Vite + React 19 + TypeScript admin panel (see its own docs/) +│ └── admin-frontend/ Vite + React + TypeScript admin panel (see its own docs/) ├── backend/ -│ ├── AdminBackend.slnx .NET solution (dotnet 10 uses .slnx, not .sln) +│ ├── AdminBackend.slnx .NET solution │ ├── AppHost/ .NET Aspire orchestrator — local dev only, see below │ ├── ServiceDefaults/ shared OpenTelemetry/health-check/service-discovery wiring │ ├── shared/ @@ -41,7 +41,7 @@ The full stack has one local orchestration path: `agenza-postgres-data` volume. Docker is required only as Aspire's container runtime for PostgreSQL. Node, - Python 3.14, and `uv` must be installed; AppHost runs the npm and locked + Python, and `uv` must be installed; AppHost runs the npm and locked `uv sync` setup resources before starting Vite and Uvicorn. A single local-development password is shared by PostgreSQL, the restricted @@ -95,11 +95,10 @@ the OpenAPI/OIDC runtime smoke instead of maintaining a parallel Compose graph. ## Adding a new backend microservice -Follow `backend/.skills/backend-new-microservice/SKILL.md` — it covers the -full checklist (layout, solution wiring, auth via Admin.Identity.Client, -shared-Postgres schema convention, Aspire, CI, docs). The short -version: copy the five-project layout, mirror identity-service's patterns, -one schema per service in the shared Postgres. +Follow `.agents/skills/agenza-backend-new-service`. Create the five base projects +and add a PersistenceTests project whenever tenant-scoped EF behavior needs +security coverage. Use the live services, central package file, solution, and +AppHost as executable references; do not copy versioned project templates. ## Adding a new AI service @@ -121,8 +120,6 @@ GitHub Actions checks remain the integration gate for `origin/main`. See ## Known gaps (tracked, not blocking) -- `apps/admin-frontend/graphify-out/` is stale (generated before the restructure) — - regenerate rather than trust it. - Database bootstrap is opt-in through `DatabaseBootstrap:RunOnStartup` (base configuration is `false`; Development explicitly enables it). The demo diff --git a/docs/QUALITY.md b/docs/QUALITY.md index f4d7eda..3413324 100644 --- a/docs/QUALITY.md +++ b/docs/QUALITY.md @@ -1,7 +1,8 @@ # Quality gates & CI -Every tool in this stack is free for public repositories. Nothing here -requires a paid plan. +This document describes executable quality gates. Provider plans, pricing, and +optional review assistants are deliberately excluded because they change +independently of repository correctness. ## Workflows (`.github/workflows/`) @@ -9,7 +10,7 @@ requires a paid plan. | ---------------------- | ----------------------------- | ------------------------------------------------------------------------------------------------------- | | `frontend-ci.yml` | frontend/backend API surfaces | Prettier, ESLint, tsc/Vite, Vitest coverage, Playwright, generated OpenAPI drift, real OIDC scope smoke | | `backend-ci.yml` | `backend/**` | warning-free build, unit coverage, and Docker-free EF tenant persistence tests | -| `ai-services-ci.yml` | `ai-services/**` | Locked Python 3.14 deps, Ruff, pytest coverage, and Aspire-equivalent Uvicorn `/health` smoke | +| `ai-services-ci.yml` | `ai-services/**` | Locked Python deps, Ruff, pytest coverage, and Aspire-equivalent Uvicorn `/health` smoke | | `codeql.yml` | all PRs/pushes + weekly cron | Static security analysis (C#, TS/JS, Python) | | `sonar.yml` | all PRs/pushes | SonarQube Cloud analysis for all three stacks (skips until `SONAR_TOKEN` exists) | | `agent-governance.yml` | all PRs/pushes | AI agent governance framework consistency — see [docs/AGENT-GOVERNANCE.md](AGENT-GOVERNANCE.md) | @@ -57,14 +58,14 @@ NuGet, pip, and the workflows' actions. Until step 4 happens, `sonar.yml` skips itself — it never blocks a PR. -## AI code review (free options) +## Agent compatibility -- **CodeRabbit** — already reviewing PRs here; the Pro plan is free for - public/open-source repositories. -- **CodeQL** — security-focused review on every PR (already enabled). -- **Claude Code** (`/install-github-app`) — adds `@claude` mention-driven - review/fix on PRs; usage is billed against an Anthropic API key, so - keep it for high-value reviews if the budget is tight. +Repository correctness does not depend on which coding agent produced a +change. Codex, GitHub Copilot, and Claude Code share the rules in `AGENTS.md`, +the portable workflows in `.agents/skills/`, and the same GitHub Actions gates. +Tool-specific bridges and local hooks are convenience layers; CI remains the +independent acceptance boundary. See +[AGENT-GOVERNANCE.md](AGENT-GOVERNANCE.md) for discovery paths and sync rules. ## Branch protection recommendation diff --git a/docs/SDD-GUIDE.md b/docs/SDD-GUIDE.md index ce9136e..21f000b 100644 --- a/docs/SDD-GUIDE.md +++ b/docs/SDD-GUIDE.md @@ -1,160 +1,62 @@ -# SDD Guide — developing with AI agents in this repo - -How a developer (human) delivers features here by directing an AI agent -instead of typing every line. This is the human-facing companion to the -agent-facing instruction files (`CLAUDE.md`, `.skills/`). - -The core idea: **the documentation is the spec, the agent is the -executor, CI is the verifier.** Your job shifts from writing code to -writing specs, reviewing diffs, and keeping the docs truthful. - ---- - -## The instruction stack (what the agent reads, in order) - +# SDD guide — working with agents in this repository + +Documentation supplies constraints and intent; the agent implements; automated +gates verify mechanics; the human reviews product correctness. + +## What to provide + +For a feature or behavior change, give the smallest complete specification: + +- desired outcome and affected app/service; +- business rules and invariants; +- public request/response/error shape when a contract changes; +- authentication/authorization expectations; +- acceptance criteria and explicit exclusions. + +Do not paste the repository's instruction files into the prompt. Agents route +from `AGENTS.md` and load matching canonical skills. If a requested fact already +exists in code, generated OpenAPI, tests, migrations, or an accepted ADR, the +agent should find it before asking. + +## Delivery loop + +1. **Specify:** record product intent and any contract that cannot be inferred. +2. **Implement:** name the outcome; optionally name the relevant skill when you + want a specific workflow emphasized. +3. **Verify:** require every gate listed by the affected `AGENTS.md`. +4. **Review:** inspect architecture, security, tenant isolation, and product + behavior rather than trusting a green build alone. +5. **Persist:** update status/contract docs and ADRs in the same change; remove + superseded teaching rather than keeping an active historical prompt. + +## Prompt shape + +```text +Outcome: +Scope / out of scope: +Business rules: +API or event contract (if affected): +Auth / tenant behavior: +Acceptance criteria: ``` -AGENTS.md (root, canonical) repo-wide non-negotiables (tenant scoping, layering) -├── CLAUDE.md (root) thin `@AGENTS.md` import — Claude Code loads this automatically -├── agent-skills/*/SKILL.md canonical, tool-portable skills (frontend feature, backend -│ use case, exception audit, migration safety, tenant -│ isolation, rule persistence, architecture review, API -│ contract review) — synced verbatim into .claude/skills/ -│ and .agents/skills/ by scripts/sync_agent_skills.py -├── apps/admin-frontend/AGENTS.md TS strictness, testing strategy, design language, comments -│ ├── CLAUDE.md thin `@AGENTS.md` import -│ ├── .skills/*/SKILL.md admin-api-contract, admin-tdd-conventions (still local — -│ │ admin-feature-vertical is an obsolete redirect stub) -│ └── docs/ STATUS · DOMAIN · API · DECISIONS · adr/ -├── backend/AGENTS.md layering, rich domain, tenant scoping, test tiers, comments -│ ├── CLAUDE.md thin `@AGENTS.md` import -│ └── .skills/backend-new-microservice/SKILL.md still local (backend-use-case is an -│ obsolete redirect stub — see agent-skills/agenza-backend-use-case) -└── docs/ VISION · MONOREPO · QUALITY · AGENT-GOVERNANCE · adr/ -``` - -See [AGENT-GOVERNANCE.md](AGENT-GOVERNANCE.md) for why AGENTS.md is the -canonical file (not CLAUDE.md) and how the skill sync works. - -You rarely need to paste any of this into a prompt — agents discover it. -What you must do is **keep it true** (see "Your responsibilities" below). - ---- - -## The loop - -1. **Spec** — write down what you want _before_ prompting: entities, - endpoints, shapes, error cases. For a REST feature that means the API - contract; for a domain change, the invariants. If the spec lives only - in your head, the agent will invent the missing parts. -2. **Prompt** — point the agent at the work, naming the feature and any - spec docs. The skills make the agent ask for what's missing (e.g. the - feature-vertical skill refuses to invent field names). -3. **Watch the gates** — the agent must land: build + tests + lint green, - coverage gate passing. That's not a courtesy, CI enforces it - (see [QUALITY.md](QUALITY.md)). -4. **Review the diff** — you review architecture and product intent; - the gates already reviewed mechanics. Check: tenant scoping, layer - boundaries, whether tests assert behavior (not implementation). -5. **Docs updated in the same change** — STATUS.md rows flipped, ADR - added if a decision was made. A PR that changes behavior without - updating STATUS is incomplete. - ---- - -## Worked examples - -### 1. Build a feature vertical (the common case) - -> Build the Services vertical in the admin frontend. API spec: -> `GET/POST /api/services`, `PUT/DELETE /api/services/{id}`. -> Service = { id: uuid, name: string (1..80), durationMinutes: int > 0, -> priceCents: int >= 0, active: bool }. Errors: 400 validation, -> 404 unknown id. Follow the agenza-frontend-feature skill. - -What should happen (and what to check): the agent reads the skill + -STATUS.md, builds domain entity → use cases → repository → hook → page -in TDD order, adds MSW handlers, flips the STATUS rows, and all gates -pass. If it starts inventing fields you didn't specify, your spec was -incomplete — fix the spec, not the diff. - -### 2. Add a backend use case / endpoint - -> In identity-service, add a "rename tenant" operation: -> PUT /internal/v1/tenants/{id} with { name }, guarded by the -> identity-admin scope. Follow the agenza-backend-use-case skill. - -Expect: a `RenameTenant` command slice (Command/Handler/Validator) under -`Application/Tenants/`, a behavior method on the `Tenant` entity (not a -public setter), the handler returning `Result` instead of throwing for -a not-found tenant, unit tests with fakes asserting on the `Result`, -manual verification of 401/403/400/happy-path (no integration-test tier, -docs/adr/0015), and the coverage gate still green. - -### 3. Stand up a new microservice - -> Create notification-service following the backend-new-microservice -> skill. First capability: POST /internal/notifications/email -> (M2M, scope notifications-api) that persists an outbox row — -> no real SMTP yet. - -Expect: five projects wired per the skill, own Postgres schema -(`notification`), auth via `Admin.Identity.Client`, an Aspire resource -entry, and a new scope seeded in identity-service. - -### 4. Fix a bug - -> Logging out and logging back in sometimes lands on /login with no -> error. Reproduce it with a failing test first, then fix. Suspects: -> silent-renewal flow (see frontend ADR 004). - -Expect: a failing test that captures the bug _before_ the fix — that's -the project's TDD convention, and it's what stops regressions. - -### 5. Make an architecture decision - -> We need file uploads (client photos). Evaluate object storage vs -> Postgres bytea for our scale, propose one, and write it as -> docs/adr/0005. Don't implement yet. - -Decisions get an ADR _before_ implementation; the next agent (or you, -in six months) reads why, not just what. - ---- - -## Prompt patterns that work here - -| Weak prompt | Strong prompt | -| ------------------------- | ------------------------------------------------------------------------- | -| "add a services page" | Names the vertical + full API contract + points at the skill (example 1) | -| "make the backend better" | One concrete outcome: "add integration tests for the userinfo endpoint" | -| "fix the login bug" | Symptom + repro steps + where you suspect it lives + "failing test first" | -| "write docs" | "Flip the STATUS rows for Services and add an ADR for the polling choice" | - -Two more habits that pay off: - -- **One vertical per session.** Small, reviewable increments beat a - 10-file mega-prompt. The build order in the frontend's STATUS.md is - the roadmap. -- **Ask for analysis without a fix** when you're exploring ("is our - tenant scoping airtight? report, don't change anything") — then a - second prompt to implement what you agreed with. ---- +For a repeatable workflow, name the matching skill under `.agents/skills/`. +Keep one-off detail in the task prompt rather than committing prompt templates. -## Your responsibilities (the parts AI can't own) +## Useful examples -- **Spec quality.** Ambiguity in, hallucination out. The API contract, - the domain invariants, and the product decisions are yours. -- **Doc truthfulness.** Stale docs are worse than no docs — an agent - trusts STATUS.md more than it trusts the code. If you hand-change - behavior, update the docs in the same commit. -- **Review.** Gates catch broken; you catch _wrong_. Tenant scoping and - security-sensitive diffs (`Admin.Identity.Client`, OpenIddict config, - anything touching tokens) deserve a human read, always. -- **Decisions.** Agents propose, ADRs record, you decide. +- “Add RenameTenant in identity-service. PUT shape and authorization are ...; + return not-found/conflict as Result. Follow agenza-backend-use-case.” +- “Build the Clients frontend vertical from this OpenAPI contract. It must clear + cached data on tenant switch and meet the listed accessibility criteria.” +- “Review tenant isolation for the new query. Report only; do not edit.” +- “Evaluate whether notifications belongs in an existing context or a new + service. Decide first; do not scaffold until the boundary is justified.” -## Definition of done (any stack) +## Human responsibilities -Build green · tests green · lint/format green · coverage gate green · -STATUS/ADR updated · CI green on the PR. If any of those is red, the -work isn't done — regardless of how good the diff looks. +- Decide product behavior and incompatible architectural choices. +- Review security-sensitive auth/tenant changes. +- Keep living docs truthful when making manual changes. +- Reject speculative abstractions even when tests pass. +- Do not declare completion while a required gate is red. diff --git a/docs/VISION.md b/docs/VISION.md index 318b3b4..b0d635c 100644 --- a/docs/VISION.md +++ b/docs/VISION.md @@ -28,7 +28,7 @@ its own domain model, its own schema, its own API. | Service | Status | Context it owns | | ------------------ | -------- | ------------------------------------------------------------ | | `identity-service` | active | Authentication (OIDC/OpenIddict), tenants, users, M2M tokens | -| `services-service` | active | The business's offering: Tags, Categories, and the Services catalog are done; appointments and clients belong here too unless they grow enough to justify their own context | +| `services-service` | active | The business's offerings context: Tags, Categories, and Services; appointments and clients belong here unless evidence justifies another context | | `notification-service` | planned | Email/SMS/push — one place for templates, delivery, retries | Cross-service communication: HTTP with M2M JWTs from identity-service @@ -51,8 +51,8 @@ credentials. They never touch another service's database. The repo is optimized for AI-assisted delivery — the developer-facing walkthrough with worked example prompts is [SDD-GUIDE.md](SDD-GUIDE.md): -1. **Instructions are layered**: root `CLAUDE.md` → per-area `CLAUDE.md` - (frontend, backend) → `.skills/` how-to guides → `docs/` references +1. **Instructions are layered**: root `AGENTS.md` → per-area `AGENTS.md` + (frontend, backend) → canonical `.agents/skills/` workflows → routed `docs/` references (STATUS, DOMAIN, API, ADRs). An agent reads the layer it needs; specs live in docs, not in chat history. 2. **State is machine-readable**: `STATUS.md` files say what's done, diff --git a/docs/adr/0011-domain-entities-validated-entirely-by-validators.md b/docs/adr/0011-domain-entities-validated-entirely-by-validators.md index debf97e..d32580b 100644 --- a/docs/adr/0011-domain-entities-validated-entirely-by-validators.md +++ b/docs/adr/0011-domain-entities-validated-entirely-by-validators.md @@ -110,7 +110,8 @@ Domain on every request. `identity-service`'s `Tenant` specifically, not a blanket rule, and `services-service`'s `Tag`/`Category`/`Service` are called out as the exception (pun intended) to that default. -- `.skills/backend-use-case/SKILL.md`'s Widget template still shows +- The former local backend-use-case skill's Widget template (deleted; retained + in Git history) still showed Domain throwing on invalid input as the **default recommendation for a brand-new service** — a new service's entity may still want Domain-level defense-in-depth before it has a full validator suite diff --git a/docs/adr/0016-ai-agent-governance-framework.md b/docs/adr/0016-ai-agent-governance-framework.md index 8f1ed18..731d96f 100644 --- a/docs/adr/0016-ai-agent-governance-framework.md +++ b/docs/adr/0016-ai-agent-governance-framework.md @@ -1,130 +1,51 @@ # ADR 0016 — Cross-tool AI agent governance framework -Status: accepted (2026-07) +Status: accepted (2026-07), amended 2026-08-03 for portable multi-agent discovery ## Context -This repository is built AI-first (docs/SDD-GUIDE.md): agents read -`CLAUDE.md`/`.skills/` and execute, CI verifies. Until now that -instruction stack was Claude Code-specific in shape (`CLAUDE.md` at each -level, `.skills/` directories read by convention rather than by any -tool's own discovery mechanism) and entirely undistributed for any other -agent — a session using OpenAI Codex, or any other `AGENTS.md`-reading -tool, had no equivalent entry point at all. - -Auditing the existing instruction stack while building its replacement -surfaced a concrete, already-real failure mode this ADR is partly a -response to: `backend/.skills/backend-use-case/SKILL.md`'s copy-paste -templates still showed a `CreateWidgetCommandValidator` taking an -`IWidgetRepository` and a `MustAsync` rule querying it — the exact shape -docs/adr/0010 introduced and docs/adr/0012 reverted. The prose earlier in -the same file had been updated to describe the reverted state correctly; -the templates underneath it, copied verbatim by whoever/whatever last -used the skill, had not. `apps/admin-frontend/.skills/admin-feature-vertical/SKILL.md` -had a milder version of the same problem: it described every form as "a -plain, dialog-agnostic ``" with no mention of React Hook Form, Zod, -or the structured server-error-to-field mapping (`serverFormError.ts`, -`useCreateInline.ts`) the codebase had already adopted. Neither drift was -caught by any existing gate, because no gate looked at instructional -content at all — CI checks code, not the documents telling an agent how -to write it. +The repository originally accumulated tool-specific `CLAUDE.md`, `.skills/`, +standalone agent personas, copied templates, living docs, and generated analysis +artifacts. Several copies taught patterns already reverted in code: repository +queries inside validators, throwing expected failures, JWT-only tenant guidance, +rejected-promise fakes, removed frontend verticals, and package versions copied +outside Central Package Management. Mechanical sync checks passed because they +verified copies, not semantic truth. ## Decision -### `AGENTS.md` is the canonical, tool-independent instruction source - -A new root `AGENTS.md`, `backend/AGENTS.md`, and -`apps/admin-frontend/AGENTS.md` hold every durable rule that previously -lived only in the matching `CLAUDE.md`. Each `CLAUDE.md` becomes a thin -`@AGENTS.md` import (plus, at the root only, Claude Code-specific -integration notes — which skills/subagents to prefer, which governance -commands to run before finishing a turn). This is a refactor, not a -rewrite: the backend and frontend `AGENTS.md` files are the previous -`CLAUDE.md` content, moved and lightly cross-referenced, not -reauthored from scratch — none of it needed to change, since it was -already tool-neutral prose. - -### `agent-skills/` is the single editable skill source - -Eight canonical skills (`agenza-backend-use-case`, `agenza-frontend-feature`, -`agenza-exception-flow-audit`, `agenza-architecture-review`, -`agenza-rule-persistence`, `agenza-api-contract-review`, -`agenza-tenant-isolation-review`, `agenza-migration-safety`) live once, -with portable frontmatter, and are synced — by content hash, never by -hand — into `.agents/skills/` (Codex) and `.claude/skills/` (Claude Code) -by `scripts/sync_agent_skills.py`. Two of the eight replace true -duplicates: `agenza-backend-use-case` replaces -`backend/.skills/backend-use-case` (fixing the `MustAsync`/repository -drift described above in the process — the new templates are a direct, -verified copy of the current `Tags` feature's actual code, not a -from-scratch rewrite), and `agenza-frontend-feature` replaces -`apps/admin-frontend/.skills/admin-feature-vertical` (documenting the -React Hook Form/Zod/structured-error pattern the old skill missed). Three -other pre-existing local skills (`backend-new-microservice`, -`admin-api-contract`, `admin-tdd-conventions`) don't duplicate a canonical -skill's purpose and stay where they are, referenced directly from the -area `AGENTS.md` files. - -### Guard scripts make drift mechanically detectable, not just documented - -`scripts/architecture_guard.py` scans both application source (backend -C#, frontend TS/TSX) and the fenced code blocks inside Markdown -instruction/skill files for the specific patterns docs/adr/0012 and -docs/adr/0014 reverted (`DuplicateEntityException`, -`BusinessExceptionHandler`, `ValidateAndThrow`, a repository dependency or -`MustAsync`/`CustomAsync` rule in a validator, a domain entity throwing -instead of returning `DomainResult`) plus a small set of frontend/ -documentation checks (`any` usage, cross-feature-page imports, coverage- -exclude drift, dangling `docs/adr/NNNN` references — the latter a second -real, already-fixed bug this repo hit once, per docs/HARDENING_REPORT.md's -finding of 14 references to a nonexistent ADR 0013). Scanning code blocks -inside Markdown specifically — not just application source — is what -would have caught the `backend-use-case` skill's stale template before -this ADR: the "no repository in a validator" rule already existed in -prose right above the offending code block. - -`scripts/check_agent_governance.py` checks the governance meta-files -themselves for structural consistency (files present, `CLAUDE.md` -importing correctly, skill frontmatter portable and valid, `.agents/skills/` -and `.claude/skills/` in sync with `agent-skills/`, no `.codex/skills` -distribution directory, every referenced ADR/script/npm-command actually -existing). - -### The same three checks run in three places, none of them trusting the others - -A Claude Code Stop hook (`scripts/claude_stop_guard.py`, wired via -`.claude/settings.json`) runs all three scripts before a turn is allowed -to end, reading `stop_hook_active` from its input to guarantee it can -never loop indefinitely. Any other agent (Codex included) gets the same -three commands documented directly in `AGENTS.md`'s "Mandatory commands" -section, since Codex has no hook mechanism to wire into. CI -(`.github/workflows/agent-governance.yml`) runs the same three commands -on every PR/push regardless of whether any agent tool is installed — the -backstop that doesn't trust either the hook or an agent's own diligence. +- `AGENTS.md` is the durable, tool-independent entry point at root and per area. +- `.agents/skills/` is the only editable repository skill source. Codex and + GitHub Copilot consume it directly; `sync_agent_skills.py` copies it to + `.claude/skills/` for Claude Code. +- Import-only `CLAUDE.md` files and a thin + `.github/copilot-instructions.md` bridge route each tool to `AGENTS.md` + without restating repository rules. +- Repository-local `agent-skills/`, `prompts/`, `.claude/agents/`, `.skills/`, + `.codex/skills/`, and standalone `.agent.md` instruction layers are + prohibited. `.claude/settings.local.json` is machine-local and ignored. +- Skills use progressive disclosure: a short task workflow routes to API, + testing, UI, migration, or other references only when the task touches them. +- Copied implementation templates are avoided when live compiled code is a + reliable reference. Package versions, file inventories, test counts, bundle + sizes, and feature status do not live in instructions. +- ADR indexes route agents around superseded decisions. Completed prompts and + generated analysis output are removed from the active corpus; Git preserves + their history. +- Governance checks enforce canonical distributions, resolved references, + absence of legacy instruction layers, and known mechanically detectable + teaching regressions. CI runs the same checks and their tests. ## Consequences -**Benefits**: one rule change now has exactly one place to land -(`agent-skills/` + the matching `AGENTS.md`) instead of needing to be -kept in sync across a Claude-specific and a hypothetical Codex-specific -copy by hand; the stale-template class of bug this ADR opens with now has -an automated check (`architecture_guard.py`'s code-block scan) instead of -depending on a human rereading every skill after every ADR; Codex (or any -other `AGENTS.md`-reading agent) gets the same instruction quality Claude -Code already had, on day one, instead of a lesser or absent instruction -set. - -**Costs**: two more directories now exist that must never be hand-edited -(`.agents/skills/`, `.claude/skills/`) — `check_agent_governance.py`'s -sync check is what makes a hand-edit there loud instead of silent; -`agent-skills/agenza-backend-use-case` and `agenza-frontend-feature` -duplicate a meaningful fraction of their now-obsolete -`backend/.skills/`/`apps/admin-frontend/.skills/` predecessors' content by -necessity (a redirect stub isn't useful on its own) — this is accepted as -the cost of having one canonical, portable copy instead of two -tool-specific ones that can drift. - -This does not reopen docs/adr/0005's CQRS-dispatcher decision, docs/adr/0012's -validator/handler split, or docs/adr/0014's Result-pattern decision — it -only changes *how* those decisions are taught to an agent and *how* their -reversal is guarded against recurring, not the decisions themselves. +Agents load less unrelated context, current code outranks stale examples, and a +rule change has one editable instructional source. The Claude distribution +still duplicates bytes required for tool discovery, but the previous third +`agent-skills/` copy and tool-specific reviewer/prompt wrappers are gone. +Content-hash checks keep the remaining distribution mechanical rather than +cognitive. + +The framework does not make prose self-verifying. Periodic architecture reviews +must still compare status, examples, and referenced symbols with the repository. +When drift is mechanically recognizable, the review adds a regression guard +instead of relying on future memory. diff --git a/docs/adr/0017-schema-scoped-migrations-history-table.md b/docs/adr/0017-schema-scoped-migrations-history-table.md index 15906a8..2b0cb2a 100644 --- a/docs/adr/0017-schema-scoped-migrations-history-table.md +++ b/docs/adr/0017-schema-scoped-migrations-history-table.md @@ -99,7 +99,7 @@ re-apply every migration from scratch, including `CREATE TABLE` statements for tables that already exist. This fails loudly (`42P07 relation already exists`) rather than silently corrupting data, but it does break local dev startup until handled. This is exactly the -kind of change `agent-skills/agenza-migration-safety` and root +kind of change `.agents/skills/agenza-migration-safety` and root `AGENTS.md`'s question policy require flagging rather than executing unattended — no docker/psql command was run as part of this change. diff --git a/docs/adr/README.md b/docs/adr/README.md new file mode 100644 index 0000000..67b49e2 --- /dev/null +++ b/docs/adr/README.md @@ -0,0 +1,48 @@ +# Cross-cutting ADR index + +Read this index first and open only ADRs relevant to the task. An ADR marked +superseded is historical evidence, not current implementation guidance. + +## Current decisions by concern + +| Concern | Current ADRs | +| --- | --- | +| Service and data topology | 0001, 0002, 0003, 0024, 0029 | +| Backend CQRS, validation, Result flow | 0005, 0006, 0007, 0009, 0012, 0014, 0018 | +| Tenant assignment and persistence | 0006, 0008, 0009, 0017, 0019, 0024, 0028 | +| Testing and runtime smokes | 0004, 0015 as narrowed by 0019/0026, 0026 | +| Agent governance | 0016 | +| Authentication UX and AI delegation | 0020, 0022 | +| Database bootstrap | 0025 as narrowed by 0027, plus 0027/0028 | +| Git workflow | 0021 as amended by 0030/0031 | +| Toolchain compatibility | 0032 | + +## Superseded decisions + +| ADR | Superseded by | Do not follow | +| --- | --- | --- | +| 0010 | 0012 | Repository-backed FluentValidation rules | +| 0011 | 0012 and 0014 | Validator-only/anemic domain model | +| 0013 | 0024 | Application-only tenant relationship enforcement | +| 0023 | 0026 | Dedicated runtime-test project | +| 0030 local-hook portion | 0031 | Repository-owned local Git hooks | + +ADRs 0005, 0006, 0008, 0009, 0012, 0015, 0017, 0021, 0025, and 0030 +contain explicitly marked historical passages. Their status header and the +newer ADR named there win over the historical body. + +## Complete register + +0001 context-aggregated services · 0002 schema per service · 0003 OpenIddict · +0004 quality stack · 0005 CQRS/vertical slices · 0006 tenant/base-entity +conventions · 0007 command binding · 0008 tenant assignment · 0009 +TenantOwnedEntity · 0010/0011 superseded validation experiments · 0012 handler +and domain correction · 0013 superseded relationship enforcement · 0014 +Result-based domain/persistence · 0015 test-tier reduction · 0016 agent +governance · 0017 migrations history · 0018 shared-kernel split · 0019 tenant +persistence tests · 0020 authentication feedback · 0021 trunk workflow · 0022 +AI tenant context · 0023 superseded runtime tests · 0024 database ownership · +0025/0027 bootstrap evolution · 0026 runtime-smoke boundary · 0028 migration +baseline · 0029 Aspire-only orchestration · 0030/0031 Git-hook evolution · 0032 +stable runtime/toolchain pins. + diff --git a/prompts/agent-task-template.md b/prompts/agent-task-template.md deleted file mode 100644 index d5efe3f..0000000 --- a/prompts/agent-task-template.md +++ /dev/null @@ -1,96 +0,0 @@ -# Agent task template (tool-neutral) - -Copy this into your prompt to whichever agent you're using (Claude Code, -OpenAI Codex, or any other agent that reads `AGENTS.md`) and fill in every -section. This template intentionally uses no tool-specific syntax (no -`/skill`, no `$skill`) — name the skills you want consulted in plain -language, as below, and the agent finds them itself. - ---- - -## Objective - - - -## Scope - - - -## Business rules / spec - - - -## Acceptance criteria - - - -## Read these first - -- Root `AGENTS.md` -- `backend/AGENTS.md` and/or `apps/admin-frontend/AGENTS.md` (whichever - area(s) this touches) -- Relevant ADRs under `docs/adr/` (name specific ones if you know them) - -## Skills to use - - - -- agenza-backend-use-case -- agenza-frontend-feature -- agenza-exception-flow-audit -- agenza-architecture-review -- evolve-modular-architecture -- agenza-rule-persistence -- agenza-api-contract-review -- agenza-tenant-isolation-review -- agenza-migration-safety - -## Allowed files / directories - - - -## Mandatory commands before calling this done - -```bash -python scripts/sync_agent_skills.py --check -python scripts/check_agent_governance.py -python scripts/architecture_guard.py - -# plus whichever of these apply to what changed: -dotnet build backend/AdminBackend.slnx -dotnet test backend/AdminBackend.slnx -npm run format:check --workspace=apps/admin-frontend -npm run lint --workspace=apps/admin-frontend -npm run build --workspace=apps/admin-frontend -npm run test:coverage --workspace=apps/admin-frontend -``` - -## Restrictions - -- Only ask a question when it could change a business rule, a public - contract, auth/authorization, tenant isolation, existing production - data, or a choice between two incompatible architectures — see the - question policy in root `AGENTS.md`. Otherwise decide and proceed. -- Don't invent requirements not stated here or implied by existing - code/ADRs. -- Don't delete tests, disable lint rules, shrink coverage gates, or widen - allowlists to make a gate pass. -- If a correction or new rule surfaces mid-task, judge whether it's - durable; if so, follow `agenza-rule-persistence` rather than leaving it - only in this conversation. - -## Report format - -At the end, report: - -1. What changed (files, one line each). -2. Which commands were run and their result (pass/fail, not just "should - work"). -3. Any question that blocked a piece of work, and why it met the - question-policy bar. -4. Anything left undone or any known risk. diff --git a/prompts/architecture-review-template.md b/prompts/architecture-review-template.md deleted file mode 100644 index c876baa..0000000 --- a/prompts/architecture-review-template.md +++ /dev/null @@ -1,75 +0,0 @@ -# Architecture review template (tool-neutral) - -For an audit request — periodic, pre-release, or triggered by a specific -concern. Fill in every section; delete this instruction line before -sending. - ---- - -## Objective - - - -## Scope - - - -## Mode - -- [ ] Review only — report findings, do not edit code -- [ ] Review + implement — diagnose, fix, validate, report evidence - -## Read these first - -- Root `AGENTS.md` and the `AGENTS.md` for each area in scope -- `docs/adr/` for anything the review might touch - -## Skills to use - - - -- agenza-architecture-review (general sweep / orchestrates the rest) -- evolve-modular-architecture (future-state, decomposition, or extraction decisions) -- agenza-exception-flow-audit (backend error handling) -- agenza-tenant-isolation-review (multi-tenancy) -- agenza-api-contract-review (backend/frontend contract drift) -- agenza-migration-safety (if a migration is in scope) - -## Mandatory commands - -Review-only mode runs these — they're all read-only: - -```bash -python scripts/sync_agent_skills.py --check -python scripts/check_agent_governance.py -python scripts/architecture_guard.py --inventory -``` - -Implement mode additionally must end with the blocking (non-`--inventory`) -guard passing, same as any other change: - -```bash -python scripts/architecture_guard.py -``` - -## Restrictions - -- Review-only mode never edits code, even for an "obvious" one-line fix — - report it instead. -- Implement mode never silently widens an allowlist, deletes a test, or - lowers a coverage gate to make a finding go away. -- Any confirmed cross-tenant data exposure is reported first, regardless - of what else is in scope. -- A finding that would change a public contract is reported for a - decision, not fixed silently. - -## Report format - -- Findings table (file/location, what's wrong, why it matters — cite the - rule/ADR/skill, severity, suggested fix), most severe first. -- If in implement mode: what was fixed, commands run and their result, - anything left as a finding-only (with justification). -- Whether any finding is a durable rule worth persisting via - `agenza-rule-persistence` rather than a one-off. diff --git a/prompts/backend-feature-template.md b/prompts/backend-feature-template.md deleted file mode 100644 index 9fc4371..0000000 --- a/prompts/backend-feature-template.md +++ /dev/null @@ -1,83 +0,0 @@ -# Backend feature template (tool-neutral) - -For a new or changed command/query/endpoint in `backend/`. Fill in every -section; delete this instruction line before sending. - ---- - -## Objective - - - -## Scope - -- Service: `` -- Feature: `Application//` -- New service? Only if this is a genuinely new bounded context — see - `.skills/backend-new-microservice/SKILL.md` before assuming yes. - -## Business rules / spec - -- Command/query shape: `` -- Validation (shape only — required/length/format/range/cross-field): - `<...>` -- Cross-aggregate rules (existence/uniqueness/in-use — these live in the - handler, not the validator): `<...>` -- Auth: `<[Authorize] default, scope(s) if M2M-only, [IgnoreTenant] only if genuinely tenant-free>` -- Error cases and their `Error.*` type (Validation/NotFound/Conflict/Forbidden): `<...>` - -## Acceptance criteria - -- [ ] Unit tests for the handler cover: happy path, not-found (if - applicable), conflict/duplicate (if applicable), domain validation - failure -- [ ] Validator test(s) cover shape rules only -- [ ] Manually verified: unauthenticated → 401, wrong scope/tenant → 403, - validation failure → 400, happy path → expected status + persisted - effect - -## Read these first - -- `backend/AGENTS.md` -- `docs/adr/0005`, `docs/adr/0007`, `docs/adr/0012`, `docs/adr/0014` - (CQRS/vertical-slice, command-binding, Result-pattern rationale) - -## Skills to use - -- agenza-backend-use-case (primary — build order, decision tree, hard - prohibitions, copy-paste templates) -- agenza-exception-flow-audit (if this touches any existing throw/try/catch) -- agenza-tenant-isolation-review (if this adds a new tenant-owned entity - or query) -- agenza-migration-safety (if this needs an EF Core migration) - -## Allowed files / directories - -`backend/services//**` (all five layers as needed), plus -`docs/adr/` if a new ADR is warranted. - -## Mandatory commands - -```bash -python scripts/sync_agent_skills.py --check -python scripts/check_agent_governance.py -dotnet build backend/AdminBackend.slnx -dotnet test backend/AdminBackend.slnx -python scripts/architecture_guard.py -``` - -## Restrictions - -- No repository dependency in a validator constructor; no `MustAsync`/ - `CustomAsync`. -- No `throw` for an expected outcome (validation, not-found, conflict, - in-use, forbidden) — `Result`/`DomainResult`/`PersistenceResult` only. -- No `DuplicateEntityException`, no `BusinessExceptionHandler`. -- No null-forgiving `!` on a repository lookup assuming a validator - guaranteed existence — fetch and null-check in the handler itself. - -## Report format - -Same as `agent-task-template.md`'s report format, plus: which `Error.*` -type each new failure path returns, and whether a migration was added -(and if so, whether `agenza-migration-safety`'s checklist was followed). diff --git a/prompts/frontend-feature-template.md b/prompts/frontend-feature-template.md deleted file mode 100644 index 9424764..0000000 --- a/prompts/frontend-feature-template.md +++ /dev/null @@ -1,98 +0,0 @@ -# Frontend feature template (tool-neutral) - -For a new or changed page/form/hook in `apps/admin-frontend`. Fill in -every section; delete this instruction line before sending. - ---- - -## Objective - - - -## Scope - -- Feature folder(s): `domain/entities/`, `application/use-cases/`, - `infrastructure/repositories/`, `presentation//` (page, dialogs, - table, filters, and that entity's own hooks all colocated there — see - `presentation/services/` for the pattern; only a piece genuinely reused - *across* entities, like `presentation/forms/`'s `CategoryForm.tsx`/ - `TagForm.tsx`, gets its own shared folder instead) -- Stub page being replaced (if any): `` - -## API spec (search before asking — see root `AGENTS.md`'s question policy) - -Before asking the user for any of this, check -`src/features/catalog/infrastructure/generated/services-api.d.ts`, the backend controller/ -DTOs under `backend/services/services-service/`, `docs/API.md`, and -`docs/adr/` — only ask what's still genuinely missing after that search. - -- Base path: `<...>` -- Methods + shapes (request/response) per operation: `<...>` -- Error codes/shapes: `<...>` -- Tenant scoping mechanism (JWT claim / header / path / query): `<...>` - -## Business rules / field constraints - - - -## Acceptance criteria - -- [ ] Domain entity + use case tests (fakes) -- [ ] Mapper tests (all fields + failure paths) -- [ ] Infrastructure repository tests (MSW) -- [ ] Hook test (fake container), tenant-scoped via `resetKey` -- [ ] Page: loading/error/success states, dark mode, 375px width, keyboard - operable, pt-BR text -- [ ] Form (if any): React Hook Form + Zod, server errors mapped to fields - -## Read these first - -- `apps/admin-frontend/AGENTS.md` -- `docs/STATUS.md`, `docs/DOMAIN.md`, `docs/API.md` - -## Skills to use - -- agenza-frontend-feature (primary) -- `apps/admin-frontend/.skills/admin-api-contract/SKILL.md` (translating - the API spec above into DTOs/mappers/MSW handlers) -- `apps/admin-frontend/.skills/admin-tdd-conventions/SKILL.md` (test - patterns, TS-strict test gotchas) -- agenza-api-contract-review (if anything about the API spec above is - uncertain against the real backend) - -## Allowed files / directories - -`apps/admin-frontend/src/**` for the feature in scope, plus -`apps/admin-frontend/src/test/mocks/handlers/` for MSW handlers. - -## Mandatory commands - -```bash -python scripts/sync_agent_skills.py --check -python scripts/check_agent_governance.py -npm run format:check --workspace=apps/admin-frontend -npm run lint --workspace=apps/admin-frontend -npm run build --workspace=apps/admin-frontend -npm run test:coverage --workspace=apps/admin-frontend -python scripts/architecture_guard.py -``` - -## Restrictions - -- No `any`, anywhere, including tests and fakes. -- No cross-feature import (a page importing another page's `domain/`/ - `application/`/`infrastructure/`). -- No raw Tailwind palette classes (`slate-*`, `teal-*`, etc.) — semantic - tokens only. -- No new global client-state store (Redux/Zustand) used as a server cache. -- No Formik/Yup without an explicit ADR. -- No hand-duplicated DTO for something already in - `src/features/catalog/infrastructure/generated/services-api.d.ts`. - -## Report format - -Same as `agent-task-template.md`'s report format, plus: screenshots or a -description of the page in both light/dark mode if a UI change was made -and a browser preview was available. diff --git a/prompts/frontend-refactor-comments-componentization.md b/prompts/frontend-refactor-comments-componentization.md deleted file mode 100644 index 91723bc..0000000 --- a/prompts/frontend-refactor-comments-componentization.md +++ /dev/null @@ -1,795 +0,0 @@ -# Refatoração do frontend — comentários, componentização e organização - -> **Status: executado.** Este prompt orientou a refatoração que já -> aconteceu (ADR 009 aplicado, `ServiceForm`/`ServiceDialog`/`useServicesPage` -> decompostos, `ServicesPage.test.tsx` dividido). As contagens de -> linhas/props e a regra de comentários descritas abaixo refletem o estado -> **anterior** à refatoração, não o atual — para o estado e as regras -> atuais, use `apps/admin-frontend/AGENTS.md` e -> `agent-skills/agenza-frontend-feature/SKILL.md` como fonte de verdade, -> não este arquivo. Mantido apenas como histórico da tarefa original. - -Use este prompt para orientar um agente de programação a continuar a -refatoração arquitetural do frontend do Agenza. - -## Prompt para o agente - -Você é o agente principal responsável por continuar e concluir a refatoração -arquitetural do frontend do Agenza. - -Repositório: - -`D:\Agenza` - -Frontend principal: - -`D:\Agenza\apps\admin-frontend` - -Esta é uma tarefa de **implementação**, não apenas de análise. Inspecione, -planeje, refatore, teste, atualize documentação e valide todos os gates. Não -encerre após apresentar um plano. - -## 1. Objetivo - -Refatorar e reorganizar o frontend para: - -- Reduzir comentários excessivos, redundantes, históricos ou incorretos. -- Simplificar código que atualmente depende de grandes blocos de comentários. -- Separar responsabilidades reais entre páginas, hooks e componentes React. -- Eliminar god hooks, prop drilling excessivo e ciclos de tipos. -- Decompor Serviços, Tags e Categorias sem criar um CRUD genérico. -- Reorganizar fisicamente a aplicação conforme o ADR 009. -- Corrigir instruções e skills que ainda ensinam a arquitetura antiga. -- Preservar autenticação, isolamento de tenant, acessibilidade e comportamento - atual. -- Manter ou melhorar build, testes e cobertura. - -Não implemente a feature Clientes nesta tarefa. `ClientsPage` ainda é um stub. -Primeiro deixe a arquitetura preparada para que Clientes nasça no padrão -correto. - -## 2. Estado atual importante - -O worktree possui aproximadamente 128 entradas modificadas, removidas ou não -rastreadas, pertencentes ao trabalho anterior do usuário. - -Regras obrigatórias: - -- Não use `git reset`, `git checkout --`, `git clean`, stash automático ou - qualquer comando destrutivo. -- Não descarte mudanças existentes. -- Não reverta arquivos apenas porque não estão no HEAD. -- Não faça commit, push, PR ou deploy. -- Diferencie o código anterior, a refatoração já presente e suas novas - alterações. -- Mantenha o trabalho atual funcional durante toda a refatoração. - -Baseline verificado anteriormente: - -- Governance checks: passaram. -- Architecture guard: passou. -- Prettier: passou. -- ESLint com `--max-warnings=0`: passou. -- Build: passou. -- Vitest: 428/428 testes passaram. -- Cobertura: - - lines: 91,03% - - statements: 90,83% - - branches: 85,58% - - functions: 84,83% - -A versão local observada era Node 22.18.0, mas o projeto exige Node 22.22.1. -Use a versão declarada em `.nvmrc` antes de considerar os resultados finais -oficiais. - -## 3. Leitura obrigatória - -Leia integralmente antes de editar: - -1. `D:\Agenza\AGENTS.md` -2. `D:\Agenza\apps\admin-frontend\AGENTS.md` -3. `D:\Agenza\apps\admin-frontend\docs\STATUS.md` -4. `D:\Agenza\apps\admin-frontend\docs\DECISIONS.md` -5. `D:\Agenza\apps\admin-frontend\docs\API.md` -6. ADRs 006, 007, 008, 009 e 010 do frontend. -7. Skills: - - `agenza-architecture-review` - - `agenza-frontend-feature` - - `agenza-rule-persistence` - - `agenza-tenant-isolation-review` - - `agenza-api-contract-review`, se algum contrato for tocado -8. `apps/admin-frontend/.skills/admin-tdd-conventions/SKILL.md` -9. Configurações de ESLint, Vitest, TypeScript, Vite e Playwright. -10. Testes relacionados a cada arquivo antes de refatorá-lo. - -A skill `agenza-frontend-feature` está semanticamente desatualizada em alguns -pontos. Onde ela conflitar com AGENTS e ADRs 006–010, considere os ADRs e as -regras mais recentes como fonte de verdade. Corrija a skill nesta mesma -tarefa. - -## 4. Modo de trabalho - -Antes de editar: - -1. Verifique `git status`. -2. Registre o baseline. -3. Mapeie imports e dependências dos arquivos que serão movidos. -4. Crie um plano por fases. -5. Adicione ou ajuste testes antes de mudanças de concorrência, sessão ou - tenant. - -Trabalhe incrementalmente. - -Após cada fase relevante: - -- Rode os testes diretamente afetados. -- Rode TypeScript/build quando imports ou tipos mudarem. -- Não acumule várias fases quebradas. -- Não esconda falhas com casts, `any`, `eslint-disable`, aumento global de - timeout ou redução de cobertura. - -Use subagentes para inspeções independentes se isso ajudar, mas evite edições -concorrentes nos mesmos arquivos. - -## 5. Política de comentários - -O padrão desejado é não comentar quando nomes, tipos e estrutura já explicam -o código. - -Um comentário local só deve permanecer quando explicar um motivo não óbvio, -como: - -- Segurança ou isolamento de tenant. -- Concorrência e prevenção de race condition. -- Comportamento peculiar do React, Radix, React Hook Form, Zod ou browser. -- Uma supressão de lint realmente inevitável. -- Restrição de contrato que não pode ser expressa por tipo ou nome. - -Regras: - -- Comentário tem no máximo uma linha, nunca um parágrafo (regra atual em - `apps/admin-frontend/AGENTS.md` — mais estrita do que "uma a três linhas"). -- Não crie JSDoc para apenas descrever uma interface, classe, hook, prop, - retorno ou método claramente nomeado. -- Não narre o que a próxima linha faz. -- Não mantenha história da refatoração no código. -- Não escreva “bug que isso substitui”, “implementação futura”, “placeholder” - ou decisões provisórias dentro de código final. -- Não replique texto de ADR, AGENTS, API.md ou skills. -- Quando o racional já estiver em ADR, preserve no máximo uma referência - curta se o comportamento local for surpreendente. -- Não mencione fixtures ou estratégia de testes em comentários de produção. -- Se um mecanismo precisa de um parágrafo para ser entendido, primeiro tente - simplificar o mecanismo, seus nomes e seus tipos. -- Não adicione um gate cego por quantidade de comentários. Qualidade de - comentário é semântica. - -### 5.1 Comentários incorretos que devem ser corrigidos - -Revise obrigatoriamente: - -- `presentation/components/ErrorBoundary.tsx` - - Não afirmar que captura erros ocorridos antes de `render`. - - Não afirmar que o log ocorre apenas em dev se `console.error` roda em - produção. -- `application/use-cases/auth/HandleAuthCallback.ts` - - Remover a afirmação de que a infraestrutura real ainda não existe. -- `infrastructure/auth/OidcAuthRepository.ts` - - Não afirmar que é o único arquivo que importa `oidc-client-ts`. -- `infrastructure/http/ApiError.ts` - - Corrigir a afirmação de que representa qualquer non-2xx. -- `presentation/hooks/useAsync.ts` - - Remover referências a features inexistentes ou consumidores antigos. - - Não afirmar garantias absolutas sobre o scheduler de passive effects sem - mecanismo comprovável. - -### 5.2 Comentários que devem sair do código - -Reduza os blocos que repetem: - -- ADR 006 em AuthProvider, useAuth e TenantBoundary. -- ADR 007 em AppError, error mapping e ErrorBoundary. -- ADR 008 em container, main e AppProviders. -- ADR 010 nos três Api repositories. -- Estratégia de fake em interfaces de produção. -- Explicações óbvias dos métodos de AuthRepository. -- JSDocs introdutórios de entidades, use cases, ports e fake repositories. -- Comentários repetidos de generation/reset em Tags, Categories e Services. - -### 5.3 Comentários que devem permanecer curtos - -Preserve, de maneira concisa: - -- Verificação server-side do `X-Tenant-Id`. -- Diferença entre timeout e falha de rede. -- Defensive copy de arrays externos. -- Peculiaridades relevantes de coerção do Zod. -- Motivo para preservar `displayTarget` durante a animação. -- Forward de `ref` para `setFocus`. -- Motivo para recarregar a página diante de chunk obsoleto. -- Justificativas locais para supressões de lint inevitáveis. - -Faça uma limpeza global de comentários somente depois de simplificar os -arquivos principais, para evitar retrabalho. - -## 6. Corrigir o snapshot de autenticação - -A refatoração anterior não concluiu o snapshot atômico da sessão. - -Atualmente `createAppContainer()` fornece ao HTTP client callbacks separados -para: - -- Obter access token. -- Obter tenant id. - -Isso pode chamar `authRepository.getCurrentSession()` duas vezes na mesma -requisição e combinar dados de snapshots diferentes durante uma transição de -sessão. - -Refatore para uma única leitura por request, com um contrato equivalente a: - -```ts -interface RequestSession { - accessToken: string - tenantId: string | null -} - -type GetRequestSession = () => Promise -``` - -O nome e a localização exatos devem respeitar as camadas finais. - -Critérios: - -- Uma chamada ao HTTP client lê a sessão uma vez. -- Token e tenant vêm do mesmo snapshot. -- Ausência de sessão invalida a autenticação. -- 401 continua notificando o `SessionEventBus`. -- Infrastructure não importa React. -- Presentation não conhece detalhes de OIDC. -- Testes comprovam uma única leitura e consistência do snapshot. -- Nenhum contrato público backend deve ser alterado. - -## 7. Simplificar useAsync - -`useAsync.ts` possui cerca de 189 linhas, sendo aproximadamente 89 de -comentários. Não resolva isso apenas apagando comentários. - -O hook deve continuar garantindo: - -- Nenhuma atualização após unmount. -- Somente a leitura mais recente pode publicar resultado. -- Mudança de `resetKey` não exibe dados da chave anterior. -- Mutação iniciada numa geração antiga não altera a geração atual. -- Refetch de uma sessão antiga não repopula a atual. -- Uma mutação confirmada não depende do sucesso do refetch posterior. -- Invalidação autoritativa de sessão continua funcionando. - -Simplifique a modelagem para que essas garantias estejam expressas no estado e -nos tipos. - -Prefira: - -- Estado associado explicitamente à sua chave/generation. -- Um reducer ou estado estruturado quando isso reduzir refs independentes. -- Derivar o estado visível pela correspondência entre `state.key` e - `resetKey`. -- IDs/generations com semântica única e nomes explícitos. -- Testes de deferred promises que provem cada race. - -Evite: - -- Afirmações frágeis sobre ordem de passive effects. -- Atualização de estado durante render se houver alternativa simples. -- Vários refs cuja relação só possa ser compreendida por um longo comentário. -- Supressões de lint espalhadas. -- Novas abstrações de server-state ou bibliotecas globais sem ADR. - -Use obrigatoriamente a skill `agenza-tenant-isolation-review` nessa parte. - -Não encerre essa fase enquanto os testes de troca de tenant, mutação pendente, -refetch antigo e unmount estiverem verdes. - -## 8. Decompor Serviços por responsabilidade - -`ServicesPage.tsx` com aproximadamente 33 linhas está adequado. Mantenha-o -como shell de composição. - -O problema está em: - -- `useServicesController.ts`: aproximadamente 253 linhas e múltiplas máquinas - de estado. -- `ServiceForm.tsx`: aproximadamente 339 linhas e 18 props. -- `ServiceDialog.tsx`: aproximadamente 153 linhas e 22 props. -- `ServicesTable.tsx`: aproximadamente 191 linhas. -- `ServicesPage.test.tsx`: aproximadamente 792 linhas. - -### 8.1 Controller - -Divida as responsabilidades em hooks feature-local, por exemplo: - -```text -useServicesPage.ts -useServiceFilters.ts -useServiceEditor.ts -useServiceDeletion.ts -servicePresentationModels.ts -``` - -Responsabilidades esperadas: - -- `useServiceFilters`: entrada de pesquisa, debounce, categoria, tag e reset - de página. -- `useServiceEditor`: target, display target, dirty state, submit, erros, - descarte e foco. -- `useServiceDeletion`: target, progresso, erro, confirmação e cancelamento. -- `useServicesPage`: compõe dados e view models; não reimplementa todas as - máquinas internamente. - -Não substitua um god hook por outro hook com nome diferente. - -Elimine o ciclo de tipos atual: - -- Controller não deve importar Props de componentes. -- Componente não deve importar tipos internos do controller. -- Coloque tipos compartilhados de apresentação em um módulo neutro - feature-local. -- Dependências devem apontar em uma única direção. - -### 8.2 ServiceForm - -Use `FormProvider` e `useFormContext` para dividir o formulário por grupos -reais: - -```text -ServiceForm/ - ServiceForm.tsx - ServiceBasicFields.tsx - ServiceDurationFields.tsx - ServiceCommercialFields.tsx - ServiceCategoryField.tsx - ServiceTagsField.tsx - serviceForm.schema.ts - serviceForm.types.ts -``` - -Possíveis responsabilidades: - -- `ServiceForm`: cria o RHF, aplica erros globais, coordena submit e ações. -- `ServiceBasicFields`: nome e descrição. -- `ServiceDurationFields`: duração mínima, padrão e máxima. -- `ServiceCommercialFields`: preço e desconto. -- `ServiceCategoryField`: categoria e criação inline. -- `ServiceTagsField`: tags e criação inline. - -Não extraia cada `TextField` individualmente. - -A divisão deve reduzir props e deixar cada seção testável. Não troque 18 props -por prop drilling entre seis componentes. - -### 8.3 Lista e tabela - -Separe: - -```text -ServicesList.tsx -ServicesTable.tsx -ServiceTableRow.tsx -ServicesPagination.tsx -``` - -- `ServicesList` decide loading, erro, empty e last-known-good. -- `ServicesTable` renderiza cabeçalho e corpo. -- `ServiceTableRow` renderiza uma entidade e suas ações. -- `ServicesPagination` cuida exclusivamente de paginação. - -Remova o wrapper duplicado de `overflow-x-auto` se `Table` já o fornece. - -### 8.4 Dialog - -Reduza a superfície de `ServiceDialog`. - -Não mantenha 22 props independentes. Prefira modelos coesos e tipados, como: - -- `editor` -- `options` -- `discardConfirmation` - -Não use um objeto genérico sem semântica apenas para esconder a contagem de -props. - -## 9. Refatorar Tags e Categorias - -As páginas continuam monolíticas e duplicadas: - -- `TagsPage.tsx`: aproximadamente 313 linhas. -- `CategoriesPage.tsx`: aproximadamente 295 linhas. - -Crie componentes feature-local: - -```text -tags/ - TagsPage.tsx - useTagsPage.ts - TagsTable.tsx - TagEditorDialog.tsx - TagDeleteDialog.tsx - -categories/ - CategoriesPage.tsx - useCategoriesPage.ts - CategoriesTable.tsx - CategoryEditorDialog.tsx - CategoryDeleteDialog.tsx -``` - -Compartilhe somente comportamentos comprovadamente idênticos: - -- `useDialogTarget` -- `useDeleteConfirmation` -- `DeleteConfirmationDialog` -- Um `CollectionFeedback`, somente se - loading/error/empty/last-known-good forem realmente iguais - -Não crie: - -- `GenericCrudPage` -- `GenericEntityForm` -- Controller CRUD universal -- Configuração declarativa gigante -- Abstração baseada apenas em Tags e Categorias “parecerem semelhantes” - -As regras, textos, tabelas e formulários continuam específicos. - -## 10. Selects criáveis - -`CreatableSingleSelect` e `CreatableMultiSelect` possuem repetição em: - -- Popover. -- Modo lista/criação. -- Loading. -- Erro/retry. -- Command list. -- Ação de criar. - -Não os funda em um mega componente com muitas condicionais e `multiple`. - -Extraia somente uma parte interna compartilhada, se a igualdade for real, -como: - -```text -CreatableSelectPanel -``` - -Mantenha separados: - -- Trigger. -- Semântica de seleção. -- Renderização dos selecionados. -- Comportamento de fechar/manter aberto. -- Remoção de chips no multi-select. - -Se a extração aumentar a complexidade ou o número de parâmetros, mantenha a -duplicação menor e documente a decisão. - -## 11. Testes devem acompanhar as novas fronteiras - -Não delete cenários ou reduza cobertura. - -Reorganize os testes: - -```text -useServiceFilters.test.tsx -useServiceEditor.test.tsx -useServiceDeletion.test.tsx -ServiceForm.schema.test.ts -ServiceForm.test.tsx -ServicesList.test.tsx -ServicesTable.test.tsx -ServiceDialog.test.tsx -ServicesPage.test.tsx -``` - -Regras: - -- `ServicesPage.test.tsx` deve preservar apenas os fluxos integrados - essenciais. -- Testes de transição pertencem aos hooks responsáveis. -- Testes de validação pura pertencem ao schema. -- Testes de foco, acessibilidade, dirty state e criação inline pertencem ao - form/dialog. -- Testes de loading/error/empty/paginação pertencem à lista/tabela. -- Renomeie o atual `ServiceForm.test.tsx` para - `ServiceForm.schema.test.ts` se ele continuar testando apenas o schema. -- Preserve as assertions comportamentais existentes, movendo-as para os - arquivos corretos. -- Não aumente globalmente timeouts. - -Aplique o mesmo princípio aos testes de Tags e Categorias. - -## 12. Executar a modularização física do ADR 009 - -O ADR 009 está `Proposed` porque a movimentação foi adiada na refatoração -anterior. Esta tarefa autoriza sua execução física. - -Faça incrementalmente: - -1. Mova `auth`. -2. Atualize imports. -3. Rode build e testes relacionados. -4. Mova `catalog`. -5. Atualize imports. -6. Rode build e testes relacionados. -7. Mova `app`. -8. Mova itens realmente compartilhados para `shared`. -9. Adicione APIs públicas e guards. -10. Rode a suíte completa. - -Estrutura-alvo: - -```text -src/ - app/ - main.tsx - App.tsx - router/ - providers/ - composition/ - - features/ - auth/ - domain/ - application/ - infrastructure/ - presentation/ - index.ts - - catalog/ - domain/ - application/ - infrastructure/ - presentation/ - tags/ - categories/ - services/ - index.ts - - shared/ - domain/ - application/ - infrastructure/ - http/ - presentation/ - components/ - hooks/ - providers/ - ui/ -``` - -Regras: - -- `domain` não depende de camada externa. -- `application` depende de domínio. -- `infrastructure` implementa ports da aplicação. -- `presentation` depende de aplicação/domínio, nunca infraestrutura. -- `app/composition` conhece as implementações concretas. -- Cada feature expõe API pública em `index.ts`. -- Imports externos não atravessam a API pública para acessar internals. -- Não crie barrel files que gerem ciclos ou exportem tudo - indiscriminadamente. -- `shared` não recebe código de negócio. -- Tags, Categories e Services permanecem juntas em `catalog`. -- Componentes específicos permanecem dentro da feature. -- Código shadcn gerado não deve ser alterado apenas para acomodar a - movimentação. - -Atualize o ADR 009 para `Accepted` somente quando a movimentação estiver -realmente concluída e validada. - -Se algum trecho do ADR se mostrar inadequado diante do código real, ajuste o -ADR com justificativa. Não abandone a movimentação apenas por ela ser extensa. - -## 13. Corrigir AGENTS, skills e documentação - -Use `agenza-rule-persistence`. - -### 13.1 Comentários - -Adicione ao AGENTS do frontend e à skill canônica (já feito — regra atual -em `apps/admin-frontend/AGENTS.md` "Comments"): - -- O padrão é código sem comentário. -- Comentário explica somente um motivo não óbvio. -- Comentário tem no máximo uma linha, nunca um parágrafo. -- JSDoc não repete tipos, nomes ou retornos. -- História e racional arquitetural pertencem aos ADRs. -- Se um bloco grande for necessário, primeiro revisar o design. -- Segurança e concorrência ficam documentadas junto ao mecanismo que as - aplica. - -Não crie limite rígido de quantidade de comentários. - -### 13.2 Componentização - -Atualize as regras para declarar: - -- Page é shell de composição. -- Hook controlador também segue responsabilidade única. -- Um componente pode ser extraído no primeiro uso e continuar feature-local. -- A regra da segunda utilização vale para promoção a `shared`, não para criar - outro arquivo. -- `TagsPage` é referência de comportamento e design, não de anatomia. -- Vários workflows, vários dialogs, clusters distintos de estado, muitos - passthrough props, ciclo de tipos ou teste de página excessivo são gatilhos - de decomposição. -- Não existe hard cap de linhas. -- `GenericCrudPage` continua proibido. - -### 13.3 Atualizar skill antiga - -A skill `agent-skills/agenza-frontend-feature/SKILL.md` ainda ensina partes -obsoletas: - -- Estrutura horizontal. -- `ApiError` chegando aos forms. -- Container antigo. -- TagsPage como arquivo a ser copiado. -- Caminhos antigos. -- Ausência de AuthProvider, SessionEventBus, TenantBoundary e AppError. - -Atualize-a para a arquitetura final baseada em: - -- `app` -- `features/auth` -- `features/catalog` -- `shared` -- Facades `{ auth, catalog }` -- `AppError` -- AuthProvider -- SessionEventBus -- TenantBoundary -- Snapshot atômico de request -- APIs públicas de feature -- Componentes feature-local - -Edite a fonte `agent-skills/`, depois sincronize pelos scripts oficiais. - -### 13.4 Documentação stale - -Revise e corrija: - -- `apps/admin-frontend/docs/API.md` -- `apps/admin-frontend/docs/DECISIONS.md` -- `apps/admin-frontend/docs/STATUS.md` -- ADRs 006–010 -- `apps/admin-frontend/AGENTS.md` -- `CLAUDE.md` relacionados -- Architecture guard -- Testes do architecture guard - -Problemas conhecidos: - -- Documentos ainda dizem que `ApiError` chega à apresentação. -- Há texto dizendo que `AppProviders` constrói o container. -- STATUS ainda descreve handlers REST como stub. -- Clientes aparece bloqueado por `HttpClient`, embora ele exista. -- `mapApiErrorToForm` pode precisar de um nome coerente com `AppError`. -- Caminhos ficarão obsoletos após ADR 009. - -Atualize os guards para fiscalizar: - -- Dependências entre layers dentro das features. -- `presentation → infrastructure`. -- Acesso externo a internals das features. -- Construção de repositories concretos fora do composition root. -- Estrutura antiga sendo reintroduzida. - -Não tente automatizar julgamento de tamanho de componentes ou qualidade -semântica de comentários com um threshold cego. - -## 14. O que não deve ser alterado sem nova autorização - -Não mude: - -- Contratos públicos de API. -- Regras de negócio. -- DTOs backend. -- Autorização ou claims. -- Migrations. -- Banco de dados. -- Infraestrutura de produção. -- Texto ou comportamento funcional sem teste que comprove a intenção. - -Não implemente Clientes, Appointments ou outras features novas. - -Não introduza Redux, Zustand ou nova biblioteca de estado. - -Não introduza outro design system. - -## 15. Validação obrigatória - -Use Node 22.22.1 ou a versão exata de `.nvmrc`. - -Após mudanças intermediárias, rode testes direcionados. - -No final: - -```bash -python scripts/sync_agent_skills.py --check -python scripts/check_agent_governance.py -python scripts/architecture_guard.py - -npm run format:check --workspace=apps/admin-frontend -npm run lint --workspace=apps/admin-frontend -npm run build --workspace=apps/admin-frontend -npm run test:coverage --workspace=apps/admin-frontend -npm run test:e2e --workspace=apps/admin-frontend - -git diff --check -``` - -Se scripts ou guards Python forem alterados, execute também seus testes. - -Critérios: - -- Zero warnings no lint. -- Nenhum teste removido ou pulado. -- Nenhum threshold reduzido. -- Cobertura não pode regredir materialmente em relação ao baseline. -- Nenhum `any`, `@ts-ignore` ou cast inseguro introduzido. -- Sem imports proibidos. -- Sem dependências circulares novas. -- Todos os textos de UI permanecem em pt-BR. -- Acessibilidade e dark mode preservados. -- Nenhum dado de tenant anterior é exibido depois de mudança de sessão. -- Playwright continua usando apenas mecanismos de teste, sem bypass de - produção. - -Não declare conclusão se algum gate obrigatório estiver vermelho. - -## 16. Critérios de aceite arquitetural - -A tarefa estará concluída quando: - -- Comentários falsos ou obsoletos forem removidos. -- Comentários restantes forem curtos e justificarem somente decisões não - óbvias. -- `useAsync` estiver mais simples e continuar protegido por testes de - concorrência. -- Uma requisição HTTP ler token e tenant do mesmo snapshot. -- `ServicesPage` continuar sendo shell de composição. -- Não existir god hook concentrando todos os workflows de Serviços. -- `ServiceForm` estiver dividido por grupos de negócio. -- `ServiceDialog` não tiver uma interface com dezenas de props independentes. -- Lista, tabela, linha e paginação tiverem responsabilidades claras. -- Tags e Categorias não concentrarem toda a máquina CRUD em uma página. -- Não existir `GenericCrudPage`. -- Selects compartilharem apenas a parte realmente idêntica. -- Testes refletirem os novos limites. -- O ciclo de tipos entre controller e dialog for eliminado. -- `src/app`, `src/features` e `src/shared` existirem conforme ADR 009. -- Auth e Catalog possuírem APIs públicas. -- Architecture guard fiscalizar a nova estrutura. -- AGENTS, skills, docs e ADRs ensinarem a arquitetura final. -- Todos os gates estiverem verdes. - -## 17. Relatório final - -Ao terminar, entregue: - -- Resumo das mudanças por fase. -- Antes/depois da organização de pastas. -- Componentes e hooks extraídos. -- Comentários removidos, encurtados ou movidos para ADR. -- Explicação da simplificação de `useAsync`. -- Evidência do snapshot atômico da sessão. -- Testes reorganizados e adicionados. -- Resultado exato de todos os gates. -- Cobertura final. -- Resultado do E2E. -- Arquivos/documentos de governança atualizados. -- Riscos ou itens não concluídos. -- Confirmação de que nenhuma alteração preexistente foi descartada. - -Comece agora verificando o worktree e usando o baseline atual. Apresente um -plano curto e continue imediatamente para a implementação. diff --git a/prompts/frontend-typescript-react-type-modeling.md b/prompts/frontend-typescript-react-type-modeling.md deleted file mode 100644 index 6f62d2f..0000000 --- a/prompts/frontend-typescript-react-type-modeling.md +++ /dev/null @@ -1,784 +0,0 @@ -# Revisão e refatoração de TypeScript no frontend React - -> **Status: executado.** As modelagens descritas abaixo (estados de auth e -> async como uniões discriminadas, `DialogTarget`, `SelectLoadState`, -> wrappers de campo com `Omit`, HttpClient com decoder) já existem no -> código atual. O bug de ENOENT na seção 4 já foi corrigido — o gate usa o -> caminho `features/catalog/infrastructure/generated/` atualmente. Mantido -> como histórico da tarefa original; para o estado atual use -> `apps/admin-frontend/AGENTS.md` e `agent-skills/agenza-frontend-feature/SKILL.md`. - -## Prompt para o agente - -Você vai revisar e, somente onde houver ganho concreto de segurança, -refatorar o uso de TypeScript no frontend React do Agenza. - -O objetivo não é aumentar a quantidade de anotações, introduzir -`React.FC` ou criar tipos sofisticados sem necessidade. O objetivo é fazer -o compilador impedir estados inválidos, reduzir coerções e garantir que -dados externos sejam validados antes de serem tratados como confiáveis. - -Trabalhe no snapshot atual do repositório. A auditoria que originou esta -tarefa foi feita sobre o commit `6f3b51c`, mas não presuma que o `HEAD` -continua igual: confira `git status`, `git log -1` e o código antes de -qualquer alteração. - -Não faça commit, push, PR ou deploy. Não altere nem apague mudanças que já -estejam no worktree. Não use comandos destrutivos de Git. - ---- - -## 1. Resultado esperado - -Ao final: - -1. o pipeline de geração/verificação dos tipos OpenAPI deve usar o caminho - feature-based atual e passar; -2. estados mutuamente exclusivos devem ser representados por uniões - discriminadas quando isso elimina combinações inválidas reais; -3. componentes não devem receber combinações incoerentes de props; -4. `unknown` deve permanecer nas fronteiras de erro, não chegar até JSX - para ser interpretado ou exibido; -5. os componentes devem receber modelos de apresentação já seguros; -6. arrays e modelos somente de leitura devem ser tipados como `readonly` - quando o consumidor não tem autorização para mutá-los; -7. respostas HTTP não devem ganhar uma falsa garantia de tipo apenas - porque o chamador escolheu um parâmetro genérico; -8. React Hook Form e Zod devem continuar preservando corretamente a - diferença entre input e output transformado; -9. nenhuma mudança de UI, fluxo de negócio, API pública, autenticação ou - tenant isolation deve ser introduzida silenciosamente; -10. build, lint, testes, cobertura e governança devem passar. - -Faça tipos representarem invariantes que realmente existem. Não transforme -cada booleano em uma máquina de estados e não crie abstrações genéricas -sem um erro concreto que elas resolvam. - ---- - -## 2. Escopo - -Escopo principal: - -```text -apps/admin-frontend/src/app/ -apps/admin-frontend/src/features/auth/ -apps/admin-frontend/src/features/catalog/ -apps/admin-frontend/src/shared/ -apps/admin-frontend/scripts/ -apps/admin-frontend/package.json -apps/admin-frontend/.prettierignore -apps/admin-frontend/eslint.config.js -agent-skills/agenza-api-contract-review/ -agent-skills/agenza-frontend-feature/ -prompts/frontend-feature-template.md -scripts/architecture_guard.py -scripts/tests/ -``` - -Arquivos espelhados em `.agents/skills/` e `.claude/skills/` nunca devem -ser editados diretamente. Edite a fonte canônica em `agent-skills/` e -execute `python scripts/sync_agent_skills.py`. - -Fora de escopo sem autorização adicional: - -- mudar endpoints, DTOs ou validações do backend; -- mudar regras de negócio; -- trocar React Hook Form, Zod, shadcn/ui ou `useAsync` por outra biblioteca; -- criar um estado global com Redux/Zustand; -- criar `GenericCrudPage`; -- introduzir branded IDs em todo o sistema; -- reescrever componentes shadcn em `src/components/ui/`; -- alterar layout, textos ou design apenas por preferência; -- fazer uma migração ampla sem testes em fases pequenas. - ---- - -## 3. Leitura obrigatória - -Leia completamente, antes de editar: - -1. `AGENTS.md`; -2. `apps/admin-frontend/AGENTS.md`; -3. `agent-skills/agenza-frontend-feature/SKILL.md`; -4. `agent-skills/agenza-api-contract-review/SKILL.md`; -5. `agent-skills/agenza-rule-persistence/SKILL.md`; -6. `apps/admin-frontend/docs/adr/009-feature-based-modularization.md`; -7. `apps/admin-frontend/docs/API.md`; -8. `apps/admin-frontend/docs/DECISIONS.md`; -9. `apps/admin-frontend/docs/STATUS.md`; -10. `apps/admin-frontend/tsconfig.app.json`; -11. `apps/admin-frontend/eslint.config.js`. - -Skills obrigatórias: - -- `agenza-frontend-feature`; -- `agenza-api-contract-review`; -- `agenza-rule-persistence`, caso uma nova regra durável seja formalizada. - -Não copie cegamente exemplos de tutoriais. O repositório usa React 19, -TypeScript strict, `exactOptionalPropertyTypes`, -`noUncheckedIndexedAccess`, `erasableSyntaxOnly`, React Hook Form e Zod. - ---- - -## 4. Primeiro: produza um baseline verificável - -Antes das mudanças: - -1. registre `git status --short`; -2. confirme as versões instaladas; -3. execute: - -```bash -npm run format:check --workspace=apps/admin-frontend -npm run lint --workspace=apps/admin-frontend -npm run build --workspace=apps/admin-frontend -npm run test:coverage --workspace=apps/admin-frontend -npm run generate:api-types:check --workspace=apps/admin-frontend -python scripts/sync_agent_skills.py --check -python scripts/check_agent_governance.py -python scripts/architecture_guard.py -``` - -Registre falhas preexistentes separadamente. Não “corrija” uma falha -reduzindo cobertura, desligando regras ou adicionando casts. - -Estado já observado na auditoria original (histórico — o gate ENOENT -abaixo já foi corrigido desde então, não reproduza esta falha esperando -encontrá-la): - -- format, lint e build passam; -- não há `any` explícito no código de produção; -- não há non-null assertions; -- ~~o gate `generate:api-types:check` falhava com `ENOENT`~~ (corrigido). - -Reproduza o baseline atual do zero; não confie neste texto. - ---- - -## 5. Corrigir primeiro o caminho dos tipos OpenAPI - -A reorganização do ADR 009 moveu o arquivo gerado, mas nem todos os -consumidores foram atualizados. - -Audite e corrija, no mínimo: - -- `apps/admin-frontend/package.json`; -- `apps/admin-frontend/scripts/checkGeneratedApiTypes.mjs`; -- `apps/admin-frontend/.prettierignore`; -- `agent-skills/agenza-api-contract-review/SKILL.md`; -- `prompts/frontend-feature-template.md`; -- comentários atuais em mappers e workflow; -- documentação normativa que ainda ensine o caminho antigo. - -Use como fonte atual: - -```text -apps/admin-frontend/src/features/catalog/infrastructure/generated/services-api.d.ts -``` - -Depois: - -1. execute a geração; -2. confirme que não foi criada uma segunda árvore - `src/infrastructure/generated/`; -3. execute o check contra o OpenAPI vivo; -4. não edite manualmente o `.d.ts`; -5. sincronize as skills; -6. adicione ou ajuste teste de regressão para que o caminho não volte a - divergir silenciosamente. - -Documentos históricos podem mencionar o caminho antigo como parte de um -relato passado. Diferencie história de documentação normativa: atualize -instruções atuais, não reescreva história sem necessidade. - -Não altere o contrato do backend para fazer esse gate passar. - -### 5.1 Tipar os request bodies contra o contrato gerado - -Os métodos de update já constroem corpos explícitos usando -`Update*Command`, mas os métodos de criação enviam o input da aplicação -diretamente. - -Audite: - -- `ApiTagRepository.create`; -- `ApiCategoryRepository.create`; -- `ApiServiceRepository.create`. - -O OpenAPI exige alguns campos nullable como propriedades obrigatórias no -JSON. O tipo da aplicação pode usar propriedades opcionais, mas a -infraestrutura precisa normalizar ausência para `null` antes de enviar. - -Construa cada corpo explicitamente e valide-o sem widening: - -```typescript -type CreateServiceRequestBody = components["schemas"]["CreateServiceCommand"]; - -const body = { - name: input.name, - description: input.description ?? null, - durationMinutes: input.durationMinutes, - minDurationMinutes: input.minDurationMinutes, - maxDurationMinutes: input.maxDurationMinutes, - price: input.price, - maxDiscountPercentage: input.maxDiscountPercentage, - categoryId: input.categoryId ?? null, - tagIds: input.tagIds ?? null, -} satisfies CreateServiceRequestBody; -``` - -Use o contrato real para Tag e Category; não copie o exemplo sem conferir -os tipos gerados. - -Requisitos: - -- o tipo OpenAPI permanece em infrastructure; -- application/domain não importam `components`; -- todos os campos obrigatórios do wire body ficam visíveis; -- propriedades opcionais da aplicação são normalizadas conscientemente; -- testes MSW validam o JSON realmente enviado; -- create e update seguem a mesma disciplina; -- não use cast para fazer um objeto incompatível “caber” no contrato. - ---- - -## 6. Não “corrigir” componentes com `React.FC` - -As seguintes decisões atuais são válidas e devem ser preservadas: - -- `function Component(props: Props): JSX.Element` é um componente - corretamente tipado; -- não usar `React.FC` não é uma deficiência; -- handlers inline devem aproveitar contextual typing; -- `children: ReactNode` explícito é adequado quando children é obrigatório; -- `ComponentProps`/`ComponentPropsWithoutRef` são adequados para wrappers; -- `forwardRef` em `TextField`/`TextAreaField` funciona e não deve ser - removido apenas porque React 19 também aceita `ref` como prop; -- `ref` como prop nos selects genéricos é válido; -- `useForm` deve continuar - distinguindo entrada textual de saída transformada pelo Zod. - -Não: - -- converta componentes em massa para `React.FC`; -- anote manualmente todos os eventos; -- adicione tipos de retorno a cada callback inline; -- troque `ReactNode` por `PropsWithChildren` sem motivo; -- crie interfaces que apenas repetem tipos já disponíveis; -- altere shadcn apenas para satisfazer preferência de estilo. - ---- - -## 7. Modelar autenticação como estado válido por construção - -O contrato atual separa: - -```typescript -status: "loading" | "authenticated" | "unauthenticated"; -tenantContext: TenantContext | null; -``` - -Isso permite combinações que não existem no produto. - -Refatore para uma união discriminada equivalente a: - -```typescript -type AuthSessionState = - | { status: "loading"; tenantContext: null } - | { status: "unauthenticated"; tenantContext: null } - | { status: "authenticated"; tenantContext: TenantContext }; -``` - -As ações de autenticação podem ser intersectadas ou agrupadas em um modelo -separado, desde que a leitura pelo consumidor preserve o narrowing. - -Avalie também um hook estrito para a árvore protegida, por exemplo -`useAuthenticatedTenant()`, que: - -- falhe explicitamente se usado fora do estado autenticado; -- retorne `TenantContext`, nunca `TenantContext | null`; -- elimine fallbacks silenciosos como um nome de empresa genérico dentro de - uma rota que deveria estar autenticada; -- não crie uma segunda fonte de estado; -- continue lendo o mesmo `AuthProvider`. - -Atualize testes para provar: - -- loading nunca carrega tenant; -- unauthenticated nunca carrega tenant; -- authenticated sempre carrega tenant; -- componentes protegidos não precisam simular combinações impossíveis; -- uma troca de tenant continua desmontando o conteúdo tenant-scoped. - -Não enfraqueça `ProtectedRoute`, `TenantBoundary` ou o isolamento de tenant. - ---- - -## 8. Modelar estados assíncronos sem permitir combinações incoerentes - -Hoje `status`, `data` e `error` são propriedades independentes. O código -precisa suportar “last known good data” após falha de refresh, portanto uma -união ingênua que proíba dados no estado de erro estará errada. - -Desenhe primeiro a tabela de estados reais: - -| Estado | dados | erro | significado | -| --------------- | --------- | -------- | ----------------------------- | -| idle | ausentes | ausente | ainda não executado | -| loading inicial | ausentes | ausente | primeira carga | -| refreshing | presentes | ausente | recarregando dados conhecidos | -| success | presentes | ausente | carga concluída | -| initial error | ausentes | presente | nada para mostrar | -| refresh error | presentes | presente | mantém last known good | - -Implemente uma união discriminada somente depois de validar essa tabela -contra os testes atuais de `useAsync`. - -Requisitos: - -- uma variante deve carregar apenas os campos válidos para ela; -- o componente deve fazer narrowing pelo discriminante; -- não duplique a mesma união literal em 12 arquivos; -- preserve proteção contra resposta fora de ordem, unmount e troca de - tenant; -- preserve `mutate` e a garantia de que uma criação bem-sucedida não - dependa do refetch; -- não exponha `unknown` diretamente ao JSX. - -`unknown` é correto dentro de `catch` e dentro do mecanismo genérico. Na -fronteira do hook/controller, converta-o para um contrato de apresentação, -por exemplo: - -```typescript -interface UiError { - message: string; - retryable: boolean; -} -``` - -ou use `AppError` diretamente quando essa for a dependência arquitetural -correta. - -Componentes como `CollectionFeedback` e `ServicesList` não devem decidir -como interpretar uma exceção arbitrária nem renderizar `Error.message` de -um erro inesperado. Eles devem receber mensagem curada e retryability já -determinadas. - -Não faça sniffing de texto de erro. - ---- - -## 9. Tornar estados de editor e dialog impossíveis de montar errado - -Substitua sentinelas como: - -```typescript -type DialogTarget = "new" | T; -``` - -por uma união que não colida com valores legítimos de `T`: - -```typescript -type DialogTarget = { kind: "create" } | { kind: "edit"; item: T }; -``` - -Revise: - -- `useDialogTarget`; -- `useServiceEditor`; -- `servicePresentationModels`; -- `TagEditorDialog`; -- `CategoryEditorDialog`; -- `ServiceDialog`; -- dialogs de exclusão. - -Não deixe `isOpen`, `displayTarget`, `code`, `title`, `submitLabel` e -`initialValues` formarem combinações independentes que o produto nunca -produz. - -Exemplo de direção: - -```typescript -type EditorViewModel = - | { state: "closed" } - | { - state: "creating"; - title: string; - submitLabel: string; - initialValues: FormValues; - // demais campos obrigatórios desta variante - } - | { - state: "editing"; - item: Item; - title: string; - submitLabel: string; - initialValues: FormValues; - // demais campos obrigatórios desta variante - }; -``` - -Não copie esse exemplo literalmente se não representar a animação de -fechamento atual. O código mantém `displayTarget` durante o fade-out; essa -necessidade real precisa continuar representada sem diálogo vazio ou -flicker. - -Coloque formas compartilhadas entre hooks e componentes em módulos -feature-local neutros. O hook não deve importar `Props` do componente, e o -componente não deve importar tipos internos do hook. - ---- - -## 10. Contratos de props compostos - -Corrija props opcionais que somente fazem sentido em conjunto. - -Exemplo atual: - -```typescript -secondaryActionLabel?: string -onSecondaryAction?: () => void -``` - -Prefira: - -```typescript -secondaryAction?: { - label: string - onAction: () => void -} -``` - -ou uma união equivalente. Um label sem callback e um callback sem label -não devem compilar. - -Revise também os selects criáveis. Hoje `status`, `error` e `onRetry` -podem ser combinados de forma incoerente. - -Crie um contrato neutro compartilhado entre single e multi select: - -```typescript -type SelectLoadState = - | { status: "loading" } - | { status: "error"; message: string; onRetry?: () => void } - | { status: "success" }; -``` - -Adapte o desenho ao comportamento real. Não force `onRetry` se existir um -erro legitimamente não retryable. - -Evite que `servicePresentationModels` importe um tipo de status declarado -dentro de um componente específico. - ---- - -## 11. Wrappers de campos devem controlar sua própria acessibilidade - -Revise `TextField` e `TextAreaField`. - -O consumidor não deve conseguir contradizer `error` fornecendo: - -```tsx -aria-invalid={false} -aria-describedby="outro-id" -``` - -Use `ComponentPropsWithoutRef` e `Omit` para retirar props que o wrapper -controla, incluindo quando aplicável: - -- `children`; -- `id`; -- `aria-invalid`; -- `aria-describedby`. - -Reintroduza `id` como obrigatório no contrato do wrapper. - -Garanta que a ordem dos spreads não permita sobrescrever atributos -calculados internamente. - -Para contador de textarea, represente a dependência real entre -`showCount`, `maxLength` e `currentLength`. Não permita `showCount: true` -sem um limite utilizável se o componente não sabe renderizar esse caso. - -Preserve o ref para o elemento DOM real e prove com teste que -`react-hook-form` consegue usar `setFocus`. - ---- - -## 12. Imutabilidade e arrays somente de leitura - -Props, view models, facades e entidades que apenas expõem coleções para -leitura devem preferir: - -```typescript -readonly Item[] -``` - -Revise especialmente: - -- `items`, `values`, `tags`, `categories`, `services`; -- `PagedServices.services`; -- inputs com `tagIds`; -- `NAV_ITEMS`; -- view models de tabelas e selects. - -Não use `readonly` onde uma API realmente precisa mutar a coleção. - -No domínio, verifique `Service.tags` e `TagSummary`: - -- o array já é copiado, mas os objetos internos continuam compartilhados; -- os campos de `TagSummary` devem ser somente de leitura; -- a entidade não deve mudar se o chamador alterar posteriormente o objeto - usado para criá-la; -- adicione teste de regressão para mutação do array e para mutação de um - elemento do array. - -Use `satisfies` em configurações constantes quando ele preservar literais -e validar a forma sem widening desnecessário. Não faça substituição em -massa apenas para “usar mais TypeScript”. - ---- - -## 13. Remover falsa segurança na fronteira HTTP - -O contrato atual permite: - -```typescript -httpClient.get(path); -``` - -e a implementação faz cast do resultado de `response.json()` para `T`. -Um parâmetro genérico não valida JSON em runtime. - -Escolha uma estratégia única, simples e testável: - -### Opção A - -O `HttpClient` retorna `unknown`; cada repository/mapper decodifica e -valida o payload antes de construir entidades. - -### Opção B - -O `HttpClient` recebe um decoder: - -```typescript -get(path: string, decode: (payload: unknown) => T): Promise -``` - -### Opção C - -Outra abordagem equivalente já sustentada pelo código/ADRs. - -Critérios: - -- não use `as T` para transformar JSON desconhecido em contrato confiável; -- não duplique manualmente tipos já gerados pelo OpenAPI; -- tipos gerados continuam descrevendo o contrato estático; -- o ponto que recebe JSON continua fazendo validação runtime; -- payload malformado deve produzir um erro interno curado, nunca um - `TypeError` acidental como `undefined.trim is not a function`; -- `204` não deve depender de `undefined as T`; -- mantenha métodos que retornam `void` corretamente separados ou - sobrecarregados; -- adicione testes para corpo ausente, propriedade ausente, tipo numérico - inesperado e envelope paginado inválido. - -Se essa escolha mudar o port compartilhado `HttpClient`, documente a razão -no ADR apropriado. Não mude o backend nem o wire contract. - ---- - -## 14. Form errors sem casts espalhados - -Os três casts: - -```typescript -field as TagFormField; -field as CategoryFormField; -field as ServiceFormField; -``` - -existem porque `Object.entries()` perde a chave genérica de -`Partial>`. - -Não crie uma proibição global de `as`. - -Avalie uma destas soluções: - -- helper compartilhado e pequeno para entries tipadas; -- representar field errors como uma lista readonly de - `{ field: TField; message: string }`; -- outra forma que preserve o campo genérico e a ordem do primeiro erro. - -Escolha somente se reduzir os três casts sem criar uma abstração mais -complexa que o problema. - -Mantenha: - -- `setError` corretamente tipado; -- `setFocus` no primeiro campo; -- erros globais separados; -- mapeamento por código/campo estruturado; -- nenhuma análise de mensagem livre. - ---- - -## 15. Testes de tipo e comportamento - -Testes devem provar invariantes, não detalhes de implementação. - -Adicione testes para: - -1. narrowing dos estados de auth; -2. estados async inicial, refreshing, success, initial error e refresh error; -3. editor fechado/create/edit sem combinações inválidas; -4. props opcionais compostas; -5. select loading/error/success; -6. atributos ARIA dos wrappers não sobrescrevíveis; -7. focus de campos controlados; -8. payload HTTP malformado; -9. imutabilidade profunda suficiente de `Service.tags`; -10. geração/check do OpenAPI no caminho novo. - -Use `expectTypeOf` quando uma garantia for exclusivamente de tipo. -Testes negativos com `@ts-expect-error` só são aceitáveis se tiverem -descrição clara, forem estáveis e agregarem uma garantia que -`expectTypeOf` não expresse melhor. - -Não: - -- teste nomes de funções internas; -- congele a implementação de hooks; -- use snapshots gigantes; -- remova testes existentes; -- use `as unknown as` para montar fixtures; -- reduza cobertura. - ---- - -## 16. Execução em fases - -Não faça uma alteração gigante. - -Ordem recomendada: - -1. corrigir pipeline/caminho OpenAPI e persistência documental; -2. tipar os request bodies contra os tipos OpenAPI; -3. autenticação discriminada; -4. estado async e normalização de erro; -5. target/editor/dialog; -6. contratos dos selects e ações opcionais; -7. wrappers de campos; -8. readonly/imutabilidade; -9. fronteira HTTP com validação runtime; -10. limpeza dos casts de formulário, somente se continuar simples; -11. documentação, skills e guards. - -Depois de cada fase: - -```bash -npm run format:check --workspace=apps/admin-frontend -npm run lint --workspace=apps/admin-frontend -npm run build --workspace=apps/admin-frontend -npm run test --workspace=apps/admin-frontend -``` - -Se uma fase quebrar, corrija-a antes de começar a próxima. - ---- - -## 17. Política de comentários - -Não explique a refatoração em JSDocs. - -Comentários: - -- zero por padrão; -- no máximo uma linha, nunca um parágrafo, para um "porquê" não evidente - (regra atual em `apps/admin-frontend/AGENTS.md`); -- não narram uma união discriminada que o próprio tipo já expressa; -- não registram “antes/depois”; -- não deixam TODOs especulativos; -- não duplicam ADRs; -- não explicam sintaxe de TypeScript ou React. - -Se o tipo precisa de um parágrafo para ser entendido, simplifique o tipo. - ---- - -## 18. Critérios de aceite - -A tarefa só pode ser considerada concluída quando: - -- [ ] o check OpenAPI lê o arquivo no caminho feature-based; -- [ ] a geração não recria `src/infrastructure/generated`; -- [ ] skill canônica, templates e documentação normativa ensinam o caminho atual; -- [ ] corpos de create/update são verificados contra os tipos OpenAPI gerados; -- [ ] campos nullable obrigatórios no JSON são enviados explicitamente; -- [ ] auth não representa authenticated com tenant nulo; -- [ ] componentes protegidos conseguem trabalhar com tenant não anulável; -- [ ] async state preserva last known good sem combinações incoerentes; -- [ ] `unknown` não chega a componentes para decidir mensagem; -- [ ] nenhum erro inesperado tem `.message` cru renderizado; -- [ ] editor/dialog usa discriminantes em vez de sentinela `'new' | T`; -- [ ] ações opcionais dependentes são agrupadas; -- [ ] select em estado de erro exige mensagem coerente; -- [ ] wrappers impedem override de ARIA que eles próprios controlam; -- [ ] coleções de leitura são `readonly` onde apropriado; -- [ ] `Service.tags` não pode ser alterado indiretamente pelo input original; -- [ ] JSON externo não vira `T` confiável por um cast genérico; -- [ ] não existe `undefined as T` para resposta 204; -- [ ] os casts de fields foram reduzidos apenas se a solução ficou mais simples; -- [ ] não foi introduzido `React.FC` em massa; -- [ ] não foi introduzido `any`, non-null assertion ou `as unknown as`; -- [ ] não foi desabilitada regra de lint; -- [ ] não foi reduzida cobertura; -- [ ] comportamento, UI, API e tenant isolation foram preservados; -- [ ] não foram adicionados comentários narrativos. - ---- - -## 19. Validação final obrigatória - -Execute: - -```bash -python scripts/sync_agent_skills.py --check -python scripts/check_agent_governance.py -python scripts/architecture_guard.py - -npm run format:check --workspace=apps/admin-frontend -npm run lint --workspace=apps/admin-frontend -npm run build --workspace=apps/admin-frontend -npm run test:coverage --workspace=apps/admin-frontend -npm run generate:api-types:check --workspace=apps/admin-frontend -``` - -Também execute `git diff --check`. - -Não declare sucesso com qualquer gate vermelho. Se o OpenAPI vivo não -estiver acessível, informe exatamente essa limitação; não simule um passe. - ---- - -## 20. Relatório final - -Entregue: - -1. diagnóstico inicial em poucas linhas; -2. tabela `problema | arquivo | risco | solução`; -3. tipos/invariantes introduzidos; -4. casts eliminados e casts mantidos com justificativa; -5. fronteiras em que `unknown` foi normalizado; -6. estratégia usada para validar JSON; -7. arquivos de governança/documentação atualizados; -8. testes adicionados; -9. comandos executados e resultado real; -10. riscos ou trabalho que permaneceu; -11. confirmação explícita de que não houve commit, push, PR ou deploy. - -Não entregue apenas “melhorei a tipagem”. Mostre quais estados inválidos -deixaram de compilar e quais payloads externos passaram a ser rejeitados -de forma controlada. diff --git a/prompts/teste-tela-qa-acessibilidade.prompt.md b/prompts/teste-tela-qa-acessibilidade.prompt.md deleted file mode 100644 index 2e54600..0000000 --- a/prompts/teste-tela-qa-acessibilidade.prompt.md +++ /dev/null @@ -1,206 +0,0 @@ -# Prompt para Teste Exploratório de Tela - -Atue como um especialista sênior em QA, UX/UI, acessibilidade e testes exploratórios. - -Sua tarefa é testar cuidadosamente a tela do sistema que está aberta no navegador. O objetivo é encontrar problemas funcionais, falhas de usabilidade, inconsistências visuais, riscos de acessibilidade e situações que possam gerar erros para o usuário. - -Importante: este é um ambiente de desenvolvimento. O agente deve explorar o sistema de forma proativa e, quando seguro e apropriado, provocar cenários intencionais de falha, erro, validação incorreta, comportamento inesperado e quebra de fluxo para revelar vulnerabilidades e pontos frágeis da interface. O foco é identificar limites, comportamentos inesperados e riscos reais de uso, sem comprometer dados reais ou executar ações destrutivas fora do escopo de teste. - -O agente tem total permissão para criar, alterar, modificar e testar diferentes estados da aplicação, inclusive interagindo com formulários, cadastros, atualizações, exclusões e demais operações necessárias para validar o comportamento da tela. Essas ações devem ser realizadas dentro do escopo de teste, com atenção ao ambiente e às restrições informadas, sem comprometer dados reais ou executar operações irreversíveis sem necessidade e sem autorização explícita. - -Além disso, o agente deve sempre responder e reportar em português do Brasil, com linguagem natural, clara e objetiva, usando formatação e vocabulário compatíveis com o contexto brasileiro. As mensagens, títulos, descrições e relatórios devem ser redigidos em português, sem mistura com inglês ou termos técnicos inadequados ao contexto local. - -## CONTEXTO DA TELA - -- Sistema/projeto: [nome do sistema] -- Tela ou funcionalidade: [nome da tela] -- Objetivo principal da tela: [o que o usuário deve conseguir fazer] -- Perfil do usuário: [administrador, cliente, atendente etc.] -- Requisitos conhecidos: [informe os requisitos ou escreva “não fornecidos”] -- Ambiente: [desenvolvimento, homologação ou produção] -- Restrições: não exclua dados reais, não faça pagamentos, não envie mensagens e não execute ações irreversíveis sem autorização. - -## INSTRUÇÕES - -1. Antes de interagir, analise toda a tela e identifique: - - objetivo aparente; - - ações disponíveis; - - campos, botões, menus, links, tabelas e mensagens; - - caminho principal esperado para o usuário; - - pontos que possam causar dúvida. - -2. Execute um teste exploratório completo: - - percorra o fluxo principal; - - teste todos os elementos interativos; - - verifique links, botões, menus, filtros, buscas, formulários, modais, paginação e ordenação; - - confirme se cada ação gera uma resposta visual adequada; - - observe carregamentos, estados vazios, mensagens de sucesso e mensagens de erro; - - verifique se cancelar, voltar, fechar e desfazer funcionam corretamente; - - procure ações sem retorno, elementos bloqueados e comportamentos inesperados. - -3. Teste entradas e cenários extremos, quando aplicável: - - campos vazios; - - apenas espaços; - - textos muito curtos e muito longos; - - números negativos, zero e valores muito altos; - - caracteres especiais, acentos e emojis; - - e-mail, telefone, data e outros formatos inválidos; - - colagem de conteúdo; - - envio repetido do formulário; - - cliques rápidos ou duplos; - - atualização da página durante uma operação; - - perda de conexão ou resposta lenta, se for possível simular com segurança; - - acesso direto a etapas intermediárias; - - dados duplicados; - - sessão expirada ou usuário sem permissão, quando aplicável; - - tentativa deliberada de quebrar o fluxo com entradas inválidas, sequências incomuns, ações inesperadas e combinações extremas, sempre dentro do ambiente de desenvolvimento e sem afetar dados reais ou operações irreversíveis. - -4. Avalie a usabilidade: - - o objetivo da tela está claro? - - o usuário sabe qual é a próxima ação? - - nomes de botões, campos e menus são compreensíveis? - - ações principais e secundárias possuem hierarquia adequada? - - há informações, campos ou etapas desnecessárias? - - mensagens explicam o problema e como resolvê-lo? - - o sistema previne erros antes que aconteçam? - - ações destrutivas pedem confirmação? - - existe retorno visual após cada interação? - - o usuário consegue se recuperar facilmente de um erro? - - o fluxo exige cliques ou esforço excessivos? - - filtros e seleções continuam aplicados quando deveriam? - - dados digitados são preservados após erros? - - a linguagem é simples, objetiva e consistente? - -5. Avalie a interface visual: - - alinhamento, espaçamento e agrupamento; - - contraste, legibilidade e tamanho dos textos; - - consistência de cores, ícones, botões e componentes; - - elementos cortados, sobrepostos ou fora da área visível; - - textos truncados ou quebrados; - - clareza dos estados normal, hover, foco, selecionado, desabilitado, carregando, sucesso e erro; - - consistência com outras áreas do sistema, quando elas estiverem disponíveis. - -6. Avalie a acessibilidade: - - navegação apenas pelo teclado; - - ordem lógica do foco; - - foco visível; - - ativação de controles por Enter e Espaço; - - rótulos claros nos campos; - - erros associados aos campos correspondentes; - - contraste suficiente; - - conteúdo compreensível sem depender somente de cores; - - textos alternativos ou nomes acessíveis em ícones e imagens; - - funcionamento com zoom de 200%, se possível; - - áreas clicáveis com tamanho adequado. - -7. Avalie a responsividade, quando possível: - - desktop, tablet e celular; - - diferentes larguras e alturas; - - telas móveis, incluindo tamanhos pequenos e orientação portrait/landscape; - - menus e tabelas em telas pequenas; - - rolagem horizontal indesejada; - - teclado virtual cobrindo campos ou botões; - - modais e mensagens fora da tela; - - facilidade de toque em botões e links; - - comportamento adequado em dispositivos móveis, incluindo componentes sobrepostos, campos inacessíveis e navegação truncada. - -8. Verifique riscos técnicos e de segurança perceptíveis pela interface: - - exposição de dados sensíveis; - - informações confidenciais presentes na URL; - - mensagens de erro com detalhes técnicos; - - ações disponíveis para usuários aparentemente sem permissão; - - envio múltiplo da mesma operação; - - perda ou duplicação de dados; - - conteúdo inserido pelo usuário sendo exibido de maneira insegura; - - diferenças entre o estado apresentado na interface e o estado real após atualizar a página. - -## REGRAS DO TESTE - -- Não considere uma suposição como um bug confirmado. -- Diferencie claramente: “bug confirmado”, “possível risco”, “problema de usabilidade” e “sugestão”. -- Para confirmar um bug, tente reproduzi-lo pelo menos duas vezes, quando isso for seguro. -- Não altere código nem corrija os problemas durante esta etapa. -- Não execute ações destrutivas ou que afetem usuários reais. -- Em ambiente de desenvolvimento, você pode intencionalmente explorar entradas extremas, ações repetidas, navegação inesperada e sequências de interação que tentem quebrar o fluxo, desde que isso não cause impacto real fora do escopo de teste. -- Registre evidências objetivas e evite avaliações vagas como “está ruim”. -- Se algum teste não puder ser realizado, explique o motivo. -- Caso tenha dúvidas sobre requisito, comportamento esperado, permissão, impacto ou contexto, solicite ajuda e não suponha situações. -- Priorize problemas que afetem a conclusão da tarefa, perda de dados, segurança, acessibilidade ou confiança do usuário. - -## RELATÓRIO FINAL - -Ao terminar, entregue o resultado em português e nesta estrutura: - -- O relatório deve ser detalhado, objetivo e acionável. -- Deve listar claramente todos os problemas identificados, o impacto para o usuário e o negócio, além das ações necessárias para corrigir ou mitigar cada item. -- Ao final, deve incluir um relatório específico com os ajustes necessários, priorizados por impacto, urgência e complexidade. -- Quando houver dúvida, o agente deve sinalizar a incerteza e pedir confirmação antes de concluir uma avaliação. - -1. RESUMO EXECUTIVO - - qualidade geral da tela; - - principais riscos; - - quantidade de problemas por severidade; - - recomendação: aprovar, aprovar com ressalvas ou não aprovar. - -2. FLUXOS TESTADOS - Para cada fluxo, indique: - - cenário; - - resultado esperado; - - resultado observado; - - status: aprovado, reprovado ou não testado. - -3. PROBLEMAS ENCONTRADOS - Para cada problema, informe: - - ID; - - título curto e objetivo; - - categoria: funcional, usabilidade, visual, acessibilidade, responsividade, desempenho ou segurança; - - classificação: bug confirmado, possível risco ou problema de usabilidade; - - severidade: crítica, alta, média ou baixa; - - frequência: sempre, intermitente ou ocorrência única; - - página ou componente afetado; - - contexto e pré-condições; - - passos exatos para reproduzir; - - resultado atual; - - resultado esperado; - - impacto para o usuário e para o negócio; - - evidência disponível; - - recomendação de correção; - - critério para validar a correção. - - Definição das severidades: - - Crítica: impede o uso principal, causa perda de dados, falha grave de segurança ou indisponibilidade. - - Alta: compromete um fluxo importante e não possui alternativa simples. - - Média: prejudica o uso, mas existe uma alternativa. - - Baixa: problema visual, textual ou inconveniente de impacto limitado. - -4. MELHORIAS DE UX/UI - Para cada melhoria: - - situação atual; - - dificuldade causada; - - mudança recomendada; - - benefício esperado; - - prioridade: alta, média ou baixa; - - esforço estimado: pequeno, médio ou grande. - -5. ACESSIBILIDADE E RESPONSIVIDADE - - problemas identificados; - - dispositivos ou dimensões avaliados; - - testes de teclado e foco; - - pontos que ainda precisam ser verificados. - -6. PONTOS POSITIVOS - - elementos e comportamentos que já estão claros, consistentes e fáceis de usar. - -7. TESTES NÃO REALIZADOS - - teste; - - motivo; - - risco restante. - -8. CHECKLIST PARA A PRÓXIMA VERSÃO - Apresente uma lista objetiva e ordenada: - - corrigir antes da publicação; - - corrigir em curto prazo; - - melhorias futuras; - - testes de regressão que devem ser repetidos. - -Finalize indicando os cinco problemas ou melhorias que devem receber atenção primeiro, considerando impacto para o usuário, frequência e esforço de correção. diff --git a/scripts/architecture_guard.py b/scripts/architecture_guard.py index 60c1f51..0c5a97f 100644 --- a/scripts/architecture_guard.py +++ b/scripts/architecture_guard.py @@ -977,7 +977,7 @@ def check_stale_patterns_in_doc_code_blocks() -> list[Finding]: def check_dangling_adr_references() -> list[Finding]: """A source comment citing docs/adr/NNNN where NNNN doesn't exist - this exact class of bug (14 references to a non-existent ADR 0013) was found - and fixed in this repo once already (see docs/HARDENING_REPORT.md).""" + and fixed in this repo once already (see docs/adr/0016).""" adr_dir = REPO_ROOT / "docs" / "adr" existing = set() if adr_dir.is_dir(): diff --git a/scripts/check_agent_governance.py b/scripts/check_agent_governance.py index a03b895..6e71de9 100644 --- a/scripts/check_agent_governance.py +++ b/scripts/check_agent_governance.py @@ -7,16 +7,19 @@ governance *meta-files themselves* are present, consistent, and in sync: - AGENTS.md exists at every required location. -- Every CLAUDE.md that should import AGENTS.md does. -- Every canonical skill under agent-skills/ has valid, portable frontmatter. -- .agents/skills/ and .claude/skills/ are byte-identical to agent-skills/. +- Every CLAUDE.md is a thin AGENTS.md import. +- GitHub Copilot has a thin bridge to the canonical instructions and skills. +- Every canonical skill under .agents/skills/ has valid, portable frontmatter. +- .claude/skills/ is byte-identical to .agents/skills/. - Every docs/adr/NNNN reference mentioned in a governance file resolves to a real ADR file. -- Every agent-skills/ reference mentioned in a governance file +- Every .agents/skills/ reference mentioned in a governance file resolves to a canonical skill. - Every scripts/*.py reference mentioned in a governance file exists. - Documented npm scripts actually exist in the relevant package.json. - .codex/skills is not used as a skill distribution directory. +- Legacy local .skills/ and standalone .agent.md instruction layers are absent. +- Known reverted teaching phrases and versioned new-service templates are absent. Usage: python scripts/check_agent_governance.py @@ -28,6 +31,7 @@ from __future__ import annotations import json +import os import re import sys from pathlib import Path @@ -49,6 +53,9 @@ REPO_ROOT / "apps" / "admin-frontend" / "CLAUDE.md", ] +REQUIRED_COPILOT_INSTRUCTIONS = REPO_ROOT / ".github" / "copilot-instructions.md" +LOCAL_SETTINGS_IGNORE = "**/.claude/settings.local.json" + FORBIDDEN_FRONTMATTER_KEYS = { "allowed-tools", "disallowed-tools", @@ -68,10 +75,62 @@ ] ADR_REF_PATTERN = re.compile(r"docs/adr/(\d{4})") -SKILL_REF_PATTERN = re.compile(r"agent-skills/([\w-]+)") +SKILL_REF_PATTERN = re.compile(r"\.agents/skills/([\w-]+)") SCRIPT_REF_PATTERN = re.compile(r"scripts/([\w-]+\.py)") NPM_RUN_PATTERN = re.compile(r"npm run ([\w:.-]+)") +LEGACY_INSTRUCTION_PATHS = [ + "agent-skills", + "backend/.skills", + "apps/admin-frontend/.skills", + "apps/admin-frontend/.agent.md", + ".claude/agents", + "prompts", +] + +STALE_TEACHING_PHRASES = { + "Every repository interface method takes `TenantContext`": ( + "frontend repositories obtain tenant identity through GetRequestSession" + ), + "Repository methods still take `TenantContext`": ( + "frontend repositories obtain tenant identity through GetRequestSession" + ), + "JWT claim (most likely": "X-Tenant-Id is an established, required boundary", + "Automatic tenant assignment has no automated regression test": ( + "ServicesService.PersistenceTests covers tenant assignment and query isolation" + ), + "Promise.reject(new Error('not implemented in this fake'))": ( + "expected frontend fake failures resolve Result.failure" + ), + "Catalog currently implements Categories": ( + "feature progress belongs in apps/admin-frontend/docs/STATUS.md" + ), + "Auth currently has real use cases": ( + "durable instructions describe the architectural decision, not current examples" + ), + "There are no integration tests": ( + "inspect current unit, persistence, contract, and runtime-smoke coverage" + ), + "README.md`'s Versions table": ( + "runtime pins and docs/adr/0032 are the executable version sources" + ), +} + +REMOVED_PRODUCT_TERMS = { + "graph" + "ify": "removed visualization tool must not return in repository content", +} + +TEXT_SCAN_EXCLUDED_DIRS = { + ".git", + ".vs", + "bin", + "coverage", + "dist", + "node_modules", + "obj", + "worktrees", +} + def check_agents_md_exists() -> list[str]: problems = [] @@ -87,12 +146,38 @@ def check_claude_md_imports() -> list[str]: if not path.is_file(): problems.append(f"missing required file: {_rel(path)}") continue - content = path.read_text(encoding="utf-8") - if "@AGENTS.md" not in content: - problems.append(f"{_rel(path)} does not import @AGENTS.md") + content = path.read_text(encoding="utf-8").strip() + if content != "@AGENTS.md": + problems.append(f"{_rel(path)} must contain only the @AGENTS.md import") return problems +def check_copilot_bridge() -> list[str]: + if not REQUIRED_COPILOT_INSTRUCTIONS.is_file(): + return [f"missing required file: {_rel(REQUIRED_COPILOT_INSTRUCTIONS)}"] + content = REQUIRED_COPILOT_INSTRUCTIONS.read_text(encoding="utf-8") + problems = [] + if "root `AGENTS.md`" not in content or "nearest nested `AGENTS.md`" not in content: + problems.append(".github/copilot-instructions.md does not route to root and nested AGENTS.md") + if ".agents/skills/" not in content: + problems.append(".github/copilot-instructions.md does not route to .agents/skills/") + return problems + + +def check_local_agent_state_ignored() -> list[str]: + gitignore = REPO_ROOT / ".gitignore" + if not gitignore.is_file(): + return ["missing .gitignore"] + entries = { + line.strip() + for line in gitignore.read_text(encoding="utf-8").splitlines() + if line.strip() and not line.lstrip().startswith("#") + } + if LOCAL_SETTINGS_IGNORE not in entries: + return [f".gitignore must contain {LOCAL_SETTINGS_IGNORE}"] + return [] + + def _parse_frontmatter(text: str) -> tuple[dict[str, str], str] | None: """Return (fields, raw_frontmatter_text) for a '---'-delimited YAML-ish frontmatter block, or None if the file has none. Deliberately simple @@ -122,7 +207,7 @@ def _parse_frontmatter(text: str) -> tuple[dict[str, str], str] | None: def check_skill_frontmatter() -> list[str]: problems = [] - source_dir = REPO_ROOT / "agent-skills" + source_dir = REPO_ROOT / ".agents" / "skills" if not source_dir.is_dir(): return [f"missing canonical skills source: {_rel(source_dir)}"] @@ -167,7 +252,7 @@ def check_skill_frontmatter() -> list[str]: def check_skills_synced() -> list[str]: problems = [] - source_dir = REPO_ROOT / "agent-skills" + source_dir = REPO_ROOT / ".agents" / "skills" if not source_dir.is_dir(): return [f"missing canonical skills source: {_rel(source_dir)}"] @@ -179,7 +264,7 @@ def check_skills_synced() -> list[str]: for rel in divergent: problems.append(f"{label}: divergent skill file {rel.as_posix()} (run sync_agent_skills.py)") for rel in extra: - problems.append(f"{label}: extra skill file {rel.as_posix()} not in agent-skills/ (manual copy?)") + problems.append(f"{label}: extra skill file {rel.as_posix()} not in .agents/skills/ (manual copy?)") return problems @@ -194,25 +279,97 @@ def check_no_codex_skills_dir() -> list[str]: return [] +def check_no_legacy_instruction_layers() -> list[str]: + problems = [] + for rel in LEGACY_INSTRUCTION_PATHS: + path = REPO_ROOT / rel + has_content = path.is_file() or ( + path.is_dir() and any(candidate.is_file() for candidate in path.rglob("*")) + ) + if has_content: + problems.append( + f"{rel} exists - use AGENTS.md and canonical .agents/skills/ only" + ) + return problems + + def _governance_adjacent_files() -> list[Path]: """Every file that might reasonably cite a docs/adr/NNNN or scripts/*.py reference: the fixed governance docs/CLAUDE.md files, plus every - canonical skill, Claude Code subagent, and tool-neutral prompt template - - all of which are exactly the kind of governance-adjacent content that - can go stale unnoticed if left out of this scan.""" + canonical skill - all of which are exactly the kind of governance-adjacent + content that can go stale unnoticed if left out of this scan.""" fixed = [ REPO_ROOT / rel for rel in list(GOVERNANCE_DOC_GLOBS) + ["CLAUDE.md", "backend/CLAUDE.md", "apps/admin-frontend/CLAUDE.md"] ] globbed = ( - sorted((REPO_ROOT / "agent-skills").glob("*/SKILL.md")) - + sorted((REPO_ROOT / ".claude" / "agents").glob("*.md")) - + sorted((REPO_ROOT / "prompts").glob("*.md")) + sorted((REPO_ROOT / ".agents" / "skills").rglob("*.md")) ) return fixed + globbed +def check_no_known_stale_teaching() -> list[str]: + problems = [] + for doc_path in _governance_adjacent_files(): + if not doc_path.is_file(): + continue + text = doc_path.read_text(encoding="utf-8") + for phrase, current_rule in STALE_TEACHING_PHRASES.items(): + if phrase in text: + problems.append( + f"{_rel(doc_path)} teaches stale phrase {phrase!r} - {current_rule}" + ) + + new_service_dir = REPO_ROOT / ".agents" / "skills" / "agenza-backend-new-service" + versioned_reference = re.compile(r"]*\bVersion=") + if new_service_dir.is_dir(): + for path in new_service_dir.rglob("*.md"): + if versioned_reference.search(path.read_text(encoding="utf-8")): + problems.append( + f"{_rel(path)} copies a versioned PackageReference - use " + "backend/Directory.Packages.props and live project files" + ) + + copied_backend_template = ( + REPO_ROOT + / ".agents" + / "skills" + / "agenza-backend-use-case" + / "references" + / "templates.md" + ) + if copied_backend_template.exists(): + problems.append( + f"{_rel(copied_backend_template)} exists - use the live Tags vertical " + "instead of copied implementation templates" + ) + + return problems + + +def check_removed_product_terms_absent() -> list[str]: + problems = [] + for root, directories, filenames in os.walk(REPO_ROOT): + directories[:] = [ + directory + for directory in directories + if directory not in TEXT_SCAN_EXCLUDED_DIRS + ] + for filename in filenames: + path = Path(root) / filename + try: + if path.stat().st_size > 2_000_000: + continue + text = path.read_text(encoding="utf-8").casefold() + except (OSError, UnicodeDecodeError): + continue + for term, rule in REMOVED_PRODUCT_TERMS.items(): + if term in text: + problems.append(f"{_rel(path)} contains removed term {term!r} - {rule}") + return problems + + def check_adr_references() -> list[str]: problems = [] adr_dir = REPO_ROOT / "docs" / "adr" @@ -271,10 +428,10 @@ def check_referenced_skills_exist() -> list[str]: seen.add(match.group(1)) for skill_name in sorted(seen): - skill_file = REPO_ROOT / "agent-skills" / skill_name / "SKILL.md" + skill_file = REPO_ROOT / ".agents" / "skills" / skill_name / "SKILL.md" if not skill_file.is_file(): problems.append( - f"referenced skill agent-skills/{skill_name} does not exist" + f"referenced skill .agents/skills/{skill_name} does not exist" ) return problems @@ -314,9 +471,14 @@ def _rel(path: Path) -> str: CHECKS = [ ("AGENTS.md files present", check_agents_md_exists), ("CLAUDE.md files import @AGENTS.md", check_claude_md_imports), + ("Copilot instructions bridge present", check_copilot_bridge), + ("tool-local agent state ignored", check_local_agent_state_ignored), ("canonical skill frontmatter valid", check_skill_frontmatter), - ("skills synced to .agents/ and .claude/", check_skills_synced), + ("skills synced to .claude/", check_skills_synced), ("no .codex/skills distribution dir", check_no_codex_skills_dir), + ("no legacy local instruction layers", check_no_legacy_instruction_layers), + ("no known stale teaching patterns", check_no_known_stale_teaching), + ("removed product terms absent", check_removed_product_terms_absent), ("ADR references resolve", check_adr_references), ("referenced skills exist", check_referenced_skills_exist), ("referenced scripts exist", check_referenced_scripts_exist), diff --git a/scripts/sync_agent_skills.py b/scripts/sync_agent_skills.py index 9e3b8ed..1c9cc61 100644 --- a/scripts/sync_agent_skills.py +++ b/scripts/sync_agent_skills.py @@ -1,7 +1,6 @@ #!/usr/bin/env python3 -"""Sync agent-skills/ (the single editable skill source) into the two -tool-specific distribution directories: .agents/skills/ (OpenAI Codex) and -.claude/skills/ (Claude Code). +"""Sync .agents/skills/ (the portable editable skill source used by Codex and +GitHub Copilot) into .claude/skills/ for Claude Code. Comparison is by content hash, never by timestamp or file mtime, so a checkout/rebase that only touches mtimes never reports a false divergence. @@ -24,9 +23,8 @@ from pathlib import Path REPO_ROOT = Path(__file__).resolve().parent.parent -SOURCE_DIR = REPO_ROOT / "agent-skills" +SOURCE_DIR = REPO_ROOT / ".agents" / "skills" TARGET_DIRS = [ - REPO_ROOT / ".agents" / "skills", REPO_ROOT / ".claude" / "skills", ] @@ -102,14 +100,14 @@ def main(argv: list[str] | None = None) -> int: "--source", type=Path, default=SOURCE_DIR, - help="canonical skills source directory (default: agent-skills/)", + help="canonical skills source directory (default: .agents/skills/)", ) parser.add_argument( "--targets", type=Path, nargs="*", default=None, - help="override the distribution target directories (default: .agents/skills, .claude/skills)", + help="override the distribution target directories (default: .claude/skills)", ) args = parser.parse_args(argv) diff --git a/scripts/tests/test_check_agent_governance.py b/scripts/tests/test_check_agent_governance.py index ec668d5..154d2b8 100644 --- a/scripts/tests/test_check_agent_governance.py +++ b/scripts/tests/test_check_agent_governance.py @@ -29,6 +29,12 @@ def _base_repo(self) -> None: self._write("CLAUDE.md", "@AGENTS.md\n") self._write("backend/CLAUDE.md", "@AGENTS.md\n") self._write("apps/admin-frontend/CLAUDE.md", "@AGENTS.md\n") + self._write( + ".github/copilot-instructions.md", + "Read the root `AGENTS.md` and nearest nested `AGENTS.md`.\n" + "Use .agents/skills/ when relevant.\n", + ) + self._write(".gitignore", "**/.claude/settings.local.json\n") self._write( "apps/admin-frontend/package.json", json.dumps({"scripts": {"lint": "eslint .", "build": "tsc -b && vite build"}}), @@ -48,6 +54,7 @@ def _patch(self): self.root / "backend" / "CLAUDE.md", self.root / "apps" / "admin-frontend" / "CLAUDE.md", ], + REQUIRED_COPILOT_INSTRUCTIONS=self.root / ".github" / "copilot-instructions.md", GOVERNANCE_DOC_GLOBS=[ "AGENTS.md", "backend/AGENTS.md", @@ -78,7 +85,7 @@ def test_claude_md_missing_import_is_reported(self) -> None: with self._patch(): problems = cag.check_claude_md_imports() - self.assertTrue(any("does not import" in p for p in problems)) + self.assertTrue(any("must contain only" in p for p in problems)) def test_claude_md_with_import_passes(self) -> None: self._base_repo() @@ -87,10 +94,61 @@ def test_claude_md_with_import_passes(self) -> None: self.assertEqual(problems, []) + def test_claude_md_with_duplicate_instructions_is_reported(self) -> None: + self._base_repo() + self._write("CLAUDE.md", "@AGENTS.md\n\nRun tests again.\n") + + with self._patch(): + problems = cag.check_claude_md_imports() + + self.assertTrue(any("must contain only" in p for p in problems)) + + def test_missing_copilot_bridge_is_reported(self) -> None: + with self._patch(): + problems = cag.check_copilot_bridge() + + self.assertTrue(any("copilot-instructions.md" in p for p in problems)) + + def test_valid_copilot_bridge_passes(self) -> None: + self._base_repo() + with self._patch(): + problems = cag.check_copilot_bridge() + + self.assertEqual(problems, []) + + def test_missing_local_settings_ignore_is_reported(self) -> None: + self._write(".gitignore", "node_modules/\n") + with self._patch(): + problems = cag.check_local_agent_state_ignored() + + self.assertTrue(any("settings.local.json" in p for p in problems)) + + def test_local_settings_ignore_passes(self) -> None: + self._base_repo() + with self._patch(): + problems = cag.check_local_agent_state_ignored() + + self.assertEqual(problems, []) + + def test_removed_visualization_term_is_reported(self) -> None: + removed_term = "graph" + "ify" + self._write("apps/admin-frontend/.prettierignore", f"{removed_term}-out\n") + with self._patch(): + problems = cag.check_removed_product_terms_absent() + + self.assertTrue(any(removed_term in problem for problem in problems)) + + def test_removed_product_scan_ignores_local_caches(self) -> None: + self._write("backend/.vs/session.txt", "graph" + "ify\n") + with self._patch(): + problems = cag.check_removed_product_terms_absent() + + self.assertEqual(problems, []) + # -- skill frontmatter ------------------------------------------------- def test_skill_frontmatter_missing_description_is_reported(self) -> None: - self._write("agent-skills/foo/SKILL.md", "---\nname: foo\n---\n\n# Foo\n") + self._write(".agents/skills/foo/SKILL.md", "---\nname: foo\n---\n\n# Foo\n") with self._patch(): problems = cag.check_skill_frontmatter() @@ -98,7 +156,7 @@ def test_skill_frontmatter_missing_description_is_reported(self) -> None: self.assertTrue(any("description" in p for p in problems)) def test_skill_frontmatter_name_mismatch_is_reported(self) -> None: - self._write("agent-skills/foo/SKILL.md", "---\nname: bar\ndescription: does things\n---\n") + self._write(".agents/skills/foo/SKILL.md", "---\nname: bar\ndescription: does things\n---\n") with self._patch(): problems = cag.check_skill_frontmatter() @@ -107,7 +165,7 @@ def test_skill_frontmatter_name_mismatch_is_reported(self) -> None: def test_skill_frontmatter_forbidden_key_is_reported(self) -> None: self._write( - "agent-skills/foo/SKILL.md", + ".agents/skills/foo/SKILL.md", "---\nname: foo\ndescription: does things\nallowed-tools: Read\n---\n", ) @@ -117,7 +175,7 @@ def test_skill_frontmatter_forbidden_key_is_reported(self) -> None: self.assertTrue(any("allowed-tools" in p for p in problems)) def test_skill_frontmatter_valid_passes(self) -> None: - self._write("agent-skills/foo/SKILL.md", "---\nname: foo\ndescription: does things\n---\n\n# Foo\n") + self._write(".agents/skills/foo/SKILL.md", "---\nname: foo\ndescription: does things\n---\n\n# Foo\n") with self._patch(): problems = cag.check_skill_frontmatter() @@ -134,7 +192,7 @@ def test_colon_in_a_wrapped_description_continuation_is_not_a_forbidden_key(self # even though _parse_frontmatter itself correctly folds it into the # description value. self._write( - "agent-skills/foo/SKILL.md", + ".agents/skills/foo/SKILL.md", "\n".join( [ "---", @@ -156,25 +214,21 @@ def test_colon_in_a_wrapped_description_continuation_is_not_a_forbidden_key(self # -- skill sync ---------------------------------------------------------- - def test_unsynced_skills_are_reported_for_both_targets(self) -> None: - self._write("agent-skills/foo/SKILL.md", "---\nname: foo\ndescription: does things\n---\n") - agents_target = self.root / ".agents" / "skills" + def test_unsynced_skills_are_reported_for_claude_target(self) -> None: + self._write(".agents/skills/foo/SKILL.md", "---\nname: foo\ndescription: does things\n---\n") claude_target = self.root / ".claude" / "skills" - with self._patch(), mock.patch.object(sas, "TARGET_DIRS", [agents_target, claude_target]): + with self._patch(), mock.patch.object(sas, "TARGET_DIRS", [claude_target]): problems = cag.check_skills_synced() - self.assertTrue(any(".agents/skills" in p for p in problems)) self.assertTrue(any(".claude/skills" in p for p in problems)) def test_synced_skills_pass(self) -> None: - self._write("agent-skills/foo/SKILL.md", "---\nname: foo\ndescription: does things\n---\n") - agents_target = self.root / ".agents" / "skills" + self._write(".agents/skills/foo/SKILL.md", "---\nname: foo\ndescription: does things\n---\n") claude_target = self.root / ".claude" / "skills" - sas.sync_target(self.root / "agent-skills", agents_target) - sas.sync_target(self.root / "agent-skills", claude_target) + sas.sync_target(self.root / ".agents" / "skills", claude_target) - with self._patch(), mock.patch.object(sas, "TARGET_DIRS", [agents_target, claude_target]): + with self._patch(), mock.patch.object(sas, "TARGET_DIRS", [claude_target]): problems = cag.check_skills_synced() self.assertEqual(problems, []) @@ -195,6 +249,76 @@ def test_codex_skills_dir_absent_passes(self) -> None: self.assertEqual(problems, []) + # -- legacy/stale instruction layers -------------------------------------- + + def test_local_skill_directory_is_reported(self) -> None: + self._write("backend/.skills/legacy/SKILL.md", "legacy\n") + + with self._patch(): + problems = cag.check_no_legacy_instruction_layers() + + self.assertTrue(any("backend/.skills" in problem for problem in problems)) + + def test_standalone_agent_file_is_reported(self) -> None: + self._write("apps/admin-frontend/.agent.md", "duplicate rules\n") + + with self._patch(): + problems = cag.check_no_legacy_instruction_layers() + + self.assertTrue(any(".agent.md" in problem for problem in problems)) + + def test_old_canonical_skill_directory_is_reported(self) -> None: + self._write("agent-skills/legacy/SKILL.md", "legacy\n") + + with self._patch(): + problems = cag.check_no_legacy_instruction_layers() + + self.assertTrue(any("agent-skills" in problem for problem in problems)) + + def test_tool_specific_agents_are_reported(self) -> None: + self._write(".claude/agents/reviewer.md", "duplicate workflow\n") + + with self._patch(): + problems = cag.check_no_legacy_instruction_layers() + + self.assertTrue(any(".claude/agents" in problem for problem in problems)) + + def test_prompt_directory_is_reported(self) -> None: + self._write("prompts/task.md", "duplicate workflow\n") + + with self._patch(): + problems = cag.check_no_legacy_instruction_layers() + + self.assertTrue(any("prompts" in problem for problem in problems)) + + def test_stale_phrase_in_skill_reference_is_reported(self) -> None: + self._write( + ".agents/skills/foo/references/testing.md", + "Automatic tenant assignment has no automated regression test\n", + ) + + with self._patch(): + problems = cag.check_no_known_stale_teaching() + + self.assertTrue(any("no automated regression" in problem for problem in problems)) + + def test_versioned_package_reference_in_new_service_skill_is_reported(self) -> None: + self._write( + ".agents/skills/agenza-backend-new-service/SKILL.md", + '\n', + ) + + with self._patch(): + problems = cag.check_no_known_stale_teaching() + + self.assertTrue(any("versioned PackageReference" in problem for problem in problems)) + + def test_legacy_instruction_layers_absent_passes(self) -> None: + with self._patch(): + problems = cag.check_no_legacy_instruction_layers() + + self.assertEqual(problems, []) + # -- ADR references ---------------------------------------------------- def test_dangling_adr_reference_is_reported(self) -> None: @@ -218,20 +342,20 @@ def test_valid_adr_reference_passes(self) -> None: # -- referenced skills ------------------------------------------------- def test_missing_referenced_skill_is_reported(self) -> None: - self._write("AGENTS.md", "Use agent-skills/missing-skill for this task.\n") + self._write("AGENTS.md", "Use .agents/skills/missing-skill for this task.\n") with self._patch(): problems = cag.check_referenced_skills_exist() self.assertEqual( problems, - ["referenced skill agent-skills/missing-skill does not exist"], + ["referenced skill .agents/skills/missing-skill does not exist"], ) def test_present_referenced_skill_passes(self) -> None: - self._write("AGENTS.md", "Use agent-skills/present-skill for this task.\n") + self._write("AGENTS.md", "Use .agents/skills/present-skill for this task.\n") self._write( - "agent-skills/present-skill/SKILL.md", + ".agents/skills/present-skill/SKILL.md", "---\nname: present-skill\ndescription: does things\n---\n", ) @@ -251,18 +375,24 @@ def test_missing_referenced_script_is_reported(self) -> None: self.assertTrue(any("does_not_exist.py" in p for p in problems)) - def test_missing_referenced_script_in_subagent_file_is_reported(self) -> None: + def test_missing_referenced_script_in_skill_is_reported(self) -> None: self._base_repo() - self._write(".claude/agents/some-reviewer.md", "Run scripts/does_not_exist.py first.\n") + self._write( + ".agents/skills/some-reviewer/SKILL.md", + "---\nname: some-reviewer\ndescription: review\n---\nRun scripts/does_not_exist.py.\n", + ) with self._patch(): problems = cag.check_referenced_scripts_exist() self.assertTrue(any("does_not_exist.py" in p for p in problems)) - def test_missing_adr_reference_in_prompt_template_is_reported(self) -> None: + def test_missing_adr_reference_in_skill_is_reported(self) -> None: self._write("docs/adr/0001-something.md", "# ADR\n") - self._write("prompts/some-template.md", "See docs/adr/0099 for context.\n") + self._write( + ".agents/skills/some-skill/SKILL.md", + "---\nname: some-skill\ndescription: test\n---\nSee docs/adr/0099.\n", + ) with self._patch(): problems = cag.check_adr_references() @@ -303,14 +433,12 @@ def test_documented_npm_command_that_exists_passes(self) -> None: def test_run_checks_clean_repo_has_no_problems(self) -> None: self._base_repo() - self._write("agent-skills/foo/SKILL.md", "---\nname: foo\ndescription: does things\n---\n") + self._write(".agents/skills/foo/SKILL.md", "---\nname: foo\ndescription: does things\n---\n") self._write("docs/adr/0001-something.md", "# ADR\n") - agents_target = self.root / ".agents" / "skills" claude_target = self.root / ".claude" / "skills" - sas.sync_target(self.root / "agent-skills", agents_target) - sas.sync_target(self.root / "agent-skills", claude_target) + sas.sync_target(self.root / ".agents" / "skills", claude_target) - with self._patch(), mock.patch.object(sas, "TARGET_DIRS", [agents_target, claude_target]): + with self._patch(), mock.patch.object(sas, "TARGET_DIRS", [claude_target]): problems = cag.run_checks() self.assertEqual(problems, [])