@specverse/engines 5.1.0 → 5.2.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/prompts/core/standard/default/analyse.prompt.yaml +5 -5
  2. package/assets/prompts/core/standard/default/app-demo.prompt.yaml +21 -1
  3. package/assets/prompts/core/standard/default/behavior.prompt.yaml +150 -0
  4. package/assets/prompts/core/standard/default/create.prompt.yaml +3 -3
  5. package/assets/prompts/core/standard/default/materialise.prompt.yaml +804 -774
  6. package/assets/prompts/core/standard/default/realize.prompt.yaml +581 -544
  7. package/assets/prompts/core/standard/v9/analyse.prompt.yaml +5 -5
  8. package/assets/prompts/core/standard/v9/app-demo.prompt.yaml +233 -0
  9. package/assets/prompts/core/standard/v9/behavior.prompt.yaml +33 -9
  10. package/assets/prompts/core/standard/v9/create.prompt.yaml +3 -3
  11. package/assets/prompts/core/standard/v9/materialise.prompt.yaml +804 -774
  12. package/assets/prompts/core/standard/v9/realize.prompt.yaml +581 -544
  13. package/dist/libs/instance-factories/cli/templates/commander/command-generator.js +184 -0
  14. package/dist/libs/instance-factories/tools/templates/mcp/mcp-server-generator.js +289 -15
  15. package/libs/instance-factories/cli/templates/commander/command-generator.ts +184 -0
  16. package/libs/instance-factories/tools/templates/mcp/mcp-server-generator.ts +322 -16
  17. package/package.json +1 -1
  18. package/assets/prompts/core/CHANGELOG.md +0 -158
  19. package/assets/prompts/core/MIGRATION-v6-to-v7.md +0 -379
  20. package/assets/prompts/core/base-terminal-prompt.md +0 -201
  21. package/assets/prompts/core/examples/example-usage.ts +0 -140
  22. package/assets/prompts/core/schemas/prompt.schema.json +0 -309
  23. package/assets/prompts/core/schemas/prompt.schema.yaml +0 -229
  24. package/assets/prompts/core/standard/archive/v1/analyse.prompt.yaml +0 -259
  25. package/assets/prompts/core/standard/archive/v1/create.prompt.yaml +0 -302
  26. package/assets/prompts/core/standard/archive/v1/materialise.prompt.yaml +0 -328
  27. package/assets/prompts/core/standard/archive/v1/realize.prompt.yaml +0 -606
  28. package/assets/prompts/core/standard/archive/v2/README.md +0 -110
  29. package/assets/prompts/core/standard/archive/v2/analyse.prompt.yaml +0 -151
  30. package/assets/prompts/core/standard/archive/v2/create.prompt.yaml +0 -151
  31. package/assets/prompts/core/standard/archive/v2/materialise.prompt.yaml +0 -132
  32. package/assets/prompts/core/standard/archive/v2/realize.prompt.yaml +0 -147
  33. package/assets/prompts/core/standard/archive/v3/README.md +0 -279
  34. package/assets/prompts/core/standard/archive/v3/analyse.prompt.yaml +0 -309
  35. package/assets/prompts/core/standard/archive/v3/create.prompt.yaml +0 -351
  36. package/assets/prompts/core/standard/archive/v3/materialise.prompt.yaml +0 -247
  37. package/assets/prompts/core/standard/archive/v3/realize.prompt.yaml +0 -344
  38. package/assets/prompts/core/standard/archive/v4/README.md +0 -79
  39. package/assets/prompts/core/standard/archive/v4/analyse.prompt.yaml +0 -204
  40. package/assets/prompts/core/standard/archive/v4/create.prompt.yaml +0 -185
  41. package/assets/prompts/core/standard/archive/v5/README.md +0 -224
  42. package/assets/prompts/core/standard/archive/v5/analyse.prompt.yaml +0 -209
  43. package/assets/prompts/core/standard/archive/v5/create.prompt.yaml +0 -225
  44. package/assets/prompts/core/standard/archive/v5/materialise.prompt.yaml +0 -242
  45. package/assets/prompts/core/standard/archive/v5/realize.prompt.yaml +0 -336
  46. package/assets/prompts/core/standard/archive/v6/README.md +0 -187
  47. package/assets/prompts/core/standard/archive/v6/analyse.prompt.yaml +0 -219
  48. package/assets/prompts/core/standard/archive/v6/create.prompt.yaml +0 -180
  49. package/assets/prompts/core/standard/archive/v6/materialise.prompt.yaml +0 -203
  50. package/assets/prompts/core/standard/archive/v6/realize.prompt.yaml +0 -215
  51. package/assets/prompts/core/standard/archive/v7/analyse.prompt.nick.yaml +0 -144
  52. package/assets/prompts/core/standard/archive/v7/analyse.prompt.old.yaml +0 -146
  53. package/assets/prompts/core/standard/archive/v7/analyse.prompt.yaml +0 -129
  54. package/assets/prompts/core/standard/archive/v7/create.prompt.yaml +0 -146
  55. package/assets/prompts/core/standard/archive/v7/materialise.prompt.yaml +0 -297
  56. package/assets/prompts/core/standard/archive/v7/realize.prompt.yaml +0 -294
  57. package/assets/prompts/core/standard/archive/v8/README.md +0 -400
  58. package/assets/prompts/core/standard/archive/v8/analyse.prompt.yaml +0 -185
  59. package/assets/prompts/core/standard/archive/v8/create.prompt.yaml +0 -203
  60. package/assets/prompts/core/standard/archive/v8/materialise.prompt.yaml +0 -297
  61. package/assets/prompts/core/standard/archive/v8/realize.prompt.yaml +0 -294
  62. package/assets/prompts/templates/api-orchestrator-template.yaml +0 -188
  63. package/assets/prompts/templates/claude-integration-template.md +0 -121
  64. package/assets/prompts/templates/terminal-prompt-template.md +0 -97
