@adobe/data-ai 0.10.21 → 0.10.22

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 (63) hide show
  1. package/.claude/rules/README.md +26 -31
  2. package/.claude/rules/app.md +46 -0
  3. package/.claude/rules/archetypes.md +5 -3
  4. package/.claude/rules/data-modelling.md +8 -7
  5. package/.claude/rules/ecs.md +11 -7
  6. package/.claude/rules/element.md +6 -5
  7. package/.claude/rules/features/data/components.md +51 -0
  8. package/.claude/rules/features/data/entities.md +37 -0
  9. package/.claude/rules/features/data/index.md +18 -70
  10. package/.claude/rules/features/data/resources.md +34 -0
  11. package/.claude/rules/features/data/values.md +36 -0
  12. package/.claude/rules/features/ecs/actions.md +31 -0
  13. package/.claude/rules/features/ecs/computed.md +31 -0
  14. package/.claude/rules/features/ecs/conformance.md +85 -0
  15. package/.claude/rules/features/ecs/core.md +43 -0
  16. package/.claude/rules/features/ecs/index.md +72 -0
  17. package/.claude/rules/features/{services/main-service → ecs}/indexes.md +6 -7
  18. package/.claude/rules/features/ecs/services.md +36 -0
  19. package/.claude/rules/features/{services/main-service → ecs}/systems.md +61 -47
  20. package/.claude/rules/features/ecs/transactions.md +32 -0
  21. package/.claude/rules/features/index.md +104 -150
  22. package/.claude/rules/features/services/index.md +52 -84
  23. package/.claude/rules/features/spec/index.md +161 -0
  24. package/.claude/rules/features/ui/binding-element.md +5 -5
  25. package/.claude/rules/features/ui/index.md +8 -3
  26. package/.claude/rules/features/ui/lazy-wrapper.md +2 -2
  27. package/.claude/rules/features/ui/presentation-test.md +1 -0
  28. package/.claude/rules/features/ui/presentation.md +1 -0
  29. package/.claude/rules/global/function-references.md +2 -2
  30. package/.claude/rules/global/namespace.md +3 -3
  31. package/.claude/rules/global/react.md +4 -4
  32. package/.claude/rules/lit.md +2 -2
  33. package/.claude/rules/plugin-modelling.md +2 -0
  34. package/.claude/rules/service.md +2 -0
  35. package/.claude/rules/versioning.md +8 -6
  36. package/.claude-plugin/plugin.json +1 -1
  37. package/package.json +3 -3
  38. package/skills/build-actions/SKILL.md +7 -8
  39. package/skills/build-app-entry/SKILL.md +12 -8
  40. package/skills/build-computed/SKILL.md +8 -7
  41. package/skills/build-core-database/SKILL.md +18 -15
  42. package/skills/build-data/SKILL.md +14 -13
  43. package/skills/build-feature/SKILL.md +2 -1
  44. package/skills/build-game/SKILL.md +6 -4
  45. package/skills/build-indexes/SKILL.md +6 -7
  46. package/skills/build-service-database/SKILL.md +8 -8
  47. package/skills/build-services/SKILL.md +9 -7
  48. package/skills/build-spec/SKILL.md +23 -0
  49. package/skills/build-systems/SKILL.md +12 -18
  50. package/skills/build-transactions/SKILL.md +8 -8
  51. package/skills/build-ui/SKILL.md +5 -4
  52. package/skills/review/SKILL.md +3 -3
  53. package/.claude/rules/features/data/state.md +0 -209
  54. package/.claude/rules/features/services/main-service/actions.md +0 -70
  55. package/.claude/rules/features/services/main-service/archetypes.md +0 -49
  56. package/.claude/rules/features/services/main-service/components.md +0 -41
  57. package/.claude/rules/features/services/main-service/computed.md +0 -82
  58. package/.claude/rules/features/services/main-service/conformance.md +0 -95
  59. package/.claude/rules/features/services/main-service/core-database.md +0 -48
  60. package/.claude/rules/features/services/main-service/index.md +0 -187
  61. package/.claude/rules/features/services/main-service/resources.md +0 -28
  62. package/.claude/rules/features/services/main-service/services.md +0 -25
  63. package/.claude/rules/features/services/main-service/transactions.md +0 -59
@@ -1,13 +1,18 @@
1
1
  ---
2
2
  paths:
3
3
  - '**/features/*/ui/**/*.ts'
4
+ - '**/features/*/ui/**/*.tsx'
4
5
  ---
5
6
 
6
7
  # ui/ — user interface
7
8
 
