@specverse/engines 5.0.0 → 5.0.2

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.
@@ -1,10 +1,12 @@
1
1
  /**
2
2
  * Rule: lifecycle-state-visible-in-detail
3
3
  *
4
- * For every model with a lifecycle, the detail view should surface
5
- * the lifecycle's state names. v1 checks the *first* (initial)
6
- * state typically what a fresh instance shows because that's
7
- * the one guaranteed to exist without creating data.
4
+ * For every model with a lifecycle, after creating a fresh entity
5
+ * the UI should surface its current lifecycle state. v2 seeds an
6
+ * entity in the initial state via the same pattern as the
7
+ * state-sync rules (buildSeedData + createEntity) because an empty
8
+ * detail view has nothing to assert against — a fresh instance is
9
+ * the minimal setup that produces state-bearing UI.
8
10
  *
9
11
  * Lifecycle shape in the spec:
10
12
  * lifecycles:
@@ -15,9 +17,9 @@
15
17
  * different shape (e.g. an explicit `states: [...]`), the parser
16
18
  * should normalize both into the same structure.
17
19
  *
18
- * One TestCase per (model, lifecycle) pair not per state, since
19
- * testing every state requires creating instances and evolving them,
20
- * which belongs to the state-sync rules.
20
+ * One TestCase per (model, lifecycle) pair. Transitions between
21
+ * states are tested by evolve-reflects-in-list; this rule is the
22
+ * thin "initial state renders at all" counterpart.
21
23
  */
22
24
  import type { UiContractRule } from '../test-case-types.js';
23
25
  export declare const RULE_ID = "lifecycle-state-visible-in-detail";