@@ -1170,6 +1170,190 @@ import type { ParserEngine } from '@specverse/types';`,
1170
1170
  await manager.processJob(jobId);
1171
1171
  console.log('Job processed successfully');`
1172
1172
  },
1173
+ // === skill subcommands ===
1174
+ 'skill.install': {
1175
+ imports: `import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync } from 'fs';
1176
+ import { resolve, dirname, join } from 'path';
1177
+ import { homedir } from 'os';
1178
+ import { fileURLToPath } from 'url';
1179
+ import { createRequire } from 'module';`,
1180
+ handler: `const requireResolve = createRequire(import.meta.url);
1181
+
1182
+ // 1. Resolve target directory.
1183
+ let targetDir: string;
1184
+ if (options.target) {
1185
+ targetDir = resolve(options.target);
1186
+ } else if (options.global) {
1187
+ targetDir = join(homedir(), '.claude', 'skills', 'specverse');
1188
+ } else {
1189
+ // Default: project-local (current working directory).
1190
+ targetDir = join(process.env.SPECVERSE_USER_CWD || process.cwd(), '.claude', 'skills', 'specverse');
1191
+ }
1192
+ mkdirSync(targetDir, { recursive: true });
1193
+ mkdirSync(join(targetDir, 'reference'), { recursive: true });
1194
+ mkdirSync(join(targetDir, 'workflows'), { recursive: true });
1195
+
1196
+ // 2. Helpers to resolve into installed packages.
1197
+ // For @specverse/self specifically, we fall back to walking up from
1198
+ // import.meta.url — handy when running the in-repo bootstrap CLI where
1199
+ // self can't resolve its own package.json via require.
1200
+ const selfRoot = (() => {
1201
+ try {
1202
+ const pkgJson = requireResolve.resolve('@specverse/self/package.json');
1203
+ return dirname(pkgJson);
1204
+ } catch {
1205
+ let dir = dirname(fileURLToPath(import.meta.url));
1206
+ for (let i = 0; i < 8; i++) {
1207
+ const candidate = join(dir, 'package.json');
1208
+ if (existsSync(candidate)) {
1209
+ try {
1210
+ const pkg = JSON.parse(readFileSync(candidate, 'utf8'));
1211
+ if (pkg?.name === '@specverse/self') return dir;
1212
+ } catch { /* continue */ }
1213
+ }
1214
+ const parent = dirname(dir);
1215
+ if (parent === dir) break;
1216
+ dir = parent;
1217
+ }
1218
+ return null;
1219
+ }
1220
+ })();
1221
+
1222
+ const resolvePkg = (pkg: string, rel: string): string | null => {
1223
+ if (pkg === '@specverse/self') {
1224
+ return selfRoot ? join(selfRoot, rel) : null;
1225
+ }
1226
+ try {
1227
+ const pkgJson = requireResolve.resolve(pkg + '/package.json');
1228
+ return join(dirname(pkgJson), rel);
1229
+ } catch { return null; }
1230
+ };
1231
+
1232
+ // 3. Copy SKILL.md from @specverse/self.
1233
+ const skillMdSrc = resolvePkg('@specverse/self', 'skills/specverse/SKILL.md');
1234
+ if (!skillMdSrc || !existsSync(skillMdSrc)) {
1235
+ console.error('Cannot locate SKILL.md in @specverse/self — install failed.');
1236
+ process.exit(1);
1237
+ }
1238
+ writeFileSync(join(targetDir, 'SKILL.md'), readFileSync(skillMdSrc, 'utf8'));
1239
+
1240
+ // 4. Copy reference files (schema + ai-guidance + minimal-example + guide).
1241
+ const refCopies: [string, string, string][] = [
1242
+ ['@specverse/entities', 'schema/SPECVERSE-SCHEMA.json', 'reference/schema.json'],
1243
+ ['@specverse/entities', 'schema/SPECVERSE-SCHEMA-AI.yaml', 'reference/ai-guidance.yaml'],
1244
+ ['@specverse/entities', 'schema/MINIMAL-SYNTAX-REFERENCE.specly', 'reference/minimal-example.specly'],
1245
+ ['@specverse/self', 'docs/guides/SPECVERSE-COMPLETE-GUIDE.md', 'reference/guide.md'],
1246
+ ];
1247
+ let refCopied = 0;
1248
+ for (const [pkg, rel, dest] of refCopies) {
1249
+ const src = resolvePkg(pkg, rel);
1250
+ if (src && existsSync(src)) {
1251
+ writeFileSync(join(targetDir, dest), readFileSync(src, 'utf8'));
1252
+ refCopied++;
1253
+ } else {
1254
+ console.warn(' (skipped ' + dest + ' — ' + pkg + '/' + rel + ' not found)');
1255
+ }
1256
+ }
1257
+
1258
+ // 5. Build cli-reference.md from the running CLI's own \`--help\`.
1259
+ // Minimal content — a pointer to the spv help and a static summary.
1260
+ const cliRef = [
1261
+ '# SpecVerse CLI Reference',
1262
+ '',
1263
+ 'Run \`spv <command> --help\` for flag details on any command. Common subcommands:',
1264
+ '',
1265
+ '| Command | Purpose |',
1266
+ '|---|---|',
1267
+ '| \`spv init <name>\` | Scaffold a new project (templates: default / full-stack / backend-only / frontend-only) |',
1268
+ '| \`spv validate <spec>\` | Parse + schema-validate a .specly file |',
1269
+ '| \`spv validate-bundle <dir>\` | Validate an entity bundle (for engine extenders) |',
1270
+ '| \`spv infer <spec>\` | Expand a minimal spec to full architecture (controllers / services / events / views) |',
1271
+ '| \`spv realize all <spec>\` | Generate production code from inferred spec + manifest |',
1272
+ '| \`spv gen diagrams <spec>\` | Emit mermaid diagrams (ER, lifecycle, architecture) |',
1273
+ '| \`spv ai template <op> <spec>\` | Dump a canonical workflow prompt filled in for a spec |',
1274
+ '| \`spv smoke\` | Verify the \`spv\` install end-to-end |',
1275
+ '| \`spv skill install [--global\\\\|--project]\` | Reinstall / refresh this skill |',
1276
+ '',
1277
+ 'For CI invocations, see the \`SPECVERSE-TOOLING.md\` guide in \`@specverse/self/docs/guides/\`.',
1278
+ '',
1279
+ ].join('\\n');
1280
+ writeFileSync(join(targetDir, 'reference', 'cli-reference.md'), cliRef);
1281
+
1282
+ // 6. Emit one workflow markdown per prompt YAML in @specverse/engines/assets/prompts/core/standard/default.
1283
+ const yamlLib = await import('js-yaml').catch(() => null) as any;
1284
+ const workflowsDir = resolvePkg('@specverse/engines', 'assets/prompts/core/standard/default');
1285
+ let workflowCount = 0;
1286
+ if (yamlLib && workflowsDir && existsSync(workflowsDir)) {
1287
+ const entries = readdirSync(workflowsDir).filter((f: string) => f.endsWith('.prompt.yaml'));
1288
+ for (const entry of entries) {
1289
+ try {
1290
+ const parsed = yamlLib.load(readFileSync(join(workflowsDir, entry), 'utf8')) as any;
1291
+ if (!parsed?.name) continue;
1292
+ const md = buildWorkflowMarkdown(parsed);
1293
+ writeFileSync(join(targetDir, 'workflows', parsed.name + '.md'), md);
1294
+ workflowCount++;
1295
+ } catch (err: any) {
1296
+ console.warn(' (skipped workflow ' + entry + ' — ' + err.message + ')');
1297
+ }
1298
+ }
1299
+ } else {
1300
+ console.warn(' (no workflows emitted — js-yaml or @specverse/engines prompts unavailable)');
1301
+ }
1302
+
1303
+ // 7. Report.
1304
+ console.log('Installed SpecVerse skill → ' + targetDir);
1305
+ console.log(' SKILL.md + ' + refCopied + ' reference file(s) + ' + workflowCount + ' workflow(s)');
1306
+ if (!options.global && !options.target) {
1307
+ console.log('');
1308
+ console.log('Tip: use --global to install to ~/.claude/skills/ for cross-project use.');
1309
+ }
1310
+
1311
+ function buildWorkflowMarkdown(parsed: any): string {
1312
+ const parts: string[] = [];
1313
+ parts.push('# SpecVerse workflow: ' + parsed.name);
1314
+ parts.push('');
1315
+ if (parsed.description) {
1316
+ parts.push('> ' + parsed.description);
1317
+ parts.push('');
1318
+ }
1319
+ const vars = Array.isArray(parsed?.user?.variables) ? parsed.user.variables : [];
1320
+ if (vars.length > 0) {
1321
+ parts.push('## Inputs');
1322
+ parts.push('');
1323
+ parts.push('| Variable | Required | Description |');
1324
+ parts.push('|---|---|---|');
1325
+ for (const v of vars) {
1326
+ const req = v.required === false ? 'no' : 'yes';
1327
+ const desc = (v.description || '').replace(/\\|/g, '\\\\|');
1328
+ parts.push('| \`' + v.name + '\` | ' + req + ' | ' + desc + ' |');
1329
+ }
1330
+ parts.push('');
1331
+ }
1332
+ if (parsed?.system?.role) {
1333
+ parts.push('## Role');
1334
+ parts.push('');
1335
+ parts.push(String(parsed.system.role).trim());
1336
+ parts.push('');
1337
+ }
1338
+ if (parsed?.system?.context) {
1339
+ parts.push('## Instructions');
1340
+ parts.push('');
1341
+ parts.push(String(parsed.system.context).trim());
1342
+ parts.push('');
1343
+ }
1344
+ if (parsed?.user?.template) {
1345
+ parts.push('## Task template');
1346
+ parts.push('');
1347
+ parts.push('When invoking this workflow, fill the variables above into the template below:');
1348
+ parts.push('');
1349
+ parts.push('\`\`\`');
1350
+ parts.push(String(parsed.user.template).trim());
1351
+ parts.push('\`\`\`');
1352
+ parts.push('');
1353
+ }
1354
+ return parts.join('\\n');
1355
+ }`
1356
+ },
1173
1357
  };