8
- The top layer: UI that binds the `services/main-service/` database to what the user sees. It
9
- owns no business logic — it subscribes to state, hands it to a pure
10
- presentation, and wires actions back to transactions.
9
+ The top layer: UI that binds the feature's `MainService` database (`ecs/main-service.ts`)
10
+ to what the user sees. It owns no business logic — it subscribes to state, hands it to
11
+ a pure presentation, and wires user input to `service.actions.*`.
12
+
13
+ Each feature has one base element (Lit: `ui/<feature>-element.ts`, a
14
+ `DatabaseElement` typed on its `MainService.plugin`; React: a `use-main-service.ts`
15
+ hook) that every element of the feature builds on.
11
16
 
12
17
  The folder name is framework-neutral; its contents are framework-specific
13
18
  (Lit elements, React components, …). Each UI unit is its own folder, named
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  paths:
3
- - 'src/**/elements/**/*.ts'
4
- - 'src/**/ui/**/*.ts'
3
+ - '**/src/**/elements/**/*.ts'
4
+ - '**/src/**/ui/**/*.ts'
5
5
  ---
6
6
 
7
7
  # Lazy Wrapper
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  paths:
3
3
  - '**/*presentation.test.ts'
4
+ - '**/*presentation.test.tsx'
4
5
  ---
5
6
 
6
7
  ## Testing
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  paths:
3
3
  - '**/*presentation.ts'
4
+ - '**/*presentation.tsx'
4
5
  ---
5
6
 
6
7
  # Presentation authoring
@@ -11,12 +11,12 @@ When a value is already a function with the signature the callee wants, pass it
11
11
 
12
12
  ```ts
13
13
  // ❌ identity wrappers — the arrow adds nothing
14
- expectConforms({ spec: (s, dt) => State.resolveBulletHits(s, dt), apply: (store, a) => hitAsteroid(store, a) });
14
+ cached((db) => Observe.withFilter(db.observe.resources.board, (b) => BoardState.deriveStatus(b)));
15
15
  onClick={() => save()}
16
16
  todos.map((t) => render(t))
17
17
 
18
18
  // ✅ pass the function itself
19
- expectConforms({ spec: State.resolveBulletHits, apply: hitAsteroid });
19
+ cached((db) => Observe.withFilter(db.observe.resources.board, BoardState.deriveStatus));
20
20
  onClick={save}
21
21
  todos.map(render)
22
22
  ```
@@ -6,7 +6,8 @@ paths:
6
6
  # Type namespace pattern
7
7
 
8
8
  Single import surface per type: `<type-name>/<type-name>.ts`. Schema in `-schema.ts`, namespace re-export from `public.js`. Types live in
9
- the `types/` layer.
9
+ the `types/` layer. **In a feature**, Data types live in `features/<name>/data/values/<type>/` with the schema in `schema.ts`
10
+ (`features/data/values.md`).
10
11
 
11
12
  ## Authority
12
13
 
@@ -161,5 +162,4 @@ Example — a type merged with one value export and any number of type-only memb
161
162
  }
162
163
 
163
164
  The canonical use is a feature's `core-database.ts` / layered `*-database.ts` (a
164
- `plugin` value plus `Store` / `Index` / `Component` / `Archetype` types) — see
165
- `features/services/main-service/core-database.md`.
165
+ `plugin` value plus `Store` / `Index` types) — see `features/ecs/core.md`.
@@ -5,7 +5,7 @@ paths:
5
5
 
6
6
  # React component authoring
7
7
 
8
- React components live in the `components/` layer. Consume Observe and void actions from plugins. Use `@adobe/data-react` for `useDatabase`,
8
+ React components live in the `ui/` layer (`features/ui/`). Consume Observe and void actions from plugins. Use `@adobe/data-react` for `useDatabase`,
9
9
  `useObservableValues`, and `DatabaseProvider`.
10
10
 
11
11
  ---
@@ -61,7 +61,7 @@ function Counter() {
61
61
  count: db.observe.resources.count,
62
62
  }));
63
63
  if (!values) return null;
64
- return presentation.render({ ...values, increment: db.transactions.increment });
64
+ return presentation.render({ ...values, increment: db.actions.increment });
65
65
  }
66
66
  ```
67
67
 
@@ -81,8 +81,8 @@ Use **verb** or **verbNoun** names — never `on*` prefix. See `features/ui/pres
81
81
  **Pass function references directly** when the signature matches; wrap in an arrow function only to supply arguments:
82
82
 
83
83
  ```tsx
84
- increment: db.transactions.increment; // reference — no wrapper needed
85
- toggleSprite: () => db.transactions.toggleSpriteActive({ entity }); // args required — wrap
84
+ increment: db.actions.increment; // reference — no wrapper needed
85
+ toggleSprite: () => db.actions.toggleSpriteActive({ entity }); // args required — wrap
86
86
  ```
