@specverse/engines 4.3.5 → 5.0.1

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 (68) hide show
  1. package/assets/examples/10-api/README.md +3 -3
  2. package/assets/prompts/core/README.md +1 -1
  3. package/dist/inference/core/rule-engine.d.ts +0 -12
  4. package/dist/inference/core/rule-engine.d.ts.map +1 -1
  5. package/dist/inference/core/rule-engine.js +99 -968
  6. package/dist/inference/core/rule-engine.js.map +1 -1
  7. package/dist/inference/core/template-helpers.d.ts +56 -0
  8. package/dist/inference/core/template-helpers.d.ts.map +1 -0
  9. package/dist/inference/core/template-helpers.js +87 -0
  10. package/dist/inference/core/template-helpers.js.map +1 -0
  11. package/dist/inference/logical/generators/service-generator.d.ts.map +1 -1
  12. package/dist/inference/logical/generators/service-generator.js +0 -4
  13. package/dist/inference/logical/generators/service-generator.js.map +1 -1
  14. package/dist/inference/ui-contracts/rules/lifecycle-state-visible-in-detail.d.ts +9 -7
  15. package/dist/inference/ui-contracts/rules/lifecycle-state-visible-in-detail.d.ts.map +1 -1
  16. package/dist/inference/ui-contracts/rules/lifecycle-state-visible-in-detail.js +27 -9
  17. package/dist/inference/ui-contracts/rules/lifecycle-state-visible-in-detail.js.map +1 -1
  18. package/dist/libs/instance-factories/cli/templates/commander/command-generator.js +27 -5
  19. package/dist/libs/instance-factories/tools/README.md +1 -1
  20. package/dist/libs/instance-factories/tools/mcp.yaml +1 -1
  21. package/dist/libs/instance-factories/tools/templates/mcp/mcp-server-generator.js +342 -116
  22. package/dist/libs/instance-factories/tools/templates/vscode/vscode-extension-generator.js +172 -8
  23. package/dist/libs/instance-factories/tools/vscode.yaml +1 -1
  24. package/libs/instance-factories/cli/templates/commander/command-generator.ts +27 -5
  25. package/libs/instance-factories/tools/README.md +1 -1
  26. package/libs/instance-factories/tools/mcp.yaml +1 -1
  27. package/libs/instance-factories/tools/templates/mcp/mcp-server-generator.ts +392 -141
  28. package/libs/instance-factories/tools/templates/vscode/static/extension.ts +9 -2
  29. package/libs/instance-factories/tools/templates/vscode/vscode-extension-generator.ts +246 -10
  30. package/libs/instance-factories/tools/vscode.yaml +1 -1
  31. package/package.json +5 -4
  32. package/libs/instance-factories/tools/templates/mcp/static/docs/DEPLOYMENT_GUIDE.md +0 -630
  33. package/libs/instance-factories/tools/templates/mcp/static/docs/HYBRID_RESOURCE_SYSTEM.md +0 -330
  34. package/libs/instance-factories/tools/templates/mcp/static/docs/deployments/EXTENSION_DEPLOYMENT.md +0 -552
  35. package/libs/instance-factories/tools/templates/mcp/static/docs/deployments/LOCAL_DEPLOYMENT.md +0 -164
  36. package/libs/instance-factories/tools/templates/mcp/static/docs/deployments/WEB_DEPLOYMENT.md +0 -247
  37. package/libs/instance-factories/tools/templates/mcp/static/package.json +0 -94
  38. package/libs/instance-factories/tools/templates/mcp/static/scripts/build-enterprise.js +0 -284
  39. package/libs/instance-factories/tools/templates/mcp/static/scripts/build-extension.js +0 -139
  40. package/libs/instance-factories/tools/templates/mcp/static/scripts/build-local.js +0 -74
  41. package/libs/instance-factories/tools/templates/mcp/static/scripts/build-web.js +0 -156
  42. package/libs/instance-factories/tools/templates/mcp/static/scripts/copy-canonical-files.js +0 -41
  43. package/libs/instance-factories/tools/templates/mcp/static/scripts/test-deployments.js +0 -259
  44. package/libs/instance-factories/tools/templates/mcp/static/scripts/test-hybrid-resources.js +0 -231
  45. package/libs/instance-factories/tools/templates/mcp/static/scripts/test-hybrid-simple.js +0 -196
  46. package/libs/instance-factories/tools/templates/mcp/static/src/controllers/MCPServerController.ts +0 -293
  47. package/libs/instance-factories/tools/templates/mcp/static/src/events/EventEmitter.ts +0 -90
  48. package/libs/instance-factories/tools/templates/mcp/static/src/index.ts +0 -24
  49. package/libs/instance-factories/tools/templates/mcp/static/src/interfaces/ResourceProvider.ts +0 -15
  50. package/libs/instance-factories/tools/templates/mcp/static/src/models/LibrarySuggestion.ts +0 -106
  51. package/libs/instance-factories/tools/templates/mcp/static/src/models/SpecVerseResource.ts +0 -75
  52. package/libs/instance-factories/tools/templates/mcp/static/src/server/mcp-server.ts +0 -239
  53. package/libs/instance-factories/tools/templates/mcp/static/src/services/CLIProxyService.ts +0 -1501
  54. package/libs/instance-factories/tools/templates/mcp/static/src/services/EmbeddedResourcesAdapter.ts +0 -211
  55. package/libs/instance-factories/tools/templates/mcp/static/src/services/EntityModuleService.ts +0 -308
  56. package/libs/instance-factories/tools/templates/mcp/static/src/services/HybridResourcesProvider.ts +0 -210
  57. package/libs/instance-factories/tools/templates/mcp/static/src/services/LibraryToolsService.ts +0 -356
  58. package/libs/instance-factories/tools/templates/mcp/static/src/services/OrchestratorBridge.ts +0 -522
  59. package/libs/instance-factories/tools/templates/mcp/static/src/services/OrchestratorToolsService.ts +0 -530
  60. package/libs/instance-factories/tools/templates/mcp/static/src/services/PromptToolsService.ts +0 -594
  61. package/libs/instance-factories/tools/templates/mcp/static/src/services/ResourcesProviderService.ts +0 -170
  62. package/libs/instance-factories/tools/templates/mcp/static/src/tests/unit/CLIProxyService.init.test.ts +0 -544
  63. package/libs/instance-factories/tools/templates/mcp/static/src/tests/unit/CLIProxyService.test.ts +0 -189
  64. package/libs/instance-factories/tools/templates/mcp/static/src/tests/unit/ResourcesProviderService.test.ts +0 -89
  65. package/libs/instance-factories/tools/templates/mcp/static/src/types/index.ts +0 -110
  66. package/libs/instance-factories/tools/templates/mcp/static/tsconfig.json +0 -28
  67. package/libs/instance-factories/tools/templates/vscode/static/schemas/specverse-v3-schema.json +0 -4279
  68. /package/libs/instance-factories/tools/templates/vscode/static/themes/{specverse-complete-theme.json → specverse-dark-theme.json} +0 -0
