Monorepo Architecture Review
Section titled “Monorepo Architecture Review”Reviewer: senior-staff (fast pass). Scope: /home/ta/projects/monorepo/.
Critical
Section titled “Critical”1. packages/components is a phantom package — no site consumes it as a workspace dep
Section titled “1. packages/components is a phantom package — no site consumes it as a workspace dep”- What:
packages/components/package.jsondeclares@smart-debt/componentswithexportsfor./ui/*and./layout/*, and contains 22 UI components + alayout/dir. But zero sites or apps depend on@smart-debt/componentsin theirpackage.json, and no source file imports from that specifier. Consumption happens via TS path aliases (@components/ui/*insites/template/tsconfig.json) and Vite resolve aliases (sites/template/astro.config.mjs,sites/sdc/astro.config.mjs) that bypass pnpm entirely and reach into../../packages/components/src/directly. - Where:
packages/components/package.json,sites/template/tsconfig.json,sites/template/astro.config.mjs,sites/sdc/astro.config.mjs. - Why it matters: The package’s
name,exports, andversionare dead metadata — they imply a published/consumable contract that doesn’t exist. New brand sites (e.g.sites/mbr/) won’t get components unless they manually replicate both the tsconfig path and the Vite alias (sdc only configures the Vite alias, not tsconfig — sdc is inconsistent with template). When you eventually extract to a real package or change folder layout, the silent path-alias coupling will break in non-obvious ways. - Fix: Pick one model and enforce it. Either (a) make it a real workspace package: add
"@smart-debt/components": "workspace:*"to each consumer’s deps, drop the path aliases, and import via@smart-debt/components/ui/Card.astro; or (b) delete thename/exportsfrompackages/components/package.json, treat it as a content folder, and document the alias pattern. Option (a) is the right call — matches the@smart-debt/design-tokenspattern already in use.
2. sdc consumes shared components via Vite alias only — no matching tsconfig path
Section titled “2. sdc consumes shared components via Vite alias only — no matching tsconfig path”- What:
sites/sdc/astro.config.mjsdefines@componentsresolve alias, butsites/sdc/tsconfig.jsondoes NOT include@components/*paths. Template has both; sdc has only the runtime alias. - Where:
sites/sdc/tsconfig.jsonvssites/sdc/astro.config.mjs:resolve.alias. - Why: Editor/
astro checkwill not resolve@components/...imports in sdc — type checks will fail or fall back toany. Pre-production this becomes silent type erosion. - Fix: Add the same
pathsblock tosites/sdc/tsconfig.jsonas in template (andsites/mbr/tsconfig.jsonif mbr ever consumes shared components).
3. apps/sd-app and packages/sd-api are not wired — polyglot boundary is undefined
Section titled “3. apps/sd-app and packages/sd-api are not wired — polyglot boundary is undefined”- What: Grepping
apps/sd-app/src/forsd-api,sd_api,sdApireturns nothing. The SvelteKit PWA does not call the Python FastAPI service.packages/sd-api/(Python, FastAPI:main.py,router.py,db.py) exists in isolation. - Where:
apps/sd-app/src/,packages/sd-api/. - Why: Either (a) the app duplicates math logic that lives in
packages/sd-math/(Python) — meaning the JS side has its own copy or stubs; or (b) the API is dormant/unused. Either way, there’s no contract (no OpenAPI client, no shared schema package, no fetch wrapper) so when one side moves the other breaks silently. - Fix: Decide the boundary now, before prod: (i) Generate a TS client from FastAPI’s OpenAPI schema into a new
packages/sd-api-client/, or (ii) if sd-api is not yet used, mark it explicitly as not-yet-wired inpackages/sd-api/README.md. Add an integration smoke test that fails loudly if the contract drifts.
Important
Section titled “Important”4. sites/template/sites/ nested dir — leftover artifact
Section titled “4. sites/template/sites/ nested dir — leftover artifact”- What:
sites/template/sites/sdc.com/node_modules/sharp/exists inside the template site. The dir is gitignored (.gitignoreline 21) so it’s not committed, but it persists on disk and signals an oldpnpm installwas run from the wrong cwd, or a prior nested structure. - Where:
sites/template/sites/sdc.com/. - Why: Confuses tooling (
find,astro checkwith broad globs), inflates dev-machine size, and the fact that.gitignoreexplicitly listssites/template/sites/shows this is recurring. Recurring artifacts in.gitignoreusually mean a tool config is wrong somewhere. - Fix:
rm -rf sites/template/sites/. Find what creates it (likely an old script or a Wrangler/Pages config invoking pnpm with wrong cwd) and fix the source. Once fixed, remove the line from.gitignore.
5. README is stale and inflates capability
Section titled “5. README is stale and inflates capability”- What: Root
README.mdclaims “92 Components” and “681+ Tests”, listssdc.com/as a folder (actual name:sdc/), and usesTemplate/capitalization (actual:template/). It does not mentionsites/mbr/(which exists) orpackages/{i18n,sd-api,sd-math}/. - Where:
README.md. - Why: Onboarding (yours or future contributor’s) starts from a wrong mental model. The capitalization mismatch (
Template/vstemplate/) is a real bug on case-sensitive Linux/CI. - Fix: Rewrite Project Structure section against
ls sites/ apps/ packages/. Replace component/test counts withpnpmcommands that report them, or drop the numbers.
6. packages/sd-math/ contains stale planning docs in src root
Section titled “6. packages/sd-math/ contains stale planning docs in src root”- What: Six
cursor-task-*.mdfiles plusasset-history-design.mdand aCLEANUP.mdsaying “When done, can purge…” live atpackages/sd-math/top level (not indocs/orarchive/). - Where:
packages/sd-math/cursor-task-*.md,packages/sd-math/CLEANUP.md. - Why: Violates your own CLAUDE.md rule (“Specs and design docs go in KB, not repos”). Mixes ephemeral task scaffolding with shippable package source.
- Fix: Move to KB (
D:\FSS\KB\Business\_WorkingOn\Projects\monorepo\archive\) or delete. The CLEANUP.md itself says to purge.
7. AI tooling config sprawl — .cursor, .gemini, .obsidian, .claude, .agent at multiple levels
Section titled “7. AI tooling config sprawl — .cursor, .gemini, .obsidian, .claude, .agent at multiple levels”- What: 11 tool-config dirs across the repo, including duplicates at both root and
sites/template/levels (.claudex2,.cursorx2,.obsidianx2).docs/.obsidianandsites/template/.obsidian— Obsidian state inside a code repo. - Where: Root,
sites/template/,docs/,sites/sdc/.vscode. - Why: Two
.claude/dirs means project context can fight (root project memory vs site-level). Obsidian state indocs/indicates the docs folder is being treated as a vault — fine, but commit hygiene is risky (.obsidian/workspace.jsonchanges on every open). - Fix: Decide which configs should be committed vs gitignored. Likely: keep root
.claude/and.cursor/only; gitignore (and remove if tracked) the per-site duplicates and all.obsidian/. Rungit ls-files | grep -E '\.(obsidian|gemini|agent)/'to see what’s tracked.
8. tsconfig drift — root tsconfig.json paths don’t match site tsconfigs
Section titled “8. tsconfig drift — root tsconfig.json paths don’t match site tsconfigs”- What: Root
tsconfig.jsondefines"@components/*": ["packages/components/*"](note:packages/components/*, notpackages/components/src/*). Site-level tsconfigs use["../../packages/components/src/ui/*"]. Root has noextends, nostrict, notarget— it’s effectively a stub. - Where:
tsconfig.json(root). - Why: If any tool reads the root tsconfig (e.g., ESLint’s parser, IDE in monorepo-wide mode), imports will resolve to the wrong dir. The root tsconfig is doing nothing useful and is misleading.
- Fix: Either delete the root tsconfig (sites extend
astro/tsconfigs/strictdirectly) or make it the genuine base with sharedcompilerOptionsand consistent paths, and have site configsextends: "../../tsconfig.json".
9. archive/brand-assets-2025-12-22/ in repo
Section titled “9. archive/brand-assets-2025-12-22/ in repo”- What:
archive/contains old brand assets. Git log shows 2 commits. - Where:
archive/. - Why: Repo bloat; should live in KB or be a tagged release artifact, not in working tree.
- Fix: Move to KB or rely on git history; delete from working tree.
10. mbr site does not declare @smart-debt/design-tokens dependency
Section titled “10. mbr site does not declare @smart-debt/design-tokens dependency”- What:
sites/templateandsites/sdcdeclare"@smart-debt/design-tokens": "workspace:*".sites/mbrdoes not. - Where:
sites/mbr/package.json. - Why: If mbr ever needs design tokens (likely, given it has brand components), it’ll be a one-off oversight.
- Fix: Add the workspace dep proactively, or document deliberately that mbr is fully brand-isolated.
11. sd-api package has no package.json-style metadata visible alongside Python tooling
Section titled “11. sd-api package has no package.json-style metadata visible alongside Python tooling”- What:
packages/sd-api/pyproject.tomlexists; good. But pnpm-workspace.yaml includespackages/*— pnpm will scan sd-api and sd-math forpackage.jsonand silently skip. Not a bug, just a quirk to be aware of. - Where:
pnpm-workspace.yaml. - Why: Polyglot pnpm workspaces work but obscure intent.
- Fix: Document in root README that
packages/{sd-api,sd-math}are Python (uv) packages, not pnpm.
12. Astro versions aligned (^5.15.3 across template/sdc/mbr) — good
Section titled “12. Astro versions aligned (^5.15.3 across template/sdc/mbr) — good”- No action. Note it as a strength.
Summary
Section titled “Summary”Biggest issue is #1 — the shared components package is consumed by alias instead of as a proper workspace dep, so its exports contract is fiction. #3 is the strategic risk for production: no defined boundary between the SvelteKit app and the Python API. #5 (stale README) is the lowest-effort, highest-trust fix.