@@ -1 +1 @@
1
- {"version":3,"file":"lifecycle-state-visible-in-detail.d.ts","sourceRoot":"","sources":["../../../../src/inference/ui-contracts/rules/lifecycle-state-visible-in-detail.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,cAAc,EAA4B,MAAM,uBAAuB,CAAC;AAEtF,eAAO,MAAM,OAAO,sCAAsC,CAAC;AAuB3D,eAAO,MAAM,6BAA6B,EAAE,cAqB3C,CAAC"}
1
+ {"version":3,"file":"lifecycle-state-visible-in-detail.d.ts","sourceRoot":"","sources":["../../../../src/inference/ui-contracts/rules/lifecycle-state-visible-in-detail.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAE,cAAc,EAA4B,MAAM,uBAAuB,CAAC;AAGtF,eAAO,MAAM,OAAO,sCAAsC,CAAC;AAuB3D,eAAO,MAAM,6BAA6B,EAAE,cAoC3C,CAAC"}
@@ -1,10 +1,12 @@
1
1
  /**
2
2
  * Rule: lifecycle-state-visible-in-detail
3
3
  *
4
- * For every model with a lifecycle, the detail view should surface
5
- * the lifecycle's state names. v1 checks the *first* (initial)
6
- * state typically what a fresh instance shows because that's
7
- * the one guaranteed to exist without creating data.
4
+ * For every model with a lifecycle, after creating a fresh entity
5
+ * the UI should surface its current lifecycle state. v2 seeds an
6
+ * entity in the initial state via the same pattern as the
7
+ * state-sync rules (buildSeedData + createEntity) because an empty
8
+ * detail view has nothing to assert against — a fresh instance is
9
+ * the minimal setup that produces state-bearing UI.
8
10
  *
9
11
  * Lifecycle shape in the spec:
10
12
  * lifecycles:
@@ -15,10 +17,11 @@
15
17
  * different shape (e.g. an explicit `states: [...]`), the parser
16
18
  * should normalize both into the same structure.
17
19
  *
18
- * One TestCase per (model, lifecycle) pair not per state, since
19
- * testing every state requires creating instances and evolving them,
20
- * which belongs to the state-sync rules.
20
+ * One TestCase per (model, lifecycle) pair. Transitions between
21
+ * states are tested by evolve-reflects-in-list; this rule is the
22
+ * thin "initial state renders at all" counterpart.
21
23
  */
24
+ import { pickDisplayAttribute, buildSeedData } from './_shared.js';
22
25
  export const RULE_ID = 'lifecycle-state-visible-in-detail';
23
26
  function extractInitialState(lifecycleDef) {
24
27
  if (!lifecycleDef)
@@ -45,6 +48,15 @@ export const lifecycleStateVisibleInDetail = (spec) => {
45
48
  const lifecycles = model.lifecycles;
46
49
  if (!lifecycles)
47
50
  continue;
51
+ // Need a way to seed an entity so there's something for the UI
52
+ // to render. Skip the test for models without a seedable shape.
53
+ const display = pickDisplayAttribute(model);
54
+ if (!display)
55
+ continue;
56
+ const marker = `ct-lcstate-${model.name}`;
57
+ const seedData = buildSeedData(model, marker);
58
+ if (!seedData)
59
+ continue;
48
60
  for (const [lcName, lcDef] of Object.entries(lifecycles)) {
49
61
  const initial = extractInitialState(lcDef);
50
62
  if (!initial)
@@ -52,10 +64,16 @@ export const lifecycleStateVisibleInDetail = (spec) => {
52
64
  cases.push({
53
65
  ruleId: RULE_ID,
54
66
  specElement: `${model.name}.${lcName}`,
55
- name: `[${RULE_ID}] ${model.name}.${lcName} initial state '${initial}' visible`,
67
+ name: `[${RULE_ID}] ${model.name}.${lcName} initial state '${initial}' visible after create`,
56
68
  steps: [
57
69
  { action: 'bootRuntime' },
58
- { action: 'navigateToModelDetail', modelName: model.name },
70
+ { action: 'navigateToModel', modelName: model.name },
71
+ {
72
+ action: 'createEntity',
73
+ modelName: model.name,
74
+ data: seedData,
75
+ uniqueField: display.name,
76
+ },
59
77
  { action: 'expectLifecycleState', modelName: model.name, stateName: initial },
60
78
  ],
61
79
  });
@@ -1 +1 @@
1
- {"version":3,"file":"lifecycle-state-visible-in-detail.js","sourceRoot":"","sources":["../../../../src/inference/ui-contracts/rules/lifecycle-state-visible-in-detail.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAIH,MAAM,CAAC,MAAM,OAAO,GAAG,mCAAmC,CAAC;AAE3D,SAAS,mBAAmB,CAAC,YAAiB;IAC5C,IAAI,CAAC,YAAY;QAAE,OAAO,IAAI,CAAC;IAE/B,8CAA8C;IAC9C,IAAI,OAAO,YAAY,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC1C,MAAM,KAAK,GAAG,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC;QACvD,IAAI,KAAK;YAAE,OAAO,KAAK,CAAC;IAC1B,CAAC;IAED,kDAAkD;IAClD,IAAI,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC,MAAM,CAAC,IAAI,YAAY,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACzE,MAAM,KAAK,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QACrC,OAAO,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,EAAE,IAAI,IAAI,IAAI,CAAC;IACjE,CAAC;IAED,8CAA8C;IAC9C,IAAI,OAAO,YAAY,CAAC,OAAO,KAAK,QAAQ;QAAE,OAAO,YAAY,CAAC,OAAO,CAAC;IAE1E,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,CAAC,MAAM,6BAA6B,GAAmB,CAAC,IAAoB,EAAc,EAAE;IAChG,MAAM,KAAK,GAAe,EAAE,CAAC;IAC7B,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QAChC,MAAM,UAAU,GAAG,KAAK,CAAC,UAA6C,CAAC;QACvE,IAAI,CAAC,UAAU;YAAE,SAAS;QAC1B,KAAK,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;YACzD,MAAM,OAAO,GAAG,mBAAmB,CAAC,KAAK,CAAC,CAAC;YAC3C,IAAI,CAAC,OAAO;gBAAE,SAAS;YACvB,KAAK,CAAC,IAAI,CAAC;gBACT,MAAM,EAAE,OAAO;gBACf,WAAW,EAAE,GAAG,KAAK,CAAC,IAAI,IAAI,MAAM,EAAE;gBACtC,IAAI,EAAE,IAAI,OAAO,KAAK,KAAK,CAAC,IAAI,IAAI,MAAM,mBAAmB,OAAO,WAAW;gBAC/E,KAAK,EAAE;oBACL,EAAE,MAAM,EAAE,aAAa,EAAE;oBACzB,EAAE,MAAM,EAAE,uBAAuB,EAAE,SAAS,EAAE,KAAK,CAAC,IAAI,EAAE;oBAC1D,EAAE,MAAM,EAAE,sBAAsB,EAAE,SAAS,EAAE,KAAK,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE;iBAC9E;aACF,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC,CAAC"}
1
+ {"version":3,"file":"lifecycle-state-visible-in-detail.js","sourceRoot":"","sources":["../../../../src/inference/ui-contracts/rules/lifecycle-state-visible-in-detail.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAGH,OAAO,EAAE,oBAAoB,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAEnE,MAAM,CAAC,MAAM,OAAO,GAAG,mCAAmC,CAAC;AAE3D,SAAS,mBAAmB,CAAC,YAAiB;IAC5C,IAAI,CAAC,YAAY;QAAE,OAAO,IAAI,CAAC;IAE/B,8CAA8C;IAC9C,IAAI,OAAO,YAAY,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC1C,MAAM,KAAK,GAAG,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC;QACvD,IAAI,KAAK;YAAE,OAAO,KAAK,CAAC;IAC1B,CAAC;IAED,kDAAkD;IAClD,IAAI,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC,MAAM,CAAC,IAAI,YAAY,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACzE,MAAM,KAAK,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QACrC,OAAO,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,EAAE,IAAI,IAAI,IAAI,CAAC;IACjE,CAAC;IAED,8CAA8C;IAC9C,IAAI,OAAO,YAAY,CAAC,OAAO,KAAK,QAAQ;QAAE,OAAO,YAAY,CAAC,OAAO,CAAC;IAE1E,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,CAAC,MAAM,6BAA6B,GAAmB,CAAC,IAAoB,EAAc,EAAE;IAChG,MAAM,KAAK,GAAe,EAAE,CAAC;IAC7B,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QAChC,MAAM,UAAU,GAAG,KAAK,CAAC,UAA6C,CAAC;QACvE,IAAI,CAAC,UAAU;YAAE,SAAS;QAE1B,+DAA+D;QAC/D,gEAAgE;QAChE,MAAM,OAAO,GAAG,oBAAoB,CAAC,KAAK,CAAC,CAAC;QAC5C,IAAI,CAAC,OAAO;YAAE,SAAS;QACvB,MAAM,MAAM,GAAG,cAAc,KAAK,CAAC,IAAI,EAAE,CAAC;QAC1C,MAAM,QAAQ,GAAG,aAAa,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;QAC9C,IAAI,CAAC,QAAQ;YAAE,SAAS;QAExB,KAAK,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;YACzD,MAAM,OAAO,GAAG,mBAAmB,CAAC,KAAK,CAAC,CAAC;YAC3C,IAAI,CAAC,OAAO;gBAAE,SAAS;YACvB,KAAK,CAAC,IAAI,CAAC;gBACT,MAAM,EAAE,OAAO;gBACf,WAAW,EAAE,GAAG,KAAK,CAAC,IAAI,IAAI,MAAM,EAAE;gBACtC,IAAI,EAAE,IAAI,OAAO,KAAK,KAAK,CAAC,IAAI,IAAI,MAAM,mBAAmB,OAAO,wBAAwB;gBAC5F,KAAK,EAAE;oBACL,EAAE,MAAM,EAAE,aAAa,EAAE;oBACzB,EAAE,MAAM,EAAE,iBAAiB,EAAE,SAAS,EAAE,KAAK,CAAC,IAAI,EAAE;oBACpD;wBACE,MAAM,EAAE,cAAc;wBACtB,SAAS,EAAE,KAAK,CAAC,IAAI;wBACrB,IAAI,EAAE,QAAQ;wBACd,WAAW,EAAE,OAAO,CAAC,IAAI;qBAC1B;oBACD,EAAE,MAAM,EAAE,sBAAsB,EAAE,SAAS,EAAE,KAAK,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE;iBAC9E;aACF,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC,CAAC"}
@@ -142,7 +142,13 @@ function generateTsconfig() {
142
142
  declaration: true,
143
143
  outDir: "./dist",
144
144
  rootDir: "./src",
145
- resolveJsonModule: true
145
+ resolveJsonModule: true,
146
+ // Under moduleResolution: 'bundler' tsc's auto-inclusion of
147
+ // @types/* packages from node_modules/@types is environment-
148
+ // dependent — Node 20 on CI doesn't pick up @types/node while
149
+ // Node 24 locally does. Listing it explicitly forces consistent
150
+ // resolution everywhere.
151
+ types: ["node"]
146
152
  },
147
153
  include: ["src/**/*"],
148
154
  exclude: ["node_modules", "dist"]
@@ -181,7 +187,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
181
187
  })),
182
188
  }));
183
189
 
184
- server.setRequestHandler(CallToolRequestSchema, async (req) => {
190
+ server.setRequestHandler(CallToolRequestSchema, async (req: { params: { name: string; arguments?: Record<string, unknown> } }) => {
185
191
  const { name, arguments: args } = req.params;
186
192
  return callTool(name, args ?? {});
187
193
  });
@@ -195,7 +201,7 @@ server.setRequestHandler(ListResourcesRequestSchema, async () => ({
195
201
  })),
196
202
  }));
197
203
 
198
- server.setRequestHandler(ReadResourceRequestSchema, async (req) => {
204
+ server.setRequestHandler(ReadResourceRequestSchema, async (req: { params: { uri: string } }) => {
199
205
  return readResource(req.params.uri);
200
206
  });
201
207
 
@@ -250,14 +256,14 @@ export function runCli(
250
256
  }
251
257
  }
252
258
 
253
- return new Promise(resolve => {
259
+ return new Promise<CliResult>(resolve => {
254
260
  const child = spawn('specverse', argv, { cwd: cwd || process.cwd(), env: process.env });
255
261
  let stdout = '';
256
262
  let stderr = '';
257
- child.stdout.on('data', d => { stdout += d.toString(); });
258
- child.stderr.on('data', d => { stderr += d.toString(); });
259
- child.on('close', code => resolve({ stdout, stderr, code }));
260
- child.on('error', err => resolve({ stdout, stderr: stderr + '\\n' + err.message, code: -1 }));
263
+ child.stdout.on('data', (d: Buffer) => { stdout += d.toString(); });
264
+ child.stderr.on('data', (d: Buffer) => { stderr += d.toString(); });
265
+ child.on('close', (code: number | null) => resolve({ stdout, stderr, code }));
266
+ child.on('error', (err: Error) => resolve({ stdout, stderr: stderr + '\\n' + err.message, code: -1 }));
261
267
  });
262
268
  }
263
269
  `;
@@ -208,6 +208,12 @@ function generateTsconfig(): string {
208
208
  outDir: './dist',
209
209
  rootDir: './src',
210
210
  resolveJsonModule: true,
211
+ // Under moduleResolution: 'bundler' tsc's auto-inclusion of
212
+ // @types/* packages from node_modules/@types is environment-
213
+ // dependent — Node 20 on CI doesn't pick up @types/node while
214
+ // Node 24 locally does. Listing it explicitly forces consistent
215
+ // resolution everywhere.
216
+ types: ['node'],
211
217
  },
212
218
  include: ['src/**/*'],
213
219
  exclude: ['node_modules', 'dist'],
@@ -247,7 +253,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
247
253
  })),
248
254
  }));
249
255
 
250
- server.setRequestHandler(CallToolRequestSchema, async (req) => {
256
+ server.setRequestHandler(CallToolRequestSchema, async (req: { params: { name: string; arguments?: Record<string, unknown> } }) => {
251
257
  const { name, arguments: args } = req.params;
252
258
  return callTool(name, args ?? {});
253
259
  });
@@ -261,7 +267,7 @@ server.setRequestHandler(ListResourcesRequestSchema, async () => ({
261
267
  })),
262
268
  }));
263
269
 
264
- server.setRequestHandler(ReadResourceRequestSchema, async (req) => {
270
+ server.setRequestHandler(ReadResourceRequestSchema, async (req: { params: { uri: string } }) => {
265
271
  return readResource(req.params.uri);
266
272
  });
267
273
 
@@ -317,14 +323,14 @@ export function runCli(
317
323
  }
318
324
  }
319
325
 
320
- return new Promise(resolve => {
326
+ return new Promise<CliResult>(resolve => {
321
327
  const child = spawn('specverse', argv, { cwd: cwd || process.cwd(), env: process.env });
322
328
  let stdout = '';
323
329
  let stderr = '';
324
- child.stdout.on('data', d => { stdout += d.toString(); });
325
- child.stderr.on('data', d => { stderr += d.toString(); });
326
- child.on('close', code => resolve({ stdout, stderr, code }));
327
- child.on('error', err => resolve({ stdout, stderr: stderr + '\\n' + err.message, code: -1 }));
330
+ child.stdout.on('data', (d: Buffer) => { stdout += d.toString(); });
331
+ child.stderr.on('data', (d: Buffer) => { stderr += d.toString(); });
332
+ child.on('close', (code: number | null) => resolve({ stdout, stderr, code }));
333
+ child.on('error', (err: Error) => resolve({ stdout, stderr: stderr + '\\n' + err.message, code: -1 }));
328
334
  });
329
335
  }
330
336
  `;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@specverse/engines",
3
- "version": "5.0.0",
4
- "description": "SpecVerse toolchain parser, inference, realize, generators, AI, registry",
3
+ "version": "5.0.2",
4
+ "description": "SpecVerse toolchain \u2014 parser, inference, realize, generators, AI, registry",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",