@sage-protocol/cli 0.4.1 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -59,6 +59,7 @@ const {
59
59
  getToolMeta,
60
60
  } = require('./services/mcp/sage-tool-registry');
61
61
  const { createToolDispatcher } = require('./services/mcp/tool-dispatcher');
62
+ const ProjectContextService = require('./services/project-context');
62
63
  const { createProposalLister } = require('./services/mcp/proposal-lister');
63
64
  const fs = require('fs');
64
65
  const path = require('path');
@@ -84,6 +85,18 @@ class SageMCPServer {
84
85
  };
85
86
 
86
87
  this.tools = [
88
+ {
89
+ name: 'get_project_context',
90
+ description: `Get context about the current Sage project (root, DAO, counts, etc).
91
+ When to use:
92
+ - At the start of a session to understand where you are
93
+ - Before suggesting tools to check if workspace/wallet is available`,
94
+ inputSchema: {
95
+ type: 'object',
96
+ properties: {},
97
+ required: []
98
+ }
99
+ },
87
100
  {
88
101
  name: 'suggest_sage_tools',
89
102
  description: `Suggest the most relevant Sage MCP tools for a given goal.
@@ -255,7 +268,7 @@ Next steps:
255
268
  properties: {
256
269
  query: { type: 'string', description: 'Free-text search applied to names, descriptions, and tags' },
257
270
  source: { type: 'string', enum: ['local', 'onchain', 'all'], description: 'Data source preference (default: all)' },
258
- subdao: { type: 'string', description: 'Optional SubDAO address when source=onchain' },
271
+ subdao: { type: 'string', description: 'Optional DAO address when source=onchain' },
259
272
  tags: { type: 'array', items: { type: 'string' }, description: 'Optional tag filters (matches any tag)' },
260
273
  includeContent: { type: 'boolean', description: 'Include raw prompt content when supported (default: false)' },
261
274
  page: { type: 'number', description: 'Results page (default: 1)' },
@@ -349,7 +362,7 @@ Next steps:
349
362
  },
350
363
  {
351
364
  name: 'search_onchain_prompts',
352
- description: 'Search for prompts directly on-chain from LibraryRegistry and SubDAO registries',
365
+ description: 'Search for prompts directly on-chain from LibraryRegistry and DAO registries',
353
366
  inputSchema: {
354
367
  type: 'object',
355
368
  properties: {
@@ -359,7 +372,7 @@ Next steps:
359
372
  },
360
373
  subdao: {
361
374
  type: 'string',
362
- description: 'Filter by specific SubDAO address (optional)'
375
+ description: 'Filter by specific DAO address (optional)'
363
376
  },
364
377
  tags: {
365
378
  type: 'array',
@@ -503,13 +516,13 @@ Next steps:
503
516
  },
504
517
  {
505
518
  name: 'list_subdaos',
506
- description: 'List all available SubDAOs in the Sage Protocol',
519
+ description: 'List all available DAOs in the Sage Protocol',
507
520
  inputSchema: {
508
521
  type: 'object',
509
522
  properties: {
510
523
  limit: {
511
524
  type: 'number',
512
- description: 'Maximum number of SubDAOs to return (default: 20)'
525
+ description: 'Maximum number of DAOs to return (default: 20)'
513
526
  }
514
527
  },
515
528
  required: []
@@ -531,10 +544,10 @@ Next steps:
531
544
  },
532
545
  {
533
546
  name: 'refresh_library_bindings',
534
- description: 'Refresh cached per-library registry mappings (optional: target a specific SubDAO)',
547
+ description: 'Refresh cached per-library registry mappings (optional: target a specific DAO)',
535
548
  inputSchema: {
536
549
  type: 'object',
537
- properties: { subdao: { type: 'string', description: 'Optional SubDAO address (0x...)' } },
550
+ properties: { subdao: { type: 'string', description: 'Optional DAO address (0x...)' } },
538
551
  required: []
539
552
  }
540
553
  },
@@ -558,10 +571,10 @@ Next steps:
558
571
  },
559
572
  {
560
573
  name: 'list_subdao_libraries',
561
- description: 'List per-library PromptRegistry mappings for a SubDAO',
574
+ description: 'List per-library PromptRegistry mappings for a DAO',
562
575
  inputSchema: {
563
576
  type: 'object',
564
- properties: { subdao: { type: 'string', description: 'SubDAO address (0x...)' } },
577
+ properties: { subdao: { type: 'string', description: 'DAO address (0x...)' } },
565
578
  required: ['subdao']
566
579
  }
567
580
  },
@@ -589,13 +602,13 @@ Next steps:
589
602
  },
590
603
  {
591
604
  name: 'propose_manifest',
592
- description: `Generate the proposal payload and CLI commands for a SubDAO update.
605
+ description: `Generate the proposal payload and CLI commands for a DAO governance update.
593
606
  Note: This tool does NOT sign transactions. It generates the hex data and CLI commands for you to execute in your terminal.`,
594
607
  inputSchema: {
595
608
  type: 'object',
596
609
  properties: {
597
610
  cid: { type: 'string', description: 'IPFS CID of the manifest' },
598
- subdao: { type: 'string', description: 'SubDAO address' },
611
+ subdao: { type: 'string', description: 'DAO address' },
599
612
  description: { type: 'string', description: 'Proposal description' },
600
613
  promptCount: { type: 'number', description: 'Number of prompts (optional override)' },
601
614
  manifest: { type: 'object', description: 'Optional manifest object for context' }
@@ -639,11 +652,11 @@ Note: This tool does NOT sign transactions. It generates the hex data and CLI co
639
652
  },
640
653
  {
641
654
  name: 'list_proposals',
642
- description: 'List active proposals for a SubDAO',
655
+ description: 'List active governance proposals for a DAO',
643
656
  inputSchema: {
644
657
  type: 'object',
645
658
  properties: {
646
- subdao: { type: 'string', description: 'SubDAO address' },
659
+ subdao: { type: 'string', description: 'DAO address' },
647
660
  state: { type: 'string', description: 'Filter by state (Active, Executed, etc)' },
648
661
  limit: { type: 'number', description: 'Max items (default 10)' }
649
662
  },
@@ -662,7 +675,7 @@ Note: This tool does NOT sign transactions. It prepares everything so you can ex
662
675
  type: 'object',
663
676
  properties: {
664
677
  manifest: { type: 'object', description: 'Manifest JSON' },
665
- subdao: { type: 'string', description: 'Optional SubDAO address for hints' },
678
+ subdao: { type: 'string', description: 'Optional DAO address for hints' },
666
679
  description: { type: 'string', description: 'Optional description override' },
667
680
  dry_run: { type: 'boolean', description: 'Validate and build proposal payload without uploading to IPFS (no network calls)' }
668
681
  },
@@ -813,7 +826,7 @@ Note: This tool does NOT sign transactions. It prepares everything so you can ex
813
826
  },
814
827
  {
815
828
  name: 'suggest_subdaos_for_library',
816
- description: 'Suggest SubDAOs that might be a good fit for publishing a given local library, and provide CLI workflows for creating your own SubDAO and pushing the library.',
829
+ description: 'Suggest DAOs that might be a good fit for publishing a given local library, and provide CLI workflows for creating your own DAO and pushing the library.',
817
830
  inputSchema: {
818
831
  type: 'object',
819
832
  properties: {
@@ -823,7 +836,7 @@ Note: This tool does NOT sign transactions. It prepares everything so you can ex
823
836
  },
824
837
  limit: {
825
838
  type: 'number',
826
- description: 'Max SubDAOs to return (default 5)',
839
+ description: 'Max DAOs to return (default 5)',
827
840
  default: 5
828
841
  },
829
842
  mode_filter: {
@@ -848,36 +861,15 @@ Note: This tool does NOT sign transactions. It prepares everything so you can ex
848
861
  },
849
862
  target: {
850
863
  type: 'string',
851
- description: 'Target SubDAO address or "auto" to pick a likely candidate',
864
+ description: 'Target DAO address or "auto" to pick a likely candidate',
852
865
  default: 'auto'
853
866
  }
854
867
  },
855
868
  required: ['library']
856
869
  }
857
870
  },
858
- {
859
- name: 'list_workspace_skills',
860
- description: 'List local workspace skills from prompts/ (e.g. prompts/skills/*.md)',
861
- inputSchema: {
862
- type: 'object',
863
- properties: {},
864
- required: []
865
- }
866
- },
867
- {
868
- name: 'get_workspace_skill',
869
- description: 'Load a workspace skill by key and return its content',
870
- inputSchema: {
871
- type: 'object',
872
- properties: {
873
- key: {
874
- type: 'string',
875
- description: 'Skill key relative to prompts dir (e.g. skills/backend-dev-guidelines)'
876
- }
877
- },
878
- required: ['key']
879
- }
880
- }
871
+ // NOTE: list_workspace_skills and get_workspace_skill have been removed.
872
+ // Use list_prompts({ kind: 'skill' }) and get_prompt({ key }) instead.
881
873
  ];
882
874
 
883
875
  // Structured logger to stderr (stdout must remain JSON-RPC only)
@@ -911,6 +903,9 @@ Note: This tool does NOT sign transactions. It prepares everything so you can ex
911
903
  // Local library helper for unified search
912
904
  this.libraryManager = new LibraryManager();
913
905
 
906
+ // Project context service for get_project_context tool
907
+ this.projectContextService = new ProjectContextService();
908
+
914
909
  // Simple rate limiter (token bucket per tool)
915
910
  this.rateConfig = { capacity: 20, refillPerSec: 0.33 }; // ~20/min
916
911
  this.rateLimiter = createRateLimiter(this.rateConfig);
@@ -1104,8 +1099,6 @@ Note: This tool does NOT sign transactions. It prepares everything so you can ex
1104
1099
  'tool:list_proposals': (params) => this.listProposals(params),
1105
1100
  'tool:publish_manifest_flow': (params) => this.publishManifestFlow(params),
1106
1101
  'tool:refresh_library_bindings': (params) => this.refreshLibraryBindings(params),
1107
- 'tool:list_workspace_skills': (params) => this.listWorkspaceSkills(params),
1108
- 'tool:get_workspace_skill': (params) => this.getWorkspaceSkill(params),
1109
1102
  'tool:update_library_metadata': (params) => this.updateLibraryMetadata(params),
1110
1103
  'tool:bulk_update_prompts': (params) => this.bulkUpdatePrompts(params),
1111
1104
  'tool:list_templates': (params) => this.listTemplates(params),
@@ -1115,6 +1108,7 @@ Note: This tool does NOT sign transactions. It prepares everything so you can ex
1115
1108
  'tool:suggest_subdaos_for_library': (params) => this.suggestSubdaosForLibrary(params),
1116
1109
  'tool:generate_publishing_commands': (params) => this.generatePublishingCommands(params),
1117
1110
  'tool:suggest_sage_tools': (params) => this.suggestSageTools(params),
1111
+ 'tool:get_project_context': (params) => this.getProjectContext(params),
1118
1112
  });
