@adobe/data-ai 0.10.22 → 0.10.24

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 (43) hide show
  1. package/.claude/rules/README.md +8 -3
  2. package/.claude/rules/ecs.md +1 -1
  3. package/.claude/rules/element.md +9 -0
  4. package/.claude/rules/features/data/components.md +6 -3
  5. package/.claude/rules/features/data/index.md +4 -1
  6. package/.claude/rules/features/data/state.md +211 -0
  7. package/.claude/rules/features/ecs/conformance.md +12 -6
  8. package/.claude/rules/features/index.md +4 -0
  9. package/.claude/rules/features/services/index.md +2 -0
  10. package/.claude/rules/features/services/main-service/actions.md +72 -0
  11. package/.claude/rules/features/services/main-service/archetypes.md +51 -0
  12. package/.claude/rules/features/services/main-service/components.md +43 -0
  13. package/.claude/rules/features/services/main-service/computed.md +84 -0
  14. package/.claude/rules/features/services/main-service/conformance.md +97 -0
  15. package/.claude/rules/features/services/main-service/core-database.md +50 -0
  16. package/.claude/rules/features/services/main-service/index.md +189 -0
  17. package/.claude/rules/features/services/main-service/indexes.md +65 -0
  18. package/.claude/rules/features/services/main-service/resources.md +30 -0
  19. package/.claude/rules/features/services/main-service/services.md +27 -0
  20. package/.claude/rules/features/services/main-service/systems.md +145 -0
  21. package/.claude/rules/features/services/main-service/transactions.md +61 -0
  22. package/.claude/rules/features/ui/index.md +2 -0
  23. package/.claude/rules/service.md +25 -75
  24. package/.claude-plugin/plugin.json +1 -1
  25. package/README.md +8 -0
  26. package/bin/cli.mjs +24 -8
  27. package/package.json +3 -3
  28. package/skills/build-actions/SKILL.md +2 -0
  29. package/skills/build-app-entry/SKILL.md +2 -0
  30. package/skills/build-computed/SKILL.md +2 -0
  31. package/skills/build-core-database/SKILL.md +2 -0
  32. package/skills/build-data/SKILL.md +2 -0
  33. package/skills/build-feature/SKILL.md +3 -0
  34. package/skills/build-indexes/SKILL.md +2 -0
  35. package/skills/build-service-database/SKILL.md +2 -0
  36. package/skills/build-services/SKILL.md +2 -0
  37. package/skills/build-spec/SKILL.md +2 -0
  38. package/skills/build-systems/SKILL.md +2 -0
  39. package/skills/build-transactions/SKILL.md +2 -0
  40. package/skills/build-ui/SKILL.md +2 -0
  41. package/skills/review/SKILL.md +2 -0
  42. package/skills/structure/SKILL.md +2 -0
  43. package/.claude/rules/features/ui/binding-element.md +0 -226