1174
1358
 
1175
1359
  function generateLeafCommand(
@@ -12,8 +12,11 @@
12
12
  */
13
13
 
14
14
  import type { TemplateContext } from '@specverse/types';
15
- import { existsSync, mkdirSync, writeFileSync } from 'fs';
16
- import { join } from 'path';
15
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
16
+ import { createRequire } from 'module';
17
+ import { dirname, join } from 'path';
18
+
19
+ const require = createRequire(import.meta.url);
17
20
 
18
21
  export default function generateMCPServer(context: TemplateContext): string {
19
22
  const { spec, outputDir } = context;
@@ -23,7 +26,14 @@ export default function generateMCPServer(context: TemplateContext): string {
23
26
  if (!existsSync(srcDir)) mkdirSync(srcDir, { recursive: true });
24
27
 
25
28
  const distribution = extractMCPDistribution(spec);
26
- const version = distribution?.version || spec?.metadata?.version || spec?.version || '5.0.0';
29
+ // Prefer @specverse/self's real version over the spec's stale component
30
+ // version label. Falls back to the spec + default.
31
+ const version =
32
+ distribution?.version ||
33
+ resolveSelfVersion() ||
34
+ spec?.metadata?.version ||
35
+ spec?.version ||
36
+ '5.1.0';
27
37
  const description = distribution?.description
28
38
  || 'SpecVerse MCP server — exposes the specverse CLI as MCP tools and the live spec schema + docs as MCP resources.';
29
39
  const displayName = distribution?.displayName || 'SpecVerse MCP';
@@ -34,10 +44,26 @@ export default function generateMCPServer(context: TemplateContext): string {
34
44
  writeFileSync(join(mcpDir, 'tsconfig.json'), generateTsconfig());
35
45
  writeFileSync(join(srcDir, 'server.ts'), generateServer(displayName, version));
36
46
  writeFileSync(join(srcDir, 'cli-runner.ts'), generateCliRunner());
37
- writeFileSync(join(srcDir, 'resources.ts'), generateResources());
47
+ writeFileSync(join(srcDir, 'resources.ts'), generateResources(cliCommands));
38
48
  writeFileSync(join(srcDir, 'tools.ts'), generateTools(cliCommands));
49
+ writeFileSync(join(srcDir, 'prompts.ts'), generatePrompts());
50
+
51
+ return `MCP server generated in: ${mcpDir}\n ${cliCommands.length} tools, 5 resources, 6 prompts (derived from @specverse/engines/assets/prompts)`;
52
+ }
39
53
 
40
- return `MCP server generated in: ${mcpDir}\n ${cliCommands.length} tools (one per CLI command), 2 resources (schema + docs)`;
54
+ /**
55
+ * Read the running @specverse/self install's version from its package.json so
56
+ * the MCP server reports a real version instead of a stale component label.
57
+ * Returns null if @specverse/self isn't resolvable (e.g. during in-repo tests).
58
+ */
59
+ function resolveSelfVersion(): string | null {
60
+ try {
61
+ const pkgPath = require.resolve('@specverse/self/package.json');
62
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
63
+ return pkg.version || null;
64
+ } catch {
65
+ return null;
66
+ }
41
67
  }
42
68
 
43
69
  // ─── spec extraction ───────────────────────────────────────────────────────
@@ -182,10 +208,13 @@ function generatePackageJson(version: string, description: string): string {
182
208
  },
183
209
  dependencies: {
184
210
  '@modelcontextprotocol/sdk': '^1.17.4',
185
- '@specverse/entities': '^5.0.0',
186
- '@specverse/self': '^5.0.0',
211
+ '@specverse/engines': '^5.2.0',
212
+ '@specverse/entities': '^5.1.0',
213
+ '@specverse/self': '^5.2.0',
214
+ 'js-yaml': '^4.1.0',
187
215
  },
188
216
  devDependencies: {
217
+ '@types/js-yaml': '^4.0.9',
189
218
  '@types/node': '^20.19.11',
190
219
  typescript: '^5.9.2',
191
220
  },
@@ -222,13 +251,15 @@ function generateTsconfig(): string {
222
251
  }
223
252
 
224
253
  function generateServer(displayName: string, version: string): string {
254
+ const instructions = SERVER_INSTRUCTIONS;
225
255
  return `#!/usr/bin/env node
226
256
  /**
227
257
  * SpecVerse MCP Server — stdio transport.
228
258
  *
229
- * Wires the MCP protocol to the generated tool registry + live resources.
230
- * No handwritten business logic everything is derived from the spec
231
- * (tools from CLI commands) or from @specverse/entities (schema + docs).
259
+ * Wires the MCP protocol to the generated tool registry + live resources
260
+ * + canonical workflow prompts. Everything is derived from the spec
261
+ * (tools from CLI commands, resources from @specverse/entities + @specverse/self,
262
+ * prompts from @specverse/engines/assets/prompts/core/standard/default).
232
263
  */
233
264
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
234
265
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
@@ -237,13 +268,21 @@ import {
237
268
  CallToolRequestSchema,
238
269
  ListResourcesRequestSchema,
239
270
  ReadResourceRequestSchema,
271
+ ListPromptsRequestSchema,
272
+ GetPromptRequestSchema,
240
273
  } from '@modelcontextprotocol/sdk/types.js';
241
274
  import { TOOLS, callTool } from './tools.js';
242
275
  import { RESOURCES, readResource } from './resources.js';
276
+ import { PROMPTS, getPrompt } from './prompts.js';
277
+
278
+ const INSTRUCTIONS = ${JSON.stringify(instructions)};
243
279
 
244
280
  const server = new Server(
245
281
  { name: ${JSON.stringify(displayName)}, version: ${JSON.stringify(version)} },
246
- { capabilities: { tools: {}, resources: {} } },
282
+ {
283
+ capabilities: { tools: {}, resources: {}, prompts: {} },
284
+ instructions: INSTRUCTIONS,
285
+ },
247
286
  );
248
287
 
249
288
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
@@ -272,11 +311,52 @@ server.setRequestHandler(ReadResourceRequestSchema, async (req: { params: { uri:
272
311
  return readResource(req.params.uri);
273
312
  });
274
313
 
314
+ server.setRequestHandler(ListPromptsRequestSchema, async () => ({
315
+ prompts: PROMPTS.map(p => ({
316
+ name: p.name,
317
+ description: p.description,
318
+ arguments: p.arguments,
319
+ })),
320
+ }));
321
+
322
+ server.setRequestHandler(GetPromptRequestSchema, async (req: { params: { name: string; arguments?: Record<string, string> } }) => {
323
+ const { name, arguments: args } = req.params;
324
+ return getPrompt(name, args ?? {});
325
+ });
326
+
275
327
  const transport = new StdioServerTransport();
276
328
  await server.connect(transport);
277
329
  `;
278
330
  }
279
331
 
332
+ const SERVER_INSTRUCTIONS = [
333
+ 'SpecVerse is a declarative specification language. You describe WHAT a system does in a .specly file, and the engines generate HOW — backend (Fastify/Prisma), frontend (React/Tailwind), CLI, tools, diagrams.',
334
+ '',
335
+ 'Before using tools, read these resources to ground yourself in the language:',
336
+ '- specverse://guide — canonical user guide (spec language, CLI reference, convention patterns)',
337
+ '- specverse://ai-guidance — curated hints for generating valid specs',
338
+ '- specverse://minimal-example — one minimal .specly showing every feature in ~100 lines',
339
+ '- specverse://schema — the JSON Schema (draft 2020-12) for .specly validation',
340
+ '- specverse://cli-reference — every spv CLI subcommand with flags + arguments',
341
+ '',
342
+ 'Common high-leverage workflows are exposed as MCP prompts (preferred entry point):',
343
+ '- create: natural-language requirements → minimal .specly',
344
+ '- analyse: existing codebase → .specly that captures what is implemented',
345
+ '- materialise: .specly → production code',
346
+ '- realize: generate deployment configs',
347
+ '- behavior: generate AI behavior function stubs',
348
+ '- app-demo: interactive spec creation/modification for the runtime interpreter',
349
+ '',
350
+ 'Tools map 1:1 to the spv CLI. Key distinctions:',
351
+ '- validate: check a .specly file parses + matches the schema',
352
+ '- validate-manifest: check an implementation manifest resolves cleanly',
353
+ '- validate-bundle: check an entity bundle (schema + examples + tests + docs + behaviour facets)',
354
+ '- infer: expand a minimal .specly to full architecture (controllers / services / events / views)',
355
+ '- realize: turn inferred spec + manifest into generated code (Fastify / Prisma / React / CLI / tools)',
356
+ '- init: scaffold a new project from a template',
357
+ ].join('\n');
358
+
359
+
280
360
  function generateCliRunner(): string {
281
361
  return `/**
282
362
  * Thin specverse CLI runner.
@@ -378,12 +458,16 @@ export async function callTool(name: string, args: Record<string, any>) {
378
458
  `;
379
459
  }
380
460
 
381
- function generateResources(): string {
461
+ function generateResources(tools: CLITool[]): string {
462
+ const cliReference = buildCliReferenceMarkdown(tools);
382
463
  return `/**
383
- * Resource registry — exposes the live SpecVerse schema + user guide as
384
- * MCP resources. Schema is read from @specverse/entities (composed fragment
385
- * aggregate); guide is read from @specverse/self (canonical home for all
386
- * user-facing guides).
464
+ * Resource registry — exposes the canonical SpecVerse reference material as
465
+ * MCP resources:
466
+ * - schema (JSON Schema draft 2020-12, from @specverse/entities)
467
+ * - ai-guidance (curated LLM hints, from @specverse/entities)
468
+ * - minimal-example (one .specly covering all features, from @specverse/entities)
469
+ * - guide (user guide, from @specverse/self)
470
+ * - cli-reference (derived from the spec at realize time, embedded below)
387
471
  */
388
472
  import { readFileSync } from 'fs';
389
473
  import { createRequire } from 'module';
@@ -409,6 +493,8 @@ function resolveSelfFile(relative: string): string {
409
493
  return join(dirname(pkg), relative);
410
494
  }
411
495
 
496
+ const CLI_REFERENCE_MARKDOWN = ${JSON.stringify(cliReference)};
497
+
412
498
  export const RESOURCES: Resource[] = [
413
499
  {
414
500
  uri: 'specverse://schema',
@@ -420,6 +506,26 @@ export const RESOURCES: Resource[] = [
420
506
  mimeType: 'application/json',
421
507
  }),
422
508
  },
509
+ {
510
+ uri: 'specverse://ai-guidance',
511
+ name: 'SpecVerse AI Guidance Schema',
512
+ description: 'YAML schema annotated with examples and LLM guidance for generating valid specs. Read this before authoring or mutating .specly files.',
513
+ mimeType: 'application/x-yaml',
514
+ resolve: () => ({
515
+ text: readFileSync(resolveEntitiesFile('schema/SPECVERSE-SCHEMA-AI.yaml'), 'utf8'),
516
+ mimeType: 'application/x-yaml',
517
+ }),
518
+ },
519
+ {
520
+ uri: 'specverse://minimal-example',
521
+ name: 'SpecVerse Minimal Example',
522
+ description: 'One complete minimal .specly demonstrating every feature (~100 lines) — component, models, relationships, lifecycle, CURVED controller, service, view, event, manifest, deployment.',
523
+ mimeType: 'text/x-yaml',
524
+ resolve: () => ({
525
+ text: readFileSync(resolveEntitiesFile('schema/MINIMAL-SYNTAX-REFERENCE.specly'), 'utf8'),
526
+ mimeType: 'text/x-yaml',
527
+ }),
528
+ },
423
529
  {
424
530
  uri: 'specverse://guide',
425
531
  name: 'SpecVerse Complete Guide',
@@ -430,6 +536,16 @@ export const RESOURCES: Resource[] = [
430
536
  mimeType: 'text/markdown',
431
537
  }),
432
538
  },
539
+ {
540
+ uri: 'specverse://cli-reference',
541
+ name: 'SpecVerse CLI Reference',
542
+ description: 'Every spv CLI subcommand with its arguments, flags, and exit codes. Derived from the spec at realize time.',
543
+ mimeType: 'text/markdown',
544
+ resolve: () => ({
545
+ text: CLI_REFERENCE_MARKDOWN,
546
+ mimeType: 'text/markdown',
547
+ }),
548
+ },
433
549
  ];
434
550
 
435
551
  const BY_URI = new Map<string, Resource>(RESOURCES.map(r => [r.uri, r]));
@@ -446,3 +562,193 @@ export async function readResource(uri: string) {
446
562
  }
447
563
  `;
448
564
  }
565
+
566
+ /**
567
+ * Build the cli-reference.md content from the parsed CLI tool list.
568
+ * Embedded as a string constant in the generated resources.ts so the MCP
569
+ * server doesn't need filesystem access for it at runtime.
570
+ */
571
+ function buildCliReferenceMarkdown(tools: CLITool[]): string {
572
+ const lines: string[] = [];
573
+ lines.push('# SpecVerse CLI Reference');
574
+ lines.push('');
575
+ lines.push('Generated from the self-spec at realize time. Every entry here matches an MCP tool exposed by this server (same name, same input schema).');
576
+ lines.push('');
577
+ for (const tool of tools) {
578
+ const cliCmd = tool.cliArgs.join(' ');
579
+ lines.push(`## \`spv ${cliCmd}\``);
580
+ lines.push('');
581
+ lines.push(tool.description);
582
+ lines.push('');
583
+ const props = (tool.inputSchema as any)?.properties || {};
584
+ const required = new Set(((tool.inputSchema as any)?.required || []) as string[]);
585
+ const positional = new Set(tool.positional);
586
+ const argEntries = Object.entries(props) as [string, any][];
587
+ if (argEntries.length > 0) {
588
+ lines.push('| Name | Kind | Required | Type | Description |');
589
+ lines.push('|---|---|---|---|---|');
590
+ for (const [name, schema] of argEntries) {
591
+ const kind = positional.has(name) ? 'positional' : 'flag';
592
+ const req = required.has(name) ? 'yes' : 'no';
593
+ const type = (schema as any)?.type || 'string';
594
+ const desc = ((schema as any)?.description || '').replace(/\|/g, '\\|');
595
+ lines.push(`| \`${name}\` | ${kind} | ${req} | ${type} | ${desc} |`);
596
+ }
597
+ lines.push('');
598
+ }
599
+ }
600
+ return lines.join('\n');
601
+ }
602
+
603
+ /**
604
+ * Emit prompts.ts — loads the canonical workflow prompts from
605
+ * @specverse/engines/assets/prompts/core/standard/default/ at module-init
606
+ * time. Each YAML prompt becomes one MCP prompt; the declared
607
+ * {{template variables}} surface as MCP prompt arguments the client fills.
608
+ * Schema paths ({{aiSchemaPath}}, {{referenceSchemaPath}},
609
+ * {{referenceExamplePath}}) are auto-filled with package-relative paths so
610
+ * the caller doesn't have to know where they live on disk.
611
+ */
612
+ function generatePrompts(): string {
613
+ return `/**
614
+ * Prompt registry — reads the canonical workflow prompts from
615
+ * @specverse/engines/assets/prompts/core/standard/default/ at startup and
616
+ * serves them as MCP prompts. Each YAML file (create, analyse, materialise,
617
+ * realize, behavior, app-demo) becomes one invocable prompt.
618
+ */
619
+ import { readdirSync, readFileSync } from 'fs';
620
+ import { createRequire } from 'module';
621
+ import { dirname, join, basename } from 'path';
622
+ import { load as yamlLoad } from 'js-yaml';
623
+
624
+ const require = createRequire(import.meta.url);
625
+
626
+ export interface PromptArgument {
627
+ name: string;
628
+ description?: string;
629
+ required: boolean;
630
+ }
631
+
632
+ export interface Prompt {
633
+ name: string;
634
+ description: string;
635
+ arguments: PromptArgument[];
636
+ source: any; // parsed YAML — kept so getPrompt can re-render
637
+ }
638
+
639
+ function resolveEntitiesFile(relative: string): string {
640
+ const pkg = require.resolve('@specverse/entities/package.json');
641
+ return join(dirname(pkg), relative);
642
+ }
643
+
644
+ function resolveEnginesDir(relative: string): string {
645
+ const pkg = require.resolve('@specverse/engines/package.json');
646
+ return join(dirname(pkg), relative);
647
+ }
648
+
649
+ // Auto-filled template vars — paths into @specverse/entities/schema/
650
+ const SCHEMA_VAR_PATHS: Record<string, string> = {
651
+ aiSchemaPath: resolveEntitiesFile('schema/SPECVERSE-SCHEMA-AI.yaml'),
652
+ referenceSchemaPath: resolveEntitiesFile('schema/MINIMAL-SYNTAX-REFERENCE.specly'),
653
+ referenceExamplePath: resolveEntitiesFile('schema/MINIMAL-SYNTAX-REFERENCE.specly'),
654
+ };
655
+
656
+ // User-facing vars the server does NOT auto-fill — surfaced as MCP prompt args.
657
+ const USER_FACING = new Set<string>([
658
+ ...Object.keys(SCHEMA_VAR_PATHS),
659
+ ]);
660
+
661
+ function loadPrompts(): Prompt[] {
662
+ const promptsDir = resolveEnginesDir('assets/prompts/core/standard/default');
663
+ const entries = readdirSync(promptsDir).filter(f => f.endsWith('.prompt.yaml'));
664
+ const prompts: Prompt[] = [];
665
+ for (const entry of entries) {
666
+ const full = join(promptsDir, entry);
667
+ try {
668
+ const parsed = yamlLoad(readFileSync(full, 'utf8')) as any;
669
+ if (!parsed?.name) continue;
670
+ const args = deriveArguments(parsed);
671
+ prompts.push({
672
+ name: parsed.name,
673
+ description: parsed.description || \`SpecVerse \${parsed.name} workflow\`,
674
+ arguments: args,
675
+ source: parsed,
676
+ });
677
+ } catch (err) {
678
+ // One bad prompt shouldn't kill the server; skip it.
679
+ console.error(\`[prompts] skipping \${basename(entry)}: \${(err as Error).message}\`);
680
+ }
681
+ }
682
+ return prompts;
683
+ }
684
+
685
+ /**
686
+ * Every SpecVerse prompt YAML declares user.variables[] in the canonical shape
687
+ * { name, type?, required?, description?, default? }. We surface the declared
688
+ * list verbatim, filtering out auto-filled schema-path vars.
689
+ */
690
+ function deriveArguments(parsed: any): PromptArgument[] {
691
+ const declared = parsed?.user?.variables;
692
+ if (!Array.isArray(declared)) return [];
693
+ return declared
694
+ .filter((v: any) => v && typeof v === 'object' && typeof v.name === 'string')
695
+ .filter((v: any) => !USER_FACING.has(v.name))
696
+ .map((v: any) => ({
697
+ name: v.name,
698
+ description: v.description || inferVarDescription(v.name, parsed),
699
+ required: v.required !== false,
700
+ }));
701
+ }
702
+
703
+ function inferVarDescription(varName: string, parsed: any): string {
704
+ // Friendly defaults for the known common vars; fall back to a humanised
705
+ // version of the variable name.
706
+ const defaults: Record<string, string> = {
707
+ requirements: 'Natural-language requirements to extract a specification from',
708
+ scale: 'Project scale — personal, business, or enterprise',
709
+ preferredTech: 'Preferred technology stack hint (e.g. "nextjs", "nestjs", "fastify+prisma", "auto")',
710
+ implementationPath: 'Path to the existing codebase to reverse-engineer into a .specly',
711
+ specPath: 'Path to an existing .specly file',
712
+ manifestPath: 'Path to an implementation manifest (manifests/implementation.yaml)',
713
+ outputDir: 'Output directory for generated artifacts',
714
+ modelName: 'Name of the model the behavior is attached to',
715
+ behaviorName: 'Name of the behavior method on the model',
716
+ };
717
+ if (defaults[varName]) return defaults[varName];
718
+ return varName.replace(/([A-Z])/g, ' $1').replace(/^./, c => c.toUpperCase());
719
+ }
720
+
721
+ function substitute(text: string, values: Record<string, string>): string {
722
+ return text.replace(/\\{\\{\\s*([a-zA-Z_][a-zA-Z0-9_]*)\\s*\\}\\}/g, (match, name) => {
723
+ if (name in values) return values[name];
724
+ if (name in SCHEMA_VAR_PATHS) return SCHEMA_VAR_PATHS[name];
725
+ return match; // unknown var — leave the placeholder so it's visible
726
+ });
727
+ }
728
+
729
+ function renderMessages(prompt: Prompt, args: Record<string, string>): { role: 'user'; content: { type: 'text'; text: string } }[] {
730
+ const src = prompt.source;
731
+ const sections: string[] = [];
732
+ if (src?.system?.role) sections.push(\`[System role]\\n\${substitute(src.system.role, args).trim()}\`);
733
+ if (src?.system?.context) sections.push(\`[Context]\\n\${substitute(src.system.context, args).trim()}\`);
734
+ if (src?.user?.template) sections.push(substitute(src.user.template, args).trim());
735
+ const text = sections.filter(Boolean).join('\\n\\n');
736
+ return [{ role: 'user', content: { type: 'text', text } }];
737
+ }
738
+
739
+ export const PROMPTS: Prompt[] = loadPrompts();
740
+
741
+ const BY_NAME = new Map<string, Prompt>(PROMPTS.map(p => [p.name, p]));
742
+
743
+ export async function getPrompt(name: string, args: Record<string, string>) {
744
+ const prompt = BY_NAME.get(name);
745
+ if (!prompt) {
746
+ throw new Error(\`Unknown prompt: \${name}\`);
747
+ }
748
+ return {
749
+ description: prompt.description,
750
+ messages: renderMessages(prompt, args),
751
+ };
752
+ }
753
+ `;
754
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@specverse/engines",
3
- "version": "5.1.0",
3
+ "version": "5.2.0",
4
4
  "description": "SpecVerse toolchain \u2014 parser, inference, realize, generators, AI, registry, bundles",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",