From 204edaf16d24b03a3629fa618e1fd3ab51ca1b6b Mon Sep 17 00:00:00 2001 From: aarroyo Date: Mon, 3 Aug 2026 08:48:37 -0500 Subject: [PATCH 1/2] feat(sdlc): contrast the phase record against the Core catalogue Advances CP-02. The Tracker had TWO artifact vocabularies that never met. `/phase-artifact-profiles` is derived from the Core, and nothing operational consulted it: grep for `IPhaseArtifactProfileSource` returned its own definition, DI, the sync service and that one endpoint. Meanwhile the record people actually fill starts with `Items` empty and `SetItem` accepts any `type` string at all. So a phase could be completed end to end with artifacts the Core does not declare, and no surface would say so. That is CP-02's Risk verbatim: the Tracker looks like it governs the SDLC while governing a copy of the standard. `GetPhaseArtifact` now resolves the phase's catalogue through the SAME seam the read-only endpoint reads -- a second copy here would have rebuilt the defect -- and returns a `coreCatalog` block. It REPORTS, it does not enforce. T-056: what a gate demands is tenant configuration, not engine code. Nothing rejects an item, changes a status or blocks a phase. Both directions are stated, because reporting only the declared side would let a record full of off-standard items look complete. Two absences are kept apart: no catalogue for the phase omits the block rather than returning an empty one. "The standard was not available" is not "the standard asks for nothing here", and the second is the kind of confident emptiness a governance surface must never produce. Scope, stated rather than implied: - construction/QA/release only. Discovery and Design keep bespoke verticals and are NOT covered -- the generic path is `SdlcPhases.GenericGatePhases`. - criterion 2 (per-tenant required/optional/not-applicable) is UNSTARTED. There is no tenant override on this catalogue and the handler takes no tenant. - `ScorecardEvaluator` and the `GateSubmission` path still do not read the source. Verified: 5 new tests pass; full suite shows 10 failures, all of which also fail on a clean worktree at HEAD (baseline 22, environment-dependent persistence/integration tests). Nothing fails only because of this change -- measured, not assumed. Co-Authored-By: Claude Opus 5 --- docs/audit/tracker-gap-reference-catalog.md | 10 +- docs/audit/tracker-gap-tracking.md | 2 +- .../PhaseArtifact/DTOs/PhaseArtifactDto.cs | 100 ++++++++++++ .../GetPhaseArtifactQueryHandler.cs | 42 ++++- .../Sdlc/GetPhaseArtifactCoreCatalogTests.cs | 149 ++++++++++++++++++ 5 files changed, 297 insertions(+), 6 deletions(-) create mode 100644 src/apps/tracker-api/Tracker.Tests/Application/Sdlc/GetPhaseArtifactCoreCatalogTests.cs diff --git a/docs/audit/tracker-gap-reference-catalog.md b/docs/audit/tracker-gap-reference-catalog.md index 045aa00c..da0c2212 100644 --- a/docs/audit/tracker-gap-reference-catalog.md +++ b/docs/audit/tracker-gap-reference-catalog.md @@ -46,10 +46,14 @@ This catalog explains each gap: problem, purpose, evidence, closure criteria, an - **Criticality:** P0 · **Complexity:** M - **Proposed fix:** Crear un `CoreArtifactFormatCatalog` que sincronice el catálogo por fase desde el Core, materialice una vista tenant-aware y permita overrides locales declarados, auditables y no destructivos. - **Acceptance criteria:** - - [ ] Discovery, design, construction, QA y release usan un catálogo de artefactos derivado del Core. - - [ ] Cada tenant puede marcar artefactos como requeridos, opcionales o no aplicables sin modificar el estándar base. - - [ ] El scorecard y las compuertas leen la misma fuente de catálogo. + - [x] **Parcial (construction/QA/release):** el expediente operativo (`GetPhaseArtifact`) ya se contrasta contra el catálogo derivado del Core, resolviendo por `IPhaseArtifactProfileSource` — la misma costura que lee `/phase-artifact-profiles`. Discovery y Design conservan sus verticales a medida y NO están cubiertas; el camino genérico solo cubre esas tres fases (`SdlcPhases.GenericGatePhases`). + - [ ] Cada tenant puede marcar artefactos como requeridos, opcionales o no aplicables sin modificar el estándar base. **Sin empezar.** No existe ningún override por tenant sobre este catálogo: el handler no recibe tenant y no hay entidad de aplicabilidad. Es la otra mitad del título de esta ficha. + - [ ] El scorecard y las compuertas leen la misma fuente de catálogo. **Parcial:** el expediente y el endpoint de solo lectura ya comparten fuente; `ScorecardEvaluator` y el camino de `GateSubmission` siguen sin consultarla. - [ ] `GAP-020` queda re-evaluado a la luz del catálogo vivo. + +**Hallazgo que motivó el cambio (2026-08-03):** el Tracker tenía DOS vocabularios de artefactos que nunca se encontraban. `/phase-artifact-profiles` derivaba del Core pero nada operativo lo consultaba; el expediente que la gente llena aceptaba cualquier cadena en `type` y arrancaba con `Items` vacío. Una fase podía completarse entera con artefactos que el Core no declara sin que ninguna superficie lo dijera — que es el **Risk** de esta ficha al pie de la letra. + +**Reporta, no impone (T-056).** El bloque `coreCatalog` dice las dos direcciones (lo declarado que falta, y lo presente que el Core no declara) y ahí se detiene. Nada rechaza un ítem ni cambia un estado: qué exige una compuerta lo configura el tenant, no lo cablea el motor. Se distingue además «no había catálogo para la fase» de «el catálogo no pide nada»: la primera omite el bloque en vez de devolver una lista vacía. - **Dependencies:** CP-01. #### CP-03 diff --git a/docs/audit/tracker-gap-tracking.md b/docs/audit/tracker-gap-tracking.md index ec4578ca..b014dd87 100644 --- a/docs/audit/tracker-gap-tracking.md +++ b/docs/audit/tracker-gap-tracking.md @@ -17,7 +17,7 @@ This board is the single source of truth for Tracker technical debt, gaps, oppor | ID | Gap | Qué significa | Ejemplo | Component | Phase | Criticality | Complexity | Status | |---|---|---|---|:---:|:---:|:---:|:---:|:---:| -| [`CP-02`](./tracker-gap-reference-catalog.md#cp-02) | El Tracker no tiene un catálogo vivo de formatos de artefacto por fase sincronizado con el Core y parametrizable por tenant | La iniciativa puede avanzar con una lista local de entregables que ya no coincide con el estándar que el Core evalúa | Core publica gates y schemas para `gate-f1..f5`; Tracker conserva catálogos derivados y un gap histórico de URLs de esquema | `Artifacts/Config` | Cross | P0 | M | `PENDING` | +| [`CP-02`](./tracker-gap-reference-catalog.md#cp-02) | El Tracker no tiene un catálogo vivo de formatos de artefacto por fase sincronizado con el Core y parametrizable por tenant | La iniciativa puede avanzar con una lista local de entregables que ya no coincide con el estándar que el Core evalúa | El expediente operativo de construction/QA/release ya se contrasta contra el catálogo derivado del Core por la misma costura que el endpoint de solo lectura; falta la parametrización por tenant, y el scorecard y las compuertas siguen sin leer esa fuente | `Artifacts/Config` | Cross | P0 | M | `PENDING` | | [`CP-06`](./tracker-gap-reference-catalog.md#cp-06) | Falta una matriz por tenant que convierta resultados del Core en decisión de compuerta distinguiendo bloqueante, requisito local y advisory | Un hallazgo técnico opcional puede terminar bloqueando una fase, o un requisito contractual puede quedar como simple recomendación | El gateway lee `overallVerdict` y `results.gate`, pero no aplica una política tenant-aware sobre `qualitySignals`, `phaseArtifacts`, `design`, `architecture` o recomendaciones | `Governance/Tenancy` | Cross | P0 | M | `PENDING` | | [`CP-12`](./tracker-gap-reference-catalog.md#cp-12) | No existe una mesa de ejecución documental por gate que permita operar cada artefacto tenant-aware de inicio a aprobación | El usuario sabe qué artefactos pide el gate, pero no tiene un lugar único para crear la copia del template, editarla, ver su estado y llevarla a aprobación | Los catálogos y wizards cubren piezas; falta el expediente operativo por iniciativa/fase/gate/artefacto | `WEB/Artifacts` | Cross | P0 | L | `PENDING` | | [`CP-15`](./tracker-gap-reference-catalog.md#cp-15) | Los artefactos y gates no tienen flujos de aprobación configurables por tenant con personas y agentes autorizados | Una fase puede depender de revisores humanos o agentes especializados, pero el Tracker no modela secuencias, quórum, delegaciones ni HITL por artefacto | Las aprobaciones existentes no resuelven miembros/equipos/agentes del tenant para cada artefacto SDLC | `Governance/Tenancy` | Cross | P0 | L | `PENDING` | diff --git a/src/apps/tracker-api/Tracker.Application/Sdlc/PhaseArtifact/DTOs/PhaseArtifactDto.cs b/src/apps/tracker-api/Tracker.Application/Sdlc/PhaseArtifact/DTOs/PhaseArtifactDto.cs index 61ce04a3..edb8dd8f 100644 --- a/src/apps/tracker-api/Tracker.Application/Sdlc/PhaseArtifact/DTOs/PhaseArtifactDto.cs +++ b/src/apps/tracker-api/Tracker.Application/Sdlc/PhaseArtifact/DTOs/PhaseArtifactDto.cs @@ -12,6 +12,51 @@ public sealed class ArtifactItemDto public string Status { get; init; } = string.Empty; } +/// +/// CP-02 — one artifact of the phase AS THE CORE DECLARES IT, next to whether this +/// record actually carries it. +/// +public sealed class CoreCatalogArtifactDto +{ + public string ArtifactKind { get; init; } = string.Empty; + public string Label { get; init; } = string.Empty; + + /// `binding` in the Core registry. Says what the STANDARD expects, not what this Tracker enforces. + public bool Required { get; init; } + + /// Whether an item of this kind exists on the record. Present, not valid — nothing here reads content. + public bool Present { get; init; } +} + +/// +/// CP-02 — the phase's artifact catalogue as the Core declares it, resolved through the +/// SAME seam the read-only `/phase-artifact-profiles` endpoint reads. +/// +/// Why this is REPORTED and never enforced. T-056: what a gate demands is +/// tenant configuration, not engine code. Comparing the record against the standard is +/// a fact; deciding that a missing artifact blocks a phase is an opinion, and it is not +/// ours to wire. So this block states both directions and stops there. +/// +/// Both directions, deliberately. `Artifacts` says what the Core declares +/// and whether the record carries it; says what the record +/// carries that the Core does not declare. Reporting only the first would make a record +/// full of off-standard items look complete. +/// +public sealed class PhaseCoreCatalogDto +{ + /// + /// `core-sync` once a sync has succeeded, `core-standin` while the mirror is being + /// served. A reader must be able to tell a synced answer from a mirrored one by + /// reading the payload rather than by assuming a sync happened. + /// + public string Source { get; init; } = string.Empty; + + public IReadOnlyList Artifacts { get; init; } = new List(); + + /// Item kinds on the record that the Core catalogue does not declare. + public IReadOnlyList UnknownItemKinds { get; init; } = new List(); +} + public sealed class PhaseArtifactDto { public Guid Id { get; init; } @@ -20,6 +65,13 @@ public sealed class PhaseArtifactDto public string Phase { get; init; } = string.Empty; public string Status { get; init; } = string.Empty; public IReadOnlyList Items { get; init; } = new List(); + + /// + /// Null when no catalogue was resolved for the phase — which is NOT the same as an + /// empty catalogue, and the difference matters: one means "the standard was not + /// available", the other means "the standard asks for nothing here". + /// + public PhaseCoreCatalogDto? CoreCatalog { get; init; } } public static class PhaseArtifactDtoMapper @@ -34,6 +86,54 @@ public static class PhaseArtifactDtoMapper Items = artifact.Items.Select(ToDto).ToList() }; + /// + /// CP-02 — the same record, contrasted against the Core's catalogue for its phase. + /// + /// Item kinds are matched case-insensitively: the catalogue writes slugs and the + /// record accepts whatever a caller sent, so a case difference would report a present + /// artifact as missing — a false gap on a governance surface, which is worse than no + /// comparison at all. + /// + public static PhaseArtifactDto ToDto( + DomainPhaseArtifact artifact, + string source, + IReadOnlyList<(string Kind, string Label, bool Required)> catalog) + { + var present = artifact.Items + .Select(i => i.Type) + .Where(t => !string.IsNullOrWhiteSpace(t)) + .ToHashSet(StringComparer.OrdinalIgnoreCase); + + var declared = catalog.Select(c => c.Kind).ToHashSet(StringComparer.OrdinalIgnoreCase); + + return new PhaseArtifactDto + { + Id = artifact.Id, + TenantId = artifact.TenantId, + InitiativeId = artifact.InitiativeId, + Phase = artifact.Phase, + Status = artifact.Status, + Items = artifact.Items.Select(ToDto).ToList(), + CoreCatalog = new PhaseCoreCatalogDto + { + Source = source, + Artifacts = catalog + .Select(c => new CoreCatalogArtifactDto + { + ArtifactKind = c.Kind, + Label = c.Label, + Required = c.Required, + Present = present.Contains(c.Kind), + }) + .ToList(), + UnknownItemKinds = present + .Where(t => !declared.Contains(t)) + .OrderBy(t => t, StringComparer.OrdinalIgnoreCase) + .ToList(), + }, + }; + } + private static ArtifactItemDto ToDto(ArtifactItem i) => new() { Type = i.Type, diff --git a/src/apps/tracker-api/Tracker.Application/Sdlc/PhaseArtifact/Queries/GetPhaseArtifact/GetPhaseArtifactQueryHandler.cs b/src/apps/tracker-api/Tracker.Application/Sdlc/PhaseArtifact/Queries/GetPhaseArtifact/GetPhaseArtifactQueryHandler.cs index 97f1a82b..6ef91d70 100644 --- a/src/apps/tracker-api/Tracker.Application/Sdlc/PhaseArtifact/Queries/GetPhaseArtifact/GetPhaseArtifactQueryHandler.cs +++ b/src/apps/tracker-api/Tracker.Application/Sdlc/PhaseArtifact/Queries/GetPhaseArtifact/GetPhaseArtifactQueryHandler.cs @@ -1,16 +1,42 @@ using Tracker.Application.Sdlc.PhaseArtifact.DTOs; using Tracker.Domain.Sdlc.PhaseArtifact; +// `PhaseArtifactDto` is a live name in BOTH the governance catalogue and this module. The +// namespace is aliased rather than imported so the two never silently resolve to each other. +using IPhaseArtifactProfileSource = Tracker.Application.Governance.ArtifactFieldSchema.IPhaseArtifactProfileSource; + namespace Tracker.Application.Sdlc.PhaseArtifact.Queries.GetPhaseArtifact; +/// +/// CP-02 — the operative phase record, contrasted against the Core's catalogue. +/// +/// What this closes. The Tracker had TWO artifact vocabularies that never met: +/// `/phase-artifact-profiles`, read-only and derived from the Core, which nothing operational +/// consulted; and this record, which the user actually fills and which accepted any `type` +/// string whatsoever. A phase could therefore be completed entirely with items the Core does +/// not declare, and no surface would say so. That is CP-02's risk verbatim: the Tracker +/// looks like it governs the SDLC while governing a copy of the standard. +/// +/// It reports, it does not enforce. T-056: what a gate demands is tenant +/// configuration, not engine code. Nothing here rejects an item, changes a status, or blocks +/// a phase — the comparison is a fact the caller can act on under its own policy. +/// +/// Same seam, deliberately. It resolves through , +/// the very source the read-only endpoint reads. Two surfaces answering the same question from +/// two catalogues is the defect; a second copy here would have rebuilt it. +/// internal sealed class GetPhaseArtifactQueryHandler : IQueryHandler { private readonly IPhaseArtifactRepository _artifacts; + private readonly IPhaseArtifactProfileSource _profiles; - public GetPhaseArtifactQueryHandler(IPhaseArtifactRepository artifacts) + public GetPhaseArtifactQueryHandler( + IPhaseArtifactRepository artifacts, + IPhaseArtifactProfileSource profiles) { _artifacts = artifacts; + _profiles = profiles; } public async Task Handle( @@ -21,6 +47,18 @@ public GetPhaseArtifactQueryHandler(IPhaseArtifactRepository artifacts) if (artifact is null || artifact.TenantId != request.TenantId) return null; - return PhaseArtifactDtoMapper.ToDto(artifact); + var profile = _profiles.GetProfiles() + .FirstOrDefault(p => string.Equals(p.Phase, artifact.Phase, StringComparison.OrdinalIgnoreCase)); + + // No profile for the phase is NOT an empty profile. Reporting "the standard asks for + // nothing here" when the truth is "the standard was not available" is the kind of + // confident emptiness a governance surface must never produce, so the block is omitted. + if (profile is null) + return PhaseArtifactDtoMapper.ToDto(artifact); + + return PhaseArtifactDtoMapper.ToDto( + artifact, + profile.Source, + profile.Artifacts.Select(a => (a.ArtifactKind, a.Label, a.Required)).ToList()); } } diff --git a/src/apps/tracker-api/Tracker.Tests/Application/Sdlc/GetPhaseArtifactCoreCatalogTests.cs b/src/apps/tracker-api/Tracker.Tests/Application/Sdlc/GetPhaseArtifactCoreCatalogTests.cs new file mode 100644 index 00000000..d5baf1f7 --- /dev/null +++ b/src/apps/tracker-api/Tracker.Tests/Application/Sdlc/GetPhaseArtifactCoreCatalogTests.cs @@ -0,0 +1,149 @@ +using Tracker.Application.Governance.ArtifactFieldSchema; +using Tracker.Application.Sdlc.PhaseArtifact.Queries.GetPhaseArtifact; +using Tracker.Domain.Sdlc.PhaseArtifact; +using DomainPhaseArtifact = Tracker.Domain.Sdlc.PhaseArtifact.PhaseArtifact; +using GovPhaseArtifactDto = Tracker.Application.Governance.ArtifactFieldSchema.PhaseArtifactDto; + +namespace Tracker.Tests.Application.Sdlc; + +/// +/// CP-02 — el expediente operativo contrastado contra el catálogo del Core. +/// +/// Lo que se prueba no es que los campos se copien, sino las propiedades que hacen que +/// este contraste sea seguro en una superficie de gobierno: que REPORTE y no imponga (T-056), +/// que diga las DOS direcciones, que no confunda «no había catálogo» con «el catálogo no pide +/// nada», y que una diferencia de mayúsculas no invente un hueco que no existe. +/// +public class GetPhaseArtifactCoreCatalogTests +{ + private static readonly Guid Tenant = Guid.NewGuid(); + private static readonly Guid Initiative = Guid.NewGuid(); + + private sealed class FakeProfiles : IPhaseArtifactProfileSource + { + private readonly IReadOnlyList _profiles; + public FakeProfiles(string source, IReadOnlyList profiles) + { + Source = source; + _profiles = profiles; + } + + public string Source { get; } + + public IReadOnlyList GetProfiles() => new List + { + new() + { + Phase = "construction", + Source = Source, + Artifacts = _profiles.ToList(), + }, + }; + } + + private sealed class FakeRepo : IPhaseArtifactRepository + { + private readonly DomainPhaseArtifact? _one; + public FakeRepo(DomainPhaseArtifact? one) => _one = one; + + public Task GetByInitiativeAndPhaseAsync( + Guid initiativeId, string phase, CancellationToken ct) => Task.FromResult(_one); + + public Task AddAsync(DomainPhaseArtifact a, CancellationToken ct) => Task.CompletedTask; + public Task UpdateAsync(DomainPhaseArtifact a, CancellationToken ct) => Task.CompletedTask; + } + + private static DomainPhaseArtifact Expediente(params (string Type, string? Ref)[] items) + { + var a = DomainPhaseArtifact.CreateForPhase(Tenant, Initiative, "construction", Guid.NewGuid()).Value; + foreach (var (type, @ref) in items) a.SetItem(type, null, @ref, null); + return a; + } + + private static GovPhaseArtifactDto Declarado(string kind, bool required = true) => + new() { ArtifactKind = kind, Label = kind.ToUpperInvariant(), Required = required }; + + private static Task Consultar( + DomainPhaseArtifact? expediente, IPhaseArtifactProfileSource profiles) => + new GetPhaseArtifactQueryHandler(new FakeRepo(expediente), profiles) + .Handle(new GetPhaseArtifactQuery(Tenant, Initiative, "construction"), CancellationToken.None); + + [Fact] + public async Task DiceLasDosDirecciones_LoQueFaltaYLoQueSobra() + { + // La razón de que existan las dos: reportar solo lo declarado haría que un expediente + // lleno de items fuera de estándar pareciera completo. + var dto = await Consultar( + Expediente(("coverage-report", "cov.xml"), ("invento-local", "x.md")), + new FakeProfiles(PhaseArtifactProfileSourceKind.CoreSync, + new[] { Declarado("coverage-report"), Declarado("integration-evidence") })); + + var cat = dto!.CoreCatalog!; + cat.Artifacts.Single(a => a.ArtifactKind == "coverage-report").Present.Should().BeTrue(); + cat.Artifacts.Single(a => a.ArtifactKind == "integration-evidence").Present.Should().BeFalse(); + cat.UnknownItemKinds.Should().ContainSingle().Which.Should().Be("invento-local"); + } + + [Fact] + public async Task REPORTA_PERO_NO_IMPONE() + { + // T-056. Un artefacto requerido ausente NO puede cambiar el estado del expediente ni + // hacer fallar la consulta: qué exige una compuerta lo configura el tenant, no lo + // cablea el motor. Si esto se rompe, el producto empezó a opinar por su cuenta. + var dto = await Consultar( + Expediente(), + new FakeProfiles(PhaseArtifactProfileSourceKind.CoreSync, new[] { Declarado("coverage-report") })); + + dto.Should().NotBeNull(); + dto!.Status.Should().Be(PhaseArtifactStatus.Draft); + dto.CoreCatalog!.Artifacts.Should().OnlyContain(a => !a.Present); + } + + [Fact] + public async Task SinCatalogoNoEsCatalogoVacio() + { + // Las dos ausencias son distintas y la diferencia importa: «el estándar no estaba + // disponible» no es «el estándar no pide nada aquí». La segunda es la clase de vacío + // confiado que una superficie de gobierno no debe producir. + var sinLaFase = new FakeProfilesOtraFase(); + var dto = await Consultar(Expediente(("coverage-report", "cov.xml")), sinLaFase); + + dto!.CoreCatalog.Should().BeNull(); + dto.Items.Should().ContainSingle("el expediente se sigue sirviendo entero"); + } + + private sealed class FakeProfilesOtraFase : IPhaseArtifactProfileSource + { + public string Source => PhaseArtifactProfileSourceKind.CoreSync; + public IReadOnlyList GetProfiles() => new List + { + new() { Phase = "qa", Source = PhaseArtifactProfileSourceKind.CoreSync }, + }; + } + + [Fact] + public async Task UnaDiferenciaDeMayusculasNoInventaUnHueco() + { + // El catálogo escribe slugs; el expediente acepta lo que le manden. Un cotejo sensible + // a mayúsculas reportaría como ausente un artefacto que está — un hueco falso, que en + // gobierno es peor que no cotejar. + var dto = await Consultar( + Expediente(("Coverage-Report", "cov.xml")), + new FakeProfiles(PhaseArtifactProfileSourceKind.CoreSync, new[] { Declarado("coverage-report") })); + + dto!.CoreCatalog!.Artifacts.Single().Present.Should().BeTrue(); + dto.CoreCatalog.UnknownItemKinds.Should().BeEmpty(); + } + + [Fact] + public async Task LaProcedenciaViajaEnElPayload() + { + // Un lector tiene que poder distinguir una respuesta sincronizada de una espejada + // leyendo el payload, no confiando en que hubo sincronización. + var dto = await Consultar( + Expediente(), + new FakeProfiles(PhaseArtifactProfileSourceKind.StandIn, new[] { Declarado("coverage-report") })); + + dto!.CoreCatalog!.Source.Should().Be(PhaseArtifactProfileSourceKind.StandIn); + } +} From ede9d3dbf0807bbeb45b7b464f2fdb6568dc3853 Mon Sep 17 00:00:00 2001 From: aarroyo Date: Mon, 3 Aug 2026 08:51:28 -0500 Subject: [PATCH 2/2] chore: untrack .codex/, a local tool config committed by accident Not part of CP-02; found because this PR first targeted main and the root-cleanliness guard rejected an unauthorized root folder. `.codex/config.toml` carries an absolute path to one developer's machine (`/Users/.../evolith_tracker/src`), so it was never shareable. It rode in on a `git add -A` in a5f719c and has been on develop and every branch off it since; main has never seen it, which is why nothing complained until now. Removed from the index and added to .gitignore so it cannot return. Left on disk -- it is a working local config, just not the repository's business. Co-Authored-By: Claude Opus 5 --- .codex/config.toml | 10 ---------- .gitignore | 5 +++++ 2 files changed, 5 insertions(+), 10 deletions(-) delete mode 100644 .codex/config.toml diff --git a/.codex/config.toml b/.codex/config.toml deleted file mode 100644 index 8b5006a4..00000000 --- a/.codex/config.toml +++ /dev/null @@ -1,10 +0,0 @@ -[mcp_servers.nx] -command = "npx" -args = [ - "-y", - "nx-mcp@latest", - "/Users/beyondnet/Source/evolith_tracker/src", - "--minimal", - "false", - "--disableTelemetry", -] diff --git a/.gitignore b/.gitignore index d63c798c..1bfc83ba 100644 --- a/.gitignore +++ b/.gitignore @@ -165,3 +165,8 @@ vite.config.ts.timestamp-* # Informe generado por .harness/scripts/coverage-dashboard.mjs; el workflow lo sube # como artifact. No se versiona ni se valida como documentacion redactada. COVERAGE_REPORT.md + +# Local editor/tool config: machine-specific absolute paths, never shared. +# Committed by accident in a5f719c (a `git add -A`) and only caught when a PR +# targeted main, where the root-cleanliness guard rejects unknown root folders. +.codex/