@genvidtech/c3-domain-manager 0.0.1 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/LICENSE +18 -0
  2. package/README.md +195 -29
  3. package/dist/adapters/locations.d.ts +22 -0
  4. package/dist/adapters/locations.d.ts.map +1 -0
  5. package/dist/adapters/locations.js +42 -0
  6. package/dist/adapters/locations.js.map +1 -0
  7. package/dist/cli.d.ts +3 -0
  8. package/dist/cli.d.ts.map +1 -0
  9. package/dist/cli.js +99 -0
  10. package/dist/cli.js.map +1 -0
  11. package/dist/domain/classification.d.ts +8 -0
  12. package/dist/domain/classification.d.ts.map +1 -0
  13. package/dist/domain/classification.js +66 -0
  14. package/dist/domain/classification.js.map +1 -0
  15. package/dist/domain/contextMap.d.ts +8 -0
  16. package/dist/domain/contextMap.d.ts.map +1 -0
  17. package/dist/domain/contextMap.js +168 -0
  18. package/dist/domain/contextMap.js.map +1 -0
  19. package/dist/domain/domainAnalysis.d.ts +25 -0
  20. package/dist/domain/domainAnalysis.d.ts.map +1 -0
  21. package/dist/domain/domainAnalysis.js +136 -0
  22. package/dist/domain/domainAnalysis.js.map +1 -0
  23. package/dist/domain/domainGenerator.d.ts +33 -0
  24. package/dist/domain/domainGenerator.d.ts.map +1 -0
  25. package/dist/domain/domainGenerator.js +307 -0
  26. package/dist/domain/domainGenerator.js.map +1 -0
  27. package/dist/domain/editorValidation.d.ts +19 -0
  28. package/dist/domain/editorValidation.d.ts.map +1 -0
  29. package/dist/domain/editorValidation.js +40 -0
  30. package/dist/domain/editorValidation.js.map +1 -0
  31. package/dist/domain/formatting.d.ts +16 -0
  32. package/dist/domain/formatting.d.ts.map +1 -0
  33. package/dist/domain/formatting.js +414 -0
  34. package/dist/domain/formatting.js.map +1 -0
  35. package/dist/domain/glossary.d.ts +20 -0
  36. package/dist/domain/glossary.d.ts.map +1 -0
  37. package/dist/domain/glossary.js +62 -0
  38. package/dist/domain/glossary.js.map +1 -0
  39. package/dist/domain/health.d.ts +14 -0
  40. package/dist/domain/health.d.ts.map +1 -0
  41. package/dist/domain/health.js +21 -0
  42. package/dist/domain/health.js.map +1 -0
  43. package/dist/domain/relationships.d.ts +13 -0
  44. package/dist/domain/relationships.d.ts.map +1 -0
  45. package/dist/domain/relationships.js +74 -0
  46. package/dist/domain/relationships.js.map +1 -0
  47. package/dist/domain/types.d.ts +290 -0
  48. package/dist/domain/types.d.ts.map +1 -0
  49. package/dist/domain/types.js +29 -0
  50. package/dist/domain/types.js.map +1 -0
  51. package/dist/index.d.ts +11 -0
  52. package/dist/index.d.ts.map +1 -0
  53. package/dist/index.js +12 -0
  54. package/dist/index.js.map +1 -0
  55. package/dist/mcp/server.d.ts +3 -0
  56. package/dist/mcp/server.d.ts.map +1 -0
  57. package/dist/mcp/server.js +519 -0
  58. package/dist/mcp/server.js.map +1 -0
  59. package/docs/TOC.md +27 -0
  60. package/docs/decisions/0001-adopt-c3source-extractors.md +67 -0
  61. package/docs/decisions/0002-configurable-locations-adapters-seam.md +72 -0
  62. package/docs/decisions/0003-adopt-loadprojectconfig-schema-first.md +73 -0
  63. package/docs/decisions/0004-adopt-mcp-utils-0.4.0-helpers.md +74 -0
  64. package/docs/decisions/0005-validateforeditor-read-side-diagnostic.md +65 -0
  65. package/docs/decisions/0006-event-variable-reference-coupling.md +77 -0
  66. package/docs/decisions/0007-project-dir-resolverootfolder.md +72 -0
  67. package/docs/decisions/0008-adopt-openproject-option-a.md +74 -0
  68. package/docs/domain-architecture.md +242 -0
  69. package/docs/releasing.md +122 -0
  70. package/package.json +79 -8
