laymos 0.0.8 → 0.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/README.md +259 -142
  2. package/dist/architecture-analysis-schema/module-source-schema.d.ts +4 -0
  3. package/dist/architecture-analysis-schema/module-source-schema.d.ts.map +1 -1
  4. package/dist/architecture-analysis-schema/module-source-schema.js +4 -0
  5. package/dist/change-set-schema/change-set-schema.js +2 -2
  6. package/dist/cli/run.d.ts +1 -1
  7. package/dist/cli/run.d.ts.map +1 -1
  8. package/dist/cli/run.js +2 -0
  9. package/dist/cli/skills.d.ts +6 -0
  10. package/dist/cli/skills.d.ts.map +1 -0
  11. package/dist/cli/skills.js +9 -0
  12. package/dist/index.d.ts +2 -2
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +2 -2
  15. package/dist/orchestrator/load-changes/index.d.ts +1 -1
  16. package/dist/orchestrator/load-changes/index.d.ts.map +1 -1
  17. package/dist/orchestrator/load-changes/index.js +1 -1
  18. package/dist/orchestrator/load-changes/load-changes.d.ts +4 -3
  19. package/dist/orchestrator/load-changes/load-changes.d.ts.map +1 -1
  20. package/dist/orchestrator/load-changes/load-changes.js +14 -9
  21. package/dist/orchestrator/load-source-files/index.d.ts +1 -1
  22. package/dist/orchestrator/load-source-files/index.d.ts.map +1 -1
  23. package/dist/orchestrator/load-source-files/index.js +1 -1
  24. package/dist/orchestrator/load-source-files/load-source-files.d.ts +20 -4
  25. package/dist/orchestrator/load-source-files/load-source-files.d.ts.map +1 -1
  26. package/dist/orchestrator/load-source-files/load-source-files.js +46 -7
  27. package/dist/services/git/changed-paths.d.ts +1 -0
  28. package/dist/services/git/changed-paths.d.ts.map +1 -1
  29. package/dist/services/git/changed-paths.js +8 -0
  30. package/dist/services/git/git.d.ts +1 -0
  31. package/dist/services/git/git.d.ts.map +1 -1
  32. package/dist/services/git/git.js +5 -1
  33. package/dist/skills-command/index.d.ts +2 -0
  34. package/dist/skills-command/index.d.ts.map +1 -0
  35. package/dist/skills-command/index.js +1 -0
  36. package/dist/skills-command/skills-command.d.ts +21 -0
  37. package/dist/skills-command/skills-command.d.ts.map +1 -0
  38. package/dist/skills-command/skills-command.js +64 -0
  39. package/dist/story/schema/index.d.ts +2 -2
  40. package/dist/story/schema/index.d.ts.map +1 -1
  41. package/dist/story/schema/index.js +1 -1
  42. package/dist/story/schema/story-report-schema.d.ts +264 -132
  43. package/dist/story/schema/story-report-schema.d.ts.map +1 -1
  44. package/dist/story/schema/story-report-schema.js +4 -4
  45. package/dist/story/story.d.ts +3 -1
  46. package/dist/story/story.d.ts.map +1 -1
  47. package/dist/story/story.js +4 -5
  48. package/package.json +18 -11
  49. package/schema.json +60 -55
  50. package/skills/deep-module/SKILL.md +55 -0
  51. package/skills/domain-modeling/ADR-FORMAT.md +47 -0
  52. package/skills/domain-modeling/CONTEXT-FORMAT.md +60 -0
  53. package/skills/domain-modeling/SKILL.md +74 -0
  54. package/skills/domain-modeling/agents/openai.yaml +3 -0
  55. package/skills/laymos/SKILL.md +130 -0
  56. package/skills/laymos/references/config.md +96 -0
  57. package/skills/laymos/references/design.md +166 -0
  58. package/skills/laymos/references/graphs.md +85 -0
  59. package/skills/laymos/references/visibility.md +60 -0
  60. package/skills/to-laymos/SKILL.md +60 -0
@@ -1,5 +1,6 @@
1
1
  import { Context, Effect } from 'effect';
2
2
  import { makeTraceRecorder } from '@pkishorez/effect-tracer/recorder';
3
+ import { FlowTelemetry } from '@pkishorez/flow';
3
4
  export class StoryContext extends Context.Service()('StoryContext') {
4
5
  }
5
6
  export const Story = {
@@ -21,13 +22,11 @@ export const Story = {
21
22
  .pipe(Effect.onExit(() => context.beginSection({ kind: 'trace', trace: recorder.snapshot() })));
22
23
  });