@@ -0,0 +1,61 @@
1
+ ---
2
+ paths:
3
+ - '**/features/*/services/main-service/transaction-database/transactions/**/*.ts'
4
+ ---
5
+
6
+ # database/transactions/ — atomic mutations
7
+
8
+ > **Legacy state-based layout**, kept for existing features (`data/state/` + `services/main-service/`). New features use the layered layout in `../../index.md`.
9
+
10
+ One mutation per file: a function taking the transaction store as its
11
+ first argument and pure `data/` args, returning `void`. It reads and
12
+ writes `t.resources` / entities; all its writes commit or roll back
13
+ together.
14
+
15
+ ```ts
16
+ import { PlayMoveArgs } from "../../../data/play-move-args/play-move-args.js";
17
+ import type { CoreDatabase } from "../../core-database/core-database.js";
18
+
19
+ export const playMove = (t: CoreDatabase.Store, { index }: PlayMoveArgs) => {
20
+ // guard, then mutate t.resources / entities / archetypes
21
+ };
22
+ ```
23
+
24
+ - Type the store parameter as `<Layer>.Store`: **`CoreDatabase.Store`** for a
25
+ transaction that reads/writes entities, resources, or archetypes (the common
26
+ case — all schema lives in `core-database`); **`IndexDatabase.Store`** the
27
+ moment it reads an index. A store *is* the transaction context: it carries the
28
+ index handles and the initiating `t.userId`. There is no separate
29
+ transaction-context type.
30
+ - Keep transactions idempotent under replay where sync/P2P applies —
31
+ validate and silently return on illegal or out-of-turn input rather
32
+ than throwing.
33
+ - Decisions come from pure `data/` helpers; the transaction only applies the
34
+ result — read the touched slice, call the `data/` transform, write the diff.
35
+ - Keep transaction files **single-export** (the `transactions/` barrel is
36
+ `export *`-ed into the plugin facet, so a second export would pollute it).
37
+ - **ECS-based feature (no `data/state/`):** there are no conformance cases, so
38
+ **every transaction carries its own unit test** — `Store.create(plugin)`, run the
39
+ transaction, assert the resulting resources/entities/archetypes. That test is the
40
+ direct substitute for the conformance oracle (see `../../index.md`, Two modes). The
41
+ bullet below applies only to **state-based** features.
42
+ - **Conformance is wired once, centrally, and auto-paired** — not per-file. The
43
+ feature's single `conformance/conformance.test.ts` `Conformance.checkFeature(spec)`
44
+ call conforms transactions: it pulls them off **`plugin.transactions`** (the
45
+ registered facet), pairs each to the **same-named** `data/state` transition, and
46
+ conforms it — seed `fromState(before)`, apply, `Match.assert` `toState ≡ after`
47
+ (state only; service effects are asserted through the action). There is **no**
48
+ `define`/`conforms` adapter and **no** `covers` guard. A transaction taking
49
+ **entity ids** takes them under the transition's own arg key (`{ id }`); the runner
50
+ resolves each id the case's `args` schema marks via the id→entity map `fromState` returns. A
51
+ transaction with **no same-named transition** is infrastructure (`setInput`,
52
+ `setBounds`) or the drag UI op (`dragTodo`) or system-dispatched — it is simply
53
+ **skipped**, no guard needed. Don't add a per-item adapter for a renamed op; add a
54
+ thin same-named transaction instead (see `conformance.md`).
55
+ - An `index.ts` barrel feeds the `transactions` plugin facet — so it must
56
+ re-export **only** the mutations. A read/query helper shared by several
57
+ transactions (`readShip`, `readBoard` — a `(t) => value` function) may live
58
+ beside them in `transactions/`, but keep it **out of the barrel**, or it gets
59
+ registered as a dispatchable transaction. (These are related helpers, so they
60
+ belong here, not in a separate folder — `global/cohesion.md`'s peer-level caution
61
+ doesn't apply.)
@@ -6,6 +6,8 @@ paths:
6
6
 
7
7
  # ui/ — user interface
8
8
 
9
+ > **Legacy feature** (has `data/state/` or `services/main-service/`)? Its data and services follow the legacy rules; the ui rules here still apply.
10
+
9
11
  The top layer: UI that binds the feature's `MainService` database (`ecs/main-service.ts`)
10
12
  to what the user sees. It owns no business logic — it subscribes to state, hands it to
11
13
  a pure presentation, and wires user input to `service.actions.*`.
@@ -6,7 +6,7 @@ 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`.
9
+ > Inside a feature, `features/services/index.md` covers the service layer; database-bound factories in `ecs/services/` follow `features/ecs/services.md`.
10
10
 
11
11
  Asynchronous data services. Live in the `services/` layer. Adhere to the namespace rule for type and function organization.
12
12
 
@@ -44,74 +44,26 @@ Compile-time check: `Assert<AsyncDataService.IsValid<ServiceInterface>>`.
44
44
 
45
45
  ---
46
46
 
47
- ## Folder structure and cohesion
48
-
49
- **MUST NOT** place implementation files alongside the interface file. The `<name>-service/` folder MUST contain only the interface file and
50
- `public.ts`.
47
+ ## Folder structure
51
48
 
52
49
  ```
53
50
  services/<name>-service/
54
- <name>-service.ts ← interface + contract types; `export * as ServiceName from './public.js'` at bottom
55
- public.ts ← re-exports factory (and other public exports) from the implementation sub-folder
56
- <impl-name>/ ← REQUIRED sub-folder for all implementation files
57
- create-<name>-service.ts ← factory function
58
- *.ts ← helpers, utils, constants (implementation-private)
59
- *.test.ts ← tests live here alongside implementation
60
- ```
61
-
62
- **Single-file exception**: a bare factory with no helpers, no utilities, and no constants MAY live as a flat file alongside the interface.
63
- The moment any additional implementation file is needed, the factory MUST move into a sub-folder.
64
-
65
- **Interface file** (`<name>-service.ts`) — interface declaration and types that define the public service contract. MUST NOT import from any
66
- implementation file or sub-folder. At the bottom of the file, exports the namespace: `export * as ServiceName from './public.js'`. This
67
- merges the interface type and the factory namespace under one identifier — callers type against the interface and invoke factory functions
68
- through the same name.
69
-
70
- **Separate types file at the interface level** — only permitted if it contains exclusively public-contract types (types that appear in the
71
- interface's method signatures or `Observe<>` generic arguments). Implementation-detail types — third-party API shapes, `window.*` global
72
- augmentations, internal utilities — MUST live inside the implementation sub-folder.
73
-
74
- **`*.test.ts` files MUST NOT appear at the interface folder level.** Every test file belongs inside the implementation sub-folder next to
75
- the code it tests; there is nothing at the interface level to test.
76
-
77
- **Implementation sub-folder** — all implementation logic including the factory, utilities, constants, helpers, and their tests. MUST NOT
78
- depend on the interface file's types via the `public.js` re-export; import directly from `<name>-service.js`.
79
-
80
- **public.ts** — MUST re-export the factory function and any other public exports from the implementation sub-folder. Does NOT re-export the
81
- interface — the interface is exported directly from `<name>-service.ts`.
82
-
83
- **No classes** — factory function or static plain object only. `this` bindings are brittle and block higher-order composition.
84
-
85
- ---
86
-
87
- ## Factory export shape — `singletonFactory` + dual export
88
-
89
- Every service factory MUST be wrapped with `singletonFactory` from `factory-functions.js`. This guarantees one instance per argument tuple
90
- when the same factory is called multiple times, preventing duplicate subscriptions and torn state.
91
-
92
- Export **both** the raw `factory` and the singleton-wrapped `create`:
93
-
94
- ```ts
95
- // <name>-service/<impl-name>/create-<name>-service.ts
96
- import { singletonFactory } from 'factory-functions.js';
97
-
98
- export const factory = (deps: Deps): NameService => {
99
- // implementation
100
- };
101
-
102
- // `factory` — for test injection (bypasses the singleton cache; each test gets a fresh instance)
103
- // `create` — for production use (singleton per deps-tuple)
104
- export const create = singletonFactory(factory);
105
- ```
106
-
107
- `public.ts` re-exports both:
108
-
109
- ```ts
110
- export { create, factory } from './<impl-name>/create-<name>-service.js';
51
+ <name>-service.ts ← interface + contract types; `export * as ServiceName from './public.js'` at bottom
52
+ create.ts ← the implementation factory (or a factory that throws when the app must inject it)
53
+ create-fake.ts ← an inert fake with every method present
54
+ public.ts ← re-exports `create` and `createFake`
111
55
  ```
112
56
 
113
- **Why:** Tests must create fresh instances without sharing a singleton cache. Production callers use `create`; tests that need isolation
114
- import `factory` directly.
57
+ - **Interface file** (`<name>-service.ts`) — the interface and the types in its method
58
+ signatures. It never imports an implementation file. The namespace export at the
59
+ bottom merges the interface type and the factories under one name: callers type
60
+ against the interface and call `ServiceName.create`.
61
+ - **An implementation that grows helpers** moves into a named sub-folder
62
+ (`create-<name>-service/` with its helpers and their tests); `public.ts` re-exports
63
+ its factory. Implementation-only types (third-party API shapes, `window.*`
64
+ augmentations) live there too, never in the interface file.
65
+ - **No classes** — factory functions or plain objects only. `this` bindings are brittle
66
+ and block higher-order composition.
115
67
 
116
68
  ---
117
69
 
@@ -124,7 +76,7 @@ identifiers in any method signature, property name, or type parameter.
124
76
 
125
77
  ```ts
126
78
  interface SessionService extends Service {
127
- readonly dunamis: DunamisSdk; // UI now knows Dunamis exists
79
+ readonly analyticsSdk: AnalyticsSdk; // UI now knows which SDK is used
128
80
  }
129
81
  ```
130
82
 
@@ -136,7 +88,7 @@ interface SessionService extends Service {
136
88
  }
137
89
  ```
138
90
 
139
- The interface is the contract between UI and data layers. If the underlying tool is swapped (Dunamis → another SDK, cookies → localStorage),
91
+ The interface is the contract between UI and data layers. If the underlying tool is swapped (one SDK → another, cookies → localStorage),
140
92
  the interface must not change. Any leak of an internal tool name into the interface couples the UI to an implementation detail it must not
141
93
  know about.
142
94
 
@@ -146,21 +98,19 @@ know about.
146
98
 
147
99
  When creating or modifying a service:
148
100
 
149
- 1. Place in `services/<name>-service/`.
101
+ 1. Place it in `services/<name>-service/`.
150
102
  2. Interface in `<name>-service.ts` — types only, extend `Service`, add `Assert<AsyncDataService.IsValid<>>`.
151
- 3. All implementation files go inside a named sub-folder (e.g. `create-<name>-service/`).
103
+ 3. Add `create.ts`, `create-fake.ts` and `public.ts`; move the implementation into a sub-folder once it needs helpers.
152
104
  4. At the bottom of `<name>-service.ts`, add `export * as ServiceName from './public.js'`.
153
- 5. In `public.ts`, re-export the factory (and any other public exports) from the implementation sub-folder.
154
- 6. For front-end services: actions return void only; observables observe Data or Service only. For back-end services: functions return
105
+ 5. For front-end services: actions return void only; observables observe Data or Service only. For back-end services: functions return
155
106
  `Promise<Data>` or `AsyncGenerator<Data>`.
156
107
 
157
108
  ---
158
109
 
159
110
  ## Verify
160
111
 
161
- When any `src/**/services/**/*.ts` file is in the diff, confirm all three invariants:
112
+ When any `services/**` file is in the diff, confirm:
162
113
 
163
- 1. **Interface/implementation separation** — every `.ts` file added or modified in `<name>-service/` is either `<name>-service.ts`,
164
- `public.ts`, or inside a named sub-folder. Any file at the root of the folder that is neither is a violation.
165
- 2. **`public.ts` completeness** — `public.ts` re-exports the factory function (and any other public exports from the sub-folder).
166
- 3. **Interface file purity** — `<name>-service.ts` contains no imports from implementation sub-folders.
114
+ 1. **Interface file purity** — `<name>-service.ts` imports no implementation file.
115
+ 2. **`public.ts` completeness** — it re-exports `create` and `createFake` (and any other public factory).
116
+ 3. **No implementation-only types in the interface** — they live with the implementation.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adobe-data-ai",
3
- "version": "0.10.22",
3
+ "version": "0.10.24",
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/README.md CHANGED
@@ -55,6 +55,14 @@ self-update**, editing your `package.json` and `.gitignore` to:
55
55
  scripts, so a `postinstall` shipped inside the package would silently not fire;
56
56
  3. gitignore the managed bundle folders.
57
57
 
58
+ Each managed folder also gets a short "externally managed — do not edit" notice so
59
+ an agent that opens it is warned before touching the regenerated files. The notice
60
+ follows your repo's instruction convention: it is written as `AGENTS.md` by default
61
+ (the cross-agent standard, which Claude Code also reads) and only as `CLAUDE.md` for a
62
+ repo that is still CLAUDE.md-only. `install` never drops a `CLAUDE.md` into a repo that
63
+ uses `AGENTS.md` — a stray `CLAUDE.md` anywhere makes Claude Code ignore every
64
+ `AGENTS.md` project-wide.
65
+
58
66
  The copied files are regenerated artifacts — never committed, never edited in
59
67
  place. The wiring is idempotent, so re-running is harmless; with `--global` or in a
60
68
  directory with no `package.json`, `install` just copies (no wiring).
package/bin/cli.mjs CHANGED
@@ -41,10 +41,13 @@ const { name: PKG_NAME, version: VERSION } = pkg;
41
41
  // each agent root.
42
42
  const BUNDLE = "adobe-data-ai";
43
43
 
44
- // A do-not-edit notice dropped at each bundle root. Claude Code loads nested
45
- // CLAUDE.md files when working in a subtree, so an agent that opens one of these
46
- // folders sees the notice before touching anything. `extra` adds a pointer to
47
- // the companion bundle.
44
+ // A do-not-edit notice dropped at each bundle root so an agent that opens one of
45
+ // these managed folders sees it before touching anything. The file is named for the
46
+ // project's instruction convention (see `noticeFilename`) — never unconditionally
47
+ // `CLAUDE.md`: Claude Code treats any CLAUDE.md in the tree as switching the whole
48
+ // project into CLAUDE.md mode and then ignores every AGENTS.md project-wide (observed
49
+ // on Claude Code 2.1.286), which would silently blank out instructions in an
50
+ // AGENTS.md-based repo. `extra` adds a pointer to the companion bundle.
48
51
  function notice(extra) {
49
52
  return `# Externally managed — do not edit
50
53
 
@@ -58,11 +61,23 @@ version, then reinstall — do not modify these files in place.
58
61
  ${extra ? `\n${extra}\n` : ""}`;
59
62
  }
60
63
 
61
- function writeMeta(bundleDir, noticeBody, extraMeta) {
62
- writeFileSync(join(bundleDir, "CLAUDE.md"), noticeBody);
64
+ // Choose the managed notice's filename from the project's instruction convention.
65
+ // Preference is AGENTS.md (the cross-agent standard, which Claude Code also reads); we
66
+ // fall back to CLAUDE.md only for a repo still on the CLAUDE.md convention (a root
67
+ // CLAUDE.md and no AGENTS.md), so the notice keeps auto-loading there. An AGENTS.md
68
+ // repo (and a greenfield one) therefore never receives a data-ai CLAUDE.md — a stray
69
+ // CLAUDE.md anywhere flips Claude Code out of AGENTS.md mode for the whole project.
70
+ function noticeFilename(base) {
71
+ const hasClaude = existsSync(join(base, "CLAUDE.md"));
72
+ const hasAgents = existsSync(join(base, "AGENTS.md"));
73
+ return hasClaude && !hasAgents ? "CLAUDE.md" : "AGENTS.md";
74
+ }
75
+
76
+ function writeMeta(bundleDir, noticeBody, extraMeta, noticeFile) {
77
+ writeFileSync(join(bundleDir, noticeFile), noticeBody);
63
78
  writeFileSync(
64
79
  join(bundleDir, ".data-ai.json"),
65
- JSON.stringify({ package: PKG_NAME, version: VERSION, ...extraMeta }, null, 2) + "\n",
80
+ JSON.stringify({ package: PKG_NAME, version: VERSION, notice: noticeFile, ...extraMeta }, null, 2) + "\n",
66
81
  );
67
82
  }
68
83
 
@@ -107,6 +122,7 @@ function installSkills(base, skills) {
107
122
  bundleDir,
108
123
  notice("The architecture rules these skills follow are installed at\n`.claude/rules/adobe-data-ai/` (referenced by name from each SKILL.md)."),
109
124
  { skills },
125
+ noticeFilename(base),
110
126
  );
111
127
  return bundleDir;
112
128
  }
@@ -118,7 +134,7 @@ function installRules(base) {
118
134
  // Copy the whole rules tree, minus the top-level README.md meta doc.
119
135
  const readme = join(rulesSrc, "README.md");
120
136
  cpSync(rulesSrc, bundleDir, { recursive: true, filter: (src) => src !== readme });
121
- writeMeta(bundleDir, notice(), { rules: countRules(rulesSrc) });
137
+ writeMeta(bundleDir, notice(), { rules: countRules(rulesSrc) }, noticeFilename(base));
122
138
  return bundleDir;
123
139
  }
124
140
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/data-ai",
3
- "version": "0.10.22",
3
+ "version": "0.10.24",
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.22"
32
+ "@adobe/data": "0.10.24"
33
33
  },
