@live-change/frontend-template 0.9.199 → 0.9.200
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.
- package/.claude/rules/live-change-backend-actions-views-triggers.md +62 -0
- package/.claude/rules/live-change-backend-event-sourcing.md +186 -0
- package/.claude/rules/live-change-backend-models-and-relations.md +72 -0
- package/.claude/rules/live-change-frontend-vue-primevue.md +26 -0
- package/.claude/settings.json +32 -0
- package/.claude/skills/create-skills-and-rules/SKILL.md +248 -0
- package/.claude/skills/live-change-backend-change-triggers/SKILL.md +186 -0
- package/.claude/skills/live-change-design-actions-views-triggers/SKILL.md +462 -0
- package/.claude/skills/live-change-design-models-relations/SKILL.md +230 -0
- package/.claude/skills/live-change-design-service/SKILL.md +133 -0
- package/.claude/skills/live-change-frontend-accessible-objects/SKILL.md +384 -0
- package/.claude/skills/live-change-frontend-accessible-objects.md +383 -0
- package/.claude/skills/live-change-frontend-action-buttons/SKILL.md +129 -0
- package/.claude/skills/live-change-frontend-action-form/SKILL.md +149 -0
- package/.claude/skills/live-change-frontend-analytics/SKILL.md +147 -0
- package/.claude/skills/live-change-frontend-command-forms/SKILL.md +216 -0
- package/.claude/skills/live-change-frontend-data-views/SKILL.md +183 -0
- package/.claude/skills/live-change-frontend-editor-form/SKILL.md +240 -0
- package/.claude/skills/live-change-frontend-locale-time/SKILL.md +172 -0
- package/.claude/skills/live-change-frontend-page-list-detail/SKILL.md +201 -0
- package/.claude/skills/live-change-frontend-range-list/SKILL.md +129 -0
- package/.claude/skills/live-change-frontend-ssr-setup/SKILL.md +119 -0
- package/.cursor/rules/live-change-backend-actions-views-triggers.mdc +88 -0
- package/.cursor/rules/live-change-backend-event-sourcing.mdc +185 -0
- package/.cursor/rules/live-change-backend-models-and-relations.mdc +62 -0
- package/.cursor/skills/create-skills-and-rules.md +248 -0
- package/.cursor/skills/live-change-backend-change-triggers.md +186 -0
- package/.cursor/skills/live-change-design-actions-views-triggers.md +178 -79
- package/.cursor/skills/live-change-design-models-relations.md +112 -50
- package/.cursor/skills/live-change-design-service.md +1 -0
- package/.cursor/skills/live-change-frontend-accessible-objects.md +384 -0
- package/.cursor/skills/live-change-frontend-action-buttons.md +1 -0
- package/.cursor/skills/live-change-frontend-action-form.md +9 -3
- package/.cursor/skills/live-change-frontend-analytics.md +1 -0
- package/.cursor/skills/live-change-frontend-command-forms.md +1 -0
- package/.cursor/skills/live-change-frontend-data-views.md +1 -0
- package/.cursor/skills/live-change-frontend-editor-form.md +135 -72
- package/.cursor/skills/live-change-frontend-locale-time.md +1 -0
- package/.cursor/skills/live-change-frontend-page-list-detail.md +1 -0
- package/.cursor/skills/live-change-frontend-range-list.md +1 -0
- package/.cursor/skills/live-change-frontend-ssr-setup.md +1 -0
- package/front/src/router.js +2 -1
- package/opencode.json +10 -0
- package/package.json +52 -50
|
@@ -132,6 +132,68 @@ definition.trigger({
|
|
|
132
132
|
})
|
|
133
133
|
```
|
|
134
134
|
|
|
135
|
+
## Change triggers – reacting to model changes
|
|
136
|
+
|
|
137
|
+
Models with relations (`propertyOf`, `itemOf`, `userItem`, etc.) automatically fire change triggers on every create/update/delete. The naming convention is `{changeType}{ServiceName}_{ModelName}`:
|
|
138
|
+
|
|
139
|
+
- `changeSvc_Model` — fires on any change (recommended, covers all cases)
|
|
140
|
+
- `createSvc_Model` / `updateSvc_Model` / `deleteSvc_Model` — specific lifecycle
|
|
141
|
+
|
|
142
|
+
Parameters: `{ objectType, object, identifiers, data, oldData, changeType }`.
|
|
143
|
+
|
|
144
|
+
```js
|
|
145
|
+
// React to any change in Schedule model from cron service
|
|
146
|
+
definition.trigger({
|
|
147
|
+
name: 'changeCron_Schedule',
|
|
148
|
+
properties: {
|
|
149
|
+
object: { type: Schedule, validation: ['nonEmpty'] },
|
|
150
|
+
data: { type: Object },
|
|
151
|
+
oldData: { type: Object }
|
|
152
|
+
},
|
|
153
|
+
async execute({ object, data, oldData }, { triggerService }) {
|
|
154
|
+
if(oldData) { /* cleanup old state */ }
|
|
155
|
+
if(data) { /* setup new state */ }
|
|
156
|
+
}
|
|
157
|
+
})
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Check `data`/`oldData`: both present = update, only `data` = create, only `oldData` = delete.
|
|
161
|
+
|
|
162
|
+
## Granting access on object creation
|
|
163
|
+
|
|
164
|
+
When a model uses `entity` with `writeAccessControl` / `readAccessControl`, the auto-generated CRUD checks roles but does **not** grant them automatically. The creator must be explicitly granted roles — typically `'owner'` — otherwise they cannot access their own object.
|
|
165
|
+
|
|
166
|
+
Use a change trigger that fires on creation (`data` exists, `oldData` does not):
|
|
167
|
+
|
|
168
|
+
```js
|
|
169
|
+
definition.trigger({
|
|
170
|
+
name: 'changeMyService_MyModel',
|
|
171
|
+
properties: {
|
|
172
|
+
object: { type: MyModel, validation: ['nonEmpty'] },
|
|
173
|
+
data: { type: Object },
|
|
174
|
+
oldData: { type: Object }
|
|
175
|
+
},
|
|
176
|
+
async execute({ object, data, oldData }, { client, triggerService }) {
|
|
177
|
+
if (!data || oldData) return // only on create
|
|
178
|
+
if (!client?.user) return
|
|
179
|
+
|
|
180
|
+
await triggerService({ service: 'accessControl', type: 'accessControl_setAccess' }, {
|
|
181
|
+
objectType: 'myService_MyModel',
|
|
182
|
+
object,
|
|
183
|
+
roles: ['owner'],
|
|
184
|
+
sessionOrUserType: 'user_User',
|
|
185
|
+
sessionOrUser: client.user,
|
|
186
|
+
lastUpdate: new Date()
|
|
187
|
+
})
|
|
188
|
+
}
|
|
189
|
+
})
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Key details:
|
|
193
|
+
- `objectType` format: `serviceName_ModelName` (e.g. `company_Company`)
|
|
194
|
+
- For public access (readable by everyone), use `accessControl_setPublicAccess` with `userRoles` / `sessionRoles`
|
|
195
|
+
- For anonymous users, use `sessionOrUserType: 'session_Session'` and `sessionOrUser: client.session`
|
|
196
|
+
|
|
135
197
|
## Pending + resolve pattern (async result)
|
|
136
198
|
|
|
137
199
|
Use this pattern when an action must wait for a result from an external process (device, worker, etc.).
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Event-sourcing data flow rules — emit events for DB writes, use triggerService for cross-service writes
|
|
3
|
+
globs: **/services/**/*.js
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# LiveChange backend – event-sourcing data flow (Claude Code)
|
|
7
|
+
|
|
8
|
+
Use these rules when implementing actions, triggers, and events in LiveChange services.
|
|
9
|
+
|
|
10
|
+
## How data flows in LiveChange
|
|
11
|
+
|
|
12
|
+
LiveChange uses an event-sourcing pattern:
|
|
13
|
+
|
|
14
|
+
1. **Actions and triggers** validate input and publish events via `emit()`.
|
|
15
|
+
2. **Events** (`definition.event()`) execute and perform actual database writes (`Model.create`, `Model.update`, `Model.delete`).
|
|
16
|
+
3. For models with **relations** (`userItem`, `itemOf`, `propertyOf`, etc.), the relations plugin auto-generates CRUD events and triggers — use those via `triggerService()`.
|
|
17
|
+
4. For **cross-service writes**, always use `triggerService()` — `foreignModel` is read-only.
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
Action/Trigger ──emit()──▶ Event handler ──▶ Model.create/update/delete
|
|
21
|
+
│
|
|
22
|
+
└──triggerService()──▶ Other service's trigger ──emit()──▶ ...
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Rule 1: No direct Model.create/update/delete in actions or triggers
|
|
26
|
+
|
|
27
|
+
Actions and triggers must **not** call `Model.create()`, `Model.update()`, or `Model.delete()` directly. Instead:
|
|
28
|
+
|
|
29
|
+
- **If the model has relations** (auto-generated CRUD) → use `triggerService()` to call the relation-declared trigger (e.g. `serviceName_createModelName`, `serviceName_updateModelName`, `serviceName_setModelName`).
|
|
30
|
+
- **If no relation trigger exists** → use `emit()` to publish an event, then define a `definition.event()` handler that performs the DB write.
|
|
31
|
+
|
|
32
|
+
### Correct: emit an event from an action
|
|
33
|
+
|
|
34
|
+
```js
|
|
35
|
+
definition.action({
|
|
36
|
+
name: 'createImage',
|
|
37
|
+
properties: { /* ... */ },
|
|
38
|
+
waitForEvents: true,
|
|
39
|
+
async execute({ image, name, width, height }, { client, service }, emit) {
|
|
40
|
+
const id = image || app.generateUid()
|
|
41
|
+
// validation, business logic...
|
|
42
|
+
emit({
|
|
43
|
+
type: 'ImageCreated',
|
|
44
|
+
image: id,
|
|
45
|
+
data: { name, width, height }
|
|
46
|
+
})
|
|
47
|
+
return id
|
|
48
|
+
}
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
definition.event({
|
|
52
|
+
name: 'ImageCreated',
|
|
53
|
+
async execute({ image, data }) {
|
|
54
|
+
await Image.create({ ...data, id: image })
|
|
55
|
+
}
|
|
56
|
+
})
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Correct: use triggerService for relation-declared triggers
|
|
60
|
+
|
|
61
|
+
```js
|
|
62
|
+
definition.action({
|
|
63
|
+
name: 'giveCard',
|
|
64
|
+
properties: { /* ... */ },
|
|
65
|
+
async execute({ receiverType, receiver }, { client, triggerService }, emit) {
|
|
66
|
+
// Use the auto-generated trigger from the relations plugin
|
|
67
|
+
await triggerService({
|
|
68
|
+
service: definition.name,
|
|
69
|
+
type: 'businessCard_setReceivedCard',
|
|
70
|
+
}, {
|
|
71
|
+
sessionOrUserType: receiverType,
|
|
72
|
+
sessionOrUser: receiver,
|
|
73
|
+
// ...
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
})
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Wrong: direct DB write in an action
|
|
80
|
+
|
|
81
|
+
```js
|
|
82
|
+
// ❌ DON'T DO THIS
|
|
83
|
+
definition.action({
|
|
84
|
+
name: 'createSomething',
|
|
85
|
+
async execute({ name }, { client }, emit) {
|
|
86
|
+
const id = app.generateUid()
|
|
87
|
+
await Something.create({ id, name }) // ❌ direct write in action
|
|
88
|
+
return id
|
|
89
|
+
}
|
|
90
|
+
})
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Rule 2: Events can only modify same-service models
|
|
94
|
+
|
|
95
|
+
Event handlers (`definition.event()`) must only write to models defined in the **same service**. Never attempt to write to a `foreignModel` — it is read-only (supports `.get()`, `.indexObjectGet()`, `.indexRangeGet()` but not `.create()`, `.update()`, `.delete()`).
|
|
96
|
+
|
|
97
|
+
For cross-service writes, use `triggerService()` from the action or trigger (before emitting):
|
|
98
|
+
|
|
99
|
+
```js
|
|
100
|
+
// ✅ Correct: cross-service write via triggerService
|
|
101
|
+
definition.trigger({
|
|
102
|
+
name: 'chargeCollected_billing_TopUp',
|
|
103
|
+
async execute(props, { triggerService }, emit) {
|
|
104
|
+
// Write to another service via its declared trigger
|
|
105
|
+
await triggerService({
|
|
106
|
+
service: 'balance',
|
|
107
|
+
type: 'balance_setOrUpdateBalance',
|
|
108
|
+
}, { ownerType: 'billing_Billing', owner: props.cause })
|
|
109
|
+
|
|
110
|
+
// Write to own service via triggerService (relation-declared trigger)
|
|
111
|
+
await triggerService({
|
|
112
|
+
service: definition.name,
|
|
113
|
+
type: 'billing_updateTopUp'
|
|
114
|
+
}, { topUp: props.cause, state: 'paid' })
|
|
115
|
+
}
|
|
116
|
+
})
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
```js
|
|
120
|
+
// ❌ DON'T DO THIS — foreignModel is read-only
|
|
121
|
+
const ExternalModel = definition.foreignModel('otherService', 'SomeModel')
|
|
122
|
+
|
|
123
|
+
definition.event({
|
|
124
|
+
name: 'SomethingHappened',
|
|
125
|
+
async execute({ id }) {
|
|
126
|
+
await ExternalModel.update(id, { status: 'done' }) // ❌ will fail
|
|
127
|
+
}
|
|
128
|
+
})
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## `waitForEvents: true`
|
|
132
|
+
|
|
133
|
+
When an action or trigger emits events and needs them processed before returning a result, set `waitForEvents: true`:
|
|
134
|
+
|
|
135
|
+
```js
|
|
136
|
+
definition.action({
|
|
137
|
+
name: 'createNotification',
|
|
138
|
+
waitForEvents: true,
|
|
139
|
+
async execute({ message }, { client }, emit) {
|
|
140
|
+
const id = app.generateUid()
|
|
141
|
+
emit({
|
|
142
|
+
type: 'created',
|
|
143
|
+
notification: id,
|
|
144
|
+
data: { message, sessionOrUserType: 'user_User', sessionOrUser: client.user }
|
|
145
|
+
})
|
|
146
|
+
return id // event is processed before this returns
|
|
147
|
+
}
|
|
148
|
+
})
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Without `waitForEvents: true`, the action returns immediately and events are processed asynchronously.
|
|
152
|
+
|
|
153
|
+
## Relation-declared triggers
|
|
154
|
+
|
|
155
|
+
When a model uses relations (`userItem`, `itemOf`, `propertyOf`, etc.), the relations plugin auto-generates CRUD triggers. Use `describe` to discover them:
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
node server/start.js describe --service myService --output yaml
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Common auto-generated trigger patterns:
|
|
162
|
+
- `serviceName_createModelName` — create a new record
|
|
163
|
+
- `serviceName_updateModelName` — update an existing record
|
|
164
|
+
- `serviceName_deleteModelName` — delete a record
|
|
165
|
+
- `serviceName_setModelName` — create or update (upsert)
|
|
166
|
+
- `serviceName_setOrUpdateModelName` — set if not exists, update if exists
|
|
167
|
+
|
|
168
|
+
Invoke them via `triggerService()`:
|
|
169
|
+
|
|
170
|
+
```js
|
|
171
|
+
await triggerService({
|
|
172
|
+
service: 'myService',
|
|
173
|
+
type: 'myService_createMyModel'
|
|
174
|
+
}, {
|
|
175
|
+
// properties matching the model's writeable fields
|
|
176
|
+
})
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Summary
|
|
180
|
+
|
|
181
|
+
| Where | Can call Model.create/update/delete? | How to change data |
|
|
182
|
+
|---|---|---|
|
|
183
|
+
| `definition.event()` | ✅ Yes (same-service models only) | Direct DB writes |
|
|
184
|
+
| `definition.action()` | ❌ No | `emit()` or `triggerService()` |
|
|
185
|
+
| `definition.trigger()` | ❌ No | `emit()` or `triggerService()` |
|
|
186
|
+
| Cross-service | ❌ Never via foreignModel | `triggerService()` to target service |
|
|
@@ -163,6 +163,48 @@ Notes:
|
|
|
163
163
|
- First argument is the service name.
|
|
164
164
|
- Second is the model name in that service.
|
|
165
165
|
|
|
166
|
+
## Auto-added fields from relations
|
|
167
|
+
|
|
168
|
+
Relations automatically add **identifier fields** and **indexes** to the model. Do **not** re-declare these in `properties`.
|
|
169
|
+
|
|
170
|
+
**Naming convention:** field name = parent model name with first letter lowercased (`Device` → `device`, `CostInvoice` → `costInvoice`).
|
|
171
|
+
|
|
172
|
+
| Relation | Field(s) auto-added | Index(es) auto-added |
|
|
173
|
+
|---|---|---|
|
|
174
|
+
| `itemOf: { what: Device }` | `device` | `byDevice` |
|
|
175
|
+
| `propertyOf: { what: Device }` | `device` | `byDevice` |
|
|
176
|
+
| `userItem` | `user` | `byUser` |
|
|
177
|
+
| `userProperty` | `user` | `byUser` |
|
|
178
|
+
| `sessionOrUserProperty` | `sessionOrUserType`, `sessionOrUser` | `bySessionOrUser` (hash) |
|
|
179
|
+
| `sessionOrUserProperty: { extendedWith: ['object'] }` | + `objectType`, `object` | composite indexes |
|
|
180
|
+
| `propertyOfAny: { to: ['owner'] }` | `ownerType`, `owner` | `byOwner` (hash) |
|
|
181
|
+
| `boundTo: { what: Device }` | `device` | `byDevice` (hash) |
|
|
182
|
+
|
|
183
|
+
For multi-parent relations (e.g. `propertyOf: [{ what: A }, { what: B }]`), all index combinations are created (`byA`, `byB`, `byAAndB`).
|
|
184
|
+
|
|
185
|
+
```js
|
|
186
|
+
// ✅ Correct — only define YOUR fields
|
|
187
|
+
definition.model({
|
|
188
|
+
name: 'Connection',
|
|
189
|
+
properties: {
|
|
190
|
+
status: { type: String } // 'device' is NOT here — auto-added by itemOf
|
|
191
|
+
},
|
|
192
|
+
itemOf: { what: Device } // adds 'device' field + 'byDevice' index
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
// ❌ Wrong — redundant field
|
|
196
|
+
definition.model({
|
|
197
|
+
name: 'Connection',
|
|
198
|
+
properties: {
|
|
199
|
+
device: { type: String }, // ❌ already added by itemOf
|
|
200
|
+
status: { type: String }
|
|
201
|
+
},
|
|
202
|
+
itemOf: { what: Device }
|
|
203
|
+
})
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Use `node server/start.js describe --service myService --model MyModel --output yaml` to see all fields including auto-added ones.
|
|
207
|
+
|
|
166
208
|
## Indexes
|
|
167
209
|
|
|
168
210
|
- Declare indexes explicitly when you frequently query by a field or field combination.
|
|
@@ -186,3 +228,33 @@ In other services, the same index may be visible with a prefixed name such as `m
|
|
|
186
228
|
- Always set `readAccessControl` and `writeAccessControl` on relations (`userItem`, `itemOf`, `propertyOf`).
|
|
187
229
|
- Treat access control as part of the model definition, not an afterthought.
|
|
188
230
|
|
|
231
|
+
## `entity` models – granting access on creation
|
|
232
|
+
|
|
233
|
+
Models with `entity` and `writeAccessControl` / `readAccessControl` check roles on every CRUD operation, but do **not** auto-grant roles to the creator. You must add a change trigger to grant the creator `'owner'` (or other roles) after creation:
|
|
234
|
+
|
|
235
|
+
```js
|
|
236
|
+
definition.trigger({
|
|
237
|
+
name: 'changeMyService_MyModel',
|
|
238
|
+
properties: {
|
|
239
|
+
object: { type: MyModel, validation: ['nonEmpty'] },
|
|
240
|
+
data: { type: Object },
|
|
241
|
+
oldData: { type: Object }
|
|
242
|
+
},
|
|
243
|
+
async execute({ object, data, oldData }, { client, triggerService }) {
|
|
244
|
+
if (!data || oldData) return // only on create
|
|
245
|
+
if (!client?.user) return
|
|
246
|
+
|
|
247
|
+
await triggerService({ service: 'accessControl', type: 'accessControl_setAccess' }, {
|
|
248
|
+
objectType: 'myService_MyModel',
|
|
249
|
+
object,
|
|
250
|
+
roles: ['owner'],
|
|
251
|
+
sessionOrUserType: 'user_User',
|
|
252
|
+
sessionOrUser: client.user,
|
|
253
|
+
lastUpdate: new Date()
|
|
254
|
+
})
|
|
255
|
+
}
|
|
256
|
+
})
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
Without this trigger, the creator cannot read or modify their own object. The `objectType` format is `serviceName_ModelName`.
|
|
260
|
+
|
|
@@ -65,6 +65,32 @@ Decision flow:
|
|
|
65
65
|
2. Is it editing a model record (create/update)? → **Yes**: use `editorData`. **No**: use `actionData`.
|
|
66
66
|
3. Only use `<command-form>` for the simplest throwaway cases.
|
|
67
67
|
|
|
68
|
+
## Form validation feedback
|
|
69
|
+
|
|
70
|
+
Every field in a form using `editorData` or `actionData` **must** show validation errors. Never use bare `InputText`, `Dropdown`, or other PrimeVue inputs without error feedback.
|
|
71
|
+
|
|
72
|
+
Three approaches (pick whichever fits the layout):
|
|
73
|
+
|
|
74
|
+
1. **AutoField without slot** — auto-picks input and shows errors. Simplest, use by default.
|
|
75
|
+
2. **AutoField with slot** — wrap a custom input inside `<AutoField>`. Still renders label + error automatically.
|
|
76
|
+
3. **Manual `Message`** — add `<Message v-if="editor.propertiesErrors?.field" severity="error" variant="simple" size="small">` below the input. Use when AutoField wrapper doesn't fit.
|
|
77
|
+
|
|
78
|
+
Always pass `:error="editor.propertiesErrors?.fieldName"` (or `formData.propertiesErrors?.fieldName` for `actionData`).
|
|
79
|
+
|
|
80
|
+
## Form element requirement
|
|
81
|
+
|
|
82
|
+
Forms using `editorData` or `actionData` with `EditorButtons` or `ActionButtons` **must** be wrapped in a `<form>` element with submit/reset handlers:
|
|
83
|
+
|
|
84
|
+
```vue
|
|
85
|
+
<!-- editorData -->
|
|
86
|
+
<form @submit.prevent="editor.save()" @reset.prevent="editor.reset()">
|
|
87
|
+
|
|
88
|
+
<!-- actionData -->
|
|
89
|
+
<form @submit.prevent="formData.submit()" @reset.prevent="formData.reset()">
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
`EditorButtons` and `ActionButtons` use `type="submit"` / `type="reset"` on their internal buttons. Without a `<form>` parent, these buttons do nothing.
|
|
93
|
+
|
|
68
94
|
### `api.command`
|
|
69
95
|
|
|
70
96
|
```js
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"permissions": {
|
|
3
|
+
"allow": [
|
|
4
|
+
"Bash(ls -d /home/m8/IdeaProjects/live-change/*/.claude/skills/)",
|
|
5
|
+
"Bash(ls -d /home/m8/IdeaProjects/live-change/*/.cursor/skills/)",
|
|
6
|
+
"Bash(node -e \"const p = require.resolve\\(''''@live-change/vue3-components''''\\); console.log\\(p\\)\")",
|
|
7
|
+
"Bash(find /home/m8/IdeaProjects/live-change/live-change-stack -path *vue3-components* -name *.js)",
|
|
8
|
+
"Read(//home/m8/IdeaProjects/**)",
|
|
9
|
+
"Bash(find /home/m8/IdeaProjects/live-change -name \"*.vue\" -type f ! -path \"*/node_modules/*\" ! -path \"*/.history/*\" -exec grep -l \"AutoField\" {})",
|
|
10
|
+
"Bash(cp /home/m8/IdeaProjects/live-change/.claude/skills/live-change-frontend-editor-form/SKILL.md /home/m8/IdeaProjects/live-change/.cursor/skills/live-change-frontend-editor-form.md)",
|
|
11
|
+
"Bash(cp /home/m8/IdeaProjects/live-change/.claude/skills/live-change-frontend-action-form/SKILL.md /home/m8/IdeaProjects/live-change/.cursor/skills/live-change-frontend-action-form.md)",
|
|
12
|
+
"Bash(cp /home/m8/IdeaProjects/live-change/.claude/rules/live-change-frontend-vue-primevue.md /home/m8/IdeaProjects/live-change/auto-firma/.claude/rules/live-change-frontend-vue-primevue.md)",
|
|
13
|
+
"Bash(cp /home/m8/IdeaProjects/live-change/.claude/rules/live-change-frontend-vue-primevue.md /home/m8/IdeaProjects/live-change/automation/.claude/rules/live-change-frontend-vue-primevue.md)",
|
|
14
|
+
"Bash(cp /home/m8/IdeaProjects/live-change/.claude/rules/live-change-frontend-vue-primevue.md /home/m8/IdeaProjects/live-change/live-change-stack/frontend/frontend-template/.claude/rules/live-change-frontend-vue-primevue.md)",
|
|
15
|
+
"Bash(cp /home/m8/IdeaProjects/live-change/.claude/skills/live-change-design-models-relations/SKILL.md /home/m8/IdeaProjects/live-change/.cursor/skills/live-change-design-models-relations.md)",
|
|
16
|
+
"Bash(cp /home/m8/IdeaProjects/live-change/.claude/skills/live-change-design-actions-views-triggers/SKILL.md /home/m8/IdeaProjects/live-change/.cursor/skills/live-change-design-actions-views-triggers.md)",
|
|
17
|
+
"Bash(cp /home/m8/IdeaProjects/live-change/.claude/rules/live-change-backend-actions-views-triggers.md /home/m8/IdeaProjects/live-change/auto-firma/.claude/rules/live-change-backend-actions-views-triggers.md)",
|
|
18
|
+
"Bash(cp /home/m8/IdeaProjects/live-change/.claude/rules/live-change-backend-models-and-relations.md /home/m8/IdeaProjects/live-change/auto-firma/.claude/rules/live-change-backend-models-and-relations.md)",
|
|
19
|
+
"Bash(cp /home/m8/IdeaProjects/live-change/.claude/rules/live-change-backend-actions-views-triggers.md /home/m8/IdeaProjects/live-change/automation/.claude/rules/live-change-backend-actions-views-triggers.md)",
|
|
20
|
+
"Bash(cp /home/m8/IdeaProjects/live-change/.claude/rules/live-change-backend-models-and-relations.md /home/m8/IdeaProjects/live-change/automation/.claude/rules/live-change-backend-models-and-relations.md)",
|
|
21
|
+
"Bash(cp /home/m8/IdeaProjects/live-change/.claude/rules/live-change-backend-event-sourcing.md /home/m8/IdeaProjects/live-change/auto-firma/.claude/rules/live-change-backend-event-sourcing.md)",
|
|
22
|
+
"Bash(cp /home/m8/IdeaProjects/live-change/.claude/rules/live-change-backend-event-sourcing.md /home/m8/IdeaProjects/live-change/automation/.claude/rules/live-change-backend-event-sourcing.md)",
|
|
23
|
+
"Bash(find /home/m8/IdeaProjects/live-change/live-change-stack/framework -type f \\\\\\(-name *.ts -o -name *.js \\\\\\))",
|
|
24
|
+
"Bash(find /home/m8/IdeaProjects/live-change/live-change-stack/services -type f -name *.js)",
|
|
25
|
+
"Bash(grep -r \"userItem\\\\|userProperty\" /home/m8/IdeaProjects/live-change/live-change-stack/services/user-service --include=*.js)"
|
|
26
|
+
],
|
|
27
|
+
"additionalDirectories": [
|
|
28
|
+
"/home/m8/IdeaProjects/live-change/.claude/skills/create-skills-and-rules",
|
|
29
|
+
"/home/m8/IdeaProjects/live-change/.claude/rules"
|
|
30
|
+
]
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: create-skills-and-rules
|
|
3
|
+
description: Create or update Claude Code skills and Cursor rules/skills with proper format and frontmatter, and configure OpenCode
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Skill: create-skills-and-rules
|
|
7
|
+
|
|
8
|
+
Use this skill when you need to **create or update skills and rules** for Claude Code (`.claude/`), Cursor (`.cursor/`), and OpenCode (`opencode.json`).
|
|
9
|
+
|
|
10
|
+
## Directory structure
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
.claude/
|
|
14
|
+
rules/ *.md – always-loaded project instructions
|
|
15
|
+
skills/ <name>/SKILL.md – step-by-step guides, invocable or auto-matched by description
|
|
16
|
+
.cursor/
|
|
17
|
+
rules/ *.mdc – Cursor rules with description/globs/alwaysApply frontmatter
|
|
18
|
+
skills/ *.md – flat files, same content as .claude/skills/ (Cursor reads them as reference)
|
|
19
|
+
opencode.json – OpenCode config: instructions array points to .claude/rules/*.md
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Cross-tool compatibility:**
|
|
23
|
+
- **Claude Code** reads both flat files and directory-based skills from `.claude/skills/`
|
|
24
|
+
- **OpenCode** reads **only** directory-based skills (`.claude/skills/<name>/SKILL.md`) — flat files are ignored
|
|
25
|
+
- **Cursor** reads flat files from `.cursor/skills/*.md` as reference documents
|
|
26
|
+
- Rules are loaded by OpenCode via `opencode.json` `instructions` array
|
|
27
|
+
|
|
28
|
+
**Standard format:** Always use directory-based skills (`<name>/SKILL.md`) for compatibility with all tools.
|
|
29
|
+
|
|
30
|
+
## Step 1 – Decide: rule or skill
|
|
31
|
+
|
|
32
|
+
| Type | Purpose | When loaded |
|
|
33
|
+
|---|---|---|
|
|
34
|
+
| **Rule** | Constraints, conventions, best practices | Automatically, based on `globs` or `alwaysApply` |
|
|
35
|
+
| **Skill** | Step-by-step implementation guide | When description matches task, or invoked via `/skill-name` |
|
|
36
|
+
|
|
37
|
+
Rules say **what to do / not do**. Skills say **how to do it step by step**.
|
|
38
|
+
|
|
39
|
+
## Step 2 – Create a skill (`.claude/skills/<name>/SKILL.md`)
|
|
40
|
+
|
|
41
|
+
### Create the directory and file
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
mkdir -p .claude/skills/my-skill-name
|
|
45
|
+
# then create .claude/skills/my-skill-name/SKILL.md
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Required frontmatter
|
|
49
|
+
|
|
50
|
+
```yaml
|
|
51
|
+
---
|
|
52
|
+
name: my-skill-name
|
|
53
|
+
description: Short description of what this skill does and when to use it
|
|
54
|
+
---
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
| Field | Required by | Description |
|
|
58
|
+
|---|---|---|
|
|
59
|
+
| `name` | OpenCode, Claude Code | Must match directory name exactly. Lowercase, hyphens only, regex `^[a-z0-9]+(-[a-z0-9]+)*$`, max 64 chars |
|
|
60
|
+
| `description` | All tools | When to use — Claude/OpenCode match this to decide auto-invocation |
|
|
61
|
+
|
|
62
|
+
### Optional frontmatter fields (Claude Code only)
|
|
63
|
+
|
|
64
|
+
```yaml
|
|
65
|
+
---
|
|
66
|
+
name: my-skill
|
|
67
|
+
description: What this skill does
|
|
68
|
+
user-invocable: true
|
|
69
|
+
disable-model-invocation: false
|
|
70
|
+
allowed-tools: Read, Grep, Bash
|
|
71
|
+
context: fork
|
|
72
|
+
agent: Explore
|
|
73
|
+
argument-hint: [filename]
|
|
74
|
+
model: sonnet
|
|
75
|
+
---
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
| Field | Default | Description |
|
|
79
|
+
|---|---|---|
|
|
80
|
+
| `user-invocable` | `true` | Show in `/` menu |
|
|
81
|
+
| `disable-model-invocation` | `false` | Prevent auto-loading by Claude |
|
|
82
|
+
| `allowed-tools` | – | Tools allowed without asking (e.g. `Read, Grep, Bash`) |
|
|
83
|
+
| `context` | – | Set to `fork` to run in isolated subagent |
|
|
84
|
+
| `agent` | – | Subagent type when `context: fork` (`Explore`, `Plan`, etc.) |
|
|
85
|
+
| `argument-hint` | – | Hint for autocomplete (e.g. `[issue-number]`) |
|
|
86
|
+
| `model` | inherited | Model override (`sonnet`, `opus`, `haiku`) |
|
|
87
|
+
|
|
88
|
+
OpenCode ignores unknown frontmatter fields, so these are safe to include.
|
|
89
|
+
|
|
90
|
+
### Dynamic substitutions in skill content
|
|
91
|
+
|
|
92
|
+
- `$ARGUMENTS` – all arguments passed when invoking
|
|
93
|
+
- `$ARGUMENTS[0]`, `$1` – specific argument by index
|
|
94
|
+
- `${CLAUDE_SESSION_ID}` – current session ID
|
|
95
|
+
- `${CLAUDE_SKILL_DIR}` – directory containing SKILL.md
|
|
96
|
+
|
|
97
|
+
### Body structure
|
|
98
|
+
|
|
99
|
+
```markdown
|
|
100
|
+
---
|
|
101
|
+
name: my-skill-name
|
|
102
|
+
description: Build X with Y and Z
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
# Skill: my-skill-name
|
|
106
|
+
|
|
107
|
+
Use this skill when you build **X** using Y and Z.
|
|
108
|
+
|
|
109
|
+
## When to use
|
|
110
|
+
|
|
111
|
+
- Bullet list of scenarios
|
|
112
|
+
|
|
113
|
+
## Step 1 – First step
|
|
114
|
+
|
|
115
|
+
Explanation + code example.
|
|
116
|
+
|
|
117
|
+
## Step 2 – Second step
|
|
118
|
+
|
|
119
|
+
Explanation + code example.
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Keep skills under 500 lines. Use clear step numbering. Include real code examples.
|
|
123
|
+
|
|
124
|
+
## Step 3 – Create a Claude Code rule (`.claude/rules/<name>.md`)
|
|
125
|
+
|
|
126
|
+
### Frontmatter
|
|
127
|
+
|
|
128
|
+
```yaml
|
|
129
|
+
---
|
|
130
|
+
description: Short description of this rule's purpose
|
|
131
|
+
globs: **/front/src/**/*.{vue,js,ts}
|
|
132
|
+
---
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
| Field | Description |
|
|
136
|
+
|---|---|
|
|
137
|
+
| `description` | What this rule covers (used for matching) |
|
|
138
|
+
| `globs` | File patterns that trigger this rule (e.g. `**/*.js`, `**/services/**/*.js`) |
|
|
139
|
+
|
|
140
|
+
### Body structure
|
|
141
|
+
|
|
142
|
+
```markdown
|
|
143
|
+
---
|
|
144
|
+
description: Rules for X development
|
|
145
|
+
globs: **/*.js
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
# Title
|
|
149
|
+
|
|
150
|
+
## Section 1
|
|
151
|
+
|
|
152
|
+
- Rule bullet points
|
|
153
|
+
- Code examples
|
|
154
|
+
|
|
155
|
+
## Section 2
|
|
156
|
+
|
|
157
|
+
- More rules
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Rules are concise. Lead with the constraint, then show a code example.
|
|
161
|
+
|
|
162
|
+
## Step 4 – Create Cursor rule (`.cursor/rules/<name>.mdc`)
|
|
163
|
+
|
|
164
|
+
Same content as the Claude rule, but with Cursor-specific frontmatter:
|
|
165
|
+
|
|
166
|
+
```yaml
|
|
167
|
+
---
|
|
168
|
+
description: Short description of this rule's purpose
|
|
169
|
+
globs: **/front/src/**/*.{vue,js,ts}
|
|
170
|
+
alwaysApply: false
|
|
171
|
+
---
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
| Field | Default | Description |
|
|
175
|
+
|---|---|---|
|
|
176
|
+
| `description` | – | What the rule does; Cursor uses this to decide when to apply |
|
|
177
|
+
| `globs` | – | Comma-separated file patterns (e.g. `**/*.tsx, src/**/*.js`) |
|
|
178
|
+
| `alwaysApply` | `false` | If `true`, applies to every session regardless of context |
|
|
179
|
+
|
|
180
|
+
**Notes:**
|
|
181
|
+
- Do NOT quote glob patterns in frontmatter
|
|
182
|
+
- Keep rules short (target 25 lines, max 50 lines for best Cursor performance)
|
|
183
|
+
- The `.mdc` extension is required for Cursor
|
|
184
|
+
|
|
185
|
+
## Step 5 – Register rules in OpenCode (`opencode.json`)
|
|
186
|
+
|
|
187
|
+
OpenCode reads `.claude/skills/<name>/SKILL.md` natively for skills (no extra step needed). But rules must be registered in `opencode.json`:
|
|
188
|
+
|
|
189
|
+
```json
|
|
190
|
+
{
|
|
191
|
+
"$schema": "https://opencode.ai/config.json",
|
|
192
|
+
"instructions": [
|
|
193
|
+
".claude/rules/live-change-backend-architecture.md",
|
|
194
|
+
".claude/rules/live-change-frontend-vue-primevue.md"
|
|
195
|
+
]
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
When you **create a new rule**, add its path to the `instructions` array in `opencode.json`.
|
|
200
|
+
|
|
201
|
+
When you **create a new skill**, no `opencode.json` change is needed — OpenCode discovers skills from `.claude/skills/<name>/SKILL.md` automatically.
|
|
202
|
+
|
|
203
|
+
**Important:** OpenCode ignores the `globs` frontmatter from Claude Code rules. All instructions listed in `opencode.json` are always loaded.
|
|
204
|
+
|
|
205
|
+
## Step 6 – Mirror Cursor skills (`.cursor/skills/<name>.md`)
|
|
206
|
+
|
|
207
|
+
Copy the skill content to `.cursor/skills/` as a **flat file** (Cursor reads flat `.md` files as reference):
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
cp .claude/skills/my-skill-name/SKILL.md .cursor/skills/my-skill-name.md
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
The content is the same (including `name` in frontmatter). Cursor ignores unknown frontmatter fields.
|
|
214
|
+
|
|
215
|
+
## Step 7 – Mirror to sub-projects
|
|
216
|
+
|
|
217
|
+
If the project has sub-projects with their own `.claude/` and `.cursor/` directories, copy the files there too:
|
|
218
|
+
|
|
219
|
+
```bash
|
|
220
|
+
for dir in automation auto-firma; do
|
|
221
|
+
# Skills: directory format for .claude, flat for .cursor
|
|
222
|
+
mkdir -p "$dir/.claude/skills/my-skill-name"
|
|
223
|
+
cp .claude/skills/my-skill-name/SKILL.md "$dir/.claude/skills/my-skill-name/SKILL.md"
|
|
224
|
+
cp .claude/skills/my-skill-name/SKILL.md "$dir/.cursor/skills/my-skill-name.md"
|
|
225
|
+
|
|
226
|
+
# Rules
|
|
227
|
+
cp .claude/rules/my-rule.md "$dir/.claude/rules/"
|
|
228
|
+
cp .cursor/rules/my-rule.mdc "$dir/.cursor/rules/"
|
|
229
|
+
done
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## Naming conventions
|
|
233
|
+
|
|
234
|
+
- Use lowercase with hyphens: `live-change-frontend-editor-form`
|
|
235
|
+
- Must match regex `^[a-z0-9]+(-[a-z0-9]+)*$` (OpenCode requirement)
|
|
236
|
+
- Prefix with domain: `live-change-frontend-*`, `live-change-backend-*`
|
|
237
|
+
- Skills describe actions: `*-editor-form`, `*-range-list`, `*-action-buttons`
|
|
238
|
+
- Rules describe scope: `*-vue-primevue`, `*-models-and-relations`
|
|
239
|
+
|
|
240
|
+
## Checklist
|
|
241
|
+
|
|
242
|
+
- [ ] Directory created: `.claude/skills/<name>/SKILL.md`
|
|
243
|
+
- [ ] Frontmatter has both `name` (matching dir) and `description`
|
|
244
|
+
- [ ] `.cursor/skills/<name>.md` mirrored (flat file, same content)
|
|
245
|
+
- [ ] `.claude/rules/*.md` created (if rule)
|
|
246
|
+
- [ ] `.cursor/rules/*.mdc` created with `globs` + `alwaysApply` (if rule)
|
|
247
|
+
- [ ] `opencode.json` `instructions` array updated (if new rule)
|
|
248
|
+
- [ ] Sub-projects updated (automation, auto-firma)
|