@specverse/engines 4.3.4 → 5.0.0

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 (66) 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/libs/instance-factories/cli/templates/commander/command-generator.js +27 -5
  15. package/dist/libs/instance-factories/scaffolding/templates/generic/package-json-generator.js +1 -1
  16. package/dist/libs/instance-factories/tools/README.md +1 -1
  17. package/dist/libs/instance-factories/tools/mcp.yaml +1 -1
  18. package/dist/libs/instance-factories/tools/templates/mcp/mcp-server-generator.js +336 -116
  19. package/dist/libs/instance-factories/tools/templates/vscode/vscode-extension-generator.js +172 -8
  20. package/dist/libs/instance-factories/tools/vscode.yaml +1 -1
  21. package/libs/instance-factories/cli/templates/commander/command-generator.ts +27 -5
  22. package/libs/instance-factories/scaffolding/templates/generic/package-json-generator.ts +10 -6
  23. package/libs/instance-factories/tools/README.md +1 -1
  24. package/libs/instance-factories/tools/mcp.yaml +1 -1
  25. package/libs/instance-factories/tools/templates/mcp/mcp-server-generator.ts +386 -141
  26. package/libs/instance-factories/tools/templates/vscode/static/extension.ts +9 -2
  27. package/libs/instance-factories/tools/templates/vscode/vscode-extension-generator.ts +246 -10
  28. package/libs/instance-factories/tools/vscode.yaml +1 -1
  29. package/package.json +5 -4
  30. package/libs/instance-factories/tools/templates/mcp/static/docs/DEPLOYMENT_GUIDE.md +0 -630
  31. package/libs/instance-factories/tools/templates/mcp/static/docs/HYBRID_RESOURCE_SYSTEM.md +0 -330
  32. package/libs/instance-factories/tools/templates/mcp/static/docs/deployments/EXTENSION_DEPLOYMENT.md +0 -552
  33. package/libs/instance-factories/tools/templates/mcp/static/docs/deployments/LOCAL_DEPLOYMENT.md +0 -164
  34. package/libs/instance-factories/tools/templates/mcp/static/docs/deployments/WEB_DEPLOYMENT.md +0 -247
  35. package/libs/instance-factories/tools/templates/mcp/static/package.json +0 -94
  36. package/libs/instance-factories/tools/templates/mcp/static/scripts/build-enterprise.js +0 -284
  37. package/libs/instance-factories/tools/templates/mcp/static/scripts/build-extension.js +0 -139
  38. package/libs/instance-factories/tools/templates/mcp/static/scripts/build-local.js +0 -74
  39. package/libs/instance-factories/tools/templates/mcp/static/scripts/build-web.js +0 -156
  40. package/libs/instance-factories/tools/templates/mcp/static/scripts/copy-canonical-files.js +0 -41
  41. package/libs/instance-factories/tools/templates/mcp/static/scripts/test-deployments.js +0 -259
  42. package/libs/instance-factories/tools/templates/mcp/static/scripts/test-hybrid-resources.js +0 -231
  43. package/libs/instance-factories/tools/templates/mcp/static/scripts/test-hybrid-simple.js +0 -196
  44. package/libs/instance-factories/tools/templates/mcp/static/src/controllers/MCPServerController.ts +0 -293
  45. package/libs/instance-factories/tools/templates/mcp/static/src/events/EventEmitter.ts +0 -90
  46. package/libs/instance-factories/tools/templates/mcp/static/src/index.ts +0 -24
  47. package/libs/instance-factories/tools/templates/mcp/static/src/interfaces/ResourceProvider.ts +0 -15
  48. package/libs/instance-factories/tools/templates/mcp/static/src/models/LibrarySuggestion.ts +0 -106
  49. package/libs/instance-factories/tools/templates/mcp/static/src/models/SpecVerseResource.ts +0 -75
  50. package/libs/instance-factories/tools/templates/mcp/static/src/server/mcp-server.ts +0 -239
  51. package/libs/instance-factories/tools/templates/mcp/static/src/services/CLIProxyService.ts +0 -1501
  52. package/libs/instance-factories/tools/templates/mcp/static/src/services/EmbeddedResourcesAdapter.ts +0 -211
  53. package/libs/instance-factories/tools/templates/mcp/static/src/services/EntityModuleService.ts +0 -308
  54. package/libs/instance-factories/tools/templates/mcp/static/src/services/HybridResourcesProvider.ts +0 -210
  55. package/libs/instance-factories/tools/templates/mcp/static/src/services/LibraryToolsService.ts +0 -356
  56. package/libs/instance-factories/tools/templates/mcp/static/src/services/OrchestratorBridge.ts +0 -522
  57. package/libs/instance-factories/tools/templates/mcp/static/src/services/OrchestratorToolsService.ts +0 -530
  58. package/libs/instance-factories/tools/templates/mcp/static/src/services/PromptToolsService.ts +0 -594
  59. package/libs/instance-factories/tools/templates/mcp/static/src/services/ResourcesProviderService.ts +0 -170
  60. package/libs/instance-factories/tools/templates/mcp/static/src/tests/unit/CLIProxyService.init.test.ts +0 -544
  61. package/libs/instance-factories/tools/templates/mcp/static/src/tests/unit/CLIProxyService.test.ts +0 -189
  62. package/libs/instance-factories/tools/templates/mcp/static/src/tests/unit/ResourcesProviderService.test.ts +0 -89
  63. package/libs/instance-factories/tools/templates/mcp/static/src/types/index.ts +0 -110
  64. package/libs/instance-factories/tools/templates/mcp/static/tsconfig.json +0 -28
  65. package/libs/instance-factories/tools/templates/vscode/static/schemas/specverse-v3-schema.json +0 -4279
  66. /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.4",
3
+ "version": "5.0.0",
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"