23
24
  },
24
- flow(effect) {
25
+ flow(effect, _options) {
25
26
  return Effect.gen(function* () {
26
27
  const context = yield* StoryContext;
27
- const recorder = makeTraceRecorder();
28
- return yield* recorder
29
- .instrument(effect)
30
- .pipe(Effect.onExit(() => Effect.forEach(recorder.snapshotFlows(), (flow) => context.beginSection({ kind: 'flow', flow }))));
28
+ const sink = FlowTelemetry.makeMemory();
29
+ return yield* effect.pipe(Effect.provideService(FlowTelemetry, sink), Effect.onExit(() => Effect.forEach(sink.journals(), (journal) => context.beginSection({ kind: 'flow', journal }))));
31
30
  });
32
31
  },
33
32
  assert(description, passed) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "laymos",
3
- "version": "0.0.8",
3
+ "version": "0.0.10",
4
4
  "private": false,
5
5
  "description": "Enforces architectural dependency rules and explores source dependencies.",
6
6
  "repository": {
@@ -34,6 +34,11 @@
34
34
  "types": "./dist/story/schema/index.d.ts",
35
35
  "import": "./dist/story/schema/index.js",
36
36
  "default": "./dist/story/schema/index.js"
37
+ },
38
+ "./skills-command": {
39
+ "types": "./dist/skills-command/index.d.ts",
40
+ "import": "./dist/skills-command/index.js",
41
+ "default": "./dist/skills-command/index.js"
37
42
  }
38
43
  },