@@ -10,11 +10,28 @@
10
10
  */
11
11
 
12
12
  import type { TemplateContext } from '@specverse/types';
13
- import { readFileSync, existsSync, readdirSync, statSync, mkdirSync, writeFileSync, copyFileSync } from 'fs';
13
+ import { readFileSync, existsSync, readdirSync, statSync, mkdirSync, writeFileSync, copyFileSync, unlinkSync } from 'fs';
14
14
  import { join, dirname, resolve } from 'path';
15
15
  import { fileURLToPath } from 'url';
16
+ import { createRequire } from 'module';
16
17
 
17
18
  const __generatorDir = dirname(fileURLToPath(import.meta.url));
19
+ const __require = createRequire(import.meta.url);
20
+
21
+ /**
22
+ * Resolve the live composed SpecVerse JSON schema from @specverse/entities.
23
+ * The schema is composed at entities build-time from per-module fragments;
24
+ * copying it into the extension at realize time keeps jsonValidation in sync
25
+ * with the real language grammar (rather than shipping a frozen static copy).
26
+ */
27
+ function resolveComposedSchemaPath(): string | null {
28
+ try {
29
+ const entitiesPkg = __require.resolve('@specverse/entities/package.json');
30
+ const schemaPath = join(dirname(entitiesPkg), 'schema', 'SPECVERSE-SCHEMA.json');
31
+ if (existsSync(schemaPath)) return schemaPath;
32
+ } catch {}
33
+ return null;
34
+ }
18
35
 
