Skip to main content
Amended by 0249: one app member builds web, PWA and desktop targets.
Amended by 0234: files shipped with a package live in assets/, exported as ./assets/*, not resources/. resources/ is reserved for @Resource declarations (0164).
Amended by 0259: where a feature module lives. The service shape of decision 1 stands.
Amended by 0264: The kind folders hooks/, widgets/, pages/, links/, drivers/ and ai/ join decision 3’s list, each created with its first file.
Amended 2026-10-21 (STA-219): config lives in src/config/, package static files in assets/, the root-module test is <name>.module.int.test.ts, and the kind-folder list is the one in ADR 0130. See the amendment at the end of this page.

Context

pnpm new still rendered shapes the owner has since replaced. Packages came in three archetypes from 0004 (domain with four layers, platform, flat lib); a service rendered an Encore unit at services/src/<name> with encore.service.ts and api.ts, which 0091 and 0124 rule out; there were app-next and app-expo kinds; and 0040 organised a service’s modules in DDD layers. The owner’s locked decisions of 2026-10-19 and 2026-10-20 replace each of these: FW1 (entries-first packages), audit Q9 (action-centric kind folders), G2 (each member declares its root module), console = Filament-style resources, TS1/TS2 (test tiers by filename infix), CF1 (config as config/<pkg>.config.ts), the code conventions (no enum, component folders, private, no Storybook), B16 (no Next.js), MR7 (a desktop app), audit Q10 (no app()/config()/logger() helpers), the declaration rule, the devtools entry and FO5/FO7 (a separate private figentra-os repo). The templates are where a shape becomes real for every new member, so they are decided here together.

Decision

  1. Service. pnpm new service <name> renders a plain-TypeScript member at services/<name>, @figentra/service-<name>, with no Encore file:
    • src/main.ts bootstraps the root module; src/<name>.module.ts is the root @Module with an empty imports list; package.json declares "stackra": { "module": "./src/<name>.module.ts" } (0090).
    • src/modules/ starts empty; its README lists the kind folders of decision 3.
    • src/contracts/index.ts is the types-only ./contracts entry (0117).
    • config/<name>.config.ts holds deploy-time defaults (CF1); src/__tests__/<name>.module.test.ts boots the root module.
    • Until @stackra/container ships @Module, the root module is a plain class with a static imports field, marked as a shortcut in the template.
  2. Package. One entries-first shape replaces the domain, platform and lib archetypes of 0004:
    • Entries are . (core, src/core/), ./contracts, ./node, ./react, ./native, ./testing and ./devtools. Core is always present; the others are chosen with --entries (or a prompt on a terminal) and are declared only with real code.
    • config/<pkg>.config.ts and resources/ (exported as ./resources/*) sit outside src/.
    • An entry never imports another entry; react and native share an internal src/ui/ folder.
    • tooling stays the tier:config preset shape.
  3. Kind folders inside a feature module (services and packages), created with their first file: models/, actions/, queries/, services/, events/, listeners/, workflows/, policies/, permissions/, settings/, contracts/, resources/ (Filament-style panel resources); and interfaces/, types/, constants/, tokens/, errors/, decorators/ once there are two or more files of that kind. There are no controllers/, dtos/, repositories/, helpers/ or utils/ folders. This replaces the four layers of 0040 decisions 1 and 2.
  4. Declarations. Decorators annotate code (classes, methods, fields); define* creates values other code references (defineToken, defineConfig, definePermissions, defineEvents, defineMeters, defineEntitlements). A package that defines decorators ships an Explorer that registers them at bootstrap through the container’s DiscoveryService.
  5. Apps. Four kinds:
    • app:console: Vite, React Router 7 with code-based routes, HeroUI (through @stackra/ui once it exists), and a panel shell that hosts Filament-style resources.
    • app:mobile: a copy of apps/mobile, so Expo SDK 57, code-based React Navigation (no expo-router), HeroUI Native and Uniwind.
    • app:desktop: Electron packaged by electron-builder (MR7), as a scaffold.
    • app:docs: Mintlify.
    There is no Next.js kind, template, tsconfig preset or catalog (B16).
  6. Rules in every template. Config is YAML where the reader supports it (MUST_STAY_JSONC, MUST_STAY_JSON); versions are catalog: or workspace:* only; tests are <subject>.test.ts in __tests__/, with .int, .contract, .e2e or .load before .test for those tiers, and the unit test task excludes the infixed tiers; every source file opens with a contract block (0007); no enum, no #private, no app()/config()/logger() helpers; a component is a folder x/{x.tsx, index.ts, __tests__/x.test.tsx}; no *.stories.*.
  7. The generator. pnpm new <kind> <name> takes the kinds service, package, tooling, app:console, app:mobile, app:desktop and app:docs. Every option is a flag (--category, --entries, --description, --title, --port, --dry-run), and --repo <path> renders into another monorepo with that repo’s LICENSE.

Consequences

  • A new member starts in its final shape and passes pnpm validate without edits; the scaffold test proves it for every kind.
  • services/example and services/health gain a root module and stackra.module; the reference services of FW10 replace them later.
  • The figentra-os repo is scaffolded with the same generator, so both repos share one member shape.
  • The service and package templates carry shortcuts until @stackra/container and @stackra/config exist; each is marked in the file it affects.
  • The electron catalog entry is unused until the first desktop app, which also adds electron to allowBuilds.

Enforcement

  • tools/scripts/__tests__/scaffold.test.mjs renders every kind and runs the validator’s member, service and exports checks on the result.
  • validate-repository.mjs checkService: a service has src/main.ts, a stackra.module that exists, and no encore.service.ts or encore.app; checkTestPlacement bans *.stories.*.
  • Planned: a validator check that a package’s entry folders match its exports and tsdown.config.ts, and an oxlint rule that entries never import each other.
Amended by 0269 (decision 6): decision 2’s entry list has one named exception, the ./e2e and ./contract entries of @stackra/testing.

Amendment 2026-10-21: config, assets and the root-module test (STA-219)

Context. Decisions 1 and 2 put config beside src/ and shipped files in resources/, and decision 1 named the root-module test <name>.module.test.ts. The scaffold and the preview repository differ on the test name, and the 2026-10-19 CF1 rule (config as config/<pkg>.config.ts) sits outside src/, against the rule that every member keeps its code in src/. The owner settled these on 2026-10-21. Decision.
  1. Config is inside src/. A service’s deploy-time defaults are src/config/<name>.config.ts, and a package’s are src/config/<pkg>.config.ts. Replace the config/ paths in decisions 1 and 2 with these. ADR 0177 carries the package side forward.
  2. Static files are assets/, not resources/. Files shipped with a package sit in an assets/ folder at the package root, exported as ./assets/*. resources/ is the kind folder of @Resource declarations (ADR 0130), and never a folder of static files. The resources/ path in decision 2 is withdrawn, and the scaffold template is changed to match (T3).
  3. The root-module test is <name>.module.int.test.ts. It lives in src/__tests__/ and boots the root module, so it is an integration test under the .int tier of ADR 0182. The unit name <name>.module.test.ts in decision 1 is withdrawn.
  4. Reference services. services/example and services/health do not yet have src/config/, src/contracts/ or src/__tests__/ (the gap in Consequences stands). Closing it belongs to the golden-members work (L7); this amendment does not move them.
Consequences.
  • The scaffold test (tools/scripts/__tests__/scaffold.test.mjs) must check the new paths; the template changes are T3.
  • A reader looking for config looks in src/config/; a reader looking for static files looks in assets/.
Enforcement. planned — the scaffold test renders the new paths, and checkService in tools/scripts/validate-repository.mjs checks src/config/ once the structure work (STA-220) lands. Until then the paths in decisions 1 and 3 are not checked.

Supersedes and sources

Supersedes: 0004. Amends 0040 (decisions 1 and 2: kind folders replace layers) and 0010 (decision 1: a service also declares its root module and holds config/). Sources: owner decisions FW1, audit Q9 and Q10, G2, TS1/TS2, CF1, B16, MR7, FO5/FO7, the code conventions, the declaration rule and the devtools entry (2026-10-19 and 2026-10-20).