1119
1113
 
1120
1114
  const toolHandlers = {
@@ -1282,93 +1276,9 @@ Note: This tool does NOT sign transactions. It prepares everything so you can ex
1282
1276
  }
1283
1277
 
1284
1278
  // ───────────────────────── Workspace skills (repo-tied skills) ─────────────────────────
1285
-
1286
- /**
1287
- * List skills defined in the current project's prompt workspace.
1288
- * Convention:
1289
- * - Workspace file: .sage/workspace.json (optional)
1290
- * - Prompts dir: workspace.promptsDir or "prompts"
1291
- * - Skill files live under promptsDir/skills/*.md
1292
- */
1293
- listWorkspaceSkills() {
1294
- try {
1295
- const { readWorkspace, DEFAULT_DIR } = require('./services/prompts/workspace');
1296
- const { findWorkspaceSkills } = require('./services/skills/discovery');
1297
- const ws = readWorkspace() || {};
1298
- const promptsDir = ws.promptsDir || DEFAULT_DIR || 'prompts';
1299
- const results = findWorkspaceSkills({ promptsDir });
1300
- if (!results.length) {
1301
- return {
1302
- content: [
1303
- {
1304
- type: 'text',
1305
- text: 'No skills found. Create prompts/skills/<name>.md or prompts/skills/<name>/SKILL.md to define skills for this repo.',
1306
- },
1307
- { type: 'text', text: '```json\n' + JSON.stringify({ skills: [] }, null, 2) + '\n```' },
1308
- ],
1309
- };
1310
- }
1311
- const textLines = results
1312
- .map(
1313
- (s, idx) =>
1314
- `${idx + 1}. **${s.name}** (${s.key})\n šŸ“ ${path.relative(process.cwd(), s.path)}${s.summary ? `\n šŸ“ ${s.summary}` : ''
1315
- }${s.tags && s.tags.length ? `\n šŸ”– ${s.tags.join(', ')}` : ''}`,
1316
- )
1317
- .join('\n\n');
1318
- return {
1319
- content: [
1320
- { type: 'text', text: `Workspace skills (${results.length})\n\n${textLines}` },
1321
- { type: 'text', text: '```json\n' + JSON.stringify({ skills: results }, null, 2) + '\n```' },
1322
- ],
1323
- };
1324
- } catch (error) {
1325
- return {
1326
- content: [{ type: 'text', text: `Error listing workspace skills: ${error.message}` }],
1327
- };
1328
- }
1329
- }
1330
-
1331
- /**
1332
- * Load a workspace skill by key and return its content.
1333
- * Key is relative to prompts dir, e.g. "skills/backend-dev-guidelines".
1334
- */
1335
- getWorkspaceSkill({ key }) {
1336
- try {
1337
- if (!key || !String(key).trim()) {
1338
- return { content: [{ type: 'text', text: 'get_workspace_skill: key is required' }] };
1339
- }
1340
- const { readWorkspace, DEFAULT_DIR } = require('./services/prompts/workspace');
1341
- const { resolveSkillFileByKey } = require('./services/skills/discovery');
1342
- const ws = readWorkspace() || {};
1343
- const promptsDir = ws.promptsDir || DEFAULT_DIR || 'prompts';
1344
- const safeKey = String(key).trim().replace(/^\/+/, '').replace(/\.md$/i, '');
1345
- const resolved = resolveSkillFileByKey({ promptsDir, key: safeKey });
1346
- if (!resolved || !fs.existsSync(resolved.path)) {
1347
- const expectedFlat = path.join(process.cwd(), promptsDir, `${safeKey}.md`);
1348
- const expectedDir = path.join(process.cwd(), promptsDir, safeKey, 'SKILL.md');
1349
- return {
1350
- content: [
1351
- {
1352
- type: 'text',
1353
- text: `Workspace skill not found for key '${safeKey}'. Expected at ${path.relative(process.cwd(), expectedFlat)} or ${path.relative(process.cwd(), expectedDir)}`,
1354
- },
1355
- ],
1356
- };
1357
- }
1358
- const body = fs.readFileSync(resolved.path, 'utf8');
1359
- return {
1360
- content: [
1361
- {
1362
- type: 'text',
1363
- text: `Loaded workspace skill '${safeKey}' from ${path.relative(process.cwd(), resolved.path)}.\n\n${body}`,
1364
- },
1365
- { type: 'text', text: '```json\n' + JSON.stringify({ key: safeKey, path: resolved.path, baseDir: resolved.baseDir, body }, null, 2) + '\n```' },
1366
- ],
1367
- };
1368
- } catch (error) {
1369
- return { content: [{ type: 'text', text: `Error loading workspace skill: ${error.message}` }] };
1370
- }
1371
- }
1279
+ // NOTE: listWorkspaceSkills and getWorkspaceSkill have been removed.
1280
+ // Use list_prompts({ kind: 'skill' }) and get_prompt({ key }) instead.
1281
+ // ─────────────────────────────────────────────────────────────────────────────────────────
1372
1282
 