19
36
  export default function generateVSCodeExtension(context: TemplateContext): string {
20
37
  const { spec, outputDir } = context;
@@ -25,17 +42,58 @@ export default function generateVSCodeExtension(context: TemplateContext): strin
25
42
  // 1. Check for distribution spec (new entity type)
26
43
  const distribution = extractDistribution(spec);
27
44
 
28
- // 2. Extract CLI commands — from distribution, spec, or standard defaults
29
- let cliCommands: Array<{ command: string; title: string; category: string }> = [];
45
+ // 2. Extract CLI commands — from distribution, spec, or standard defaults.
46
+ // Commands with subcommands in the spec (e.g. `realize`, `gen`, `dev`) are
47
+ // auto-expanded into their leaf form (`realize.all`, `gen.diagrams`, …) so
48
+ // they can be invoked independently; the parent name survives as a
49
+ // `submenu` marker for the context-menu generator below.
50
+ type CliCmd = { command: string; title: string; category: string; submenu?: string; submenuLabel?: string };
51
+ let cliCommands: CliCmd[] = [];
52
+ const submenuDefs: Array<{ id: string; label: string }> = [];
30
53
  if (distribution?.commands) {
31
- cliCommands = distribution.commands.map((cmd: any) => {
54
+ const specCommandsByName = buildCLICommandMap(spec);
55
+ const standardByName = new Map(
56
+ getStandardCommands().map(c => [c.command, c])
57
+ );
58
+
59
+ cliCommands = distribution.commands.flatMap((cmd: any): CliCmd[] => {
32
60
  const parts = (cmd.from || cmd.command || '').split('.');
33
61
  const name = parts[parts.length - 1] || 'unknown';
34
- return {
35
- command: `specverse.${name}`,
36
- title: cmd.title || `${capitalize(name)}: ${cmd.description || ''}`,
62
+
63
+ // If the referenced spec command has subcommands, expand into leaves
64
+ // and declare a submenu so the right-click shows `<Parent> ▶ <child>`.
65
+ const specNode = findSpecCommand(spec, cmd.from || name);
66
+ const subs = specNode?.subcommands as Record<string, any> | undefined;
67
+ if (subs && Object.keys(subs).length > 0) {
68
+ const submenuId = `specverse.${name}`;
69
+ const submenuLabel = cmd.title || specNode?.description || capitalize(name);
70
+ submenuDefs.push({ id: submenuId, label: capitalize(name) });
71
+ return Object.entries(subs).map(([childName, childDef]: [string, any]) => ({
72
+ command: `specverse.${name}.${childName}`,
73
+ title: `${capitalize(name)} ${capitalize(childName)}`,
74
+ category: 'SpecVerse',
75
+ submenu: submenuId,
76
+ submenuLabel,
77
+ }));
78
+ }
79
+
80
+ // Leaf command.
81
+ const commandId = `specverse.${name}`;
82
+ let title = cmd.title;
83
+ if (!title && cmd.from) {
84
+ const specCmd = specCommandsByName.get(cmd.from);
85
+ if (specCmd?.description) title = specCmd.description;
86
+ }
87
+ if (!title) {
88
+ const std = standardByName.get(commandId);
89
+ if (std) title = std.title;
90
+ }
91
+ if (!title) title = capitalize(name);
92
+ return [{
93
+ command: commandId,
94
+ title,
37
95
  category: 'SpecVerse',
38
- };
96
+ }];
39
97
  });
40
98
  }
41
99
  if (cliCommands.length === 0) {
@@ -73,6 +131,75 @@ export default function generateVSCodeExtension(context: TemplateContext): strin
73
131
  scopeName: lang.grammar,
74
132
  path: `./syntaxes/${lang.id}.tmLanguage.json`,
75
133
  }));
134
+ // Activation: leave activationEvents empty — modern VSCode (1.74+)
135
+ // auto-generates activation from `contributes.languages`, and an explicit
136
+ // entry triggers the extension-editing linter's "can be removed" hint.
137
+ packageJson.activationEvents = [];
138
+ const fileMatch = distribution.languages
139
+ .flatMap((lang: any) => (lang.extensions || []).map((ext: string) => `*${ext}`));
140
+ if (fileMatch.length > 0) {
141
+ packageJson.contributes.jsonValidation = [{
142
+ fileMatch,
143
+ url: './schemas/specverse-schema.json',
144
+ }];
145
+ }
146
+
147
+ // Right-click menus. Leaf commands become direct entries; commands
148
+ // with children collapse into a submenu keyed by the parent id
149
+ // (declared in `contributes.submenus`). The gating uses
150
+ // `resourceExtname` for the sidebar's explorer/context and
151
+ // `editorLangId` for the file's editor/context.
152
+ const extnames = distribution.languages
153
+ .flatMap((lang: any) => lang.extensions || []) as string[];
154
+ const langIds = distribution.languages.map((lang: any) => lang.id) as string[];
155
+ const explorerWhen = extnames.map(e => `resourceExtname == ${e}`).join(' || ');
156
+ const editorWhen = langIds.map(id => `editorLangId == ${id}`).join(' || ');
157
+
158
+ // Ordered top-level entries: walk distribution.commands in spec order,
159
+ // emit a submenu ref for expanded parents and a command ref for leaves.
160
+ const seenSubmenus = new Set<string>();
161
+ type TopEntry = { submenu?: string; command?: string };
162
+ const topEntries: TopEntry[] = [];
163
+ for (const c of cliCommands) {
164
+ if (c.submenu) {
165
+ if (!seenSubmenus.has(c.submenu)) {
166
+ seenSubmenus.add(c.submenu);
167
+ topEntries.push({ submenu: c.submenu });
168
+ }
169
+ } else {
170
+ topEntries.push({ command: c.command });
171
+ }
172
+ }
173
+
174
+ if (topEntries.length > 0 && (explorerWhen || editorWhen)) {
175
+ const explorerMenu = topEntries.map((e, i) => ({
176
+ ...(e.submenu ? { submenu: e.submenu } : { command: e.command }),
177
+ when: explorerWhen,
178
+ group: `specverse@${i + 1}`,
179
+ }));
180
+ const editorMenu = topEntries.map((e, i) => ({
181
+ ...(e.submenu ? { submenu: e.submenu } : { command: e.command }),
182
+ when: editorWhen,
183
+ group: `specverse@${i + 1}`,
184
+ }));
185
+ const menus: Record<string, any[]> = {
186
+ 'explorer/context': explorerMenu,
187
+ 'editor/context': editorMenu,
188
+ };
189
+ // Children under each submenu id.
190
+ for (const sub of submenuDefs) {
191
+ menus[sub.id] = cliCommands
192
+ .filter(c => c.submenu === sub.id)
193
+ .map(c => ({ command: c.command }));
194
+ }
195
+ packageJson.contributes.menus = menus;
196
+ if (submenuDefs.length > 0) {
197
+ packageJson.contributes.submenus = submenuDefs.map(s => ({
198
+ id: s.id,
199
+ label: s.label,
200
+ }));
201
+ }
202
+ }
76
203
  }