39
44
  "bin": {
@@ -42,29 +47,31 @@
42
47
  "files": [
43
48
  "bin",
44
49
  "dist",
50
+ "skills",
45
51
  "schema.json",
46
52
  "README.md",
47
53
  "LICENSE"
48
54
  ],
49
55
  "dependencies": {
50
- "@effect/platform-node": "4.0.0-rc.110",
51
- "@effect/platform-node-shared": "4.0.0-rc.110",
52
- "oxc-parser": "0.144.0",
56
+ "@effect/platform-node": "4.0.0-rc.112",
57
+ "@effect/platform-node-shared": "4.0.0-rc.112",
58
+ "oxc-parser": "0.148.0",
53
59
  "oxc-resolver": "11.24.2",
54
- "tsx": "4.23.12",
60
+ "tsx": "4.23.13",
55
61
  "yoctocolors": "2.2.0",
56
- "@pkishorez/effect-tracer": "0.0.8"
62
+ "@pkishorez/flow": "0.0.10",
63
+ "@pkishorez/effect-tracer": "0.0.10"
57
64
  },
58
65
  "devDependencies": {
59
- "@types/node": "26.2.0",
66
+ "@types/node": "26.4.1",
60
67
  "@typescript/native": "npm:typescript@7.0.2",
61
- "effect": "4.0.0-rc.110",
62
- "tsx": "4.23.12",
68
+ "effect": "4.0.0-rc.112",
69
+ "tsx": "4.23.13",
63
70
  "typescript": "npm:@typescript/typescript6@6.0.2",
64
- "vitest": "4.1.10"
71
+ "vitest": "4.1.11"
65
72
  },
66
73
  "peerDependencies": {
67
- "effect": "4.0.0-rc.110"
74
+ "effect": "4.0.0-rc.112"
68
75
  },
69
76
  "publishConfig": {
70
77
  "access": "public"
package/schema.json CHANGED
@@ -16,11 +16,7 @@
16
16
  "sourceRoots": {
17
17
  "type": "array",
18
18
  "description": "Canonical project-relative files or folders that define the complete static analysis universe.",
19
- "allOf": [
20
- {
21
- "minItems": 1
22
- }
23
- ],
19
+ "minItems": 1,
24
20
  "items": {
25
21
  "type": "string"
26
22
  }
@@ -62,11 +58,7 @@
62
58
  "paths": {
63
59
  "type": "array",
64
60
  "description": "Canonical project-relative paths belonging to this Layer. No declared Layer scopes may overlap, within one Layer or across Layers.",
65
- "allOf": [
66
- {
67
- "minItems": 1
68
- }
69
- ],
61
+ "minItems": 1,
70
62
  "items": {
71
63
  "type": "string"
72
64
  }
@@ -83,12 +75,33 @@
83
75
  "description": "Human-readable summary of this Layer."
84
76
  },
85
77
  "docsPath": {
86
- "$ref": "#/definitions/Union_"
78
+ "anyOf": [
79
+ {
80
+ "type": "string"
81
+ },
82
+ {
83
+ "type": "null"
84
+ }
85
+ ],
86
+ "description": "Canonical project-relative path to a markdown file documenting this entity. Read-only: Laymos never writes to it."
87
87
  },
88
88
  "modules": {
89
89
  "type": "object",
90
90
  "additionalProperties": {
91
- "$ref": "#/definitions/Objects_"
91
+ "type": "object",
92
+ "properties": {
93
+ "shared": {
94
+ "type": "boolean",
95
+ "description": "Whether peers in the same Layer may import this Module. Illegal inside a Module Graph, where Rules govern peer access."
96
+ },
97
+ "exposed": {
98
+ "type": "boolean",
99
+ "description": "Whether other Layers may import this Module."
100
+ }
101
+ },
102
+ "additionalProperties": false,
103
+ "title": "Module",
104
+ "description": "A Configured Module keyed by its canonical source file or directory — project-relative when declared in a Layer, Module Graph-relative when declared in a Module Graph. Both flags default to false, so a Module is importable by nobody until it says otherwise."
92
105
  },
93
106
  "description": "Every free-form Configured Module in this Layer, keyed by canonical project-relative source file or directory."
94
107
  },
@@ -113,19 +126,36 @@
113
126
  "description": "Canonical project-relative directory rooting this Module Graph. Every member lives below it and every file below it must belong to a member."
114
127
  },
115
128
  "docsPath": {
116
- "$ref": "#/definitions/Union_"
129
+ "anyOf": [
130
+ {
131
+ "type": "string"
132
+ },
133
+ {
134
+ "type": "null"
135
+ }
136
+ ],
137
+ "description": "Canonical project-relative path to a markdown file documenting this entity. Read-only: Laymos never writes to it."
117
138
  },
118
139
  "modules": {
119
140
  "type": "object",
120
141
  "additionalProperties": {
121
- "$ref": "#/definitions/Objects_"
142
+ "type": "object",
143
+ "properties": {
144
+ "shared": {
145
+ "type": "boolean",
146
+ "description": "Whether peers in the same Layer may import this Module. Illegal inside a Module Graph, where Rules govern peer access."
147
+ },
148
+ "exposed": {
149
+ "type": "boolean",
150
+ "description": "Whether other Layers may import this Module."
151
+ }
152
+ },
153
+ "additionalProperties": false,
154
+ "title": "Module",
155
+ "description": "A Configured Module keyed by its canonical source file or directory — project-relative when declared in a Layer, Module Graph-relative when declared in a Module Graph. Both flags default to false, so a Module is importable by nobody until it says otherwise."
122
156
  },
123
157
  "description": "Every member of this Module Graph, keyed relative to its path. At least two members, at least one exposed, and no member may be Shared.",
124
- "allOf": [
125
- {
126
- "minProperties": 2
127
- }
128
- ]
158
+ "minProperties": 2
129
159
  },
130
160
  "rules": {
131
161
  "type": "object",
@@ -157,11 +187,7 @@
157
187
  "description": "A named, configured group of project-relative paths, owning the Modules and Module Graphs declared within it."
158
188
  },
159
189
  "description": "Every Layer in the project, keyed by id, each owning its Modules and Module Graphs.",
160
- "allOf": [
161
- {
162
- "minProperties": 1
163
- }
164
- ]
190
+ "minProperties": 1
165
191
  },
166
192
  "layerGraphs": {
167
193
  "type": "object",
@@ -180,7 +206,15 @@
180
206
  "description": "Human-readable summary of this LayerGraph."
181
207
  },
182
208
  "docsPath": {
183
- "$ref": "#/definitions/Union_"
209
+ "anyOf": [
210
+ {
211
+ "type": "string"
212
+ },
213
+ {
214
+ "type": "null"
215
+ }
216
+ ],
217
+ "description": "Canonical project-relative path to a markdown file documenting this entity. Read-only: Laymos never writes to it."
184
218
  },
185
219
  "rules": {
186
220
  "type": "object",
@@ -210,34 +244,5 @@
210
244
  ],
211
245
  "additionalProperties": false,
212
246
  "title": "Laymos Config",
213
- "description": "Declares a project's Layers, their Modules and Module Graphs, and its LayerGraphs.",
214
- "definitions": {
215
- "Union_": {
216
- "anyOf": [
217
- {
218
- "type": "string"
219
- },
220
- {
221
- "type": "null"
222
- }
223
- ],
224
- "description": "Canonical project-relative path to a markdown file documenting this entity. Read-only: Laymos never writes to it."
225
- },
226
- "Objects_": {
227
- "type": "object",
228
- "properties": {
229
- "shared": {
230
- "type": "boolean",
231
- "description": "Whether peers in the same Layer may import this Module. Illegal inside a Module Graph, where Rules govern peer access."
232
- },
233
- "exposed": {
234
- "type": "boolean",
235
- "description": "Whether other Layers may import this Module."
236
- }
237
- },
238
- "additionalProperties": false,
239
- "title": "Module",
240
- "description": "A Configured Module keyed by its canonical source file or directory — project-relative when declared in a Layer, Module Graph-relative when declared in a Module Graph. Both flags default to false, so a Module is importable by nobody until it says otherwise."
241
- }
242
- }
247
+ "description": "Declares a project's Layers, their Modules and Module Graphs, and its LayerGraphs."
243
248
  }
