diff --git a/.github/workflows/module-health.yml b/.github/workflows/module-health.yml new file mode 100644 index 000000000..f4396dccd --- /dev/null +++ b/.github/workflows/module-health.yml @@ -0,0 +1,44 @@ +name: Module Health + +on: + schedule: + # Mondays 06:00 UTC + - cron: '0 6 * * 1' + workflow_dispatch: + +permissions: + contents: write + +jobs: + audit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Run audits + run: | + node scripts/audit-modules.mjs + node scripts/audit-module-coupling.mjs + node scripts/audit-complexity.mjs || true + node scripts/build-complexity-map.mjs + + - name: Commit reports if changed + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + PATHS="docs/module-health.md docs/module-coupling.md docs/complexity-hotspots.md docs/complexity-map.html apps/mana/apps/web/static/admin/complexity-map.html" + if git diff --quiet $PATHS 2>/dev/null; then + echo "No changes to reports." + exit 0 + fi + git add $PATHS 2>/dev/null || true + git commit -m "chore(audit): weekly module health report + + Co-Authored-By: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>" + git push diff --git a/apps/mana/apps/web/src/lib/modules/complexity/ListView.svelte b/apps/mana/apps/web/src/lib/modules/complexity/ListView.svelte new file mode 100644 index 000000000..e6da8a26b --- /dev/null +++ b/apps/mana/apps/web/src/lib/modules/complexity/ListView.svelte @@ -0,0 +1,66 @@ + + + +{#if !isAdmin} +
+ +

Admin-only

+

+ Die Complexity Map ist ein internes Diagnose-Tool und nur für Admin-Nutzer sichtbar. +

+
+{:else} +
+
+
+

Codebase Complexity Map

+

+ Area = LOC · color = git churn (6 months) · hover for details +

