Project Anatomy — Polyglot Coexistence Contract
SSOT for the top-level directory anatomy every consumer project follows, and for how multiple stacks coexist without breaking each other's tree. Per-stack inner trees live in each
stack.yaml::structure. TASK-351.
The top-level anatomy
<project>/
├── src/
│ ├── backend/ # THE backend — single-backend projects only
│ ├── services/<name>/ # multi-backend projects: one subtree per service
│ ├── frontend/ # web frontend (nextjs, vue-nuxt, …)
│ ├── mobile/ # mobile app (react-native, flutter, …)
│ └── shared/
│ ├── contracts/ # OpenAPI / proto / json-schema — the ONLY
│ │ # cross-language boundary
│ └── <lang>/ # same-language shared code (go/, ts/, py/)
├── docs/ · .coding-os/ · AGENTS.md · Makefile
Rules:
- One owner per subtree. A stack writes only inside its declared
structure.root(enforced by scaffold-boundary; seeenforce-scaffold-boundary.sh). - Languages never share a subtree. Cross-language communication goes
through
src/shared/contracts/artifacts only — never direct imports. - Same-language reuse lives in
src/shared/<lang>/; promote code there when a second service needs it (reuse-first; nudge ships with TASK-366).
Multi-backend relocation rule
When a project composes two or more stacks that declare the same
structure.root (e.g. go-fiber + fastapi, both src/backend/), cos init
relocates each colliding stack's scaffold subtree to
src/services/<stack-id>/. Single-owner roots stay exactly where they are —
existing single-backend projects are untouched.
The relocation is path-prefix rewriting; the stack's inner tree (declared
in structure.tree) is preserved beneath the new root. It applies in two
places: scaffold copy + boundary aggregation (TASK-351), and profile
aggregation for every derived artifact (TASK-355, next section).
Glob/verify propagation for relocated services (regen chain)
cli.stack_registry.relocate_profile(profile, new_root) is the single
remap primitive. Both world builders (cli.main._build_world for cos init,
cli.update._aggregate_world for cos update) compute
cli.stack_registry.service_relocations(registry, templates) and swap each
colliding profile for its relocated copy before aggregate() — so every
derived artifact downstream is service-scoped with no per-artifact special
cases:
| Profile field | Remap |
|---|---|
skill_enforcement[].globs, rules[].globs, dimensions[].read_files |
path-prefix: src/backend/** → src/services/<id>/** |
verify[].glob/cmd, substitutions values, structure.root/tree |
boundary-aware text swap of the declared root |
makefile_targets[].name |
suffixed -<stack-id> (two relocated stacks both declare lint-backend; aggregate dedupes by name, so unsuffixed names silently drop one stack's suite) |
verify[].suites + suite names inside substitutions |
same -<stack-id> rename so the Verification Matrix points at real targets |
VERIFY_{BACKEND,FRONTEND,MOBILE}{,_GLOB,_SUITES} joined |
aggregator joins distinct per-stack values with " | " (plain merge was last-wins — one relocated stack's suite silently vanished from AGENTS.md) |
Invariants:
- Single-owner stays byte-identical. No collision →
service_relocationsis empty → profiles pass through untouched (golden parity guards this). - Meta-repo regen never relocates.
src/scripts/regen_rules.pyaggregates ALL stacks for the global registry tables and intentionally skips relocation — collisions across the whole catalog are expected there. - Cross-service walls are generated. Boundary aggregation appends each
relocated root to every other stack's
forbids_writing_in, soenforce-scaffold-boundary.shflags an unowned write that crosses into a sibling service — no stack ever hand-lists a sibling's subtree. - Session primer follows the boundary file.
skill_primer.pyderivesdeclared root → relocated rootper stack from the aggregated$COS_STATE_DIR/scaffold-boundary.yaml(no new state file) and remaps the per-glob card; enforcement hooks (enforce-skill.shgeneric branches,enforce-scaffold-boundary.sh) are path-agnostic / boundary-driven and need no change.
Per-stack structure declaration
Each stack.yaml declares:
structure:
root: src/backend # the subtree this stack owns
tree: | # canonical inner layout (reference for agents)
src/backend/
├── cmd/<binary>/
└── internal/{domain,ports,adapters}
notes: "hexagonal: domain/ports/adapters; internal/-centric per Go community convention"
Canonical inner trees (research-grounded, 2025/2026 community conventions):
| Language / stack | Inner convention |
|---|---|
| Go (plain, chi, fiber) | internal/-centric; hexagonal internal/{domain,ports,adapters} + cmd/<binary>; avoid pkg-maximalism |
| Python (fastapi) | app/{api,services,db,schemas} — thin routes, logic in services |
| Python (django) | apps-per-domain under the project package |
| TypeScript (nextjs) | app/ router + components/ lib/ |
| TypeScript (plain) | src/index.ts entry, strict tsconfig |
| React Native | src/mobile/{screens,components,navigation,hooks} |
See also
- scaffold-boundary contract — per-stack write boundaries
- template-authoring.md § Language layer — language/extends composition
- hub-architecture.md § Symlink health — propagation