@@ -0,0 +1,72 @@
1
+ # ADR 0002: Configurable config/extracted locations via a `src/adapters` resolution seam
2
+
3
+ **Status:** Accepted
4
+ **Date:** 2026-06-03
5
+ **Issue:** #7 — make the domain-config path and extracted-output dir overridable
6
+
7
+ > Recovered retroactively (2026-06-30) from commit `64554bd`.
8
+
9
+ ---
10
+
11
+ ## Context
12
+
13
+ The tool originally hardcoded its paths: `domain-config.json` and `extracted/`
14
+ were always resolved as `path.join(PROJECT_ROOT, …)`. The MCP server recomputed
15
+ `path.join(PROJECT_ROOT, "domain-config.json")` inline in five places. This
16
+ blocked legitimate uses — a config file living outside the project root, a
17
+ custom output directory, or a no-side-effect validation pass that writes nothing
18
+ into the project tree.
19
+
20
+ Both the CLI and the MCP server need the same resolution logic, so the question
21
+ was not only *what* to make configurable but *where* the resolution should live
22
+ so the two adapters share one implementation without leaking it onto the public
23
+ library API.
24
+
25
+ ## Decision
26
+
27
+ **Introduce a pure `src/adapters/locations.ts` seam — `resolveLocations(opts,
28
+ projectRoot)` returning a `ResolvedLocations`** — that resolves the
29
+ domain-config path, the extracted-output directory, and the ephemeral-temp
30
+ behaviour from CLI flags / `startServer` options. Add two global CLI flags
31
+ (`--config`, `--extracted`) and thread the server through a single
32
+ `CONFIG_PATH`/`CONFIG_WATCH_KEY` set once at `startServer` time.
33
+
34
+ Resolution rules:
35
+
36
+ - **Absolute operator paths pass through; relative paths rebase to the project
37
+ root.** Operator-supplied paths are trusted — paths outside the project root
38
+ are intentionally allowed.
39
+ - **`--extracted none`** routes generation into an ephemeral `os.tmpdir()`
40
+ directory, removed in a `finally` (CLI) or the shutdown handler (server).
41
+ - `configWatchKey` is the forward-slash-normalized absolute config path, used by
42
+ the server's `ExpectedChanges` self-write suppression on both add and consume,
43
+ so it stays correct for a custom config name/location.
44
+
45
+ The `src/adapters/` layer holds code shared *between* the CLI and MCP adapters
46
+ and is **deliberately not re-exported** from the public library API
47
+ (`src/index.ts`).
48
+
49
+ ## Alternatives Considered
50
+
51
+ **Put the resolution helpers in `src/domain/` (the pure core).** Rejected:
52
+ location resolution is adapter concern (CLI flags / server options), not domain
53
+ analysis. Placing it in the core would either pollute the public API surface or
54
+ force the pure functions to know about CLI/server option shapes.
55
+
56
+ **Duplicate the resolution in each adapter.** Rejected: the watch-key
57
+ normalization and the ephemeral-temp lifecycle are subtle enough that two copies
58
+ would drift. A single tested seam (16 unit tests, including a real
59
+ `ExpectedChanges` add/consume round-trip) is the safer factoring.
60
+
61
+ ## Consequences
62
+
63
+ - A new architectural layer, `src/adapters/`, is established for
64
+ CLI↔server-shared code that should not be public. Later decisions extend it —
65
+ see [[0003-adopt-loadprojectconfig-schema-first]] (the `configDir`/
66
+ `configFileName` split lives here) and
67
+ [[0007-project-dir-resolverootfolder]] (`resolveProjectRoot` joins it).
68
+ - The server's five inline `path.join` recomputations collapse to one
69
+ module-level `CONFIG_PATH`; ephemeral extracted dirs are cleaned up on
70
+ shutdown.
71
+ - The explicit `.version(PKG_VERSION)` yargs wiring (the issue #3 guard) is left
72
+ untouched by this change.
@@ -0,0 +1,73 @@
1
+ # ADR 0003: Adopt `@genvid/mcp-utils` 0.3.0 `loadProjectConfig`; make `DomainConfig` schema-first
2
+
3
+ **Status:** Accepted
4
+ **Date:** 2026-06-03
5
+ **Issue:** #9 — adopt the mcp-utils 0.3.0 configuration API
6
+
7
+ > Recovered retroactively (2026-06-30) from commit `99f01f2`.
8
+
9
+ ---
10
+
11
+ ## Context
12
+
13
+ Config loading was three unguarded `JSON.parse(...) as DomainConfig` casts — one
14
+ each in the pure core, the CLI, and the MCP server. A missing file, malformed
15
+ JSON, a schema violation, or a path escape produced an opaque parse throw, and
16
+ the `as DomainConfig` cast asserted a shape the loader never actually checked.
17
+ The `DomainConfig` family was also a set of hand-written TypeScript interfaces
18
+ kept in sync with the runtime expectations by hand.
19
+
20
+ `@genvid/mcp-utils` 0.3.0 shipped `loadProjectConfig(projectRoot, fileName,
21
+ schema)` — an async, never-throwing read+merge+zod-validate that returns either
22
+ the typed config or a structured `CallToolResult` error — plus its `isMcpError`
23
+ guard. This builds directly on the resolution seam from
24
+ [[0002-configurable-locations-adapters-seam]].
25
+
26
+ ## Decision
27
+
28
+ **Adopt `loadProjectConfig` everywhere config is read, and make `DomainConfig`
29
+ schema-first.**
30
+
31
+ 1. **Schema-first types.** Replace the `DomainConfig` / `DomainDefinition` /
32
+ `SharedSubdomainDefinition` / `Relationship` interfaces with lenient zod
33
+ schemas; export `DomainConfigSchema` as the single source of truth and derive
34
+ the types via `z.infer`. Schemas use `.passthrough()` so unknown keys survive
35
+ the MCP server's load→mutate→write round-trip; non-essential fields are
36
+ optional; `description` stays required. The derived types are structurally
37
+ identical to the old interfaces, so all consumers compile unchanged.
38
+
39
+ 2. **Keep MCP types out of the pure core.** The core's `loadConfig(configDir,
40
+ configFileName)` becomes an **async throwing wrapper** around
41
+ `loadProjectConfig` that throws on `isMcpError` (prefixed
42
+ `loadProjectConfig(…)`), so the pure core never surfaces `CallToolResult`.
43
+ `generateDomainIndex` becomes async.
44
+
45
+ 3. **Server calls `loadProjectConfig` directly** so its tool handlers can return
46
+ the structured `CallToolResult` error verbatim; its caches store only on
47
+ success. `configDir`/`configFileName` (added to `ResolvedLocations`) thread
48
+ the resolved location through — `path.join(configDir, configFileName)` always
49
+ equals the absolute `configPath`, preserving the outside-root `--config` case.
50
+
51
+ ## Alternatives Considered
52
+
53
+ **Have the pure core return `CallToolResult` too.** Rejected: it would leak the
54
+ MCP error type into the public library API and into the CLI, which has no use for
55
+ a `CallToolResult`. The throwing wrapper keeps the core MCP-agnostic while the
56
+ server still gets the structured error by calling `loadProjectConfig` directly.
57
+
58
+ **Keep hand-written interfaces, validate separately.** Rejected: two sources of
59
+ truth (the interface and a parallel validator) inevitably drift. Deriving the
60
+ types from the schema makes the validator authoritative for free.
61
+
62
+ ## Consequences
63
+
64
+ - Config errors (missing file, bad JSON, schema violation, path escape) now
65
+ produce a clear, structured `loadProjectConfig(domain-config.json): …` error
66
+ instead of an opaque parse throw — aborting the CLI command or returning a
67
+ structured MCP tool error.
68
+ - The lenient `.passthrough()` schema is what makes the MCP mutate tools safe:
69
+ extra config keys survive a `set-overrides`/`remove-overrides` round-trip.
70
+ - `generateDomainIndex` and the affected CLI handlers become async.
71
+ - This is the first mcp-utils helper adoption; the pattern of "verify the packed
72
+ `.d.ts`, not the release notes" continues in
73
+ [[0004-adopt-mcp-utils-0.4.0-helpers]].
@@ -0,0 +1,74 @@
1
+ # ADR 0004: Adopt `@genvid/mcp-utils` 0.4.0 server helpers; harden mutate writes
2
+
3
+ **Status:** Accepted
4
+ **Date:** 2026-06-09
5
+ **Issue:** #10 / #11 — upgrade mcp-utils to 0.4.0 and adopt its helpers
6
+
7
+ > Recovered retroactively (2026-06-30) from commit `66debb4`.
8
+
9
+ ---
10
+
11
+ ## Context
12
+
13
+ `src/mcp/server.ts` had grown hand-rolled equivalents of plumbing that
14
+ `@genvid/mcp-utils` already exported (some since 0.3.0, adopted only now): local
15
+ `READ_ONLY`/`REGENERATE`/`MUTATE` annotation objects, a local
16
+ `paginatedResponse` helper, and ad-hoc result+footer string assembly. More
17
+ seriously, the `set-overrides` and `remove-overrides` handlers had **no
18
+ try/catch around their write path**: a failed `fs.writeFileSync` propagated
19
+ uncaught out of the tool callback instead of returning a `CallToolResult` error,
20
+ and a partial write left `txId` un-bumped while the on-disk file had changed —
21
+ and the watcher swallows its own event via `expectedChanges` — so the client
22
+ would never learn to reconcile.
23
+
24
+ mcp-utils 0.4.0 is additive/drop-in and provides `mcpContent` (single-block
25
+ result+footer), `withMcpErrors` (wraps an async handler so thrown errors become
26
+ `CallToolResult` errors, with an `onError` hook), and surfaces the annotation
27
+ constants and `paginatedContent` already present since 0.3.0.
28
+
29
+ ## Decision
30
+
31
+ **Bump to `^0.4.0` and adopt the library helpers, replacing the hand-rolled
32
+ equivalents; wrap the mutate handlers in `withMcpErrors` with an `onError` txId
33
+ bump.**
34
+
35
+ - Drop the local annotation objects → import `READ_ONLY`/`REGENERATE`/`MUTATE`.
36
+ - `set-overrides`/`remove-overrides` build responses with `mcpContent(body,
37
+ \`txId: ${txId}\`)`.
38
+ - `read-domain-index` uses `paginatedContent`; the stale-index warning rides as
39
+ its trailing footer.
40
+ - Wrap both mutate handlers in `withMcpErrors` with an `onWriteError` hook that
41
+ **bumps `txId` and logs** on write failure, so a failed write (a) returns a
42
+ proper error result and (b) forces the client to re-read. The early-return
43
+ validation paths (txId mismatch, override validation, empty input) return
44
+ `isError` without throwing, so they correctly bypass `onError`. `regenerate`
45
+ keeps its existing try/catch and is left unwrapped.
46
+
47
+ The issue's proposed `{prefix}` adoption was **dropped**: verification showed
48
+ `loadConfig` unwraps rather than produces errors, so it has no valid target.
49
+
50
+ ## Alternatives Considered
51
+
52
+ **Keep the hand-rolled helpers.** Rejected: they had silently sat as duplicates
53
+ of the library's exports, and the dedup removes a maintenance burden. The
54
+ annotation constants in particular must match the library's shapes exactly.
55
+
56
+ **Add a bespoke try/catch to each mutate handler instead of `withMcpErrors`.**
57
+ Rejected: `withMcpErrors` standardizes the thrown-error → `CallToolResult`
58
+ conversion and gives the `onError` side-effect hook for free; bespoke try/catch
59
+ would re-implement it inconsistently across the two handlers.
60
+
61
+ ## Consequences
62
+
63
+ - Observable response-shape deltas (no server test harness guards these):
64
+ `read-domain-index` collapses from two content blocks to one; the stale-index
65
+ warning moves into the paginated footer; the mutate `txId:` line now hugs the
66
+ body with a single newline instead of a blank line. All intentional.
67
+ - A failed mutate write now returns an error result **and** bumps `txId` so the
68
+ client reconciles. Known residual gap: the in-memory config is mutated before
69
+ the write, so on failure the cache can still diverge from disk — out of scope;
70
+ the txId bump at least signals reconciliation.
71
+ - Reinforces the standing rule (recorded in CLAUDE.md from this issue's retro):
72
+ when bumping mcp-utils, audit the server for hand-rolled equivalents and verify
73
+ the API against the packed types, not the release notes — the same discipline
74
+ applied in [[0003-adopt-loadprojectconfig-schema-first]].
@@ -0,0 +1,65 @@
1
+ # ADR 0005: Adopt `validateForEditor` as a read-side editor-strictness diagnostic
2
+
3
+ **Status:** Accepted
4
+ **Date:** 2026-06-11
5
+ **Issue:** #13 (reframing #12) — adopt c3source 1.4.0 `validateForEditor`
6
+
7
+ > Recovered retroactively (2026-06-30) from commit `bc76696`,
8
+ > CLAUDE.md, and `docs/domain-architecture.md`.
9
+
10
+ ---
11
+
12
+ ## Context
13
+
14
+ `@genvidtech/c3source` 1.4.0 shipped `validateForEditor(sheet)` /
15
+ `EditorValidationIssue`, which checks a parsed event sheet against the structural
16
+ rules the C3 editor enforces on import (e.g. a `variable` event missing its
17
+ `comment` field, or a `group` event missing its `description`).
18
+
19
+ The originating issue (#12) framed this as a guard to run **"before write-out"** —
20
+ the implicit assumption being that `c3-domain-manager` writes or modifies C3
21
+ event sheets and should validate them before doing so. It does not.
22
+ `c3-domain-manager` only ever *reads* event sheets to analyze them; the only files
23
+ it writes are its own `extracted/` markdown index and (via MCP mutate tools) the
24
+ `domain-config.json`. There is no C3-sheet write site for a "before write-out"
25
+ guard to attach to. (This "verify the integration site the issue assumes actually
26
+ exists" lesson is now recorded in CLAUDE.md.)
27
+
28
+ ## Decision
29
+
30
+ **Adopt `validateForEditor`, but as a read-side diagnostic — not a write
31
+ guard.** Add `validateEditorStrictness` / `formatEditorStrictnessReport`
32
+ (`src/domain/editorValidation.ts`) and expose them as the `validate-editor` CLI
33
+ subcommand and the MCP `READ_ONLY` "Validate Editor Strictness" tool.
34
+
35
+ The diagnostic re-walks `eventSheets/` fresh from disk (it does **not** consume
36
+ the cached `DomainData[]`), attributes each sheet to a domain via `classifyFile`,
37
+ runs `validateForEditor` per sheet, and returns issues grouped by sheet. Sheets
38
+ matching no domain are still validated and reported under `"(unclassified)"`. The
39
+ report surfaces sheets the C3 editor would refuse to import, so the user can fix
40
+ them in the editor.
41
+
42
+ ## Alternatives Considered
43
+
44
+ **Implement the issue as written — a pre-write guard.** Rejected because the
45
+ premise is false: there is no C3-sheet write path in this tool. Forcing a guard
46
+ in would have meant inventing a write site that does not exist.
47
+
48
+ **Skip adoption entirely** (no write site ⇒ no use). Rejected: the validation
49
+ has clear standalone value as a read-side health check, independent of any write.
50
+ Reframing it as a diagnostic preserves the value without the bogus premise.
51
+
52
+ ## Consequences
53
+
54
+ - `validate-editor` is the first read-side capability that intentionally does
55
+ **not** read the cached domain index — it re-walks sheets from disk, so the MCP
56
+ tool deliberately omits the stale-index warning that other read tools append
57
+ (index freshness is irrelevant to its output).
58
+ - `editorValidation.ts` is independent of the `DomainData[]` pipeline that every
59
+ other downstream module consumes; it shares only `classifyFile`.
60
+ - Continues the c3source-supersedes-local-logic pattern of
61
+ [[0001-adopt-c3source-extractors]]; the file-discovery half of this walk is
62
+ later migrated to the project handle in [[0008-adopt-openproject-option-a]]
63
+ (via `hasEventSheets()` / `findAllEventSheets()`).
64
+ - Establishes the durable lesson — verify the integration site an issue assumes
65
+ actually exists before building to its premise.
@@ -0,0 +1,77 @@
1
+ # ADR 0006: Add event-variable references as a second cross-domain coupling source
2
+
3
+ **Status:** Accepted
4
+ **Date:** 2026-06-11
5
+ **Issue:** #14 — enrich the cross-domain dependency graph
6
+
7
+ > Recovered retroactively (2026-06-30) from commit `cc8b5fd`,
8
+ > CLAUDE.md, and `docs/domain-architecture.md`.
9
+
10
+ ---
11
+
12
+ ## Context
13
+
14
+ Cross-domain coupling was derived from a single source: `include` events. When a
15
+ sheet in domain A includes a sheet in domain B, that is an include edge A → B
16
+ (the `includesFrom` / `includedBy` graphs). But C3 event sheets couple in a
17
+ second way the include graph misses entirely: a sheet can **reference an event
18
+ variable declared in another sheet** via System ACEs. Two domains can be coupled
19
+ through shared global variables without any include relationship between them.
20
+
21
+ c3source 1.4.0 also shipped the primitives needed to detect this:
22
+ `getEventVarReferenceName` / `EVENTVAR_REFERENCE_ACES`, plus the
23
+ `visitEvents` / `hasConditions` / `hasActions` tree-visitors.
24
+
25
+ ## Decision
26
+
27
+ **Derive a second, sibling coupling source — event-variable references — and
28
+ aggregate it with include coupling under union semantics.**
29
+
30
+ - `extractEventVarDecls` indexes top-level (`variable`) declarations per domain;
31
+ `extractEventVarRefs` collects System-ACE references via
32
+ `getEventVarReferenceName` + the visitors. `computeDomainData` resolves each
33
+ reference to its declaring domain(s) and builds `referencesFrom` /
34
+ `referencedBy`, sibling to the include maps.
35
+
36
+ **Resolution policy:**
37
+
38
+ - **Global-scope approximation** — only top-level (sheet-root) `variable` events
39
+ are indexed as declarations (C3 cross-sheet references require globals).
40
+ Variables inside groups/functions are deliberately excluded.
41
+ - **Attribute-to-all on collision** — a name declared at the top level of
42
+ multiple domains creates an edge to every declaring domain.
43
+ - **Unresolved references produce no edge** — a referenced name with no indexed
44
+ declaration anywhere is silently ignored (no diagnostics bucket).
45
+ - **Same-domain references produce no edge** — consistent with include coupling.
46
+
47
+ No `domain-config.json` schema change is required — reference coupling is derived
48
+ entirely from sheet content.
49
+
50
+ ## Alternatives Considered
51
+
52
+ **Fold references into the existing include edges.** Rejected: the two coupling
53
+ kinds have different meanings and different fixes, so they are kept distinct. The
54
+ context map renders references as a separate `observed-ref` edge kind
55
+ (`[observed-ref]` in text, `-.->|var|` in Mermaid), with precedence declared >
56
+ observed (include) > observed-ref.
57
+
58
+ **Track unresolved references in a diagnostics bucket.** Rejected as scope creep
59
+ for this issue — the "unresolved → no edge" path is simple and correct for the
60
+ coupling question; a diagnostics surface can come later if warranted.
61
+
62
+ **Index variables declared inside groups/functions too.** Rejected: those are
63
+ not visible across sheets in C3, so a cross-sheet reference to one is genuinely
64
+ unresolvable — excluding them is the correct behaviour, not a limitation to fix.
65
+
66
+ ## Consequences
67
+
68
+ - Both coupling sources are aggregated with **union semantics** across every
69
+ downstream consumer: health (Ca/Ce count the deduped union),
70
+ `validateBoundaries` (a reference to an undeclared domain is an `undeclared`
71
+ violation just like an include), the context map (the `observed-ref` edge
72
+ kind), and domain pages (two new reference subsections).
73
+ - Reuses the c3source 1.4.0 primitives adopted alongside
74
+ [[0005-validateforeditor-read-side-diagnostic]] — the same release supplied
75
+ both the validator and these reference helpers.
76
+ - Full policy and aggregation are documented in `docs/domain-architecture.md`
77
+ ("Cross-domain coupling sources").
@@ -0,0 +1,72 @@
1
+ # ADR 0007: Add `--project-dir` via `@genvid/mcp-utils` 0.5.0 `resolveRootFolder`
2
+
3
+ **Status:** Accepted
4
+ **Date:** 2026-06-17
5
+ **Issue:** #16 — set the C3 project source root explicitly
6
+
7
+ > Recovered retroactively (2026-06-30) from commit `a2130aa`,
8
+ > CLAUDE.md, and `docs/domain-architecture.md`.
9
+
10
+ ---
11
+
12
+ ## Context
13
+
14
+ The C3 project source root — the directory whose `eventSheets/`, `layouts/`, and
15
+ `scripts/` are scanned — was effectively `process.cwd()`. There was no way to
16
+ point the tool at a sibling directory, and no way to disambiguate a repository
17
+ that hosts more than one C3 project. The issue asked to add a `--project-dir`
18
+ flag and, implicitly, to hand-roll the root-discovery logic (explicit path → env
19
+ var → marker-file search → cwd fallback).
20
+
21
+ While scoping it, `@genvid/mcp-utils` 0.5.0 turned out to ship exactly that
22
+ resolver: `resolveRootFolder` resolves a project root from an explicit path, an
23
+ env var, or `project.c3proj` marker discovery, returning `ResolvedRoot |
24
+ CallToolResult` and never throwing. This is the inverse of the usual lesson — the
25
+ issue said "hand-roll Y," but a dependency bump already provided Y. (`project.c3proj`
26
+ itself comes from `@genvidtech/c3source`'s `PROJECT_MANIFEST_FILE`, exported since
27
+ 1.5.0.)
28
+
29
+ ## Decision
30
+
31
+ **Add `--project-dir` (and `C3_PROJECT_DIR`), implemented as `resolveProjectRoot`
32
+ in `src/adapters/locations.ts` — a thin wrapper over mcp-utils'
33
+ `resolveRootFolder`** passing `PROJECT_MANIFEST_FILE` as the discovery marker
34
+ rather than hand-rolling the resolution.
35
+
36
+ Resolution precedence (highest to lowest):
37
+
38
+ 1. `--project-dir <path>` — relative resolves against the **current working
39
+ directory**, absolute used as-is, no containment restriction (`../sibling` is
40
+ valid).
41
+ 2. `C3_PROJECT_DIR` env var — same rules.
42
+ 3. Discovery — the current dir and its immediate children (depth 1) are searched
43
+ for a `project.c3proj` marker. Exactly one match becomes the root; two or more
44
+ matches print an ambiguity error and exit non-zero (the intended behaviour for
45
+ a repo hosting multiple C3 projects).
46
+ 4. Fallback — the current working directory (preserves prior behaviour).
47
+
48
+ The MCP server does **not** re-run discovery; the root is fixed at `startServer`
49
+ time. Note `--project-dir` resolves relative to **cwd**, whereas `--config`/
50
+ `--extracted` resolve relative to the project root (per
51
+ [[0002-configurable-locations-adapters-seam]]).
52
+
53
+ ## Alternatives Considered
54
+
55
+ **Hand-roll the resolution as the issue framed it.** Rejected once
56
+ `resolveRootFolder` was found: it provides the same precedence chain, the
57
+ never-throw `ResolvedRoot | CallToolResult` contract, and the marker search —
58
+ adopting it avoids duplicating root-discovery logic that mcp-utils now owns. This
59
+ is the standing "verify whether a dep bump already ships the primitive" discipline
60
+ applied in reverse.
61
+
62
+ **Silently pick the first match on ambiguity.** Rejected: a repo with multiple
63
+ C3 projects is a real configuration the tool should not guess at — erroring and
64
+ requiring an explicit `--project-dir` is safer than analyzing the wrong project.
65
+
66
+ ## Consequences
67
+
68
+ - `resolveProjectRoot` joins `resolveLocations` in the `src/adapters/` shared
69
+ layer; the new root feeds the existing location resolution unchanged.
70
+ - Adopting `resolveRootFolder` is what raised the mcp-utils floor to `^0.5.0`.
71
+ - Full precedence table and the cwd-vs-project-root resolution distinction are
72
+ documented in `docs/domain-architecture.md` ("Paths and locations").
@@ -0,0 +1,74 @@
1
+ # ADR 0008: Adopt `C3Project`/`openProject` for C3 file discovery (Option A)
2
+
3
+ **Status:** Accepted
4
+ **Date:** 2026-06-29
5
+ **Issue:** #19 — migrate `@genvid/c3source` → `@genvidtech/c3source` 1.7.0
6
+
7
+ ---
8
+
9
+ ## Context
10
+
11
+ `c3-domain-manager` discovers C3 source files by hardcoding section-folder joins
12
+ (`path.join(rootDir, "eventSheets")`, `path.join(rootDir, "layouts")`,
13
+ `path.join(rootDir, "scripts")`) in `src/domain/domainGenerator.ts`
14
+ (`computeDomainData`, `findScriptEntries`), `src/domain/editorValidation.ts`,
15
+ and `src/domain/domainAnalysis.ts` (`listUncategorized`), then — for the
16
+ discovery path — calling the free functions `find_all_eventsheets_path` /
17
+ `find_all_layouts_path`.
18
+
19
+ The `@genvidtech/c3source` 1.7.0 bump completes the `C3Project` handle's section
20
+ coverage. `openProject(root): C3Project` exposes canonical section dirs
21
+ (`eventSheetsDir`, `layoutsDir`, `scriptsDir`, …) and
22
+ `findAllEventSheets()` / `findAllLayouts()` / `findAllScripts()` walkers,
23
+ centralizing the C3-folder facts that were previously hand-rolled here.
24
+ Per the standing guidance in `CLAUDE.md` ("when bumping c3source, check whether
25
+ new exports supersede local C3-parsing logic"), this bump triggered a formal
26
+ evaluation of three adoption options.
27
+
28
+ ## Decision
29
+
30
+ **Option A — call `openProject(rootDir)` locally at the top of each pure
31
+ function** (`computeDomainData`, `validateEditorStrictness`, and
32
+ `listUncategorized`) and use `project.findAllEventSheets()` /
33
+ `project.findAllLayouts()` / `project.scriptsDir` (the discovery path), or the
34
+ `project.eventSheetsDir` / `layoutsDir` / `scriptsDir` directory fields where a
35
+ function does its own walk (`listUncategorized`'s `collectFiles`). The pure
36
+ functions keep their existing `rootDir`-first signatures and their position on
37
+ the `src/index.ts` public surface is unchanged.
38
+
39
+ ## Alternatives Considered
40
+
41
+ **Option B — thread a `C3Project` handle through the public API.**
42
+ Change `computeDomainData` / `validateEditorStrictness` to accept a `C3Project`
43
+ instead of `rootDir`. Rejected: exposes a dependency type on the pure-core public
44
+ surface (`src/index.ts` re-exports these) and ripples into `generateDomainIndex`,
45
+ `cli.ts`, `server.ts`, and every test — large blast radius for thin gain.
46
+
47
+ **Option C — adopt only the dir-path constants, keep free `find_all_*` functions.**
48
+ Use `project.eventSheetsDir` etc. as inputs but keep the existing free-function
49
+ walkers. Rejected: keeps two import styles, forgoes the missing-dir robustness of
50
+ the handle's walkers, and offers almost no improvement over the status quo.
51
+
52
+ ## Consequences
53
+
54
+ - Removes all hardcoded C3 section-folder name literals from this repo; the folder
55
+ facts now live in `c3source`. (`listUncategorized`'s swap is purely cosmetic —
56
+ its `collectFiles`/`collectRootTsFiles` walkers already return `[]` on a missing
57
+ dir, so only the discovery path in `computeDomainData` gains the behavioural fix
58
+ below.)
59
+ - **Behavioral improvement (deliberate):** a project missing `eventSheets/` or
60
+ `layouts/` previously caused `computeDomainData` to throw `ENOENT`;
61
+ `findAllEventSheets()` / `findAllLayouts()` return `[]` instead, so analysis
62
+ continues gracefully. Pinned by new tests. `editorValidation` preserves its
63
+ existing skip-log via `project.hasEventSheets()`.
64
+ - **Scope limit — `findScriptEntries` is not replaced:** `project.findAllScripts()`
65
+ returns a flat list of `.ts` paths without the `{relativePath, isDirectory}`
66
+ directory entries and `LAYER_DIRS` recursion that classification depends on.
67
+ Only its input narrows to `project.scriptsDir`; it still throws on a missing
68
+ `scripts/` dir (unchanged).
69
+ - `findScriptEntries`'s narrowed signature (`rootDir` → `scriptsDir`) is a
70
+ public-API change with no known external consumers; noted for release notes.
71
+ - **Deferred — `comparisonSymbol`/`COMPARISON_OPERATORS` (also new in 1.7.0):**
72
+ no integration site exists today (this repo renders no condition/comparison
73
+ params), so adoption is deferred to a future measurement spike if a real
74
+ diagnostic warrants it.