+
+ + Full page → + +
+ + {#if loadError} +
+
+

Map nicht generiert.

+

+ Im Repo-Root pnpm audit:map ausführen. +

+
+
+ {:else} +
+ +
+ {/if} +
+{/if} diff --git a/apps/mana/apps/web/src/routes/(app)/admin/complexity/+page.svelte b/apps/mana/apps/web/src/routes/(app)/admin/complexity/+page.svelte new file mode 100644 index 000000000..ecbc5ba7c --- /dev/null +++ b/apps/mana/apps/web/src/routes/(app)/admin/complexity/+page.svelte @@ -0,0 +1,75 @@ + + +
+
+
+

Complexity Map

+

+ Interactive treemap of the entire codebase. Area = lines of code, color = git change + frequency (last 6 months). +

+
+ +
+ + {#if error} +
+

Map not yet generated.

+

+ Run pnpm audit:map from the repo root. It + writes to + static/admin/complexity-map.html. +

+
+ {/if} + +
+ +
+ +
+ Related reports + +

+ All reports regenerate automatically every Monday 06:00 UTC via the + module-health GitHub Action. +

+
+
diff --git a/apps/mana/apps/web/static/admin/complexity-map.html b/apps/mana/apps/web/static/admin/complexity-map.html new file mode 100644 index 000000000..48e2ce711 --- /dev/null +++ b/apps/mana/apps/web/static/admin/complexity-map.html @@ -0,0 +1,115 @@ + + + + +Mana — Complexity Map + + + +
+

Mana Complexity Map

+ Area = LOC · Color = git changes (last 6.months) + + coldhot + +
+
+
+ + + + \ No newline at end of file diff --git a/docs/complexity-hotspots.md b/docs/complexity-hotspots.md new file mode 100644 index 000000000..96360bcd4 --- /dev/null +++ b/docs/complexity-hotspots.md @@ -0,0 +1,94 @@ +# Cognitive Complexity Hotspots + +_Generated 2026-04-14 — heuristic scan (no ESLint deps)_ + +Complexity = sum of decision points per function (`if`, `for`, `while`, `case`, `catch`, ternary, `&&`, `||`, `??`). Threshold ≥ 10. + +**84 functions** exceed threshold across the scanned tree. Showing top 84. + +| # | Complexity | Lines | Function | File | +|---:|---:|---:|---|---| +| 1 | 77 | 217 | `createEventsRoutes` | `services/mana-events/src/routes/events.ts` | +| 2 | 65 | 426 | `createTokenManager` | `packages/shared-auth/src/core/tokenManager.ts` | +| 3 | 43 | 83 | `filteredTasks` | `apps/mana/apps/web/src/lib/modules/todo/components/pages/TodoPage.svelte` | +| 4 | 42 | 38 | `createContact` | `apps/mana/apps/web/src/lib/modules/contacts/stores/contacts.svelte.ts` | +| 5 | 42 | 226 | `createRsvpRoutes` | `services/mana-events/src/routes/rsvp.ts` | +| 6 | 41 | 389 | `createAuthRoutes` | `services/mana-auth/src/routes/auth.ts` | +| 7 | 38 | 258 | `createCreditService` | `packages/credits/src/createCreditService.ts` | +| 8 | 35 | 36 | `updateContact` | `apps/mana/apps/web/src/lib/modules/contacts/stores/contacts.svelte.ts` | +| 9 | 33 | 237 | `createPlayerStore` | `apps/mana/apps/web/src/lib/modules/music/stores/player.svelte.ts` | +| 10 | 32 | 80 | `logDay` | `apps/mana/apps/web/src/lib/modules/period/stores/dayLogs.svelte.ts` | +| 11 | 32 | 203 | `createDecksStore` | `apps/mana/apps/web/src/lib/modules/presi/stores/decks.svelte.ts` | +| 12 | 27 | 200 | `useAiTierItems` | `apps/mana/apps/web/src/lib/components/layout/use-ai-tier-items.svelte.ts` | +| 13 | 27 | 219 | `createEncryptionVaultRoutes` | `services/mana-auth/src/routes/encryption-vault.ts` | +| 14 | 25 | 221 | `useTaskForm` | `apps/mana/apps/web/src/lib/modules/todo/composables/useTaskForm.svelte.ts` | +| 15 | 23 | 39 | `onPhotoSelected` | `apps/mana/apps/web/src/lib/modules/food/ListView.svelte` | +| 16 | 23 | 80 | `updateTask` | `apps/mana/apps/web/src/lib/modules/todo/stores/tasks.svelte.ts` | +| 17 | 21 | 143 | `createSettingsRoutes` | `services/mana-auth/src/routes/settings.ts` | +| 18 | 21 | 59 | `handleKeydown` | `packages/shared-ui/src/command-bar/CommandBar.svelte` | +| 19 | 21 | 87 | `dropTarget` | `packages/shared-ui/src/dnd/drop-target.ts` | +| 20 | 21 | 62 | `handleKeydown` | `packages/shared-ui/src/quick-input/InputBar.svelte` | +| 21 | 20 | 43 | `publishEvent` | `apps/mana/apps/web/src/lib/modules/events/stores/events.svelte.ts` | +| 22 | 20 | 34 | `handleKeydown` | `apps/mana/apps/web/src/routes/(app)/+layout.svelte` | +| 23 | 20 | 90 | `passiveDropZone` | `packages/shared-ui/src/dnd/passive-drop.ts` | +| 24 | 19 | 29 | `syncSnapshotIfPublished` | `apps/mana/apps/web/src/lib/modules/events/stores/events.svelte.ts` | +| 25 | 18 | 49 | `updateDream` | `apps/mana/apps/web/src/lib/modules/dreams/stores/dreams.svelte.ts` | +| 26 | 18 | 103 | `uploadRoutes` | `services/mana-media/apps/api/src/routes/upload.ts` | +| 27 | 17 | 44 | `flushCompleteEvents` | `apps/mana/apps/web/src/lib/data/sync.ts` | +| 28 | 17 | 40 | `loadPhotos` | `apps/mana/apps/web/src/lib/modules/photos/stores/photos.svelte.ts` | +| 29 | 17 | 80 | `logPosition` | `apps/mana/apps/web/src/lib/modules/places/stores/tracking.svelte.ts` | +| 30 | 17 | 56 | `handleQuickSubmit` | `apps/mana/apps/web/src/lib/modules/times/components/EntryForm.svelte` | +| 31 | 17 | 27 | `handleReanalyze` | `apps/mana/apps/web/src/routes/(app)/food/[id]/+page.svelte` | +| 32 | 17 | 141 | `dragSource` | `packages/shared-ui/src/dnd/drag-source.ts` | +| 33 | 16 | 26 | `updateAlarm` | `apps/mana/apps/web/src/lib/modules/times/stores/alarms.svelte.ts` | +| 34 | 16 | 33 | `saveEdit` | `apps/mana/apps/web/src/routes/(app)/food/[id]/+page.svelte` | +| 35 | 16 | 118 | `createGeocodeRoutes` | `services/mana-geocoding/src/routes/geocode.ts` | +| 36 | 15 | 62 | `handleSend` | `apps/mana/apps/web/src/routes/(app)/playground/+page.svelte` | +| 37 | 15 | 225 | `createBetterAuth` | `services/mana-auth/src/auth/better-auth.config.ts` | +| 38 | 15 | 42 | `handleKeydown` | `packages/shared-ui/src/organisms/network/NetworkGraph.svelte` | +| 39 | 14 | 21 | `saveField` | `apps/mana/apps/web/src/lib/modules/contacts/views/DetailView.svelte` | +| 40 | 14 | 24 | `createAlarm` | `apps/mana/apps/web/src/lib/modules/times/stores/alarms.svelte.ts` | +| 41 | 14 | 55 | `handleBenchmark` | `apps/mana/apps/web/src/routes/(app)/llm-test/+page.svelte` | +| 42 | 14 | 198 | `createAdminRoutes` | `services/mana-auth/src/routes/admin.ts` | +| 43 | 14 | 59 | `handleLogin` | `packages/shared-auth-ui/src/pages/LoginPage.svelte` | +| 44 | 14 | 42 | `add` | `packages/shared-hono/src/auth.ts` | +| 45 | 14 | 17 | `handleKeyDown` | `packages/shared-stores/src/keyboard-shortcuts.ts` | +| 46 | 13 | 28 | `useFoodMealsSince` | `apps/mana/apps/web/src/lib/modules/body/queries.ts` | +| 47 | 13 | 15 | `updateEvent` | `apps/mana/apps/web/src/lib/modules/events/stores/events.svelte.ts` | +| 48 | 13 | 22 | `useAllFirsts` | `apps/mana/apps/web/src/lib/modules/firsts/queries.ts` | +| 49 | 13 | 132 | `createMoodsStore` | `apps/mana/apps/web/src/lib/modules/moodlit/stores/moods.svelte.ts` | +| 50 | 13 | 62 | `uploadAll` | `apps/mana/apps/web/src/lib/modules/picture/ListView.svelte` | +| 51 | 13 | 26 | `handleKeydown` | `apps/mana/apps/web/src/routes/(app)/calc/standard/+page.svelte` | +| 52 | 13 | 71 | `handleRegister` | `packages/shared-auth-ui/src/pages/RegisterPage.svelte` | +| 53 | 13 | 35 | `handleKeydown` | `packages/shared-ui/src/molecules/FilterDropdown.svelte` | +| 54 | 12 | 34 | `onSearch` | `apps/mana/apps/web/src/lib/modules/times/quick-input-adapter.ts` | +| 55 | 12 | 84 | `createSplitStore` | `apps/mana/apps/web/src/lib/splitscreen/store.svelte.ts` | +| 56 | 12 | 30 | `handleAnalyzePhoto` | `apps/mana/apps/web/src/routes/(app)/food/add/+page.svelte` | +| 57 | 12 | 96 | `createFeedbackService` | `packages/feedback/src/createFeedbackService.ts` | +| 58 | 11 | 22 | `onCreate` | `apps/mana/apps/web/src/lib/modules/calendar/quick-input-adapter.ts` | +| 59 | 11 | 21 | `updatePlace` | `apps/mana/apps/web/src/lib/modules/places/stores/places.svelte.ts` | +| 60 | 11 | 23 | `saveField` | `apps/mana/apps/web/src/lib/modules/plants/views/DetailView.svelte` | +| 61 | 11 | 35 | `initFromTask` | `apps/mana/apps/web/src/lib/modules/todo/composables/useTaskForm.svelte.ts` | +| 62 | 11 | 41 | `execute` | `apps/mana/apps/web/src/lib/modules/todo/tools.ts` | +| 63 | 11 | 38 | `scored` | `apps/mana/apps/web/src/lib/modules/todo/utils/time-estimator.ts` | +| 64 | 11 | 124 | `handleAuthReady` | `apps/mana/apps/web/src/routes/(app)/+layout.svelte` | +| 65 | 11 | 35 | `claimItem` | `apps/mana/apps/web/src/routes/rsvp/[token]/+page.svelte` | +| 66 | 11 | 28 | `handlePointerMove` | `packages/shared-ui/src/dnd/passive-drop.ts` | +| 67 | 11 | 44 | `calculatePosition` | `packages/shared-ui/src/molecules/ConfirmationPopover.svelte` | +| 68 | 11 | 80 | `results` | `packages/shared-ui/src/navigation/GlobalSpotlight.svelte` | +| 69 | 11 | 74 | `accountLinks` | `packages/shared-ui/src/navigation/PillNavigation.svelte` | +| 70 | 11 | 29 | `handleCreate` | `packages/shared-uload/src/ShareModal.svelte` | +| 71 | 10 | 41 | `uploadFile` | `apps/mana/apps/web/src/lib/components/wallpaper/WallpaperPicker.svelte` | +| 72 | 10 | 41 | `executeCurrentStep` | `apps/mana/apps/web/src/lib/modules/companion/components/RitualRunner.svelte` | +| 73 | 10 | 20 | `onSearch` | `apps/mana/apps/web/src/lib/modules/contacts/quick-input-adapter.ts` | +| 74 | 10 | 12 | `createDream` | `apps/mana/apps/web/src/lib/modules/dreams/stores/dreams.svelte.ts` | +| 75 | 10 | 27 | `mergeSymbols` | `apps/mana/apps/web/src/lib/modules/dreams/stores/dreams.svelte.ts` | +| 76 | 10 | 12 | `markAsLived` | `apps/mana/apps/web/src/lib/modules/firsts/stores/firsts.svelte.ts` | +| 77 | 10 | 40 | `logHabit` | `apps/mana/apps/web/src/lib/modules/habits/stores/habits.svelte.ts` | +| 78 | 10 | 64 | `setSchedule` | `apps/mana/apps/web/src/lib/modules/habits/stores/habits.svelte.ts` | +| 79 | 10 | 27 | `handleTimerEnd` | `apps/mana/apps/web/src/lib/modules/stretch/components/SessionPlayer.svelte` | +| 80 | 10 | 14 | `createTask` | `apps/mana/apps/web/src/lib/modules/todo/stores/tasks.svelte.ts` | +| 81 | 10 | 27 | `handleSuggestFromText` | `apps/mana/apps/web/src/routes/(app)/food/add/+page.svelte` | +| 82 | 10 | 85 | `redeemGift` | `services/mana-credits/src/services/gift-code.ts` | +| 83 | 10 | 65 | `deliveryRoutes` | `services/mana-media/apps/api/src/routes/delivery.ts` | +| 84 | 10 | 25 | `handlePointerMove` | `packages/shared-ui/src/dnd/drop-target.ts` | diff --git a/docs/complexity-map.html b/docs/complexity-map.html new file mode 100644 index 000000000..48e2ce711 --- /dev/null +++ b/docs/complexity-map.html @@ -0,0 +1,115 @@ + + + + +Mana — Complexity Map + + + +
+

Mana Complexity Map

+ Area = LOC · Color = git changes (last 6.months) + + coldhot + +
+
+
+ + + + \ No newline at end of file diff --git a/docs/module-coupling.md b/docs/module-coupling.md new file mode 100644 index 000000000..bc7d1e8d8 --- /dev/null +++ b/docs/module-coupling.md @@ -0,0 +1,136 @@ +# Module Coupling Report + +_Generated 2026-04-14_ + +- **fan-in** = how many other modules import from this module (high = shared / core) +- **fan-out** = how many other modules this module imports from (high = tightly coupled / leaky) + +Ideal: most modules have fan-in ≤ 2 and fan-out ≤ 2. Outliers are refactor candidates. + +## Ranked by fan-in (shared modules) + +| Module | fan-in | fan-out | Imported by | +|---|---:|---:|---| +| `calendar` | 1 | 1 | `todo` | +| `period` | 1 | 0 | `core` | +| `food` | 1 | 0 | `body` | +| `todo` | 1 | 1 | `calendar` | +| `admin` | 0 | 0 | — | +| `api-keys` | 0 | 0 | — | +| `automations` | 0 | 0 | — | +| `body` | 0 | 1 | — | +| `calc` | 0 | 0 | — | +| `cards` | 0 | 0 | — | +| `chat` | 0 | 0 | — | +| `citycorners` | 0 | 0 | — | +| `companion` | 0 | 0 | — | +| `contacts` | 0 | 0 | — | +| `context` | 0 | 0 | — | +| `core` | 0 | 1 | — | +| `dreams` | 0 | 0 | — | +| `drink` | 0 | 0 | — | +| `events` | 0 | 0 | — | +| `activity` | 0 | 0 | — | +| `feedback` | 0 | 0 | — | +| `finance` | 0 | 0 | — | +| `firsts` | 0 | 0 | — | +| `goals` | 0 | 0 | — | +| `guides` | 0 | 0 | — | +| `habits` | 0 | 0 | — | +| `help` | 0 | 0 | — | +| `inventory` | 0 | 0 | — | +| `journal` | 0 | 0 | — | +| `mail` | 0 | 0 | — | +| `meditate` | 0 | 0 | — | +| `memoro` | 0 | 0 | — | +| `mood` | 0 | 0 | — | +| `moodlit` | 0 | 0 | — | +| `music` | 0 | 0 | — | +| `myday` | 0 | 0 | — | +| `news` | 0 | 0 | — | +| `notes` | 0 | 0 | — | +| `photos` | 0 | 0 | — | +| `picture` | 0 | 0 | — | +| `places` | 0 | 0 | — | +| `plants` | 0 | 0 | — | +| `playground` | 0 | 0 | — | +| `presi` | 0 | 0 | — | +| `profile` | 0 | 0 | — | +| `questions` | 0 | 0 | — | +| `recipes` | 0 | 0 | — | +| `settings` | 0 | 0 | — | +| `skilltree` | 0 | 0 | — | +| `sleep` | 0 | 0 | — | +| `spiral` | 0 | 0 | — | +| `storage` | 0 | 0 | — | +| `stretch` | 0 | 0 | — | +| `subscription` | 0 | 0 | — | +| `themes` | 0 | 0 | — | +| `times` | 0 | 0 | — | +| `uload` | 0 | 0 | — | +| `who` | 0 | 0 | — | +| `zitare` | 0 | 0 | — | + +## Ranked by fan-out (leaky modules) + +| Module | fan-out | fan-in | Imports from | +|---|---:|---:|---| +| `body` | 1 | 0 | `food` | +| `calendar` | 1 | 1 | `todo` | +| `core` | 1 | 0 | `period` | +| `todo` | 1 | 1 | `calendar` | +| `admin` | 0 | 0 | — | +| `api-keys` | 0 | 0 | — | +| `automations` | 0 | 0 | — | +| `calc` | 0 | 0 | — | +| `cards` | 0 | 0 | — | +| `chat` | 0 | 0 | — | +| `citycorners` | 0 | 0 | — | +| `companion` | 0 | 0 | — | +| `contacts` | 0 | 0 | — | +| `context` | 0 | 0 | — | +| `period` | 0 | 1 | — | +| `dreams` | 0 | 0 | — | +| `drink` | 0 | 0 | — | +| `events` | 0 | 0 | — | +| `activity` | 0 | 0 | — | +| `feedback` | 0 | 0 | — | +| `finance` | 0 | 0 | — | +| `firsts` | 0 | 0 | — | +| `food` | 0 | 1 | — | +| `goals` | 0 | 0 | — | +| `guides` | 0 | 0 | — | +| `habits` | 0 | 0 | — | +| `help` | 0 | 0 | — | +| `inventory` | 0 | 0 | — | +| `journal` | 0 | 0 | — | +| `mail` | 0 | 0 | — | +| `meditate` | 0 | 0 | — | +| `memoro` | 0 | 0 | — | +| `mood` | 0 | 0 | — | +| `moodlit` | 0 | 0 | — | +| `music` | 0 | 0 | — | +| `myday` | 0 | 0 | — | +| `news` | 0 | 0 | — | +| `notes` | 0 | 0 | — | +| `photos` | 0 | 0 | — | +| `picture` | 0 | 0 | — | +| `places` | 0 | 0 | — | +| `plants` | 0 | 0 | — | +| `playground` | 0 | 0 | — | +| `presi` | 0 | 0 | — | +| `profile` | 0 | 0 | — | +| `questions` | 0 | 0 | — | +| `recipes` | 0 | 0 | — | +| `settings` | 0 | 0 | — | +| `skilltree` | 0 | 0 | — | +| `sleep` | 0 | 0 | — | +| `spiral` | 0 | 0 | — | +| `storage` | 0 | 0 | — | +| `stretch` | 0 | 0 | — | +| `subscription` | 0 | 0 | — | +| `themes` | 0 | 0 | — | +| `times` | 0 | 0 | — | +| `uload` | 0 | 0 | — | +| `who` | 0 | 0 | — | +| `zitare` | 0 | 0 | — | diff --git a/docs/module-health.md b/docs/module-health.md new file mode 100644 index 000000000..727f812a7 --- /dev/null +++ b/docs/module-health.md @@ -0,0 +1,120 @@ +# Module Health Report + +_Generated 2026-04-14 — git window: 6.months_ + +**Score** = `LOC × log₂(changes + 2)`. High score = big *and* churny = refactor candidate. + +**Totals:** web `103,053` · api `4,400` · services `35,660` LOC + +## Frontend modules (`apps/mana/apps/web/src/lib/modules`) + +| Module | LOC | Files | Largest file (LOC) | Changes (6mo) | Last changed | Score | +|---|---:|---:|---|---:|---|---:| +| `calendar` | 8,379 | 38 | `calendar/components/EventDetailModal.svelte` (657) | 26 | 19 hours ago | 40,281 | +| `todo` | 6,817 | 56 | `todo/stores/tasks.svelte.ts` (472) | 27 | 3 hours ago | 33,117 | +| `times` | 5,334 | 32 | `times/types.ts` (454) | 10 | 17 hours ago | 19,122 | +| `body` | 4,337 | 22 | `body/stores/body.svelte.ts` (467) | 11 | 46 minutes ago | 16,049 | +| `period` | 3,182 | 19 | `period/ListView.svelte` (780) | 17 | 17 hours ago | 13,517 | +| `dreams` | 2,835 | 10 | `dreams/ListView.svelte` (998) | 19 | 17 hours ago | 12,452 | +| `skilltree` | 3,178 | 20 | `skilltree/types.ts` (589) | 12 | 17 hours ago | 12,100 | +| `contacts` | 2,590 | 17 | `contacts/components/pages/ContactPage.svelte` (564) | 19 | 46 minutes ago | 11,376 | +| `habits` | 2,792 | 14 | `habits/ListView.svelte` (593) | 12 | 17 hours ago | 10,630 | +| `events` | 2,899 | 19 | `events/views/DetailView.svelte` (555) | 10 | 17 hours ago | 10,393 | +| `places` | 2,386 | 10 | `places/ListView.svelte` (1017) | 17 | 19 hours ago | 10,136 | +| `moodlit` | 2,143 | 13 | `moodlit/components/mood/MoodFullscreen.svelte` (613) | 10 | 2 days ago | 7,683 | +| `photos` | 2,147 | 17 | `photos/ListView.svelte` (430) | 9 | 46 minutes ago | 7,427 | +| `stretch` | 4,685 | 13 | `stretch/ListView.svelte` (710) | 1 | 2 hours ago | 7,426 | +| `memoro` | 1,352 | 12 | `memoro/views/DetailView.svelte` (320) | 23 | 70 minutes ago | 6,278 | +| `zitare` | 1,667 | 16 | `zitare/views/DetailView.svelte` (249) | 11 | 2 days ago | 6,169 | +| `guides` | 1,765 | 10 | `guides/views/DetailView.svelte` (583) | 9 | 46 minutes ago | 6,106 | +| `music` | 1,600 | 13 | `music/ListView.svelte` (402) | 11 | 17 hours ago | 5,921 | +| `news` | 1,909 | 16 | `news/ListView.svelte` (364) | 6 | 17 hours ago | 5,727 | +| `plants` | 2,421 | 16 | `plants/views/DetailView.svelte` (744) | 3 | 46 minutes ago | 5,621 | +| `chat` | 1,440 | 14 | `chat/views/DetailView.svelte` (273) | 12 | 17 hours ago | 5,483 | +| `calc` | 1,872 | 16 | `calc/components/CasioSkin.svelte` (285) | 5 | 2 days ago | 5,255 | +| `companion` | 1,460 | 9 | `companion/components/CompanionChat.svelte` (538) | 10 | 2 hours ago | 5,234 | +| `core` | 1,414 | 13 | `core/widgets/NutritionProgressWidget.svelte` (177) | 9 | 46 minutes ago | 4,892 | +| `inventory` | 2,076 | 20 | `inventory/queries.ts` (327) | 3 | 17 hours ago | 4,820 | +| `sleep` | 2,303 | 11 | `sleep/ListView.svelte` (559) | 2 | 2 hours ago | 4,606 | +| `questions` | 1,227 | 12 | `questions/stores/answers.svelte.ts` (271) | 10 | 17 hours ago | 4,399 | +| `firsts` | 1,835 | 8 | `firsts/ListView.svelte` (1266) | 3 | 17 hours ago | 4,261 | +| `picture` | 1,096 | 9 | `picture/ListView.svelte` (379) | 9 | 4 days ago | 3,792 | +| `who` | 1,035 | 8 | `who/views/PlayView.svelte` (306) | 8 | 2 days ago | 3,438 | +| `cards` | 993 | 13 | `cards/components/CreateDeckModal.svelte` (156) | 9 | 17 hours ago | 3,435 | +| `notes` | 981 | 9 | `notes/ListView.svelte` (405) | 9 | 17 hours ago | 3,394 | +| `journal` | 1,418 | 8 | `journal/ListView.svelte` (854) | 3 | 17 hours ago | 3,292 | +| `mood` | 1,534 | 9 | `mood/ListView.svelte` (548) | 2 | 2 hours ago | 3,068 | +| `drink` | 1,461 | 8 | `drink/ListView.svelte` (820) | 2 | 19 hours ago | 2,922 | +| `presi` | 767 | 9 | `presi/stores/decks.svelte.ts` (233) | 10 | 4 days ago | 2,750 | +| `storage` | 825 | 10 | `storage/stores/files.svelte.ts` (269) | 8 | 17 hours ago | 2,741 | +| `uload` | 885 | 7 | `uload/queries.ts` (270) | 5 | 4 days ago | 2,485 | +| `finance` | 905 | 8 | `finance/ListView.svelte` (417) | 4 | 17 hours ago | 2,339 | +| `citycorners` | 787 | 10 | `citycorners/queries.ts` (175) | 5 | 4 days ago | 2,209 | +| `recipes` | 1,352 | 8 | `recipes/ListView.svelte` (884) | 1 | 17 hours ago | 2,143 | +| `meditate` | 2,068 | 15 | `meditate/components/SessionPlayer.svelte` (551) | 0 | 17 hours ago | 2,068 | +| `automations` | 998 | 6 | `automations/ListView.svelte` (723) | 2 | 2 days ago | 1,996 | +| `food` | 1,742 | 15 | `food/mutations.test.ts` (294) | 0 | 46 minutes ago | 1,742 | +| `playground` | 715 | 9 | `playground/ListView.svelte` (155) | 2 | 4 days ago | 1,430 | +| `context` | 447 | 7 | `context/queries.ts` (155) | 5 | 4 days ago | 1,255 | +| `goals` | 556 | 2 | `goals/GoalEditor.svelte` (303) | 2 | 46 minutes ago | 1,112 | +| `mail` | 1,038 | 9 | `mail/ListView.svelte` (575) | 0 | 20 hours ago | 1,038 | +| `subscription` | 793 | 1 | `subscription/ListView.svelte` (793) | 0 | 2 hours ago | 793 | +| `api-keys` | 686 | 1 | `api-keys/ListView.svelte` (686) | 0 | 2 hours ago | 686 | +| `spiral` | 624 | 4 | `spiral/stores/mana-spiral.svelte.ts` (232) | 0 | 9 days ago | 624 | +| `themes` | 280 | 1 | `themes/ListView.svelte` (280) | 1 | 78 minutes ago | 444 | +| `activity` | 183 | 1 | `activity/ListView.svelte` (183) | 2 | 46 minutes ago | 366 | +| `admin` | 265 | 1 | `admin/ListView.svelte` (265) | 0 | 2 hours ago | 265 | +| `myday` | 231 | 1 | `myday/ListView.svelte` (231) | 0 | 18 hours ago | 231 | +| `profile` | 181 | 1 | `profile/ListView.svelte` (181) | 0 | 2 hours ago | 181 | +| `settings` | 101 | 1 | `settings/ListView.svelte` (101) | 0 | 2 hours ago | 101 | +| `help` | 40 | 1 | `help/ListView.svelte` (40) | 0 | 2 hours ago | 40 | +| `feedback` | 21 | 1 | `feedback/ListView.svelte` (21) | 0 | 2 hours ago | 21 | + +## API modules (`apps/api/src/modules`) + +| Module | LOC | Files | Largest file (LOC) | Changes (6mo) | Last changed | Score | +|---|---:|---:|---|---:|---|---:| +| `who` | 1,065 | 4 | `who/data/characters.ts` (490) | 3 | 4 days ago | 2,473 | +| `research` | 827 | 4 | `research/orchestrator.ts` (389) | 2 | 2 days ago | 1,654 | +| `traces` | 307 | 1 | `traces/routes.ts` (307) | 3 | 2 days ago | 713 | +| `todo` | 301 | 1 | `todo/routes.ts` (301) | 3 | 2 days ago | 699 | +| `presi` | 265 | 2 | `presi/routes.ts` (188) | 4 | 2 days ago | 685 | +| `news` | 190 | 1 | `news/routes.ts` (190) | 3 | 2 days ago | 441 | +| `picture` | 158 | 1 | `picture/routes.ts` (158) | 3 | 6 days ago | 367 | +| `guides` | 219 | 1 | `guides/routes.ts` (219) | 1 | 6 days ago | 347 | +| `storage` | 134 | 1 | `storage/routes.ts` (134) | 3 | 6 days ago | 311 | +| `music` | 122 | 1 | `music/routes.ts` (122) | 3 | 6 days ago | 283 | +| `chat` | 130 | 1 | `chat/routes.ts` (130) | 2 | 6 days ago | 260 | +| `contacts` | 102 | 1 | `contacts/routes.ts` (102) | 3 | 6 days ago | 237 | +| `food` | 222 | 1 | `food/routes.ts` (222) | 0 | 47 minutes ago | 222 | +| `plants` | 118 | 1 | `plants/routes.ts` (118) | 1 | 47 minutes ago | 187 | +| `context` | 87 | 1 | `context/routes.ts` (87) | 2 | 6 days ago | 174 | +| `calendar` | 111 | 1 | `calendar/routes.ts` (111) | 0 | 12 days ago | 111 | +| `moodlit` | 42 | 1 | `moodlit/routes.ts` (42) | 0 | 12 days ago | 42 | + +## Services (`services/`) + +| Module | LOC | Files | Largest file (LOC) | Changes (6mo) | Last changed | Score | +|---|---:|---:|---|---:|---|---:| +| `mana-auth` | 5,206 | 32 | `encryption-vault/index.ts` (607) | 45 | 47 minutes ago | 28,917 | +| `mana-notify` | 3,139 | 22 | `services/mana-notify/internal/handler/notifications.go` (493) | 20 | 6 days ago | 13,998 | +| `mana-sync` | 2,484 | 13 | `services/mana-sync/internal/sync/handler.go` (436) | 22 | 47 minutes ago | 11,389 | +| `mana-tts` | 2,444 | 10 | `services/mana-tts/app/main.py` (678) | 12 | 6 days ago | 9,305 | +| `mana-credits` | 2,140 | 23 | `sync-billing.ts` (357) | 15 | 2 hours ago | 8,747 | +| `mana-stt` | 1,948 | 9 | `services/mana-stt/app/main.py` (393) | 20 | 6 days ago | 8,687 | +| `mana-search` | 2,029 | 14 | `services/mana-search/internal/search/searxng.go` (305) | 16 | 4 days ago | 8,461 | +| `mana-llm` | 2,314 | 22 | `services/mana-llm/src/providers/ollama.py` (349) | 9 | 5 days ago | 8,005 | +| `mana-media` | 1,571 | 15 | `upload.ts` (393) | 24 | 47 minutes ago | 7,384 | +| `mana-events` | 2,063 | 20 | `services/mana-events/src/__tests__/items.test.ts` (344) | 5 | 6 days ago | 5,792 | +| `mana-api-gateway` | 1,381 | 12 | `services/mana-api-gateway/internal/service/apikeys.go` (257) | 11 | 7 days ago | 5,110 | +| `mana-crawler` | 1,411 | 8 | `services/mana-crawler/internal/crawler/crawler.go` (365) | 8 | 7 days ago | 4,687 | +| `mana-geocoding` | 900 | 10 | `services/mana-geocoding/src/routes/geocode.ts` (219) | 11 | 3 days ago | 3,330 | +| `mana-subscriptions` | 830 | 15 | `subscriptions.ts` (223) | 5 | 2 days ago | 2,330 | +| `mana-user` | 792 | 20 | `tags.ts` (211) | 4 | 6 days ago | 2,047 | +| `news-ingester` | 876 | 10 | `services/news-ingester/src/sources.ts` (261) | 3 | 5 days ago | 2,034 | +| `mana-image-gen` | 851 | 5 | `services/mana-image-gen/app/main.py` (365) | 3 | 6 days ago | 1,976 | +| `mana-video-gen` | 685 | 3 | `services/mana-video-gen/app/main.py` (405) | 3 | 6 days ago | 1,591 | +| `mana-analytics` | 470 | 12 | `feedback.ts` (127) | 5 | 6 days ago | 1,319 | +| `mana-mail` | 1,267 | 20 | `jmap-client.ts` (323) | 0 | 20 hours ago | 1,267 | +| `mana-voice-bot` | 507 | 2 | `services/mana-voice-bot/app/main.py` (505) | 2 | 6 days ago | 1,014 | +| `mana-landing-builder` | 352 | 8 | `services/mana-landing-builder/src/builder/builder.service.ts` (225) | 5 | 7 days ago | 988 | diff --git a/scripts/audit-complexity.mjs b/scripts/audit-complexity.mjs new file mode 100644 index 000000000..2704d4fa4 --- /dev/null +++ b/scripts/audit-complexity.mjs @@ -0,0 +1,175 @@ +#!/usr/bin/env node +// Lightweight per-function complexity audit. No deps. +// Heuristic: counts decision points (if / else if / for / while / switch case / catch / ternary / && / ||) per function body. +// Not as rigorous as SonarJS cognitive complexity, but finds the same outliers. +// Output: docs/complexity-hotspots.md — top 50 functions. + +import { readdirSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs'; +import { join, relative, extname } from 'node:path'; + +const ROOT = new URL('..', import.meta.url).pathname.replace(/\/$/, ''); +const SCAN_ROOTS = ['apps/mana/apps/web/src', 'apps/api/src', 'services', 'packages']; +const CODE_EXT = new Set(['.ts', '.tsx', '.js', '.mjs', '.svelte']); +const IGNORE = new Set(['node_modules', '.svelte-kit', 'dist', 'build', 'coverage', '.turbo']); + +function walk(dir) { + const out = []; + let entries; + try { + entries = readdirSync(dir, { withFileTypes: true }); + } catch { + return out; + } + for (const e of entries) { + if (IGNORE.has(e.name)) continue; + const p = join(dir, e.name); + if (e.isDirectory()) out.push(...walk(p)); + else if (e.isFile() && CODE_EXT.has(extname(e.name))) out.push(p); + } + return out; +} + +// Strip /* */ and // comments and string contents to avoid false matches. +function sanitize(src) { + return src + .replace(/\/\*[\s\S]*?\*\//g, '') + .replace(/\/\/[^\n]*/g, '') + .replace(/`[^`\\]*(?:\\.[^`\\]*)*`/g, '``') + .replace(/'[^'\\\n]*(?:\\.[^'\\\n]*)*'/g, "''") + .replace(/"[^"\\\n]*(?:\\.[^"\\\n]*)*"/g, '""'); +} + +// For .svelte: extract + + +`; + +const outputs = [ + join(ROOT, 'docs', 'complexity-map.html'), + join(ROOT, 'apps/mana/apps/web/static/admin/complexity-map.html'), +]; +for (const p of outputs) { + mkdirSync(join(p, '..'), { recursive: true }); + writeFileSync(p, html); + console.log(`Wrote ${relative(ROOT, p)}`); +}