ELAS

Get started with ELAS

You've got ELAS running — now make your first change. Just follow the steps below.

About 10 minutes  ·  no experience needed
1 · Triage2 · Rules3 · Spec 4 · Where code goes5 · Test6 · Ship

1 What kind of change is it? · the key habit

Type your change — get instant guidance

Before writing code, figure out if your change is about how the app looks (frontend) or how it works (backend). Type it below:

Frontend

Run this for the real, AI-powered answer (and the exact next steps):

npx tsx scripts/triage.ts "describe your change"

The command always works in your terminal too:

npx tsx scripts/triage.ts "make the contact status show on mobile"

2 Build it the right way

Five rules we always follow — they keep the app safe and consistent:

One company at a time Data is always scoped by orgId.
The server decides access Hide buttons with useAccess().can(...); the server re-checks.
Artie can do it too If it's an action, add it as a tool.
Works on a phone Check it looks good at 375px wide.
Reuse, don't recreate Build on what already exists.
The full how-to Everything is in docs/BUILDING.md.
REUSE

Before building any UI, browse the Component Library. It shows every shared component rendered on the page — search/filter them, interact, and copy the code. Reuse one instead of making a new version.

Open the Component Library → Open Storybook →

3 Write a spec first · only for Backend / Both

PIPELINE

Spec → TypeScript → finished, in one loop. The spec.yaml is the source of truth; the in-repo generator turns it into typed code; make spec builds & verifies it.

idea → triage → spec-new / spec-draft → author spec.yaml → spec-gen → fill <spec:impl> + tests → make spec → PR
                       (scaffold)          (your contract)   (gen TS)    (your logic)                (build+test+drift)

Backend / Both → use the generator below. The repo's own make spec-gen IS the generator (you can author the files in Kiro, a spec-driven IDE, but that's optional). Frontend → your normal editor (VS Code / Cursor) + Claude Code — no spec.

1 · Scaffold the folder — from a plain-English idea (AI drafts spec.yaml), or blank:

make spec-draft FEATURE=warranties DESC="warranties with an expiry date"
make spec-new FEATURE=warranties

Either creates specs/warranties/ with the four files below. (No Docker? use npm run spec:draft / npm run spec:new.)

2 · Author them — fill spec.yaml (the contract the generator reads) + the prose. Templates:

spec.yaml  — the machine-readable contract (the generator reads this)
# Machine-readable contract for this feature — the generator reads THIS file. # Run `npm run spec:gen` after editing. feature: __FEATURE__ module: operations # sales | operations | finance | hr | timesheets | core | agent | integrations entities: - name: __FEATURE__ # ERP resource key — camelCase plural, unique label: __FEATURE__ # human label (sidebar / bulk import) typed: false # false = schemaless JSONB (no migration); true = a Drizzle table importable: true # adds it to bulk import fields: # type: text | number | boolean | date | enum(values) | ref(resource) | json | string[] - { name: name, type: text, required: true } # - { name: amount, type: number } # - { name: status, type: enum, values: [open, closed], default: open } # actions: # optional → governed MCP tools (agent ⊆ user); logic goes in <spec:impl> # - { name: do_something, permission: operations.edit, undoable: true, args: { id: number } } # screens: # optional → sidebar-discoverable screen scaffolds # - { id: __FEATURE__, label: __FEATURE__, module: operations }
requirements.md  — what it must do (numbered, testable)
# __FEATURE__ — Requirements WHAT & WHY in 1–2 sentences. Then numbered, **testable** acceptance criteria. - R1. <a record/field/rule, stated so it can be asserted in a test> - R2. <who can do what — maps to a permission, e.g. `operations.edit`> - R3. <a derived rule — implement by hand in <spec:impl>, cover with a unit test>
design.md  — the data, the API, and where each rule lives
# __FEATURE__ — Design The shape lives in `spec.yaml` (the generator reads it). This file is the human narrative: the data model, the API contract, and — crucially — **where each non-trivial rule lives**. ## Data model See `spec.yaml`. (Schemaless ERP resource unless an entity is `typed: true`.) ## API Governed generic CRUD via `/api/erp/__FEATURE__` (GET→view, POST/PATCH→edit, DELETE→delete). Add a dedicated route only for behavior CRUD can't express. ## Rules → location | Rule (from requirements.md) | Where it's implemented | |---|---| | R3 (example derived rule) | hand-written in the `<spec:impl>` region of the service/tool stub | ## UI hints <list / form fields / status chips, etc.>
tasks.md  — your build checklist
# __FEATURE__ — Tasks - [ ] Fill in `spec.yaml` (entities, fields, module, actions, screens). - [ ] `npm run spec:gen` — generate the contract (registry, types, permissions, scaffolds). - [ ] Implement business logic in the `<spec:impl>` regions of any generated tool/service stub. - [ ] Write unit tests mapping each acceptance criterion (`*.test.ts`); `npm test` green. - [ ] If any entity is `typed: true`: `npx tsx scripts/pg/migrate.ts` to apply the migration. - [ ] `npm run spec:check` + `npm run build` green; open the PR (spec + diff).

3 · Generate the TypeScript, then 4 · finish it out:

make spec-gen
make spec

spec-gen writes the registration regions + a screen scaffold; make spec regenerates, type-checks, builds, tests & runs the drift gate — all in the dev container.

RULE

Generated vs yours. The generator owns the // <generated:spec> regions; you own spec.yaml, the <spec:impl> logic, and the tests. Never hand-edit a generated region — CI's spec:check drift gate will fail. After generating, spec-gen prints the few manual hookups it can't place for you (the sidebar entry, the Artie tool, a typed-table migration).

See a real, finished example: specs/org-chart.

4 Where do I paste the code?

Each kind of change has a home. Put your code here:

What you're addingWhere it goes
A new screen / pagecomponents/screens/YourScreen.tsx + a route at app/dashboard/your-screen/page.tsx, then wire it into components/AppSidebar.tsx + lib/features.ts
A reusable UI piececomponents/ (shared) or components/ui/ (shadcn primitives)
Business logic / a calculator / a rulelibs/ (pure functions — put a *.test.ts right next to it)
A new kind of record (data type)register it in libs/erp/resources.ts + libs/erp/types.ts + libs/access.ts — CRUD, API & Artie access come for free (usually no migration)
A custom API endpointapp/api/.../route.ts — wrap it with withRoute(...) and check can(...)
Let Artie do an actionadd a tool in libs/artie/tools.ts
Testsnext to the file, named thing.test.ts
Your specspecs/your-feature/

Not sure which file? Re-run the triage command from step 1 — its full output names the exact files and the workflow.

5 Test & check

Before you share, both of these must pass (green):

npm test
npm run build

Added a calculation or rule in libs/? Add a quick *.test.ts next to it.

Need the real app (backend/AI)? Make a .env.local with DATABASE_URL, BETTER_AUTH_URL, BETTER_AUTH_SECRET, and ALLOW_DEV_NO_AUTH=true. Full list in README.md §7.

6 Ship it

Merged PRs go to staging automatically; a maintainer cuts a release to send it to production.

Where to go next

These open in the app's built-in doc viewer — rendered straight from the repo's Markdown files at build time, so they're never a stale copy (run the app first):

Browse every shared component (live "Storybook")localhost:4000/dev/components — run the app first
Understand the big pictureREADME
Build a feature / tool / integrationBUILDING.md
Learn the spec processSESSION_SUMMARY.md
Understand permissionsACCESS.md
The full contributor mapCONTRIBUTING.md

Tip: each doc page links across to the others and to the component library.