77
204
 
78
205
  // Themes from distribution
@@ -95,6 +222,38 @@ export default function generateVSCodeExtension(context: TemplateContext): strin
95
222
  copyRecursive(staticDir, extensionDir);
96
223
  }
97
224
 
225
+ // 4a. Drop in the live composed SpecVerse JSON schema as ./schemas/specverse-schema.json
226
+ // (overrides any static copy so the extension's validation stays in sync
227
+ // with the real language schema rather than a frozen hand-maintained copy).
228
+ const schemaSrc = resolveComposedSchemaPath();
229
+ if (schemaSrc) {
230
+ const schemasDir = join(extensionDir, 'schemas');
231
+ if (!existsSync(schemasDir)) mkdirSync(schemasDir, { recursive: true });
232
+ copyFileSync(schemaSrc, join(schemasDir, 'specverse-schema.json'));
233
+ }
234
+
235
+ // 4b. Rename the copied grammar file to match the distribution's language id.
236
+ // Static asset is named generically (e.g. `specverse.tmLanguage.json`); the
237
+ // extension's grammar contribution points to `./syntaxes/<lang.id>.tmLanguage.json`,
238
+ // so the file on disk must match the id declared in the distribution.
239
+ if (distribution?.languages && distribution.languages.length > 0) {
240
+ const syntaxesDir = join(extensionDir, 'syntaxes');
241
+ if (existsSync(syntaxesDir)) {
242
+ const langId = distribution.languages[0].id;
243
+ const targetName = `${langId}.tmLanguage.json`;
244
+ const targetPath = join(syntaxesDir, targetName);
245
+ if (!existsSync(targetPath)) {
246
+ const existing = readdirSync(syntaxesDir).find(
247
+ f => f.endsWith('.tmLanguage.json') && f !== targetName,
248
+ );
249
+ if (existing) {
250
+ copyFileSync(join(syntaxesDir, existing), targetPath);
251
+ unlinkSync(join(syntaxesDir, existing));
252
+ }
253
+ }
254
+ }
255
+ }
256
+
98
257
  // 5. Create src directory and copy extension.ts