@@ -0,0 +1,55 @@
1
+ ---
2
+ name: deep-module
3
+ description: Deep modules — folders with a narrow index.ts door over a wide interior orchestrated by a mandatory <name>.ts. Use when the user asks to deep-modularize, encapsulate, or tighten the public surface of a folder, runs /deep-module, or another skill needs the deep-module shape.
4
+ ---
5
+
6
+ # deep-module
7
+
8
+ A **deep module** is a folder with a narrow door and a wide interior: very little exported, lots of well-organised implementation inside. It has a name and a specific responsibility (or a few related ones) that read like a story, and everything it exposes is _deep_ — real capability, never a shallow pass-through.
9
+
10
+ ## The shape
11
+
12
+ For a module named `hello`:
13
+
14
+ ```
15
+ hello/
16
+ ├── index.ts # the door: pure barrel, re-exports from hello.ts only
17
+ ├── hello.ts # the orchestrator: everything public is implemented here
18
+ ├── <role>.ts # helpers, one clear reason to exist each, named by role
19
+ └── <child>/ # nested deep module, private to this module
20
+ ├── index.ts
21
+ └── child.ts
22
+ ```
23
+
24
+ ## Hard laws
25
+
26
+ 1. **`index.ts` is a pure barrel.** Re-exports only — it is the one file outsiders import from. Any logic it wants belongs in `hello.ts`.
27
+
28
+ 2. **`<name>.ts` is mandatory** in every deep module, nested ones included. It is the single implementation point for the public surface: every exposed function is implemented here; an exposed service (e.g. an Effect service and its layer) lives here. A deep function orchestrates the helper files to produce its result; the other files hold only helpers. `index.ts` re-exports exclusively from `<name>.ts`.
29
+
30
+ 3. **Export functionality, not types.** Default is zero exported types. A type earns export only when an external caller provably cannot be written without naming it (e.g. it annotates a value the caller constructs and hands back). A type that only appears as a parameter or inferred return stays inside. When unsure, leave it out and add it back when a real caller breaks.
31
+
32
+ 4. **Nested deep modules are private to their parent.** Their functionality is used only within the parent's limits. The test: if `<name>.ts` would re-export a child's function verbatim, with no orchestration on top, that module is not a child — **promote it to a sibling** deep module. A deep module is self-contained; children exist only to serve it.
33
+
34
+ ## Interior guidelines
35
+
36
+ Heuristics in service of one goal — **human understandability**: a reader should juggle only two or three concepts at one level. When guidelines conflict, the version that is easier to hold in your head wins.
37
+
38
+ - **`<name>.ts` reads like a story.** Top-to-bottom orchestration of named interior pieces; the heavy lifting is delegated. Reading it alone tells you what the module does.
39
+ - **Each file has a specific reason to exist.** Name by role — `header.tsx`, `state.ts`, `parser.ts`. A junk-drawer name (`utils.ts`, `helpers.ts`) means a concern hasn't been identified yet; find the concern and name it.
40
+ - **Group into nested modules.** When several files serve one distinct internal responsibility, move them into a nested deep module (same shape, recursively). Ideally one level of nesting is enough; the two-or-three focus budget applies to both breadth and depth — a wide god-module rebuilt as a deep tower just moves the violation.
41
+ - **Generic-first composition.** Behind app-specific logic there is usually a generic capability hiding (a tree, a picker, a state machine). Extract the generic core with an app-agnostic interface, compose the specific on top. Understandability comes from "specialize a generic", not from splitting for its own sake.
42
+ - **No hidden magic.** Behavior is fully predictable from the interface. Internal state, I/O, and effects are fine when passed in or localized in an obviously-named interior file. Significant state or config — anything a caller might need to read, control, or substitute — belongs at the door; trivial local detail stays inside.
43
+ - **Errors and edge cases live in the type.** If a module can fail or return empty, the interface says so — a typed result, an explicit shape, a declared throw.
44
+ - **Minimal ≠ trivial.** A single large export can be the entire surface; "minimal" counts doors, not capability. Size is fine — tangled responsibility is the enemy. One reason to change per unit.
45
+ - **The barrel is the spec.** `index.ts` tells an outsider everything they can do with the module, in one glance.
46
+
47
+ ## Applying it to a folder
48
+
49
+ The module tells its own story — its responsibility decides what it exposes, never its consumers. External callers are not this skill's concern.
50
+
51
+ 1. **Design** — draft the tree: `<name>.ts` orchestrator, role-named helpers, nested modules for grouped concerns, siblings for anything that must leave. Done when every export and every exported type has survived a why-challenge.
52
+ 2. **Summarize for the user** — present the proposed tree, the reason each file exists, and how the structure communicates the module's story. The user adds detail; rework the design until they say okay.
53
+ 3. **Implement** — only after the user's okay, reshape the files and write `index.ts`. Done when the folder matches the approved tree.
54
+
55
+ Composes with other skills: e.g. `/grill-me` + `/deep-module` to interview the user about the design before reshaping.
@@ -0,0 +1,47 @@
1
+ # ADR Format
2
+
3
+ ADRs live in `docs/adr/` and use sequential numbering: `0001-slug.md`, `0002-slug.md`, etc.
4
+
5
+ Create the `docs/adr/` directory lazily — only when the first ADR is needed.
6
+
7
+ ## Template
8
+
9
+ ```md
10
+ # {Short title of the decision}
11
+
12
+ {1-3 sentences: what's the context, what did we decide, and why.}
13
+ ```
14
+
15
+ That's it. An ADR can be a single paragraph. The value is in recording _that_ a decision was made and _why_ — not in filling out sections.
16
+
17
+ ## Optional sections
18
+
19
+ Only include these when they add genuine value. Most ADRs won't need them.
20
+
21
+ - **Status** frontmatter (`proposed | accepted | deprecated | superseded by ADR-NNNN`) — useful when decisions are revisited
22
+ - **Considered Options** — only when the rejected alternatives are worth remembering
23
+ - **Consequences** — only when non-obvious downstream effects need to be called out
24
+
25
+ ## Numbering
26
+
27
+ Scan `docs/adr/` for the highest existing number and increment by one.
28
+
29
+ ## When to offer an ADR
30
+
31
+ All three of these must be true:
32
+
33
+ 1. **Hard to reverse** — the cost of changing your mind later is meaningful
34
+ 2. **Surprising without context** — a future reader will look at the code and wonder "why on earth did they do it this way?"
35
+ 3. **The result of a real trade-off** — there were genuine alternatives and you picked one for specific reasons
36
+
37
+ If a decision is easy to reverse, skip it — you'll just reverse it. If it's not surprising, nobody will wonder why. If there was no real alternative, there's nothing to record beyond "we did the obvious thing."
38
+
39
+ ### What qualifies
40
+
41
+ - **Architectural shape.** "We're using a monorepo." "The write model is event-sourced, the read model is projected into Postgres."
42
+ - **Integration patterns between contexts.** "Ordering and Billing communicate via domain events, not synchronous HTTP."
43
+ - **Technology choices that carry lock-in.** Database, message bus, auth provider, deployment target. Not every library — just the ones that would take a quarter to swap out.
44
+ - **Boundary and scope decisions.** "Customer data is owned by the Customer context; other contexts reference it by ID only." The explicit no-s are as valuable as the yes-s.
45
+ - **Deliberate deviations from the obvious path.** "We're using manual SQL instead of an ORM because X." Anything where a reasonable reader would assume the opposite. These stop the next engineer from "fixing" something that was deliberate.
46
+ - **Constraints not visible in the code.** "We can't use AWS because of compliance requirements." "Response times must be under 200ms because of the partner API contract."
47
+ - **Rejected alternatives when the rejection is non-obvious.** If you considered GraphQL and picked REST for subtle reasons, record it — otherwise someone will suggest GraphQL again in six months.
@@ -0,0 +1,60 @@
1
+ # CONTEXT.md Format
2
+
3
+ ## Structure
4
+
5
+ ```md
6
+ # {Context Name}
7
+
8
+ {One or two sentence description of what this context is and why it exists.}
9
+
10
+ ## Language
11
+
12
+ **Order**:
13
+ {A one or two sentence description of the term}
14
+ _Avoid_: Purchase, transaction
15
+
16
+ **Invoice**:
17
+ A request for payment sent to a customer after delivery.
18
+ _Avoid_: Bill, payment request
19
+
20
+ **Customer**:
21
+ A person or organization that places orders.
22
+ _Avoid_: Client, buyer, account
23
+ ```
24
+
25
+ ## Rules
26
+
27
+ - **Be opinionated.** When multiple words exist for the same concept, pick the best one and list the others under `_Avoid_`.
28
+ - **Keep definitions tight.** One or two sentences max. Define what it IS, not what it does.
29
+ - **Only include terms specific to this project's context.** General programming concepts (timeouts, error types, utility patterns) don't belong even if the project uses them extensively. Before adding a term, ask: is this a concept unique to this context, or a general programming concept? Only the former belongs.
30
+ - **Group terms under subheadings** when natural clusters emerge. If all terms belong to a single cohesive area, a flat list is fine.
31
+
32
+ ## Single vs multi-context repos
33
+
34
+ **Single context (most repos):** One `CONTEXT.md` at the repo root.
35
+
36
+ **Multiple contexts:** A `CONTEXT-MAP.md` at the repo root lists the contexts, where they live, and how they relate to each other:
37
+
38
+ ```md
39
+ # Context Map
40
+
41
+ ## Contexts
42
+
43
+ - [Ordering](./src/ordering/CONTEXT.md) — receives and tracks customer orders
44
+ - [Billing](./src/billing/CONTEXT.md) — generates invoices and processes payments
45
+ - [Fulfillment](./src/fulfillment/CONTEXT.md) — manages warehouse picking and shipping
46
+
47
+ ## Relationships
48
+
49
+ - **Ordering → Fulfillment**: Ordering emits `OrderPlaced` events; Fulfillment consumes them to start picking
50
+ - **Fulfillment → Billing**: Fulfillment emits `ShipmentDispatched` events; Billing consumes them to generate invoices
51
+ - **Ordering ↔ Billing**: Shared types for `CustomerId` and `Money`
52
+ ```
53
+
54
+ The skill infers which structure applies:
55
+
56
+ - If `CONTEXT-MAP.md` exists, read it to find contexts
57
+ - If only a root `CONTEXT.md` exists, single context
58
+ - If neither exists, create a root `CONTEXT.md` lazily when the first term is resolved
59
+
60
+ When multiple contexts exist, infer which one the current topic relates to. If unclear, ask.
@@ -0,0 +1,74 @@
1
+ ---
2
+ name: domain-modeling
3
+ description: Build and sharpen a project's domain model. Use when the user wants to pin down domain terminology or a ubiquitous language, record an architectural decision, or when another skill needs to maintain the domain model.
4
+ ---
5
+
6
+ # Domain Modeling
7
+
8
+ Actively build and sharpen the project's domain model as you design. This is the _active_ discipline — challenging terms, inventing edge-case scenarios, and writing the glossary and decisions down the moment they crystallise. (Merely _reading_ `CONTEXT.md` for vocabulary is not this skill — that's a one-line habit any skill can do. This skill is for when you're changing the model, not just consuming it.)
9
+
10
+ ## File structure
11
+
12
+ Most repos have a single context:
13
+
14
+ ```
15
+ /
16
+ ├── CONTEXT.md
17
+ ├── docs/
18
+ │ └── adr/
19
+ │ ├── 0001-event-sourced-orders.md
20
+ │ └── 0002-postgres-for-write-model.md
21
+ └── src/
22
+ ```
23
+
24
+ If a `CONTEXT-MAP.md` exists at the root, the repo has multiple contexts. The map points to where each one lives:
25
+
26
+ ```
27
+ /
28
+ ├── CONTEXT-MAP.md
29
+ ├── docs/
30
+ │ └── adr/ ← system-wide decisions
31
+ ├── src/
32
+ │ ├── ordering/
33
+ │ │ ├── CONTEXT.md
34
+ │ │ └── docs/adr/ ← context-specific decisions
35
+ │ └── billing/
36
+ │ ├── CONTEXT.md
37
+ │ └── docs/adr/
38
+ ```
39
+
40
+ Create files lazily — only when you have something to write. If no `CONTEXT.md` exists, create one when the first term is resolved. If no `docs/adr/` exists, create it when the first ADR is needed.
41
+
42
+ ## During the session
43
+
44
+ ### Challenge against the glossary
45
+
46
+ When the user uses a term that conflicts with the existing language in `CONTEXT.md`, call it out immediately. "Your glossary defines 'cancellation' as X, but you seem to mean Y — which is it?"
47
+
48
+ ### Sharpen fuzzy language
49
+
50
+ When the user uses vague or overloaded terms, propose a precise canonical term. "You're saying 'account' — do you mean the Customer or the User? Those are different things."
51
+
52
+ ### Discuss concrete scenarios
53
+
54
+ When domain relationships are being discussed, stress-test them with specific scenarios. Invent scenarios that probe edge cases and force the user to be precise about the boundaries between concepts.
55
+
56
+ ### Cross-reference with code
57
+
58
+ When the user states how something works, check whether the code agrees. If you find a contradiction, surface it: "Your code cancels entire Orders, but you just said partial cancellation is possible — which is right?"
59
+
60
+ ### Update CONTEXT.md inline
61
+
62
+ When a term is resolved, update `CONTEXT.md` right there. Don't batch these up — capture them as they happen. Use the format in [CONTEXT-FORMAT.md](./CONTEXT-FORMAT.md).
63
+
64
+ `CONTEXT.md` should be totally devoid of implementation details. Do not treat `CONTEXT.md` as a spec, a scratch pad, or a repository for implementation decisions. It is a glossary and nothing else.
65
+
66
+ ### Offer ADRs sparingly
67
+
68
+ Only offer to create an ADR when all three are true:
69
+
70
+ 1. **Hard to reverse** — the cost of changing your mind later is meaningful
71
+ 2. **Surprising without context** — a future reader will wonder "why did they do it this way?"
72
+ 3. **The result of a real trade-off** — there were genuine alternatives and you picked one for specific reasons
73
+
74
+ If any of the three is missing, skip the ADR. Use the format in [ADR-FORMAT.md](./ADR-FORMAT.md).
@@ -0,0 +1,3 @@
1
+ interface:
2
+ display_name: 'Domain Modeling'
3
+ short_description: 'Build and sharpen a domain model'
@@ -0,0 +1,130 @@
1
+ ---
2
+ name: laymos
3
+ description: Laymos architecture rules — the layer stack, module visibility, module graphs, naming, config, and CLI. Use when planning or changing a project's architecture, when reading laymos.config.json or laymos output, or when another skill needs the Laymos model.
4
+ ---
5
+
6
+ # Laymos
7
+
8
+ Reference. Read it before planning or changing a project's architecture.
9
+ It answers _what_, never _how to run a session_.
10
+
11
+ ## What is Laymos trying to achieve?
12
+
13
+ Stable code at the bottom. Volatile code on top. Dependencies point down.
14
+
15
+ The bottom knows nothing about the top. So the bottom stays small, testable,
16
+ and reusable, and features churn in the top where churn is cheap.
17
+ This is stratified design, from _Grokking Simplicity_ by Eric Normand.
18
+
19
+ One common stack, bottom to top:
20
+
21
+ - **domain** — the words of the problem. Types, schemas, pure functions. No I/O.
22
+ - **clients** — the outside world, wrapped. Third-party APIs, the database, the filesystem.
23
+ - **services** — one responsibility each, built on domain and clients.
24
+ - **orchestrator** — puts services and clients together to get the real work done.
25
+ - **entry** — where the host starts. A CLI, a route, a worker.
26
+
27
+ That stack is a mental model, not a rule. Layers come from the application you
28
+ are building, and you name them for the jobs your project actually has.
29
+ The direction is the part that never changes.
30
+
31
+ Read `references/design.md` before choosing module boundaries, splitting or
32
+ combining modules, declaring a module graph, or designing orchestration.
33
+
34
+ ## What is Laymos?
35
+
36
+ Laymos is a CLI tool. It reads `laymos.config.json`.
37
+
38
+ It lints declared dependency rules, inspects layers and modules, and runs the
39
+ project's executable Stories.
40
+
41
+ The config is the source of architectural truth. Source code is evidence.
42
+ When the two disagree, the config states the intent and the lint reports the gap.
43
+
44
+ ## What is a layer?
45
+
46
+ A layer is a group of files with one architectural job.
47
+
48
+ A layer owns dependency rights. `a -> b` reads "a may depend on b".
49
+ Rights are transitive: if `a -> b` and `b -> c`, then `a` may depend on `c`.
50
+ Anything not permitted is a violation. The rule graph must be acyclic.
51
+
52
+ Every analyzed supported file belongs to exactly one layer. Layers may not overlap.
53
+ A layer with no outgoing rule is a valid leaf.
54
+
55
+ Create a layer when a job needs its own dependency direction.
56
+ Folders, teams, and display groups are not layers.
57
+ A one-module layer is fine when that module needs distinct rights.
58
+
59
+ ## What is a module?
60
+
61
+ A module is a source boundary inside one layer. It owns one capability and the
62
+ design decisions that capability hides behind a small, stable door.
63
+
64
+ A module is a file or a directory. That is its _shape_.
65
+ A module declares two booleans, `shared` and `exposed`, both defaulting to
66
+ false. They control layer-wide and cross-layer access; module graph rules grant
67
+ access between members separately. Read `references/visibility.md` when deciding.
68
+
69
+ A directory module needs a root `index.ts` when it is shared, exposed, or a
70
+ module graph member. That index is a thin door over a wide interior — read the
71
+ `deep-module` skill for its shape. When a file module needs an entry point, its
72
+ own file is that entry point.
73
+
74
+ Two modules never overlap. Every analyzed file in a layer belongs to one module.
75
+
76
+ ## What is a module graph?
77
+
78
+ A named, bounded set of modules inside one layer, rooted at a directory, whose
79
+ connections are declared as rules. It describes one capability too large for a
80
+ single module: normally one facade is exposed and the other members stay private.
81
+
82
+ Unlike a layer graph it is a disjoint unit — its rules are never unioned with
83
+ another graph's, are **not** transitive, and are checked for cycles on their own.
84
+ Module graphs do not nest. Read `references/graphs.md` before declaring one.
85
+
86
+ A layer holds free-form modules, module graphs, or both. A layer groups by
87
+ architectural role; a graph describes how modules work together.
88
+
89
+ ## How do I name things?
90
+
91
+ Use the project's own words. The `domain-modeling` skill owns those words;
92
+ read it when a term is missing or contested.
93
+
94
+ - Layer ids and folders are lowercase kebab-case.
95
+ - Name a layer for its dependency job, not its folder.
96
+ - Name a module with a concrete noun: `file-graph`, `project-config`.
97
+ - Name work that runs with verb-noun: `load-project`.
98
+ - `utils`, `helpers`, `common`, `misc`, `lib`, `impl` name nothing.
99
+ Use the capability they hide.
100
+ - A module's identity is its full configured path. The short name is a label.
101
+
102
+ Write a layer description as the job it owns. Write a module job as one sentence.
103
+
104
+ ## Which command answers which question?
105
+
106
+ | Question | Command |
107
+ | ---------------------------------------------------------------- | ----------------------------------------------- |
108
+ | Does the project obey its rules? | `laymos lint` |
109
+ | Are layer coverage and links correct? | `laymos lint layers` |
110
+ | Are module boundaries and entry points correct? | `laymos lint modules` |
111
+ | What is the whole architecture? | `laymos inspect project` |
112
+ | What is in this layer, and what may it reach? | `laymos inspect layer <layer-name>` |
113
+ | What visibility, shape, surface, and deps does this module have? | `laymos inspect module <module-path>` |
114
+ | Which layer and module owns this file, and what does it import? | `laymos inspect file <file-path> [--recursive]` |
115
+ | Do the executable Stories pass? | `laymos stories [--concurrency <n>]` |
116
+
117
+ Every command takes `--config <path>`. It defaults to `./laymos.config.json`.
118
+ Add `--json` to any `inspect` command and parse the result. Without it, read the tree.
119
+ Exit `0` is clean. Exit `1` means violations, an inspection cycle, or non-passing
120
+ Stories. Exit `2` means a broken config or an operational failure.
121
+
122
+ Use the project's package runner when `laymos` is not on `PATH`.
123
+
124
+ ## What do the inspect fields mean?
125
+
126
+ - `shared` and `exposed` are the configured intent.
127
+ - `graph` names the module graph a member belongs to.
128
+ - `shape` is file or directory.
129
+ - `observedKind` is the module's current position in the import graph.
130
+ - Imports show current use. They do not decide the target design.