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