repo-tree
GitHub解析 LangWatch 仓库目录结构,指导代码、模块及服务的正确放置位置,解决包与应用的归属疑问。
Trigger Scenarios
Install
npx skills add langwatch/langwatch --skill repo-tree -g -y
SKILL.md
Frontmatter
{
"name": "repo-tree",
"description": "Where a thing lives in the LangWatch tree and what each top-level folder is for. Use when someone says 'where does this go', 'which package', 'new package', 'is this a module or a package', 'apps vs modules vs packages vs services vs tools', 'what is a generated module list', 'catalogue.json', 'generate:modules', 'which app runs this', or opens a folder and cannot tell what it owns. Teaches the map and the placement test; the record is dev\/docs\/ARCHITECTURE.md sections 1 to 3.",
"user-invocable": true
}
The repository tree
One pnpm workspace (TypeScript), Go services and Python services beside it. The rule that names every package: where the code runs, or what it declares (ARCHITECTURE.md §2). The tree is the contract; when this skill and the linter disagree, the linter wins (§17).
The map
| Folder | Holds | Rule |
|---|---|---|
apps/ |
api, worker, tasks, backend (api and worker in one process), ui, server (npx CLI), scenario-child, plus the *-web internal consoles |
An app is src/main.ts and src/config.ts; scenario-child is the isolated runner (§4). No product code (§1). |
modules/<name>/ |
One feature: contract/ process/ browser/ client/, plus specs/ and adrs/ |
A module is an isolated microservice (§3). |
enterprise/modules/<name>/ |
The same shape, licence-gated at runtime | Always installed; refuses per organisation (§3, §11). |
packages/ |
Framework only | A package earns its place by being framework, not feature (§2). |
services/ |
Go (aigateway, nlpgo, langyagent, the *sims), Python (langevals) |
Not in the Node module system. |
tools/ |
Dev tooling: thuishaven (haven), dev-runtime, workerrun |
Not shipped. |
specs/ |
Cross-cutting Gherkin feature files | Module specs live in modules/<name>/specs/. |
sdks/, mcp/, docs/ |
Published SDKs, the MCP server, the public docs site | Not the product. |
dev/ |
Architecture record, ADRs, best practices, dev scripts | dev/docs/ARCHITECTURE.md is the one record. |
Rules that matter
- Apps hold no product code.
apps/api/src/main.tsboots a server and serves the installed modules. If you are writing behaviour in an app, it belongs in a module. - Feature code in
packages/is a defect. If the code names a product subject (trace, monitor, invite), it is a module. - One subject, one owning module.
modules/catalogue.jsonmaps every subject to exactly one module. Another module reaches the subject through the owner's*Apiand contract only (§3).modules/README.mdprints that map with each module's tables and peers, and every module has its ownREADME.md(ownershipskill). - The prefix tells you the graph. Nothing
browser-*in a server graph; noprocess*package in a web graph (§2). A contract imports the light@langwatch/modulecore, plus only the portable surfaces §2 names (config and secret handles, errors, the span vocabulary,@langwatch/eventing/tables); never a runtime. - Generated lists are never edited. The module list each app carries
(process halves in
apps/{api,worker,tasks}/src/process-modules.generated.ts; browser halves inapps/ui/src/browser-modules.generated.ts) is written bypnpm generate:modulesfrom the catalogue. Add a module by editingmodules/catalogue.json, then run the generator. - Shared browser code has three homes, no kits. Component: the design
system. Pure domain logic: the owner's contract. Data: the owner's
<name>-client(§3.4). A*-browser-kitpackage is a deleted spelling. - A package named by a module has four suffixes:
@langwatch/<f>-contract,-process,-browser,-client. Enterprise ones carryenterprise-in the name (@langwatch/enterprise-billing-process). - A portable library both halves import sits inside the owning module
(
modules/analytics/filtersexists; the record also namesmodules/trace/query-language, not in the tree yet), never inpackages/(§2). It is the one sanctioned folder beside the four halves,specs/andadrs/.
Placement test
Ask in order. Stop at the first yes.
| Question | Goes to |
|---|---|
| Does it name a product subject? | The module the catalogue gives that subject. |
| Does a screen render it? | modules/<f>/browser/ (layers in the browser-module skill). |
| Is it a component several modules share? | packages/design-system (§2). |
| Is it data several browsers read? | The owner's modules/<f>/client/. |
Is it a schema, error or *Api another module calls? |
The owner's contract/. |
| Does it start a process, parse config or open stores? | A framework package (process, process-stores, config). |
| Is it a backfill or data move for a release? | A defineMigrationStep in the module's .withMigrations (ledger, run by the worker's upgrade). |
| Is it a job an operator starts by name? | The module's process/src/tasks/, run by apps/tasks, outside the ledger. |
| Is it Go or Python? | services/<name>. |
| Is it dev-only tooling? | tools/<name>. |
If no row fits, that is a design question. Do not invent a folder: ask.
Worked example: modules/monitor
The smallest full module. Read it before inventing a layout.
modules/monitor/
├── adrs/ specs/
├── contract/src/ monitor.api.ts monitor.errors.ts monitor.trpc.ts index.ts
├── process/src/ monitor.module.ts index.ts app/ services/ repositories/
│ rules/ transport/
└── browser/src/ monitor.web.ts model/ behavior/ ui/{elements,blocks,sections}
Its catalogue entry is one line of data: id: "monitor", root: "modules/monitor",
classification: "core", subjects: ["monitor"]. modules/organization owns
seven subjects (group, invite, membership, organization, team, ...), so
one module may own several subjects. modules/automation adds eventing/,
channels/ and migrations/ to process/src/; add a folder only when the
module has that kind of thing.
Traps
- Reading
dist/or*.generated.tsas source. Both are outputs. Fix the catalogue or the source, regenerate. - A new folder under a module root. The root holds the four halves, a
module-owned portable library,
specs/andadrs/only. The folder grammar ispackages/oxlint-rules/grammar/feature-layout-policy.mjs; the linter refuses a stray folder (see thelintingskill). - A folder that keeps growing. Past the
source-folder-shapebudget it is grouped intofeatures/<concern>/, one level only (§3.4). The number is the policy's (FOLDER_BUDGETin its source), not this skill's. - A second
config.tsor a dev-only branch in an app. The samemain.tsruns on a laptop, in CI and in production; only the parsed environment differs. - Putting a table or a query in a module that does not own the subject.
Owning a table means owning every query against it; another module reads it only through a
declared share (
ownershipskill). - A
utils/,lib/,helpers/,domain/folder. Deleted spellings. Pure decisions go inrules/; behaviour inservices/. - Naming a package
*-web. Today that means an internal console underapps/*-web; module browser halves are*-browser.
Links
dev/docs/ARCHITECTURE.md§1 (the product), §2 (package family), §3 (a module), §3.4 (browser half, no kits), §16 (names in flight), §18 (Nx runs the tasks).- The generated pages:
modules/README.md,packages/README.md,apps/README.md, theenterprise/indexes and one per module;ownershipreads them,readmesmaintains them. - The module skills:
modulefor anatomy,module-clientfor clients,browser-modulefor the browser half. - Process composition and what a module may demand (peers, config, stores,
entitlements): the
module-dependenciesandprocess-compositionskills. Not taught here.
Version History
- c0c14dc Current 2026-10-19 19:34