87
87
 
88
88
  ---
@@ -7,7 +7,7 @@ paths:
7
7
 
8
8
  # Lit element authoring
9
9
 
10
- Lit elements live in the `components/` layer per the structure rule. Consume Observe and void actions from plugins per the service rule.
10
+ Lit elements live in the `ui/` layer (`features/ui/`). Consume Observe and void actions from plugins per the service rule.
11
11
 
12
12
  ---
13
13
 
@@ -58,7 +58,7 @@ render() {
58
58
  userProfile: this.service.services.authentication.userProfile,
59
59
  }));
60
60
  if (!values) return;
61
- return presentation.render({ ...values, toggleView: () => this.service.transactions.setViewVisible({ name, visible: true }) });
61
+ return presentation.render({ ...values, toggleView: () => this.service.actions.setViewVisible({ name, visible: true }) });
62
62
  }
63
63
  ```
64
64
 
@@ -31,6 +31,8 @@ authoring abstraction.
31
31
 
32
32
  ## File naming
33
33
 
34
+ Outside `features/`. Inside a feature, the `features/ecs/` naming applies (`<layer>-database.ts`).
35
+
34
36
  Each file whose primary export is a `Database.Plugin` (created or
35
37
  combined) ends in `-plugin.ts` — e.g. `node-plugin.ts`, `model-plugin.ts`,
36
38
  `pbr-core-plugin.ts`. The suffix is for discovery: cursor rules,
@@ -6,6 +6,8 @@ paths:
6
6
 
7
7
  # Service authoring
8
8
 
9
+ > Inside a feature, `features/services/index.md` governs the folder layout: a small service keeps `create.ts` and `create-fake.ts` flat beside the interface (the single-file exception below); one with helpers moves its implementation into a sub-folder. Database-bound factories in `ecs/services/` follow `features/ecs/services.md`.
10
+
9
11
  Asynchronous data services. Live in the `services/` layer. Adhere to the namespace rule for type and function organization.
10
12
 
11
13
  **Data** = readonly JSON values, `ReadonlySet`, `ReadonlyMap`, or Blobs.
@@ -12,6 +12,9 @@ persisted document can be upgraded to the current schema on load. You (human or
12
12
  agent) should almost never have to reason about this by hand: **change the schema,
13
13
  run the tests, and the failing test prints the exact fix to apply.**
14
14
 
15
+ The history lives in the app root, `src/app/versioning/` (`app.md`), because the
16
+ persisted data covers every feature's schema in the database.
17
+
15
18
  ## The pieces (all from `@adobe/data/ecs`)
16
19
 
17
20
  - **`versions.ts`** — `export const versions: readonly VersionEntry[]`. `entries[i]`
@@ -59,7 +62,7 @@ run the tests, and the failing test prints the exact fix to apply.**
59
62
  writes to SEVERAL components (a cross-component move, deriving a new component from
60
63
  others, splitting one into many) must be hand-written to your upgrade algorithm:
61
64
  query the input components, compute, and write the outputs directly on the store.
62
- Add a test case either way (rule 6).
65
+ Add a test case either way (the guard test below).
63
66
  5. **Removing a component drops its data.** If the data still matters, migrate it in a
64
67
  handler BEFORE the removal entry.
65
68
  6. **Versioned schemas must be REAL** — declare a `type` (or `enum`/`const`), and the
@@ -83,7 +86,7 @@ history. Its message is a literal recipe. To fix it:
83
86
  and the `changes` block the error printed verbatim. `db.version` follows the history
84
87
  automatically (`= versions.length - 1`) — there is no resource default to update.
85
88
  2. If the error marks a change **BREAKING — needs a handler**, add a `handler` to that
86
- entry AND add a matching test case under `testUpgradeHandlers` (rule 6). The error
89
+ entry AND add a matching test case under `testUpgradeHandlers` (the guard test below). The error
87
90
  may point at `Store.remapComponent(...)` for an isolated single-component change, but
88
91
  that is only a suggestion — write whatever upgrade code the change actually needs.
89
92
 
@@ -97,17 +100,17 @@ schema (schema coverage) AND every entry with a `handler` has a passing test cas
97
100
 
98
101
  ```ts
99
102
  import { Database, assertVersioning, createVersionUpgrader } from "@adobe/data/ecs";
100
- import { MainService } from "../main-service.js";
103
+ import { AppSchema } from "../schema.js";
101
104
  import { versions } from "./versions.js";
102
105
 