1373
1283
  async getPromptByName(params = {}) {
1374
1284
  const name = String(params.name || '').trim();
@@ -1461,38 +1371,87 @@ Note: This tool does NOT sign transactions. It prepares everything so you can ex
1461
1371
  return this.searchPromptsUnifiedHandler(options);
1462
1372
  }
1463
1373
 
1464
- async listPrompts({ source = 'local', library = '', limit = 20 } = {}) {
1374
+ async listPrompts({ source = 'local', library = '', limit = 20, kind, publishable } = {}) {
1465
1375
  try {
1466
- const results = await this.searchPromptsUnifiedHandler({
1467
- query: '',
1468
- source,
1469
- includeContent: false,
1470
- page: 1,
1471
- pageSize: limit
1472
- });
1473
-
1474
- if (library && results?.content?.[1]?.text) {
1475
- const jsonMatch = results.content[1].text.match(/```json\n([\s\S]+)\n```/);
1476
- if (jsonMatch) {
1477
- const data = JSON.parse(jsonMatch[1]);
1478
- const libraryLower = library.toLowerCase();
1479
- data.results = data.results.filter(r =>
1480
- r.library?.name?.toLowerCase().includes(libraryLower) ||
1481
- r.library?.cid?.toLowerCase().includes(libraryLower)
1482
- );
1483
- data.total = data.results.length;
1376
+ // If source is 'workspace' or we want to include workspace artifacts, use ArtifactManager
1377
+ const includeWorkspace = source === 'local' || source === 'workspace' || source === 'all';
1378
+
1379
+ let workspaceResults = [];
1380
+ if (includeWorkspace) {
1381
+ const ArtifactManager = require('./services/artifact-manager');
1382
+ const artifactManager = new ArtifactManager();
1383
+ const filter = {};
1384
+ if (kind) filter.kind = kind;
1385
+ if (publishable !== undefined) filter.publishable = publishable;
1386
+
1387
+ const artifacts = await artifactManager.listArtifacts(filter);
1388
+ workspaceResults = artifacts.map(a => ({
1389
+ resultType: 'prompt',
1390
+ origin: 'workspace',
1391
+ source: 'Workspace',
1392
+ key: a.key,
1393
+ name: a.meta?.title || a.key,
1394
+ description: a.meta?.summary || '',
1395
+ tags: a.meta?.tags || [],
1396
+ content: a.body,
1397
+ kind: a.kind,
1398
+ publishable: a.publishable,
1399
+ publishing: a.publishing,
1400
+ targets: a.targets,
1401
+ filePath: a.filePath,
1402
+ }));
1403
+ }
1404
+
1405
+ // Also get library prompts if not workspace-only
1406
+ let libraryResults = [];
1407
+ if (source !== 'workspace') {
1408
+ const results = await this.searchPromptsUnifiedHandler({
1409
+ query: '',
1410
+ source: source === 'workspace' ? 'local' : source,
1411
+ includeContent: false,
1412
+ page: 1,
1413
+ pageSize: limit
1414
+ });
1484
1415
 
1485
- const formatted = this.formatUnifiedResults(data.results, { total: data.total, page: 1, pageSize: limit });
1486
- return {
1487
- content: [
1488
- { type: 'text', text: formatted },
1489
- { type: 'text', text: '```json\n' + JSON.stringify(data, null, 2) + '\n```' }
1490
- ]
1491
- };
1416
+ if (results?.content?.[1]?.text) {
1417
+ const jsonMatch = results.content[1].text.match(/```json\n([\s\S]+)\n```/);
1418
+ if (jsonMatch) {
1419
+ const data = JSON.parse(jsonMatch[1]);
1420
+ libraryResults = (data.results || []).map(r => ({
1421
+ ...r,
1422
+ kind: r.kind || 'prompt',
1423
+ publishable: true, // library prompts are published
1424
+ publishing: { status: 'published' }
1425
+ }));
1426
+ }
1492
1427
  }
1493
1428
  }
1494
1429
 
1495
- return results;
1430
+ // Merge and filter
1431
+ let allResults = [...workspaceResults, ...libraryResults];
1432
+
1433
+ // Filter by library if specified
1434
+ if (library) {
1435
+ const libraryLower = library.toLowerCase();
1436
+ allResults = allResults.filter(r =>
1437
+ r.library?.name?.toLowerCase().includes(libraryLower) ||
1438
+ r.library?.cid?.toLowerCase().includes(libraryLower) ||
1439
+ r.origin === 'workspace' // workspace prompts don't have library
1440
+ );
1441
+ }
1442
+
1443
+ // Apply limit
1444
+ allResults = allResults.slice(0, limit);
1445
+
1446
+ const data = { results: allResults, total: allResults.length, page: 1, pageSize: limit };
1447
+ const formatted = this.formatUnifiedResults(allResults, { total: data.total, page: 1, pageSize: limit });
1448
+
1449
+ return {
1450
+ content: [
1451
+ { type: 'text', text: formatted },
1452
+ { type: 'text', text: '```json\n' + JSON.stringify(data, null, 2) + '\n```' }
1453
+ ]
1454
+ };
1496
1455
  } catch (error) {
1497
1456
  return { content: [{ type: 'text', text: `Error listing prompts: ${error.message}` }] };
1498
1457
  }
@@ -1504,6 +1463,41 @@ Note: This tool does NOT sign transactions. It prepares everything so you can ex
1504
1463
  return { content: [{ type: 'text', text: 'Error: key parameter is required' }] };
1505
1464
  }
1506
1465
 
1466
+ // First try workspace artifacts (ArtifactManager)
1467
+ const ArtifactManager = require('./services/artifact-manager');
1468
+ const artifactManager = new ArtifactManager();
1469
+ const artifact = await artifactManager.getArtifact(key);
1470
+
1471
+ if (artifact) {
1472
+ const prompt = {
1473
+ resultType: 'prompt',
1474
+ origin: 'workspace',
1475
+ source: 'Workspace',
1476
+ key: artifact.key,
1477
+ name: artifact.meta?.title || artifact.key,
1478
+ description: artifact.meta?.summary || '',
1479
+ tags: artifact.meta?.tags || [],
1480
+ content: artifact.body,
1481
+ kind: artifact.kind,
1482
+ publishable: artifact.publishable,
1483
+ publishing: artifact.publishing,
1484
+ targets: artifact.targets,
1485
+ filePath: artifact.filePath,
1486
+ };
1487
+
1488
+ const kindBadge = prompt.kind !== 'prompt' ? ` [${prompt.kind}]` : '';
1489
+ const publishBadge = prompt.publishable ? '' : ' (local only)';
1490
+ const text = `**${prompt.name}**${kindBadge}${publishBadge}\n\nšŸ”‘ Key: ${prompt.key}\nšŸ“‚ Source: Workspace\nšŸ“„ ${prompt.description || 'No description'}\nšŸ·ļø Tags: ${prompt.tags?.join(', ') || 'None'}\nšŸ“Œ Kind: ${prompt.kind}\nšŸ“¤ Publishable: ${prompt.publishable}\nšŸ“Š Status: ${prompt.publishing?.status || 'unknown'}\n\n**Content:**\n\`\`\`\n${prompt.content || '(No content)'}\n\`\`\``;
1491
+
1492
+ return {
1493
+ content: [
1494
+ { type: 'text', text },
1495
+ { type: 'text', text: '```json\n' + JSON.stringify(prompt, null, 2) + '\n```' }
1496
+ ]
1497
+ };
1498
+ }
1499
+
1500
+ // Fall back to library search
1507
1501
  const results = await this.searchPromptsUnifiedHandler({
1508
1502
  query: key,
1509
1503
  source: 'local',
@@ -1532,7 +1526,12 @@ Note: This tool does NOT sign transactions. It prepares everything so you can ex
1532
1526
  return { content: [{ type: 'text', text: `No prompt found with key "${key}"` }] };
1533
1527
  }
1534
1528
 
1535
- const text = `**${prompt.name}**\n\nšŸ”‘ Key: ${prompt.key}\nšŸ“š Library: ${prompt.library?.name || 'Unknown'}\nšŸ“„ ${prompt.description || 'No description'}\nšŸ·ļø Tags: ${prompt.tags?.join(', ') || 'None'}\n\n**Content:**\n\`\`\`\n${prompt.content || '(No content)'}\n\`\`\``;
1529
+ // Add kind/publishable for library prompts
1530
+ prompt.kind = prompt.kind || 'prompt';
1531
+ prompt.publishable = true;
1532
+ prompt.publishing = { status: 'published' };
1533
+
1534
+ const text = `**${prompt.name}**\n\nšŸ”‘ Key: ${prompt.key}\nšŸ“š Library: ${prompt.library?.name || 'Unknown'}\nšŸ“„ ${prompt.description || 'No description'}\nšŸ·ļø Tags: ${prompt.tags?.join(', ') || 'None'}\nšŸ“Œ Kind: ${prompt.kind}\n\n**Content:**\n\`\`\`\n${prompt.content || '(No content)'}\n\`\`\``;
1536
1535
 
1537
1536
  return {
1538
1537
  content: [
@@ -1665,6 +1664,61 @@ Note: This tool does NOT sign transactions. It prepares everything so you can ex
1665
1664
  }
1666
1665
  }
1667
1666
 
1667
+ async getProjectContext(params) {
1668
+ try {
1669
+ const ctx = await this.projectContextService.getProjectContext(params || {});
1670
+ const lines = [];
1671
+ lines.push('āœ… Project context');
1672
+ lines.push(`Project root: ${ctx.projectRoot || 'not found'}`);
1673
+ lines.push(`Prompts dir: ${ctx.promptsDir || 'prompts'}`);
1674
+ if (ctx.subdao) {
1675
+ lines.push(`SubDAO: ${ctx.subdao}`);
1676
+ }
1677
+ if (ctx.registry) {
1678
+ lines.push(`Library registry: ${ctx.registry}`);
1679
+ }
1680
+ if (ctx.network) {
1681
+ lines.push(`Network: ${ctx.network}`);
1682
+ }
1683
+ if (ctx.rpcUrl) {
1684
+ lines.push(`RPC URL: ${ctx.rpcUrl}`);
1685
+ }
1686
+ if (ctx.workspace) {
1687
+ lines.push(
1688
+ `Workspace artifacts: prompts=${ctx.workspace.promptCount}, skills=${ctx.workspace.skillCount}, snippets=${ctx.workspace.snippetCount}, total=${ctx.workspace.total}`
1689
+ );
1690
+ }
1691
+ if (ctx.agentSurfaces) {
1692
+ const surfaces = [];
1693
+ if (ctx.agentSurfaces.cursorRulesDir) {
1694
+ surfaces.push(`cursor rules at ${ctx.agentSurfaces.cursorRulesDir}`);
1695
+ }
1696
+ if (ctx.agentSurfaces.claudeManifest) {
1697
+ surfaces.push(`CLAUDE manifest at ${ctx.agentSurfaces.claudeManifest}`);
1698
+ }
1699
+ if (ctx.agentSurfaces.copilotPromptsDir) {
1700
+ surfaces.push(`Copilot prompts at ${ctx.agentSurfaces.copilotPromptsDir}`);
1701
+ }
1702
+ if (Array.isArray(ctx.agentSurfaces.agentsFiles) && ctx.agentSurfaces.agentsFiles.length) {
1703
+ surfaces.push(`AGENTS files: ${ctx.agentSurfaces.agentsFiles.join(', ')}`);
1704
+ }
1705
+ if (surfaces.length) {
1706
+ lines.push('Agent surfaces:');
1707
+ surfaces.forEach((s) => lines.push(`- ${s}`));
1708
+ }
1709
+ }
1710
+
1711
+ return {
1712
+ content: [
1713
+ { type: 'text', text: lines.join('\n') },
1714
+ { type: 'text', text: '```json\n' + JSON.stringify(ctx, null, 2) + '\n```' },
1715
+ ],
1716
+ };
1717
+ } catch (error) {
1718
+ return { content: [{ type: 'text', text: `Error retrieving project context: ${error.message}` }] };
1719
+ }
1720
+ }
1721
+
1668
1722
  async listTemplates(params = {}) {
1669
1723
  try {
1670
1724
  const result = this.templateManager.listTemplates(params || {});
@@ -3207,7 +3261,7 @@ Use \`help(topic="create")\` for more details.
3207
3261
  description: t.description,
3208
3262
  recommended_slots: t.recommended_slots?.map(s => s.label) || []
3209
3263
  }));
3210
-
3264
+
3211
3265
  return {
3212
3266
  content: [
3213
3267
  {
@@ -3234,7 +3288,7 @@ Use \`help(topic="create")\` for more details.
3234
3288
 
3235
3289
  // 1. Validate Template
3236
3290
  const templateDef = templates[template] || templates['custom'];
3237
-
3291
+
3238
3292
  // 2. Build Prompt (One-Shot)
3239
3293
  // Note: run_persona_interview assumes the *agent* has already done the planning/filling.
3240
3294
  // It bypasses the SlotPlanner and just builds the artifact.
@@ -3242,7 +3296,7 @@ Use \`help(topic="create")\` for more details.
3242
3296
  // We need the full slot definitions to build dynamic sections.
3243
3297
  // Since we skipped the planner, we'll use the template's recommended slots as the definition.
3244
3298
  const slots = templateDef.recommended_slots || [];
3245
-
3299
+
3246
3300
  const systemPrompt = builder.buildSystemPrompt(template, slots, answers || {});
3247
3301
 
3248
3302
  let result = {
@@ -3255,17 +3309,17 @@ Use \`help(topic="create")\` for more details.
3255
3309
  if (save) {
3256
3310
  const persistence = new MetapromptPersistence(config);
3257
3311
  const slug = saveKey || `${template}-${Date.now().toString().slice(-6)}`;
3258
-
3312
+
3259
3313
  // Save dummy history so the artifact exists
3260
3314
  const historyPaths = persistence.saveMetaprompt(slug, {
3261
3315
  templateKey: template,
3262
3316
  transcript: [{ role: 'system', content: 'Generated via MCP run_persona_interview (one-shot).' }],
3263
3317
  answers: answers || {}
3264
3318
  });
3265
-
3319
+
3266
3320
  // Save Skill
3267
3321
  const skillPath = persistence.saveSkill(slug, systemPrompt);
3268
-
3322
+
3269
3323
  result.slug = slug;
3270
3324
  result.paths = {
3271
3325
  metaprompt: historyPaths.metaprompt,