ai/skills/aidd-ecs/SKILL.md
Enforces @adobe/data/ecs best practices. Use this whenever @adobe/data/ecs is imported, when creating or modifying Database.Plugin definitions, or when working with ECS components, resources, transactions, actions, systems, or services.
npx skillsauth add paralleldrive/aidd aidd-ecsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Plugins are created with Database.Plugin.create() from @adobe/data/ecs.
Properties must appear in this exact order. All are optional.
PluginPropertyOrder [
"extends — Plugin, base plugin to extend"
"services — (db) => ServiceInstance, singleton service factories"
"components — schema object, ECS component schemas"
"resources — { default: value as Type }, global resource schemas"
"archetypes — ['comp1', 'comp2'], standard ECS archetypes; storage tables for efficient insertions"
"computed — (db) => Observe<T>, computed observables"
"transactions — (store, payload) => void, synchronous deterministic atomic mutations"
"actions — (db, payload) => T, general functions"
"systems — { create: (db) => fn | void }, per-frame (60fps) or init-only"
]
Constraints {
Properties must appear in this exact order; wrong order throws at runtime
}
Single extension — one plugin extends another:
export const authPlugin = Database.Plugin.create({
extends: environmentPlugin,
services: {
auth: db => AuthService.createLazy({ services: db.services }),
},
});
Combine — extends accepts only one plugin. To extend from multiple use Database.Plugin.combine:
export const generationPlugin = Database.Plugin.create({
extends: Database.Plugin.combine(aPlugin, bPlugin),
computed: {
max: db => Observe.withFilter(
Observe.fromProperties({
a: db.observe.resources.a,
b: db.observe.resources.b
}),
({ a, b }) => Math.max(a, b)
)
},
});
Final composition — combine all plugins into the app plugin:
export const appPlugin = Database.Plugin.combine(
corePlugin, themePlugin, dataPlugin,
authPlugin, uiPlugin, featurePlugin
);
export type AppPlugin = typeof appPlugin;
export type AppDatabase = Database.Plugin.ToDatabase<AppPlugin>;
Factory functions creating singleton services. Extended plugin services initialize first, so db.services has access to them.
services: {
environment: _db => EnvironmentService.create(),
}
Schema objects defining ECS component data. Use schema imports from type namespaces or inline schemas. See data-modeling.md for a simple example.
components: {
layout: Layout.schema,
layoutElement: { default: null as unknown as HTMLElement, transient: true },
layoutLayer: F32.schema,
},
Non-persistable values (e.g. HTML elements, DOM refs) must use transient: true — excluded from serialization.
Global state not tied to entities. Use as Type to provide the compile-time type — without it the value is treated as a const literal. See data-modeling.md for patterns.
resources: {
themeColor: { default: 'dark' as ThemeColor },
themeScale: { default: 'medium' as ThemeScale },
},
Use null as unknown as Type for resources initialized later in a system initializer:
resources: {
connection: { default: null as unknown as WebSocket },
},
Standard ECS archetypes. Used for querying and inserting related components. See data-modeling.md for a simple example.
archetypes: {
Layout: ['layout', 'layoutElement', 'layoutLayer'],
},
Factory returning Observe<T> or (...args) => Observe<T>. Receives full db.
computed: {
max: db => Observe.withFilter(
Observe.fromProperties({
a: db.observe.resources.a,
b: db.observe.resources.b,
}),
({ a, b }) => Math.max(a, b)
),
},
Synchronous, deterministic atomic mutations. Receive store and a payload. Store allows direct, immediate mutation of all entities, components, and resources.
transactions: {
updateLayout: (store, { entity, layout }: { entity: Entity; layout: Layout }) => {
store.update(entity, { layout });
},
setThemeColor: (store, color: ThemeColor) => {
store.resources.themeColor = color;
},
},
StoreAPI {
"store.update(entity, data)" = "update entity components"
"store.resources.x = value" = "mutate resources"
"store.get(entity, 'component')" = "read component value"
"store.read(entity)" = "read all entity component values"
"store.read(entity, archetype)" = "read entity component values in archetype"
"store.select(archetype.components, { where })" = "query entities"
}
General functions with access to the full db. Can return anything or nothing. UI components that call actions MUST never consume returned values — call for side effects only. Consuming return values violates unidirectional flow (data down via Observe, actions up as void). Call at most one transaction per action; multiple transactions corrupt the undo/redo stack.
actions: {
generateNewName: async (db) => {
const generatedName = await db.services.nameGenerator.generateName();
db.transactions.setName(generatedName);
},
getAuth: db => db.services.auth,
},
create receives db and may optionally return a per-frame function (60fps) or just initialize values. Always called synchronously when database.extend(plugin) runs.
systems: {
ui_state_plugin_initialize: {
create: db => {
db.transactions.registerViews(views);
},
},
layout_plugin__system: {
create: db => {
const observer = new ResizeObserver(/* ... */);
Database.observeSelectDeep(db, db.archetypes.Layout.components)(entries => {
// react to entity changes
});
},
},
},
System scheduling (optional):
systems: {
physics: {
create: db => () => { /* per-tick work */ },
schedule: {
before: ['render'],
after: ['input'],
during: ['simulation'],
},
},
},
Schedule {
before: "hard ordering constraints"
after: "hard ordering constraints"
during: "soft preference for same execution tier"
}
PluginNaming {
file: "*-plugin.ts (kebab-case) — e.g. layout-plugin.ts"
export: "*Plugin (camelCase) — e.g. layoutPlugin"
system: "plugin_name__system (snake_case, double underscore) — e.g. layout_plugin__system"
initSystem: "plugin_name_initialize — e.g. ui_state_plugin_initialize"
}
export type MyDatabase = Database.Plugin.ToDatabase<typeof myPlugin>;
export type MyStore = Database.Plugin.ToStore<typeof myPlugin>;
fn whenCreatingOrModifyingPlugin() {
Constraints {
Verify property order matches (extends, services, components, resources, archetypes, computed, transactions, actions, systems)
Use extends for single-parent; Database.Plugin.combine() for multiple peers
Ensure services only access db.services from extended plugins (not forward references)
Export type *Database = Database.Plugin.ToDatabase<typeof *Plugin> when consumers need typed db access
Follow naming conventions for files, exports, and systems
}
}
documentation
Top tier author skill for delivering essential truths with the persuasive power to inspire positive change. Use when writing, reviewing, editing, or scoring any content.
development
Guide for crafting high-quality AIDD skills. Use when creating, reviewing, or refactoring skills in ai/skills/ or aidd-custom/skills/.
testing
Reflective Thought Composition. Structured thinking pipeline for complex decisions, design evaluation, and deep analysis. Use when quality of reasoning matters more than speed of response.
tools
Teaches agents how to write correct riteway ai prompt evals (.sudo files) for multi-step flows that involve tool calls. Use when writing prompt evals, creating .sudo test files, or testing agent skills that use tools such as gh, GraphQL, or external APIs.