99
258
  const srcDir = join(extensionDir, 'src');
100
259
  if (!existsSync(srcDir)) mkdirSync(srcDir, { recursive: true });
@@ -152,6 +311,83 @@ function extractDistribution(spec: any): any | null {
152
311
  return allDistributions.length > 0 ? allDistributions[0] : null;
153
312
  }
154
313
 
314
+ /**
315
+ * Build a map of spec-defined CLI commands keyed by their dotted path.
316
+ *
317
+ * Handles the spec shape:
318
+ * components.CLI.commands.specverse.subcommands.validate.description
319
+ *
320
+ * Keys produced: "CLI.validate", "CLI.validate" (from rootCommand),
321
+ * "specverse.validate", "validate".
322
+ *
323
+ * This lets distribution `from:` refs resolve regardless of which key
324
+ * flavour the author uses.
325
+ */
326
+ /**
327
+ * Look up a spec command by dotted ref (e.g. "CLI.realize" or "realize").
328
+ * Returns the raw spec node — `{ description, subcommands?, ... }` — or null.
329
+ * Used to detect whether a distribution command has subcommands to expand.
330
+ */
331
+ function findSpecCommand(spec: any, ref: string): any | null {
332
+ if (!ref) return null;
333
+ const parts = ref.split('.');
334
+ const target = parts[parts.length - 1];
335
+ const components = spec?.components || {};
336
+ const componentList = Array.isArray(components)
337
+ ? components
338
+ : Object.entries(components).map(([name, data]) => ({ name, ...(data as any) }));
339
+ for (const comp of componentList) {
340
+ const cliCommands = (comp as any)?.commands;
341
+ if (!cliCommands) continue;
342
+ for (const [, rootDef] of Object.entries(cliCommands as Record<string, any>)) {
343
+ const subcommands = (rootDef as any)?.subcommands || {};
344
+ if (subcommands[target]) return subcommands[target];
345
+ }
346
+ }
347
+ return null;
348
+ }
349
+
350
+ function buildCLICommandMap(spec: any): Map<string, { description?: string; title?: string }> {
351
+ const map = new Map<string, { description?: string; title?: string }>();
352
+ const components = spec?.components || {};
353
+ const componentList = Array.isArray(components)
354
+ ? components
355
+ : Object.entries(components).map(([name, data]) => ({ name, ...(data as any) }));
356
+
357
+ for (const comp of componentList) {
358
+ const compName = (comp as any).name || '';
359
+ const cliCommands = (comp as any)?.commands;
360
+ if (!cliCommands) continue;
361
+
362
+ for (const [rootName, rootDef] of Object.entries(cliCommands as Record<string, any>)) {
363
+ const subcommands = (rootDef as any)?.subcommands || {};
364
+ for (const [subName, subDef] of Object.entries(subcommands as Record<string, any>)) {
365
+ const sub = subDef as any;
366
+ const entry = { description: sub.description, title: sub.title };
367
+ // Register under a few keys so `from: CLI.validate` or `from: specverse.validate` both work
368
+ if (compName) map.set(`${compName}.${subName}`, entry);
369
+ map.set(`${rootName}.${subName}`, entry);
370
+ if (!map.has(subName)) map.set(subName, entry);
371
+
372
+ const nestedSubs = sub?.subcommands;
373
+ if (nestedSubs) {
374
+ for (const [nestedName, nestedDef] of Object.entries(nestedSubs as Record<string, any>)) {
375
+ const nestedEntry = {
376
+ description: (nestedDef as any).description,
377
+ title: (nestedDef as any).title,
378
+ };
379
+ const qualified = `${subName}.${nestedName}`;
380
+ if (compName) map.set(`${compName}.${qualified}`, nestedEntry);
381
+ map.set(`${rootName}.${qualified}`, nestedEntry);
382
+ if (!map.has(qualified)) map.set(qualified, nestedEntry);
383
+ }
384
+ }
385
+ }
386
+ }
387
+ }
388
+ return map;
389
+ }
390
+
155
391
  function extractCLICommands(spec: any): Array<{ command: string; title: string; category: string }> {
156
392
  const commands: Array<{ command: string; title: string; category: string }> = [];
157
393
 
@@ -221,7 +457,7 @@ function generatePackageJson(
221
457
  publisher: 'specverse',
222
458
  engines: { vscode: '^1.80.0' },
223
459
  categories: ['Programming Languages', 'Linters', 'Snippets'],
224
- activationEvents: ['onLanguage:specverse'],
460
+ activationEvents: [],
225
461
  main: './dist/extension.js',
226
462
  contributes: {
227
463
  languages: [{
@@ -237,7 +473,7 @@ function generatePackageJson(
237
473
  }],
238
474
  jsonValidation: [{
239
475
  fileMatch: ['*.specly', '*.specverse'],
240
- url: './schemas/specverse-v3-schema.json',
476
+ url: './schemas/specverse-schema.json',
241
477
  }],
242
478
  themes: [
243
479
  { label: 'SpecVerse Dark', uiTheme: 'vs-dark', path: './themes/specverse-complete-theme.json' },
@@ -9,7 +9,7 @@ metadata:
9
9
  tags: [vscode, ide, extension, language-support]
10
10
 
11
11
  compatibility:
12
- specverse: "^4.0.0"
12
+ specverse: ">=4.0.0"
13
13
  node: ">=18.0.0"
14
14
 
15
15
  capabilities:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@specverse/engines",
3
- "version": "4.3.5",
3
+ "version": "5.0.1",
4
4
  "description": "SpecVerse toolchain — parser, inference, realize, generators, AI, registry",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -42,12 +42,13 @@
42
42
  "clean": "rm -rf dist"
43
43
  },
44
44
  "dependencies": {
45
- "@specverse/types": "^4.1.0",
46
- "@specverse/entities": "^4.1.0",
47
- "@specverse/runtime": "^4.1.23",
45
+ "@specverse/entities": "^5.0.0",
46
+ "@specverse/runtime": "^5.0.0",
47
+ "@specverse/types": "^5.0.0",
48
48
  "ajv": "^8.17.0",
49
49
  "ajv-formats": "^2.1.0",
50
50
  "glob": "^10.0.0",
51
+ "handlebars": "^4.7.9",
51
52
  "js-yaml": "^4.1.0",
52
53
  "semver": "^7.0.0",
53
54
  "yaml": "^2.8.1"