Project structure — evolutionary; substrate at apps/sdlc/lib/ today, the ontological solution as end-state
Status: open/accepted
Summary
Section titled “Summary”- Project structure is evolutionary: the SDLC/ontological product —
substrate, protocol adapters, harness surfaces, and its applications —
lives under
apps/sdlc/today and merges intosolutions/ontological/at end-state: one solution-local cluster whose consumers reach it only through published surfaces. See Decision and End-state target. - Vocabulary. An adapter is a protocol surface over the substrate (CLI, MCP, HTTP); a harness is an agent runner that consumes the substrate (Claude Code, Gemini); an application is a deployable expression of SDLC for people (web dashboard, desktop app). A harness is conceptually app-like; an application may bundle and deploy harnesses within itself.
- The repo is polyglot — at least two ecosystems (TypeScript and Rust, n ≥ 2). The dashboard is TypeScript; a planned Tauri app brings Rust. This drives the workspace tooling in D-0012-monorepo-tooling.
- Substrate lives at
apps/sdlc/lib/today, headed for the solution’s substrate subtree. No top-levelsrc/tree: consumption through published surfaces (compiled binary / npm externally, launcher shim / dev-link locally) keeps the source solution-local. See Current state. apps/<name>/holds deployable applications (each possibly polyglot internally);packages/<ecosystem>/holds shared libraries partitioned by language. Tooling by D-0012-monorepo-tooling; dependency direction by S-0008-apps-consume-substrate-through-published-surfaces.- A solution (
solutions/<name>/) is a cluster of apps and packages that belong to one solution and are shared only within it — typically a nested{apps/, packages/{ts,rust}}. Its sharing scope is narrower than repo-widepackages/and wider than a single app. The tell: a solution carries multiple deployables (CLI, desktop, web, workers) sharing solution-local packages; an app is a single deployable, however polyglot inside. Placement follows sharing scope, and a package earns a wider home by observed use, not by up-front guess. See Placement and promotion. - The move is decided (2026-08-14) and executes as one dedicated mechanical
PR in a freeze window — after the in-flight
apps/sdlcPRs land and orchestrator dispatch is paused. See Transition rule. apps/sdlc/lib/internals and the registry/adapter-generation model are fixed by D-0007-deterministic-op-substrate, which refines this ADR.sdlc.yamland similar repo-level configs live at the repo root intentionally: SDLC’s configuration should be visible at top level. See Resolved questions.
Decision
Section titled “Decision”Project structure is evolutionary. The end-state is the ontological
solution: SDLC and ontological are one product — a control plane bundled
and deployed to manage projects — and its whole cluster (substrate, protocol
adapters, harness surfaces, applications, solution-local packages, its own
planning unit) lives under solutions/ontological/. The separations survive
as solution-internal subtrees under the same import discipline. The cluster
stays at apps/sdlc/ until the move window. This ADR fixes both ends, the
path between them, and the rule for when the move happens.
End-state target
Section titled “End-state target”docs/ repo documentation; the root planning unitsolutions/ ontological/ the merged SDLC + ontological product — one deployable control plane. Internal subtrees keep this ADR's separations: lib/ (substrate), cli/ + later mcp/ http/ (protocol adapters), the harness surfaces (Claude plugin tree), apps/ (design-studio, sdf, suspenders), packages/, docs/planning/ augmented/ sibling solutions determined/apps/ standalone single-deployable applications (pumice, zoo, …)packages/ shared libraries, partitioned by ecosystem — see D0012sites/ generated docs sitesThe substrate is the solution’s lib/ subtree. Protocol adapters
(cli/, later mcp/ and http/) are generated from the substrate’s op
registry (per D-0007-deterministic-op-substrate) and are
consumer-agnostic — a plain shell, CI, or any agent can drive them. Each
harness surface (the Claude plugin tree today) consumes the substrate
through an adapter; no harness depends on another. Applications — the
solution’s own and everything outside it — consume the product only through
its published surfaces
(S-0008-apps-consume-substrate-through-published-surfaces): the compiled
binary / npm artifact externally, the launcher shim or dev-link locally, a
service’s HTTP/JSON API, or generated bindings — never by importing the
source. An application may still include and deploy one or more harnesses
within itself (a desktop app shipping an embedded agent). A solution
(solutions/<name>/) stages a related cluster of apps and packages that are
shared only among themselves, typically nesting its own
{apps/, packages/{ts,rust}}. Shared libraries live in
packages/<ecosystem>/, partitioned by language because a library is
single-ecosystem — a package reaches that tier by being used outside its
origin solution, per Placement and promotion.
Tooling, build, and ship mechanics for apps and packages are fixed by
D-0012-monorepo-tooling; the dependency rule by
S-0008-apps-consume-substrate-through-published-surfaces.
What the ADR fixes is the separation — substrate, adapters, harness surfaces, and applications stay distinct subtrees inside the solution, never collapsed into one another — and the scope: the whole cluster is solution-local, because every consumer reaches it through published surfaces. The solution’s exact internal subtree names are its own (Placement and promotion).
Current state
Section titled “Current state”docs/ as aboveapps/ deployable applications (D0012) — agentpants/, legacy-polish/, ontological/, pumice/, sdlc/, zoo/solutions/ solution clusters — augmented/, determined/packages/ shared libraries by ecosystem (D0012) — ts/, rust/sites/ Astro docs sites (generated-content consumers; see D0010)The former plugin/ tree is gone; SDLC lives at apps/sdlc/.
apps/sdlc/lib/ houses the substrate; its internal organization
(registry.ts, config/, util/, model/, services/) is fixed by
D-0007-deterministic-op-substrate, and nothing under it is inherently
harness-specific. The sdlc CLI under apps/sdlc/cli/ is a protocol adapter
(consumer-agnostic, runs in a plain shell);
apps/sdlc/{skills,.claude-plugin}/ is the harness-coupled surface. Both
move with the cluster into solutions/ontological/.
apps/sdlc/ also carries a desktop app (desktop/), experiences, and
products alongside the CLI — multiple deployables sharing local code, the
solution shape named in Placement and promotion.
That observation is now the ruling: the cluster is headed for
solutions/ontological/. apps/pumice/
shows the contrasting case: internally polyglot (nested crates and packages),
but a single deployable, so an app.
The solutions/ tier is live: solutions/augmented/ (first occupant) and
solutions/determined/ (moved from apps/determined/). packages/rust/ is
populated, no longer reserved.
Transition rule
Section titled “Transition rule”The move apps/sdlc/ → solutions/ontological/ is decided (2026-08-14). It
executes as one dedicated mechanical PR in a freeze window:
- the in-flight
apps/sdlcPRs have landed or been parked; - orchestrator dispatch is paused;
git mvpreserves history. Deployables (desktop, products, experiences) may stage first; the substrate subtree moves last, where the tooling references concentrate.
The accompanying sweep: tsconfig @lib/@cli aliases, .moon/workspace.yml
globs and the sdf source entry, root package.json workspaces, lefthook
commands, the project-check scripts’ path prefixes, CI path filters, the
release pipeline, and harness registrations (dev-link re-link). Consumers are
unaffected: published surfaces do not move.
Per-application build and ship (e.g. committing built output into the plugin) stays fixed in each application’s own decision.
Ship now, defer correctly. Today’s only harness is the Claude Code plugin.
Authoring substrate under apps/sdlc/lib/ matches that deployable’s mental
model with zero ceremony: no build step, no symlink, no divergence between
authored tree and runtime tree. The plugin is self-contained as authored.
Keep the consumer/substrate distinction discoverable. The end-state target
names where substrate, adapters, harnesses, and applications intend to live. A
reader asking “what is consumer-agnostic here?” sees the answer: anything
under apps/sdlc/lib/ or apps/sdlc/cli/ today. Everything under
apps/sdlc/skills/ and apps/sdlc/.claude-plugin/ is Claude-coupled. The
same answer holds inside the solution at end-state — the subtrees keep the
distinction.
Preserve P-0008-harness-agnostic-substrate. The substrate must remain
operable without any one consumer. Hosting it under apps/sdlc/lib/ does not
violate that, as long as no code in apps/sdlc/lib/ reaches into
harness-coupled surface (apps/sdlc/skills/, apps/sdlc/.claude-plugin/).
The boundary is enforced by import discipline, not by directory placement,
until the lift.
Substrate ships unbuilt; build tooling is for applications. The substrate ships from source with no build step — authored path equals runtime path. Build tooling enters only for applications, which have a concrete consumer; an application’s build copies application output, not substrate, so the debugging tax of a build step is paid only where it buys something. The substrate itself stays unbuilt.
Layout rules
Section titled “Layout rules”- New substrate code goes to
apps/sdlc/lib/<domain>/by default, organized per D-0007-deterministic-op-substrate (registry.ts,config/,util/,model/,services/). - Per-domain subdirs under
apps/sdlc/lib/<domain>/are reserved for grouped data (the wayentities/<type>/groups per-type declarations). Code files stay flat at the domain root until concerns cluster. - Protocol-adapter code goes to
apps/sdlc/cli/(the only adapter today;mcpandhttpwould land as sibling trees); harness-coupled code goes toapps/sdlc/skills/andapps/sdlc/.claude-plugin/. - Per D0007, capability code becomes a registry op in
apps/sdlc/lib/exposed via the generated CLI, not a standalone script. - Skills (
apps/sdlc/skills/<skill>/) may carry genuinely skill-private scratch helpers. An operation that acts on a domain entity or shared infra belongs inapps/sdlc/lib/from the start; S-0001-co-locate-first-promote-when-shared governs skill-private helpers. - Import discipline: nothing under
apps/sdlc/lib/imports fromapps/sdlc/skills/,apps/sdlc/.claude-plugin/, or other harness-coupled surface. The dependency arrow points only inward. - A standalone application goes in
apps/<name>/; a related cluster goes insolutions/<name>/; code shared across applications or solutions goes inpackages/<ecosystem>/<name>/(partitioned by language —packages/ts/,packages/rust/). Which of the three, and when a package moves between them, is fixed by Placement and promotion. An application consumes the substrate through an adapter (thesdlcCLI, a service’s HTTP/JSON API, or generated bindings), never by importingapps/sdlc/lib/— see S-0008-apps-consume-substrate-through-published-surfaces. An application may include and deploy harnesses within itself. Build and ship mechanics are fixed by D-0012-monorepo-tooling.
Placement and promotion
Section titled “Placement and promotion”Placement follows sharing scope, not kind:
| Location | Sharing scope |
|---|---|
apps/<name>/ | A standalone application — shared with nothing |
solutions/<s>/ | Related apps + packages shared WITHIN the one solution |
packages/<eco>/ | A package that spans 2+ apps or solutions — shared repo-wide |
Shape is how sharing scope shows itself. A solution carries multiple
deployables — CLI, desktop app, web app, workers — that share
solution-local packages; solution-local packages exist precisely because
several deployables need them. An app is a single deployable, even when
internally polyglot: nested packages or crates consumed only by that one
deployable keep it an app (apps/pumice/). Several sub-apps sharing local
packages is solution shape (apps/sdlc/ — desktop, CLI, experiences,
products — wears it today; an observation, not a placement ruling).
Solution-local packages stay workspace members: each solution enrolls its
subtrees explicitly in the root registries (root package.json workspaces,
Cargo.toml members, .moon/workspace.yml projects) — per-solution
entries, not tier-wide wildcards — so workspace:* and { workspace = true }
resolve as before. The tier fixes sharing scope; a solution’s internal layout
is its own (solutions are heterogeneous — one may be a bare
{apps/, packages/} container, another a full product suite with its own
tiering): the directory is a sharing-scope signal, not a resolution
barrier.
A package earns a wider home by observed use; its name never changes:
| Gate | Move | Trigger |
|---|---|---|
| tier 0 → 1 | solution-local → packages/<eco>/ | A consumer OUTSIDE the origin solution imports it. A directory move plus a glob-membership change; resolution is unchanged because the package name does not move |
| tier 1 → 2 | shared packages/ → derived library | It clears the derived-library bar — stable API, docs, ownership — per LIBRARIES.md |
| tier 2 → 3 | derived library → externally-owned | It nears 1.0: the standalone repo becomes the source of truth and this monorepo consumes published releases like any third-party dependency — per the tier-3 roster in LIBRARIES.md. First occupant: rust-path-opener |
Default rule. Default a new cluster into solutions/<name>/ rather than
scattering apps into apps/ and packages flat into packages/; a package
EARNS packages/ by being reached for outside its solution; a redundant
solution-local package is retired/folded within its solution.
When end-state arrives
Section titled “When end-state arrives”The move is mechanical: the cluster relocates wholesale, and import paths, workspace registries, and tooling references update in the same PR. Contracts on the published surfaces stay the same, so consumers notice nothing — the binary, npm artifact, plugin trees, and APIs are unchanged.
Migration
Section titled “Migration”augmented_books is the reference migration for the solutions/ tier. The
augmented family was adopted flat into apps/ and packages/ts/, which
scattered a single-solution cluster across two repo-wide roots and forced a
convergence audit (T-JCML, now superseded). The staging tier answers that:
relatedness is observed in place under solutions/augmented/, and each package
earns packages/ by cross-solution use rather than by up-front guess. The move
itself is T-C9RD.
Out of scope
Section titled “Out of scope”- Move timing. Scheduled by the freeze-window rule; tracked on the backlog. This ADR fixes the destination, not the date.
- The solution’s internal renames. App and subtree names inside
solutions/ontological/are owned by the ontological solution plan (solutions/ontological/docs/plans/ontological.md). - Adapter / op-registry internals. The registry, op model, and adapter-generation mechanics are owned by D-0007-deterministic-op-substrate, not this ADR.
Resolved questions
Section titled “Resolved questions”- Top-level trees vs the solution model. Resolved 2026-08-14: the
solution model. Earlier revisions targeted top-level
src//adapters//harnesses/trees; consumption through published surfaces makes the cluster solution-local, so those trees are no longer planned. Substrate bridging is likewise resolved in substance: consumers get the product via its published artifacts (binary, npm, dev-link), never the source. - Where
sdlc.yamllives. At the repo root, intentionally. SDLC is a sufficiently important subsystem to a consuming project that its configuration should be visible at project root, not buried in aconfig/directory. - Trigger for the lift. Superseded: the move is decided; execution is gated by the freeze window, not by triggers — see Transition rule.
- This ADR is the architectural complement to P-0008-harness-agnostic-substrate: P0008 names the value; this ADR fixes where the substrate lives in the tree today and where it is headed.
- Import discipline for the new trees is enforced by S-0008-apps-consume-substrate-through-published-surfaces (apps and packages reach the substrate only through an adapter); the same inward-only arrow applies cross-harness at end-state.
- The entity directories at
apps/sdlc/lib/model/entities/(one per entity type — backlog, capability, decision, driver, milestone, principle, product, reference, standard, task, term) are substrate — declarations consumable by any harness or app, underapps/sdlc/lib/today, solution-internal at end-state.