103
106
  describe("database schema versions", () => {
104
107
  it("the version history is consistent (schema folds + every handler tested)", () =>
105
108
  assertVersioning({
106
109
  // the version-configured db, so db.version = the history's current version
107
- database: Database.create(MainService.plugin, { versioning: createVersionUpgrader(versions) }),
110
+ database: Database.create(AppSchema.plugin, { versioning: createVersionUpgrader(versions) }),
108
111
  entries: versions,
109
112
  handlers: {
110
- // Append a case per version that adds a `handler` (rule 6):
113
+ // Append a case per version that adds a `handler` (the guard test below):
111
114
  // <version>: { setup: (store) => <populate the version-(v-1) store, return entities>,
112
115
  // expect: (store, setup) => <assert the version-v result> },
113
116
  },
@@ -116,7 +119,6 @@ describe("database schema versions", () => {
116
119
  ```
117
120
 
118
121
  (`assertVersionsMatchSchema` and `testUpgradeHandlers` remain exported for bespoke use.)
119
- ```
120
122
 
121
123
  ## Example: a breaking change (number → object) at version 1
122
124
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adobe-data-ai",
3
- "version": "0.10.21",
3
+ "version": "0.10.22",
4
4
  "description": "Architecture skills for @adobe/data — data-oriented modelling, archetype iteration, hot-path performance, and related conventions.",
5
5
  "author": {
6
6
  "name": "Adobe"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/data-ai",
3
- "version": "0.10.21",
3
+ "version": "0.10.22",
4
4
  "description": "Cross-agent architecture skills for @adobe/data — installable as a Claude Code plugin or copied into any Agent-Skills-compatible agent (Cursor, Codex).",
5
5
  "type": "module",
6
6
  "private": false,
@@ -29,12 +29,12 @@
29
29
  "skills"
30
30
  ],
31
31
  "peerDependencies": {
32
- "@adobe/data": "0.10.21"
32
+ "@adobe/data": "0.10.22"
33
33
  },
34
34
  "devDependencies": {
35
35
  "typescript": "^5.8.3",
36
36
  "vitest": "^1.6.0",
37
- "@adobe/data": "0.10.21"
37
+ "@adobe/data": "0.10.22"
38
38
  },
39
39
  "license": "MIT",
40
40
  "scripts": {
@@ -1,16 +1,15 @@
1
1
  ---
2
2
  name: build-actions
3
- description: Build a feature's action-database — async orchestration over services and transactions. If the feature has async flows or side effects outside of the main store.
3
+ description: Build a feature's ecs/actions layer — the operations the app performs.
4
4
  input: feature
5
5
  output: feature
6
6
  ---
7
7
 
8
- Skip if this feature doesn't contain or require actions.
8
+ Create `ecs/actions/`: `action-database.ts` (extends the previous layer, adds `actions`
9
+ from `./index.js`), one action per file, and a barrel. Every spec action has a
10
+ same-named action. It awaits services and commits through at most one transaction
11
+ (add any transaction `build-transactions` missed). Point `ecs/main-service.ts` at the top layer.
9
12
 
10
- Create `services/main-service/action-database/`: `action-database.ts` (extends `ServiceDatabase`, adds `actions`
11
- from `./actions/index.js`) plus an `actions/` folder — one async orchestrator per file. An action
12
- takes `db: ServiceDatabase`, awaits `services/` ports, then commits **exactly one** transaction.
13
+ Gate: `checkFeature` passes for each op as it is added.
13
14
 
14
- The top main-service layer; comes after `service-database`.
15
-
16
- The how is in the auto-loading `features/services/main-service/actions.md` rule.
15
+ The how is in the auto-loading `features/ecs/actions.md` rule.
@@ -1,15 +1,19 @@
1
1
  ---
2
2
  name: build-app-entry
3
- description: Wire built features into a running application — import peer schemas, lazy-load peers, and mount the root UI.
3
+ description: Build an application's src/app/ composition root.
4
4
  input: app
5
5
  output: app
6
6
  ---
7
7
 
8
- - The base (`features/main`) core-database `imports` each peer feature's core-database
9
- plugin, so all schemas coexist, persist, and sync while the base type stays decoupled.
10
- - The base reaches a peer only through a lazy element wrapper (dynamic import), so peers
11
- load on demand.
12
- - The entry point creates the live database from `MainService.plugin` (the base
13
- feature's assembled database — see `features/services/main-service/index.md`) and mounts the root UI.
8
+ Create `src/app/`:
14
9
 
15
- See `features/index.md` (one-app-many-features) for the wiring rules.
10
+ - `schema.ts` — the composed plugin: the base feature's `MainService.plugin`, `imports`
11
+ of the core schema of each feature that loads lazily (single-feature apps skip this).
12
+ - `versioning/` — `versions.ts` + `versions.test.ts` over the composed schema, when
13
+ the app persists.
14
+ - `persistence.test.ts` — a save/load round-trip, when the app persists.
15
+ - `main.ts` — creates the database, injects every service whose `create` throws, and
16
+ mounts the root UI. Point `index.html` at it.
17
+
18
+ The how is in the auto-loading `app.md` and `versioning.md` rules, and
19
+ `features/index.md` (one app, many features).
@@ -1,16 +1,17 @@
1
1
  ---
2
2
  name: build-computed
3
- description: Build a feature's computed-database — derived observable values.
3
+ description: Build a feature's ecs/computed layer. Optional.
4
4
  input: feature
5
5
  output: feature
6
6
  ---
7
7
 
8
- Skip if this feature doesn't contain or require computed values.
8
+ Skip if the spec has no derivations and the UI needs no derived values.
9
9
 
10
- Create `services/main-service/computed-database/`: `computed-database.ts` (extends the previous layer, adds
11
- `computed` from `./computed/index.js`) plus a `computed/` folder — one `cached` derived value
12
- per file + barrel. Derivation logic lives in `data/`; a computed only wires a db observable to it.
10
+ Create `ecs/computed/`: `computed-database.ts` (extends the previous layer, adds
11
+ `computed` from `./index.js`), one `cached` derived value per file, and a barrel. Wire
12
+ `data/values/` helpers to the minimal observables. Name each entity-id-list computed in
13
+ the implementation's `hydrate`, and pass `computedPlugin: ComputedDatabase.plugin`.
13
14
 
14
- Comes after `transaction-database`.
15
+ Gate: each derivation's computed conforms.
15
16
 
16
- The how is in the auto-loading `features/services/main-service/computed.md` rule.
17
+ The how is in the auto-loading `features/ecs/computed.md` rule.
@@ -1,24 +1,27 @@
1
1
  ---
2
2
  name: build-core-database
3
- description: Build a feature's schema — the core-database (components, resources, archetypes). The first main-service layer.
3
+ description: Build a feature's ECS schema (ecs/core) and its conformance wiring. The first ecs layer.
4
4
  input: feature
5
5
  output: feature
6
6
  ---
7
7
 
8
- Create `services/main-service/core-database/` — `core-database.ts` plus one single-export file per facet
9
- the feature actually uses (`components`, `resources`, `archetypes` are each optional on
10
- `Database.Plugin.create`). Create only the facet files you need; omit the rest rather than
11
- shipping empty `Database.components({})` boilerplate. A feature with **no entities** (a
12
- singleton whose whole state is resources) is just `resources.ts` + `core-database.ts`.
8
+ Create:
13
9
 
14
- - `components.ts` → `Database.components({ document, settings, presence, session })` (scopes optional) — only if the feature has entities.
15
- - `resources.ts` → `Database.resources({ … })` (every entry needs a `default`) — for singleton / global state.
16
- - `archetypes.ts` → `Database.archetypes(components, { … })` — only alongside components.
17
- - `core-database.ts` → `Database.Plugin.create({ imports?, components?, resources?, archetypes? })`, exporting the `CoreDatabase` namespace.
10
+ - `ecs/core/archetypes.ts` — `Database.archetypes(components, { … })`, spreading each
11
+ `data/entities` tuple and adding implementation-only columns (never a
12
+ `nonPersistent` column without a real default).
13
+ - `ecs/core/core-database.ts` — `Database.Plugin.create({ extends?, components?,
14
+ resources?, archetypes? })` from the `data/` barrels, exporting the `CoreDatabase`
15
+ namespace. A feature built on another extends that feature's core.
16
+ - `ecs/main-service.ts` — `export { CoreDatabase as MainService }`, moved up as layers
17
+ are added.
18
+ - `ecs/conformance/` — `projection.ts`, `implementation.ts`
19
+ (`Conformance.implementation(spec, …)`) and `conformance.test.ts`
20
+ (`Conformance.checkFeature`).
18
21
 
19
- The schema root; comes after `data/` (and `services/`). Bind each schema from its `data/`
20
- type — don't author shapes here. Components use JSON schemas; resources use the
21
- `{ default }` convention (a schemaless aggregate binds via `{ default: State.create() }`).
22
+ Gate: the samples round-trip through the projection. Until the later layers exist,
23
+ `checkFeature` lists each spec op without an implementation by name; that list is
24
+ the work remaining.
22
25
 
23
- The how is in the auto-loading `features/services/main-service/index.md` + `components.md` / `resources.md` /
24
- `archetypes.md` rules.
26
+ The how is in the auto-loading `features/ecs/index.md`, `core.md` and `conformance.md`
27
+ rules.
@@ -1,22 +1,23 @@
1
1
  ---
2
2
  name: build-data
3
- description: Build a feature's data/ layer — data-type namespaces and the State spec. The first, foundational feature phase.
3
+ description: Build a feature's data/ layer — values, components, resources, entities. The first, foundational phase.
4
4
  input: feature
5
5
  output: feature
6
6
  ---
7
7
 
8
- Create the feature's `data/` layer (pure spec; depends on nothing but `@adobe/data`
9
- and other `data/` declarations):
8
+ Create the feature's `data/` layer: pure Data declarations, depending only on
9
+ `@adobe/data` and the `data/` of features this one builds on.
10
10
 
11
- - one namespace folder per data type — `<type>/{<type>.ts, public.ts, <helper>.ts}` (+ `schema.ts` only when a runtime boundary needs it);
12
- - `data/state/` — the `State` aggregate plus its pure transforms/derivations.
11
+ - `values/<type>/` — one namespace per Data type: a hand-written type, a schema pinned
12
+ to it (`type _Pin = Assert<Equal<Schema.ToType<typeof schema>, <Type>>>`) when
13
+ something needs one, and pure helpers, each with a test.
14
+ - `components/<name>.ts` — one schema per file, scope spread from `Scope`, plus an
15
+ `index.ts` barrel. Include implementation-only columns (tags, drag offsets).
16
+ - `resources/<name>.ts` — one schema per file, `satisfies ResourceSchema`, plus a barrel.
17
+ - `entities/<entity>.ts` — the spec-visible entity as a component-name tuple, with its
18
+ type derived by `Database.EntityType`, plus a barrel.
13
19
 
14
- **Always create `data/state/`** — even for a shell feature whose only field is a
15
- boolean or enum. A feature with ECS resources/transactions but no `State` is
16
- incomplete: the transaction must wrap a pure transform, not invent the logic. In rare circumstances, there may be no real state in a feature in which case the state type = {}
20
+ Create only the folders the feature needs. Gate: it compiles and the helper tests
21
+ pass.
17
22
 
18
- The hand-authored type in `<type>.ts` is the source of truth. The schema is optional up front — add `schema.ts` only when a runtime boundary (persistence, wire, or an ECS component/resource) needs one, write it to match the type, and pin it with `type _Pin = Assert<Equal<Schema.ToType<typeof schema>, <Type>>>` so it cannot drift. `Schema.ToType` is used only in that assertion, never as the exported type.
19
- Helpers are pure and unit-tested. Run this first — every other layer imports `data/`.
20
-
21
- The how is in the auto-loading rules: `features/data/index.md`, `features/data/state.md`, and
22
- `global/namespace.md`.
23
+ The how is in the auto-loading `features/data/` rules and `global/namespace.md`.
@@ -8,11 +8,12 @@ output: feature
8
8
  /graph-execute
9
9
  /build-data
10
10
  /build-services
11
+ /build-spec
11
12
  /build-core-database
12
13
  /build-indexes
13
14
  /build-transactions
14
- /build-computed
15
15
  /build-service-database
16
+ /build-computed
16
17
  /build-actions
17
18
  /build-systems
18
19
  /build-ui
@@ -7,15 +7,17 @@ output: game
7
7
 
8
8
  /build-application — a game is an application.
9
9
 
10
- Map game concepts to feature layers: data/ = state + rules (+ per-tick step math),
11
- components/archetypes = entity kinds, indexes = spatial/lookup queries,
12
- transactions = moves/spawns, computed = score/status/winner, ui = view + input.
10
+ Map game concepts to feature layers: data/ = value types, rules math, components,
11
+ resources (the board, the player, the score) and entities (the pieces); spec/ = the
12
+ game state + moves (+ one pure function per system); indexes = spatial/lookup queries;
13
+ transactions + actions = moves/spawns; computed = score/status/winner; ui = view + input.
13
14
 
14
15
  - **Turn-based** uses the reactive transaction → computed → ui loop, no systems
15
16
  (see data-lit-tictactoe).
16
17
  - **Real-time** adds the `build-systems` phase: a per-frame tick loop (movement,
17
18
  collision, lifetime) driven by the scheduler, over many entities in distinct
18
19
  archetypes. Systems must stay O(1) per touched entity — never project the whole
19
- `State` into/out of the store each frame (see `features/services/main-service/systems.md`).
20
+ `State` into/out of the store each frame, and never turn per-row work into per-entity
21
+ transactions. Each system conforms alone and as a frame (see `features/ecs/systems.md`).
20
22
  This is where components, archetypes, indexes, and systems all carry real
21
23
  weight.
@@ -1,16 +1,15 @@
1
1
  ---
2
2
  name: build-indexes
3
- description: Build a feature's index-database — component/archetype indexes for O(1) lookup. As needed.
3
+ description: Build a feature's ecs/indexes layer. Optional.
4
4
  input: feature
5
5
  output: feature
6
6
  ---
7
7
 
8
- Skip if this feature doesn't contain or require indexes.
8
+ Skip if the feature needs no indexed lookups.
9
9
 
10
- Create `services/main-service/index-database/`: `index-database.ts` (extends `CoreDatabase`, adds the `indexes`
11
- facet from `./indexes/index.js`) plus an `indexes/` folder — one index per file + an `index.ts`
12
- barrel.
10
+ Create `ecs/indexes/`: `index-database.ts` (extends `CoreDatabase`, adds `indexes` from
11
+ `./index.js`), one index per file, and an `index.ts` barrel.
13
12
 
14
- Only if the feature needs indexed lookups. Comes after `core-database`.
13
+ Gate: conformance unchanged, plus index tests where an index has logic.
15
14
 
16
- The how is in the auto-loading `features/services/main-service/indexes.md` rule.
15
+ The how is in the auto-loading `features/ecs/indexes.md` rule.
@@ -1,16 +1,16 @@
1
1
  ---
2
2
  name: build-service-database
3
- description: Build a feature's service-database — db-bound service factories. If the feature exposes services.
3
+ description: Build a feature's ecs/services layer — registering services on the database. Optional.
4
4
  input: feature
5
5
  output: feature
6
6
  ---
7
7
 
8
- Create `services/main-service/service-database/`: `service-database.ts` (extends the previous layer, registers the
9
- `services` facet). Two forms:
8
+ Skip if the feature has no services.
10
9
 
11
- - db-bound factories `create<Name>Service(db, …)` in `service-database/services/` (one per file + barrel), for services that read `db.observe.*` / call `db.transactions.*`; or
12
- - plain async ports registered straight from the feature's `services/` contracts.
10
+ Create `ecs/services/service-database.ts` (extends the previous layer), registering
11
+ each `services/` service's `create` under its `Services` key, plus any database-bound
12
+ factories (one per file + barrel) for services that read observables or call
13
+ transactions. Pin with `Assert<Equal<ServiceDatabase["services"], Services>>`. Give
14
+ the implementation base fakes for services whose `create` throws.
13
15
 
14
- Comes after `computed-database`.
15
-
16
- The how is in the auto-loading `features/services/main-service/services.md` rule.
16
+ The how is in the auto-loading `features/ecs/services.md` rule.
@@ -1,16 +1,18 @@
1
1
  ---
2
2
  name: build-services
3
- description: Build a feature's services/ layer — async capability contracts (ports to the outside world). Optional.
3
+ description: Build a feature's services/ layer — async data services (interface, create, createFake). Optional.
4
4
  input: feature
5
5
  output: feature
6
6
  ---
7
7
 
8
- Skip if this feature doesn't contain or require services.
8
+ Skip if the feature talks to nothing outside itself.
9
9
 
10
- Create the feature's `services/` layer: one `<name>-service/` namespace folder per async
11
- contract — an `interface` (validated with `AsyncDataService.IsValid`), async-only members,
12
- and `create*` factories.
10
+ Create one `services/<name>-service/` namespace per service: the interface (an async
11
+ data service, validated with `AsyncDataService.IsValid`), `create` (the
12
+ implementation, or a factory that throws when the app must inject it),
13
+ `createFake`, and `public.ts`. Add `services/services.ts` with the `Services` map when
14
+ spec actions inject services.
13
15
 
14
- Only if the feature talks to the outside world. Comes after `data/`, before `services/main-service/`.
16
+ Depends only on `data/`. Gate: per-service unit tests.
15
17
 
16
- The how is in the auto-loading `features/services/index.md` rule (and `global/namespace.md`).
18
+ The how is in the auto-loading `features/services/index.md` rule.
@@ -0,0 +1,23 @@
1
+ ---
2
+ name: build-spec
3
+ description: Build a feature's spec/ — the State, its actions and derivations, and their cases. Test tier.
4
+ input: feature
5
+ output: feature
6
+ ---
7
+
8
+ Create the feature's `spec/` folder, the specification the ECS will be conformed to:
9
+
10
+ - `state.ts` — `State` built from `data/` types (singletons named for resources,
11
+ `ReadonlyMap` entity collections), pinned with `Conformance.StateMatches`; `create.ts`,
12
+ `samples.ts`, `public.ts`.
13
+ - One file per **action** (each operation the app performs) and per **derivation**
14
+ (each derived value it shows), each with its inert `*.cases.ts`. A real-time feature
15
+ adds `systems.ts`: one pure function per ECS system it will have, named the same,
16
+ each with cases, plus `noOp` states every system leaves unchanged.
17
+ - `transforms.ts` (the barrel of every action and derivation), `spec.ts`
18
+ (`Conformance.spec`) and `spec.test.ts` (`Conformance.checkSpec`).
19
+
20
+ Build one action or derivation plus its cases at a time. Logic the ECS will share goes
21
+ into `data/values/` helpers, never here. Gate: `checkSpec` passes.
22
+
23
+ The how is in the auto-loading `features/spec/index.md` rule.
@@ -1,27 +1,21 @@
1
1
  ---
2
2
  name: build-systems
3
- description: Build a feature's system-database — the real-time tick loop (systems the scheduler runs each frame). For real-time features only.
3
+ description: Build a real-time feature's ecs/systems layer — the per-frame tick loop. Optional.
4
4
  input: feature
5
5
  output: feature
6
6
  ---
7
7
 
8
- Skip if this feature doesn't contain or require systems.
9
- Most games that use a canvas will use systems but most applications will not.
8
+ Skip unless the feature is real-time (games, simulations).
10
9
 
11
- Create `services/main-service/system-database/system-database.ts`: `Database.Plugin.create({ extends:
12
- Database.Plugin.combine(<currentTop>.plugin, scheduler), systems })` — combine `scheduler` with the
13
- feature's **current top** main-service layer (`ActionDatabase` / `ServiceDatabase` / `ComputedDatabase`,
14
- whichever it built), never a hardcoded `ComputedDatabase`, so systems *and* any services/actions
15
- compose into the one `MainService`. The `systems`
16
- map is declared **inline** (see `features/services/main-service/systems.md` — inline is required for `db` to be
17
- typed and for system-name inference; a `systems/` folder is optional, only for extracted
18
- per-frame body helpers).
10
+ Create `ecs/systems/system-database.ts`: `Database.Plugin.create({ extends:
11
+ Database.Plugin.combine(<currentTop>.plugin, scheduler), systems: { … } })`, with the
12
+ `systems` map declared **inline** so `db` and the system names are inferred. Hot
13
+ per-row work writes columns in place; discrete events dispatch transactions. Order
14
+ systems under `schedule`; it is the only statement of order.
19
15
 
20
- - A system is a `SystemDeclaration`: `{ create, schedule?: { before?, after?, during? } }`.
21
- `create(db)` runs once (capture queries / index handles / closures); the returned function
22
- advances the world one tick. Return `void` for an init-only system (seeding entities).
23
- - Order under `schedule` (mirror the `data/` step's internal sequence); drive the loop by
24
- combining `scheduler` (rAF), gated by the `schedulerState` resource.
16
+ Add `frame: { args, unmodelled?, cases? }` to the implementation: a writer per frame
17
+ arg, each system the spec can't model (wasm, init-only) with its reason, and
18
+ whole-frame cases in `ecs/conformance/frame.cases.ts`. Gate: every
19
+ system's own cases and the frame cases pass.
25
20
 
26
- **Only for real-time features** (games, sims) — turn-based features skip this phase entirely.
27
- Comes after `build-computed`. The how is in the auto-loading `features/services/main-service/systems.md` rule.
21
+ The how is in the auto-loading `features/ecs/systems.md` rule.
@@ -1,16 +1,16 @@
1
1
  ---
2
2
  name: build-transactions
3
- description: Build a feature's transaction-database — atomic mutations over the store.
3
+ description: Build a feature's ecs/transactions layer.
4
4
  input: feature
5
5
  output: feature
6
6
  ---
7
7
 
8
- Create `services/main-service/transaction-database/`: `transaction-database.ts` (extends the
9
- preceding layer — `IndexDatabase` if the feature built indexes, else `CoreDatabase` — adds
10
- `transactions` from `./transactions/index.js`) plus a `transactions/` folder — one mutation per
11
- file + barrel. Type the store param `CoreDatabase.Store` (entities/resources/archetypes) or
12
- `IndexDatabase.Store` (reads an index).
8
+ Create `ecs/transactions/`: `transaction-database.ts` (extends the previous layer, adds
9
+ `transactions` from `./index.js`), one mutation per file, and a barrel. Type the store
10
+ `CoreDatabase.Store`, or `IndexDatabase.Store` once it reads an index. Guard by entity
11
+ kind, and apply `data/values/` helpers; never import `spec/`.
13
12
 
14
- Comes after `index-database` (or `core-database` if no indexes).
13
+ Build the transactions the spec actions will need. They are conformed through the
14
+ actions that call them, so their gate is `build-actions`; add any missing one there.
15
15
 
16
- The how is in the auto-loading `features/services/main-service/transactions.md` rule.
16
+ The how is in the auto-loading `features/ecs/transactions.md` rule.