Scope-first source organization
Status: approved plan for R0a
(decision); the
roadmap
owns the delivery order. Implemented: every package was moved into owned scopes
on 2026-09-29, and the maia-source ESLint rules enforce the contract below
at error severity. The SO6 migrations removed every violation that predated
the 2026-10-01 rules, so the shrink-only baseline is empty.
Planned: the open SO1a tooling gaps and the behavior work in SO3–SO5 and SO7.
Repository rules keep the authority, Effect, dependency and verification requirements. The roadmap owns current implementation status. Do not copy these rules into every folder.
Scopes
A scope is one named responsibility, such as interaction, terminal control, observation or connection discovery. It is not a folder for all types, all services or all components of unrelated features.
- Keep the existing packages. Add a package only for a shared boundary with at least two real consumers.
- Put implementation, private models, policies, fixtures and tests beside their owner. Split a large scope by lifecycle, trust or behavior, not by file count.
- Do not create empty scopes, one folder per function, forwarding chains without consumers, or mandatory service/controller/repository layers.
- Name files by responsibility:
interaction/recovery.ts, nothelpers.ts. A scopedpolicy.tsmay hold that scope's limits; a repository-wide constants barrel may not. - A move preserves public imports, operation IDs, wire meanings, database identities, receipts, confirmations and resource lifetimes. Simplify duplicated lifecycle and metadata handling, never authentication, revision checks, containment, cleanup, bounds or verification.
A scope grows by real responsibilities, not a template:
interaction/
├── index.ts # export-only public entry
├── interaction.ts # the scope's main file
├── interaction.test.ts
├── recovery.ts
├── recovery.test.ts
└── testing/
└── fixture.ts # scoped fixture, not a test bucket
A private type stays with its implementation until a second real module needs it. A folder that only contains child scopes needs no index.
Export-only indexes
An index re-exports explicit names from its owner and implements nothing:
export { makeInteractionClient } from "./client.js";
export type { InteractionClient } from "./client.js";
- No declarations, imports, initialization, default expressions, schema
construction, layer or registry assembly, or request handlers in
index.ts. - No wildcard exports: every exported name is a public commitment.
- Client assembly goes in a named module (for example
local-node-client.ts), registry assembly in the registry's owning module, and executable startup inbin/or a named composition module. - A source root may expose the package through
src/index.ts, tested bysrc/index.test.ts. Everything else lives in scopes. - Use the package's module-resolution convention, including
.jsspecifiers for compiled Node ESM.
Import direction
- Within a scope, import the implementation file directly, never the scope's own index.
- A relative import that leaves its folder targets the other scope's entry
(
../session/index.jsor../session), never a private file (../session/turn.js), including a parent reaching into a child. Children do not import the parent index. - A
testing/folder belongs to its enclosing scope; only tests and test support import it, from that scope or a scope nested in it. - Across packages, use declared package exports, never another package's
src/ordist/path. The internal tree implies no new package subpaths. - Node-only transport and discovery stay under the explicit
@maiaos/sdk/nodesubpath, never re-exported from the SDK root or a shared UI index. - Keep dependencies acyclic. An export-only barrel can still create a cycle or pull a native dependency into a browser bundle.
- Tests may use private files of their own scope; boundary tests use public exports; fixtures never become exported production capabilities.
- A temporary forwarder for a moved private path needs an exact consumer, a removal condition and an owner.
Private Effect entries
In packages/protocol a scope may add one private entry, effect-schema.ts,
beside its transport-neutral index.ts (D-062).
Protocol modules and tests import another scope's Effect Schema values through
it. In packages/sdk a scope may add one private entry, effect.ts, holding
its Effect-native operations (D-068); the scope's
Promise facade and other scopes' Effect code import it. No index.ts, the
package entry included, imports one, so the public declaration graph never
reaches effect. The policy object names each workspace with its entry stem.
Tests and fixtures
- A test sits beside the file it verifies:
x.test.ts,x.spec.tsorx.integration.test.tsbesidex.ts. A test of the whole scope is<folder>.test.tsorindex.test.ts. testing/holds scoped support fixtures, not a parallel production system or a replacement for colocated tests. Share a fixture through thetesting/folder of the nearest scope its users share, such assrc/testing/.- Product journeys stay in
e2e/. Framework routes stay thin and delegate to tested feature scopes. - Effect-owned behavior uses deterministic layers, clocks and scoped resources. Native, transport and storage contracts still need real-boundary tests.
- Changing directory depth means updating runner discovery, compiled test paths, source maps, build inputs and fixture paths. Prove that a nested sentinel test runs and that a failing one fails the runner. A zero-test run is not success.
Naming
| Item | Convention | Example |
|---|---|---|
| Folder | kebab-case noun for one responsibility; no generic bucket | managed-turn/ |
| File | kebab-case; does not repeat its folder's name | native-host/frame.ts, not native-host/native-host-frame.ts |
| Main file | <folder>.ts may repeat the folder name | voice/voice.ts |
| Test | <file>.test.ts, <file>.integration.test.ts or <file>.spec.ts beside <file>.ts | frame.test.ts; scope-wide voice.test.ts or index.test.ts |
| Entry | export-only index.ts | export { frame } from "./frame.js"; |
| Fixtures | direct files of the scope's testing/ | testing/session-fixture.ts |
| Platform variant | <file>.web.tsx or <file>.native.tsx beside <file>.tsx; counts as one module | renderer.web.tsx |
| React hook | use-<subject>.ts beside its subject; naming rules read <subject> | terminal-session/use-controller.ts, not use-terminal-session-controller.ts |
Generic folder names (common, constants, helpers, shared, types,
utils, tests, __tests__) and generic file names (common, constants,
helpers, misc, shared, types, utils) are rejected: name the owner.
Sub-scopes
| Limit | Value | Rule |
|---|---|---|
| Implementation files in one folder | at most 12 | folder-size |
| Siblings sharing their first two kebab words | at most 3 | prefix-cluster |
Folder levels below the source root (src/) | at most 5 | max-depth |
Tests, index.ts, declaration files, testing/ support and framework route
files do not count toward folder size, and platform variants of one module count
once. When four siblings share a prefix, move them into a sub-scope named after
it and drop the prefix: node-api-server.ts becomes node-api/server.ts. Split
an oversized folder by lifecycle, trust or behavior. The thresholds live in one
policy object.
Framework and native code
- The Expo Router route tree keeps its framework filenames; do not add index routes to satisfy the barrel rule. Route behavior lives in feature scopes.
- Generated declarations, executable bootstrap and test-support folders get only their rule-specific treatment; ordinary feature code is not exempt.
- Rust uses idiomatic modules and colocated Rust tests; Swift and other native files keep their platform boundary. No TypeScript indexes in native code, generated output, assets or documentation. ESLint checks do not cover Rust or Swift organization.
- Planned (D-182): new Rust crates for the terminal
screen engine/client, native process/PTY host and registry-generated binding
live under
crates/in a root Cargo workspace beside pnpm. Desktop's existing Tauri crate stays inapps/desktop/src-tauri. RN0 freezes the crate conventions here (naming, module layout, lints, error types and whereunsafemay appear), and the gate checks them with cargo fmt and clippy.services/nodeand its database migrations remain TypeScript/Effect.
Responsibility map
The same domain name may appear in several layers, but each layer owns a different responsibility. An interaction schema, SDK request, Node handler and view are not four implementations of answer authorization.
| Owner | Responsibility | Must not own |
|---|---|---|
packages/protocol | Operation metadata, schemas, errors, events, receipts and exposure policy | I/O, runtime services, client rendering or Node handlers |
packages/sdk | Typed requests, correlation/output verification, cancellation and explicit transports | Business authorization, durable storage, implicit Node startup |
services/node | Authentication/authorization, durable state, execution, event order and authoritative projections | Client-local focus, drafts or rendering |
packages/client-core | Portable client use cases, recovery and bounded projections over SDK | Another transport, native APIs, durable authority or worker supervision |
apps/cli | Parsing, SDK-backed command execution and bounded output | Hidden orchestration or a parallel backend |
apps/client | Rendering, local presentation state and explicit platform adapters | Direct Node transport/storage, model-controlled authority |
apps/desktop | Native host capabilities and platform transport/lifecycle adapters | Business-operation implementation |
packages/agent | Official worker integration and admitted resource/operation ports | Independent task truth or ambient authority |
Product requests go through public SDK operations to the Node. Rendering and
pure local selection may stay plain TypeScript; Effect owns non-trivial request,
stream, admission, deadline, cleanup and cancellation behavior. Keep standard
AbortSignal and the SDK public facade; expose Effect types from the SDK only
through @maiaos/sdk/node/effect (D-068).
Remaining target splits (SO6)
Create these only with the work that needs them. container.* and execution
workspace.* keep their meaning until the R1b naming migration.
- SDK: split
node/intodiscovery/,transport/andobservation/, andvoice/intoorchestrator/,managed-turn/andpi-relay/. Preserve the voice subpaths and package declarations, and keep authored request-specific binding checks when consolidating mutation-uncertainty handling. - Node: done in SO6g. The Node API lives in
runtime/node-api/withhttp/,observation/andrequest/, andstorage/holdsdatabase/,journal/,schema/(the migration registry and SQLite policy) andmigrations/(vNNN.tsfiles in ranges of at most 12). Domain persistence lives with its domain; database ownership, transactions, the journal and migrations stay central. Shared lower layers, such as the journal, the Session record and the process resource contract, have their own scopes so that no scope entry loads a module that imports back into its importer. Never split an atomic write for folder symmetry, never change historical migration contents, and never make a runtime scope request-scoped because its handler moved. - Client: done in SO6c. It keeps
app/,features/,platform/andui/; large features are split by behavior (features/voice/intocapture/,connection/,realtime/,managed-turn/,session/andcockpit/;features/workspace/intocoordinator/,demo-automations/,portfolio/andscope/;features/terminal/intoclient/,output/,renderer/andsession/), and the design foundation lives inui/theme/. Platform APIs and provider adapters stay at the platform edge. Reuse the Tamagui design system (decision). - Client core: add
observation/with its vertical slice. Recovery stores are bounded, ephemeral and owned above the component they serve; they never become server truth. - CLI: keep command-family scopes and a dependency-light help path; root command composition goes in named modules.
ESLint enforcement
The maia-source rules
are exported through an export-only entry
and composed at error severity in eslint.config.mjs
and apps/client/eslint.config.mjs. They cover JavaScript and TypeScript under
apps/*/src, packages/*/src and services/*/src. Each rule lives in its own
folder with passing and failing fixtures, and each message names the fix and an
anchor of this document.
| Rule | Checks |
|---|---|
layout | Only index.ts and index.test.ts at a src/ root; export-only indexes; generic folders; entry and tests |
folder-case | kebab-case folder names |
generic-file | generic file names |
no-stutter | file names that repeat their folder |
folder-size | implementation files per folder |
prefix-cluster | siblings that share their first two kebab words |
max-depth | folder levels below the source root |
test-pairing | a test beside the file it verifies, or the scope-wide test |
scope-entry-imports | relative imports that leave a scope target its index; test support stays with tests |
Rules run per file, so the pre-commit hook can lint one staged file; directory rules read that file's on-disk siblings. An unreadable directory is an error that no baseline entry can suppress.
Exemptions
Each exemption covers only the rules named, never neighboring helpers:
- Expo:
index.tsx,_layout.tsxand+not-found.tsxat any depth underapps/client/src/app/. Other Expo route names, such as[id].tsx, need an explicit admission in the rule, its fixtures and this list. - Route files need no index or test, do not count toward folder size and do not
stutter. Route segments are not generic folders and may use
(group),[param],[...rest]and[[...optional]]. - Bootstrap: direct non-test files in a workspace's
src/bin/need no index or test. - Fixtures: direct non-test files in a scope's
testing/(includingsrc/testing/) need no index or test and do not count toward folder size or prefix clusters;testing/child/fixture.tsand tests intesting/are not exempt. - Platform variants:
.web,.native,.iosand.androidfiles count as one module and pair with that module's tests. - Outside the source roots:
e2e/journeys, packagescripts/, native code and build output.
scripts/quality/lint-policy.mjs requires each of the three configs to import
the canonical sourceOrganization export and compose it in its default array.
It also forbids inline ESLint comments, local rule overrides and oversized
sources. Do not disable the rules inline, downgrade them or ignore source trees.
Legacy baseline
scripts/quality/lint-policy-baseline.json holds the lint-policy findings that
predate the policy and, under sourceOrganization, each legacy (rule, path)
with its occurrence count. ESLint suppresses a violation only up to its recorded
count, so a new violation fails at once; the config carries the baseline's
digest in its settings, so cached lint results expire when the baseline changes. pnpm lint:policy (part of
pnpm check:quality) scans the tree, prints the counts per rule and workspace,
and fails when a violation is missing from the baseline or an entry exceeds its
current count; a tooling test runs the same check. After a migration, run
node scripts/eslint/source-organization/audit/audit.mjs --prune, which lowers or
removes entries and never adds one (it refuses while a directory is unreadable),
and commit the smaller file. Since SO6g the sourceOrganization section is
empty: every source-organization violation fails at once. The same ledger
engine keeps the Effect-boundary baseline.
Dependency graph baseline
scripts/quality/dependency-graph.mjs (part of pnpm lint:policy, so of
pnpm check:changed and CI) resolves every import under each workspace src/
to a source file and records, in scripts/quality/dependency-graph-baseline.json:
exact edges inside recorded cycles and membership by kind; new acyclic edges
are free; F2 owns package edges.
- Resolution. Relative specifiers go through the TypeScript resolver;
workspace packages (including a package importing itself) go through their
package.jsonexports. An internal import that does not resolve is a diagnostic that fails the check. Assets and external modules are ignored. - Kinds.
static(a value import or re-export),lazy(import()) andtype(type-only, erased at build). - Cycles. Strongly connected components at three levels:
static,withLazy(static and lazy edges) andwithTypes(all edges). Each component lists its member files and the file edges inside it, by kind.
The check fails on a cycle that no recorded one covers, on a new edge inside a
recorded cycle, and on a recorded cycle that changed or is gone. A swap of equal
size or a reversed edge therefore fails. There is no accept mode: a new cycle is
removed, never recorded. After you shrink a cycle, run
node scripts/quality/dependency-graph.mjs --prune and commit the smaller
file; --prune refuses while anything is new or unresolved. To read an entry,
look at its level first: a static cycle can fail at load time, a withLazy
one when the lazy path runs, a withTypes one only couples types. A full scan
takes about 2.5 s.
Package ownership
scripts/quality/package-ownership.mjs (also part of pnpm lint:policy) reads
the same production source and checks, per workspace:
-
Every package an import names is in
dependencies,optionalDependenciesorpeerDependencies. Type-only imports count too, because published declarations expose them. AdevDependencypasses only whenbundledDevelopmentDependencieslists it with a reason, such as a package an executable build bundles; a listed entry that source no longer imports, or that is not adevDependency, fails as stale. -
A workspace import names a subpath the target package
exports. -
Workspaces import each other along the delivery order: packages never import apps or services,
protocolimports no workspace package, andsdkimports onlyprotocol(layerRules). -
A relative import never leaves its workspace; cross-workspace imports use the package name.
-
A tsconfig alias is a package-shaped name that
tscemits unchanged, so no workspace may use one. -
A dynamic
import()whose target is not a string literal fails in the dependency graph check, unlesscomputedImportAllowanceslists its file and line with a reason.
Workspaces come from pnpm-workspace.yaml, so a package without a src/ is
still known. Node builtins, self imports and test files are skipped. Unused
declared dependencies are not reported, because configuration files load some
packages.
Limits: the rules check names, paths, siblings and import specifiers. They do not prove that a scope is meaningful, a test covers its sibling, an authorization boundary is right, or a barrel adds no cycle. Those stay design review and build or contract checks.
Tooling checks:
node --test "scripts/eslint/**/*.test.mjs"
pnpm test:tooling
pnpm typecheck:tooling
pnpm exec eslint scripts/eslint/source-organization --max-warnings 0
Reachable files
scripts/quality/reachable-files.mjs (part of pnpm lint:policy) keeps files
that no production entry imports out of the Node. A type-only import counts as
reaching a file, so the check finds unreferenced files, not proven dead code. Every non-test source file under
services/node/src must be reachable through static, lazy or type imports
from an entry point: the package bin, every dist/ file a workspace script or a
package.json script loads, and the files started by path that pathStartedEntries lists with the
reason each is an entry. A file that only tests or testing/ support import is
unreachable. Existing unreachable files sit in
scripts/quality/reachable-files-baseline.json, each with a reason, and the
file only shrinks: node scripts/quality/reachable-files.mjs --prune removes
resolved entries and never adds one. To add a workspace, list its entry points
in the script.
Phases and acceptance
SO IDs are work-plan labels, not operations or runtime resources. Each owner
admits bounded work with exact files, criteria, independent review and recovery
evidence. Keep structural and behavioral changes separately reviewable. The
final gates are always pnpm check:quality and pnpm test; a scoped pass must
report unrelated failures and is not release qualification.
SO0 — baseline and ownership
Repeated for each admission. Record the exact revision, dirty-file hashes, public exports, operation IDs, runner discovery, dependency boundaries and relevant artifact and startup sizes. Separate pre-existing failures from new ones and rerun the parity checker instead of reusing old findings. Exit: a reproducible baseline and an agreed scope.
SO1 — parity and status closure
Owners: protocol/SDK, CLI inventory and quality tooling. Repair the parity
checker's inputs (scripts/quality/registry-parity.mjs), verify every exposed
command and SDK binding, and update help identities. Consolidate metadata around
the protocol registry without removing authored response-binding and authority
checks. Exit: parity passes for the current surface; component docs keep
source-present, integrated, verified and release-qualified apart. Maps to R0.
SO1a — tooling hardening before structural migration
Owners: quality/ESLint, hook and build-tooling owners with each package's
runner and framework owners. No new package, dependency or policy framework.
Closed already: lint-cache inputs (turbo.json lint tasks include the root
config, scripts/eslint/source-organization/** and the baseline, tested by
scripts/quality/turbo-lint-inputs.test.mjs), framework routes at any depth
(rule fixtures for nested Expo and Next routes), recursive test discovery in
packages/agent, packages/client-core, packages/sdk and apps/client, rule
fixtures that fail on ignored, unlinted or unparsable files, and
scope-entry-imports for scope entries and test-support imports. Open gaps:
- Test discovery. Closed for the package runners: the CLI and SDK
runners collect
.test-dist/**recursively, and the client runner collectssrc/**/*.test.ts, where an isolated failing nested sentinel returned nonzero. Built-artifact and E2E runners stay separate. - Dependency boundaries. Self-index and parent-index imports and
cross-package
src/ordist/access are not checked yet. Acceptance: positive and negative fixtures across import and re-export forms. Cycles and browser/native isolation fail the owning graph or build checks. - Config-policy agreement. The root and client configs keep local rule overrides that lint-policy forbids (held in the baseline). Acceptance: all three configs satisfy the policy without exceptions; shared-rule false positives are fixed at their owner with regressions.
- Staged-tree correctness.
scripts/hooks/staged-files.mjslists only added, copied, modified and renamed paths, and staged text is checked against working-tree siblings, so unstaged indexes or tests can satisfy a commit and deletions escape scope checks. Acceptance: isolated Git fixtures for partial staging, unstaged-only siblings, staged deletions and renames, and complete staged scopes; never modify the developer's index.
Review rule (not mechanical): an empty test file satisfies adjacency and an empty index satisfies the entry check, so reviewers check each scope's real responsibility, consumers, public surface and executed tests. Do not require one test per file or assertion counts.
Exit: independent review of the shared checks and each admitted package's runner cases; remaining package cases recorded and rerun before their own migrations.
SO3 — request and mutation lifecycle
Owners: SDK request boundary and client-core Effect owners. Today request
and controller orchestration is split across Effect, Promise code and React
hooks, and mutations normalize uncertain acknowledgements differently.
Centralize non-trivial orchestration, cancellation and cleanup in Effect behind
the existing facade and AbortSignal. Share mutation-outcome normalization
while keeping operation-specific scope, digest, revision and command binding;
reconcile the original command ID, never retry an unknown mutation under a new
one. Exit: tests for pre-dispatch cancellation and rejection, post-dispatch
loss, cancellation and timeout, malformed envelopes, correlation mismatch,
replay, original-key retention and unmount cleanup, each defect reproduced
without the fix first.
SO4 — independent client selection and voice ownership
Owners: protocol → SDK → Node → CLI → client-core/client and voice adapters.
Today UI selection calls workspace.select on the singleton active Workspace
context, and the Node voice orchestrator keeps at most four contexts
(MAX_ACTIVE_VOICE_CONTEXTS), evicting the oldest on refresh. Separate local
navigation from immutable command target and authority snapshots; replace the
global eviction with bounded client- or Session-owned admission that rejects
excess work explicitly. Exit: two independently selected clients, repeated
refresh by one without invalidating the other, stale and wrong-owner denial,
target-switch races, reconnect and disconnect without cancelling durable work;
the multi-client minimum qualified through DF-28. Maps to R1/R7.
SO5 — observation latency and fairness
Owners: Node observation/storage and SDK subscription owners, then client
projections. Today an empty journal read waits for the 5-second heartbeat
(NODE_API_OBSERVATION_HEARTBEAT_MS) before reading again. Wake readers on
commit independently of the heartbeat, with the journal as replay authority; use
bounded per-observer queues, explicit slow-consumer gaps, resets and
disconnects, and control reserves. Client callbacks never backpressure workers.
Exit: an event-after-empty-read regression; authenticated snapshot, replay,
gap and cancellation tests; malformed frames, slow consumers, reconnect storms
and bounded memory under overload; committed-to-visible p95 under 250 ms on
Desktop and 500 ms on mobile on qualified profiles, and at most 10% p95
regression for healthy, control and worker traffic (DF-28). Maps to R1/R9.
SO6 — client separation and remaining scopes
Owners: client-core and renderer/platform owners, then protocol, CLI, agent and Node scope owners. React renders; Effect services own resource workflows; platform adapters translate capabilities, not business policy. Deliver the remaining splits and remove temporary forwarders after their last consumer. Exit: per-scope unit and contract tests, built boundaries, renderer, accessibility and native checks where relevant, no forbidden imports or cycles, and no artifact or startup growth without measured justification.
SO7 — deterministic presentation, AI mode, qualification
Owners: protocol/SDK/Node and client presentation owners, after R9a's dependency, Effect Schema and catalog admission and the SO4/SO5 contracts (decision). Map mounted product views to real SDK operations; qualify the official json-render packages, transitive closure, licenses and renderer parity before installing them. Build deterministic composition first, then bounded Jev ranking after R10a. Trusted confirmations stay authored; a provider outage keeps manual views and control and reports AI mode unavailable under the shared readiness contract. Exit: PF-27/DF-27 view parity and PF-28/DF-28 isolation and fairness, malformed stream handling, UI crashes, Jev outage, native, accessibility and reconnect evidence.
Verification for each scope
- Read the scope's code, callers, schemas, state owner, tests and component rules; capture a baseline and claim exact files.
- Keep moves separate from behavior changes; keep a move map and the original export identity.
- Run formatting, lint, types, colocated tests, registry parity, nested discovery and affected public import and declaration tests.
- Exercise affected behavior and failure paths at the real boundary (SQLite, authenticated transport, built CLI, Node or native host) when those contracts change.
- Measure artifact size, dependency graph, CLI cold start and renderer startup when entrypoints or bundling change.
- Check documentation links and status; later edits invalidate earlier checks.
- Keep redacted logs, commands, toolchain, hashes and skipped checks outside build cleanup.
Roll back only the exact owned patch, never a shared checkout or another owner's work. A layout rollback never rolls back live data, processes or schemas.
Completed phases
Rows moved verbatim from the roadmap R0a table once done.
| Phase | Deliverable and owner | Entry / exit and product mapping |
|---|---|---|
| SO1b — organization rules / Done | Quality tooling adds folder-case, generic-file, folder-size (at most 12 files), prefix-cluster (fewer than 4 shared prefixes), no-stutter, scope-entry-imports, max-depth (at most 5 levels) and test-pairing beside layout: one folder per rule with passing and failing fixtures, thresholds in one policy object, and messages that name the fix and a policy anchor (decision). | Root/client/site configs run every rule at error; a single staged file and an unreadable directory are covered; ESLint and the full-tree scan agree per rule. |
| SO1c — shrink-only baseline / Done | One tracked baseline in scripts/quality/lint-policy-baseline.json records each legacy (rule, path) with its count. ESLint suppresses recorded counts only; pnpm lint:policy prints counts per rule and workspace; audit.mjs --prune only lowers entries. | A new violation fails lint and the tooling test; a stale or too-high entry fails until removed. Recorded 1 Oct 2026: 925 entries, 2403 occurrences. |
SO6a — packages/agent / Done | Drop the folder prefix from extension files and name worker tests after the files they verify; exports and the bin path unchanged. | Baseline 7 -> 0 entries; 66 tests in 16 files before and after; typecheck, build and public declarations unchanged. |
SO6b — packages/client-core / Done | Drop the folder prefix from interaction and terminal files, merge portfolio failure tests into the scope test, and collect nested tests; export keys unchanged, two targets renamed. | Baseline 7 -> 0 entries; 68 tests before and after (14 -> 13 files); a nested sentinel that the old runner glob missed is collected. |
SO6c — apps/client / Done | Split features/voice (six scopes), features/terminal and features/workspace (four each), move the design foundation to ui/theme/, give the error clipboard its own platform scope, drop folder prefixes in features, platform/ and platform/desktop, route relative imports through scope entries and collect client tests recursively; routes and lazy chunks unchanged, the unmounted terminal client left out of the platform entry. | Baselines 162 -> 0 entries (296 occurrences) and 8 -> 0; 188 client unit tests before and after; E2E 115 passed, 1 skipped, 1 known WebKit timeout before and after. |
SO6d — packages/protocol / Done | Drop names that repeat their folder; route imports through Effect-free scope entries and private effect-schema.ts entries (D-062); split execution/ into catalog, placement, relocation and worktree scopes; move shared wire primitives to envelope/ and turn events to session/; split contracts per family so tests pair; give feature descriptors and the operation registry their own scopes. | Baseline 135 -> 0 entries (378 -> 0 occurrences); 481 -> 483 tests (two new scope tests), names unchanged; public export names locked and identical; no export or scope entry reaches effect. |
SO6e — packages/sdk / Done | Drop folder prefixes from execution, node, request, session and voice files, pair the node, client and project-workspace tests with the files they verify, and collect nested tests; export keys unchanged, two voice subpath targets renamed. | Baseline 25 -> 0 entries (25 occurrences); 122 tests before and after (24 -> 20 files); runtime and declared exports identical per key; a nested sentinel is collected and fails the run. |
SO6f — apps/cli / Done | Route cross-scope imports through scope entries, move family output projections into output/ sub-scopes and argument, prompt and stdin input into command-line/input/, load Node-backed families on demand, and pair tests with their files. | Baseline 97 -> 0 entries; 359 -> 364 test runs (six formatter tests and a help-path guard added, two duplicate runs gone); standalone 259,256 bytes; help loads no Node client. |
SO6g — services/node / Done | Split runtime/process/resource (contract, tree, launch, native host), storage (database, journal, schema, versioned migrations as storage/migrations/v030-v039/v035.ts), runtime (errors, Node API with http, observation and request), execution and the Session and Terminal storages; give shared lower layers their own scopes so scope entries stay acyclic; move shared fixtures to src/testing/; name tests after the files they verify; route every cross-scope import through a scope entry. | Baseline 484 -> 0 entries (1441 -> 0 occurrences), so the repository baseline is empty; 1930 -> 1932 tests (two new Session digest tests), names unchanged; migration registry and fresh schema identical; no import cycle; public exports and bin unchanged; maia-node.mjs 2,999,442 -> 3,006,730 bytes (+0.24%: 56 entry modules, and three modules that entries now load at startup). |
Imported from Maia OS docs at a2175bb