34
34
  "devDependencies": {
35
35
  "typescript": "^5.8.3",
36
36
  "vitest": "^1.6.0",
37
- "@adobe/data": "0.10.22"
37
+ "@adobe/data": "0.10.24"
38
38
  },
39
39
  "license": "MIT",
40
40
  "scripts": {
@@ -5,6 +5,8 @@ input: feature
5
5
  output: feature
6
6
  ---
7
7
 
8
+ Legacy feature (has `data/state/` or `services/main-service/`)? Extend it in place per the legacy rules; don't migrate unless asked.
9
+
8
10
  Create `ecs/actions/`: `action-database.ts` (extends the previous layer, adds `actions`
9
11
  from `./index.js`), one action per file, and a barrel. Every spec action has a
10
12
  same-named action. It awaits services and commits through at most one transaction
@@ -5,6 +5,8 @@ input: app
5
5
  output: app
6
6
  ---
7
7
 
8
+ A legacy feature (has `data/state/` or `services/main-service/`) exposes its `MainService` from `services/main-service/`; compose it from there.
9
+
8
10
  Create `src/app/`:
9
11
 
10
12
  - `schema.ts` — the composed plugin: the base feature's `MainService.plugin`, `imports`
@@ -5,6 +5,8 @@ input: feature
5
5
  output: feature
6
6
  ---
7
7
 
8
+ Legacy feature (has `data/state/` or `services/main-service/`)? Extend it in place per the legacy rules; don't migrate unless asked.
9
+
8
10
  Skip if the spec has no derivations and the UI needs no derived values.
9
11
 
10
12
  Create `ecs/computed/`: `computed-database.ts` (extends the previous layer, adds
@@ -5,6 +5,8 @@ input: feature
5
5
  output: feature
6
6
  ---
7
7
 
8
+ Legacy feature (has `data/state/` or `services/main-service/`)? Extend it in place per the legacy rules; don't migrate unless asked.
9
+
8
10
  Create:
9
11
 
10
12
  - `ecs/core/archetypes.ts` — `Database.archetypes(components, { … })`, spreading each
@@ -5,6 +5,8 @@ input: feature
5
5
  output: feature
6
6
  ---
7
7
 
8
+ Legacy feature (has `data/state/` or `services/main-service/`)? Extend it in place per the legacy rules; don't migrate unless asked.
9
+
8
10
  Create the feature's `data/` layer: pure Data declarations, depending only on
9
11
  `@adobe/data` and the `data/` of features this one builds on.
10
12
 
@@ -5,6 +5,9 @@ input: feature
5
5
  output: feature
6
6
  ---
7
7
 
8
+ If the feature already has `data/state/` or `services/main-service/`, it uses the legacy
9
+ state-based layout: extend it in place per those legacy rules; don't migrate it unless asked.
10
+
8
11
  /graph-execute
9
12
  /build-data
10
13
  /build-services
@@ -5,6 +5,8 @@ input: feature
5
5
  output: feature
6
6
  ---
7
7
 
8
+ Legacy feature (has `data/state/` or `services/main-service/`)? Extend it in place per the legacy rules; don't migrate unless asked.
9
+
8
10
  Skip if the feature needs no indexed lookups.
9
11
 
10
12
  Create `ecs/indexes/`: `index-database.ts` (extends `CoreDatabase`, adds `indexes` from
@@ -5,6 +5,8 @@ input: feature
5
5
  output: feature
6
6
  ---
7
7
 
8
+ Legacy feature (has `data/state/` or `services/main-service/`)? Extend it in place per the legacy rules; don't migrate unless asked.
9
+
8
10
  Skip if the feature has no services.
9
11
 
10
12
  Create `ecs/services/service-database.ts` (extends the previous layer), registering
@@ -5,6 +5,8 @@ input: feature
5
5
  output: feature
6
6
  ---
7
7
 
8
+ Legacy feature (has `data/state/` or `services/main-service/`)? Extend it in place per the legacy rules; don't migrate unless asked.
9
+
8
10
  Skip if the feature talks to nothing outside itself.
9
11
 
10
12
  Create one `services/<name>-service/` namespace per service: the interface (an async
@@ -5,6 +5,8 @@ input: feature
5
5
  output: feature
6
6
  ---
7
7
 
8
+ Legacy feature (has `data/state/` or `services/main-service/`)? Extend it in place per the legacy rules; don't migrate unless asked.
9
+
8
10
  Create the feature's `spec/` folder, the specification the ECS will be conformed to:
9
11
 
10
12
  - `state.ts` — `State` built from `data/` types (singletons named for resources,
@@ -5,6 +5,8 @@ input: feature
5
5
  output: feature
6
6
  ---
7
7
 
8
+ Legacy feature (has `data/state/` or `services/main-service/`)? Extend it in place per the legacy rules; don't migrate unless asked.
9
+
8
10
  Skip unless the feature is real-time (games, simulations).
9
11
 
10
12
  Create `ecs/systems/system-database.ts`: `Database.Plugin.create({ extends:
@@ -5,6 +5,8 @@ input: feature
5
5
  output: feature
6
6
  ---
7
7
 
8
+ Legacy feature (has `data/state/` or `services/main-service/`)? Extend it in place per the legacy rules; don't migrate unless asked.
9
+
8
10
  Create `ecs/transactions/`: `transaction-database.ts` (extends the previous layer, adds
9
11
  `transactions` from `./index.js`), one mutation per file, and a barrel. Type the store
10
12
  `CoreDatabase.Store`, or `IndexDatabase.Store` once it reads an index. Guard by entity
@@ -5,6 +5,8 @@ input: feature
5
5
  output: feature
6
6
  ---
7
7
 
8
+ Legacy feature (has `data/state/` or `services/main-service/`)? Extend it in place per the legacy rules; don't migrate unless asked.
9
+
8
10
  Create `ui/`: one folder per UI unit —
9
11
  `<name>/{<name>.ts (lazy wrapper), <name>-element.ts (container), <name>-presentation.ts (pure render), <name>.css.ts}`.
10
12
  The element subscribes to `MainService` state and calls `service.actions.*`; the
@@ -6,6 +6,8 @@ input: a path to built output (a layer, a feature, or a whole app)
6
6
  output: a review — OPTIMAL, or a list of issues each tagged code | skill | rule
7
7
  ---
8
8
 
9
+ Check a legacy feature (has `data/state/` or `services/main-service/`) against the legacy rules; don't flag the layout itself.
10
+
9
11
  Read the files under the given path and assess them against the relevant `features/`
10
12
  rules (each layer's rule and the cross-cutting rules — `namespace`, `data-modelling`,
11
13
  `type-casts`, `cohesion`, …):
@@ -3,5 +3,7 @@ name: structure
3
3
  description: Use when laying out or reasoning about a feature's file/folder structure and layer dependencies.
4
4
  ---
5
5
 
6
+ Legacy feature (has `data/state/` or `services/main-service/`)? It keeps its state-based layout (the legacy rules); don't migrate unless asked.
7
+
6
8
  See `features/index.md` (and the per-folder `features/` rules) for how features are
7
9
  structured and layered.