@sage-protocol/cli 0.4.0 → 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.
- package/dist/cli/browser-wallet-integration.js +0 -1
- package/dist/cli/cast-wallet-manager.js +0 -1
- package/dist/cli/commands/interview.js +149 -0
- package/dist/cli/commands/personal.js +138 -79
- package/dist/cli/commands/prompts.js +242 -87
- package/dist/cli/commands/stake-status.js +0 -2
- package/dist/cli/config.js +28 -8
- package/dist/cli/governance-manager.js +28 -19
- package/dist/cli/index.js +32 -8
- package/dist/cli/library-manager.js +16 -6
- package/dist/cli/mcp-server-stdio.js +759 -156
- package/dist/cli/mcp-server.js +4 -30
- package/dist/cli/metamask-integration.js +0 -1
- package/dist/cli/privy-wallet-manager.js +2 -2
- package/dist/cli/prompt-manager.js +0 -1
- package/dist/cli/services/artifact-manager.js +198 -0
- package/dist/cli/services/doctor/fixers.js +1 -1
- package/dist/cli/services/mcp/env-loader.js +2 -0
- package/dist/cli/services/mcp/prompt-result-formatter.js +8 -1
- package/dist/cli/services/mcp/quick-start.js +14 -15
- package/dist/cli/services/mcp/sage-tool-registry.js +322 -0
- package/dist/cli/services/mcp/tool-args-validator.js +43 -0
- package/dist/cli/services/metaprompt/anthropic-client.js +87 -0
- package/dist/cli/services/metaprompt/interview-driver.js +161 -0
- package/dist/cli/services/metaprompt/model-client.js +49 -0
- package/dist/cli/services/metaprompt/openai-client.js +67 -0
- package/dist/cli/services/metaprompt/persistence.js +86 -0
- package/dist/cli/services/metaprompt/prompt-builder.js +186 -0
- package/dist/cli/services/metaprompt/session.js +18 -80
- package/dist/cli/services/metaprompt/slot-planner.js +115 -0
- package/dist/cli/services/metaprompt/templates.json +130 -0
- package/dist/cli/services/project-context.js +98 -0
- package/dist/cli/subdao.js +0 -3
- package/dist/cli/sxxx-manager.js +0 -1
- package/dist/cli/utils/aliases.js +0 -6
- package/dist/cli/utils/tx-wait.js +0 -3
- package/dist/cli/wallet-manager.js +18 -19
- package/dist/cli/walletconnect-integration.js +0 -1
- package/dist/cli/wizard-manager.js +0 -1
- package/package.json +3 -1
- package/dist/cli/commands/prompt-test.js +0 -176
- package/dist/cli/commands/prompt.js +0 -2531
|
@@ -53,7 +53,13 @@ const { createLibraryLister, formatLibraryList } = require('./services/mcp/libra
|
|
|
53
53
|
const { createSubdaoDiscovery } = require('./services/mcp/subdao-discovery');
|
|
54
54
|
const { createUnifiedPromptSearcher } = require('./services/mcp/unified-prompt-search');
|
|
55
55
|
const { createToolArgsValidator } = require('./services/mcp/tool-args-validator');
|
|
56
|
+
const {
|
|
57
|
+
TOOL_REGISTRY,
|
|
58
|
+
getToolsForCategory,
|
|
59
|
+
getToolMeta,
|
|
60
|
+
} = require('./services/mcp/sage-tool-registry');
|
|
56
61
|
const { createToolDispatcher } = require('./services/mcp/tool-dispatcher');
|
|
62
|
+
const ProjectContextService = require('./services/project-context');
|
|
57
63
|
const { createProposalLister } = require('./services/mcp/proposal-lister');
|
|
58
64
|
const fs = require('fs');
|
|
59
65
|
const path = require('path');
|
|
@@ -79,6 +85,53 @@ class SageMCPServer {
|
|
|
79
85
|
};
|
|
80
86
|
|
|
81
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
|
+
},
|
|
100
|
+
{
|
|
101
|
+
name: 'suggest_sage_tools',
|
|
102
|
+
description: `Suggest the most relevant Sage MCP tools for a given goal.
|
|
103
|
+
When to use:
|
|
104
|
+
- You have a vague goal and want help choosing the right Sage tools
|
|
105
|
+
- You want a ranked list of 3–5 tools with rationale and required parameters
|
|
106
|
+
|
|
107
|
+
This tool does not execute any actions; it only recommends which tools to call next.`,
|
|
108
|
+
inputSchema: {
|
|
109
|
+
type: 'object',
|
|
110
|
+
properties: {
|
|
111
|
+
goal: { type: 'string', description: 'Free-text description of what you are trying to do' },
|
|
112
|
+
stage: {
|
|
113
|
+
type: 'string',
|
|
114
|
+
enum: ['prompt_workspace', 'persona', 'libraries', 'governance', 'treasury', 'discovery'],
|
|
115
|
+
description: 'Optional hint about which area you are working in',
|
|
116
|
+
},
|
|
117
|
+
context: {
|
|
118
|
+
type: 'object',
|
|
119
|
+
description: 'Optional execution context (workspace, wallet, etc.)',
|
|
120
|
+
properties: {
|
|
121
|
+
hasWorkspace: { type: 'boolean', description: 'Whether a Sage prompt workspace is present' },
|
|
122
|
+
hasWallet: { type: 'boolean', description: 'Whether a wallet is configured/available' },
|
|
123
|
+
currentTask: { type: 'string', description: 'Short description of what you just did' },
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
limit: { type: 'number', description: 'Maximum number of primary recommendations (default: 5)' },
|
|
127
|
+
includeAlternatives: {
|
|
128
|
+
type: 'boolean',
|
|
129
|
+
description: 'Whether to include a secondary list of alternative tools (default: false)',
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
required: ['goal'],
|
|
133
|
+
},
|
|
134
|
+
},
|
|
82
135
|
{
|
|
83
136
|
name: 'quick_create_prompt',
|
|
84
137
|
description: `Quickly create a new prompt. Handles library creation automatically.
|
|
@@ -215,7 +268,7 @@ Next steps:
|
|
|
215
268
|
properties: {
|
|
216
269
|
query: { type: 'string', description: 'Free-text search applied to names, descriptions, and tags' },
|
|
217
270
|
source: { type: 'string', enum: ['local', 'onchain', 'all'], description: 'Data source preference (default: all)' },
|
|
218
|
-
subdao: { type: 'string', description: 'Optional
|
|
271
|
+
subdao: { type: 'string', description: 'Optional DAO address when source=onchain' },
|
|
219
272
|
tags: { type: 'array', items: { type: 'string' }, description: 'Optional tag filters (matches any tag)' },
|
|
220
273
|
includeContent: { type: 'boolean', description: 'Include raw prompt content when supported (default: false)' },
|
|
221
274
|
page: { type: 'number', description: 'Results page (default: 1)' },
|
|
@@ -309,7 +362,7 @@ Next steps:
|
|
|
309
362
|
},
|
|
310
363
|
{
|
|
311
364
|
name: 'search_onchain_prompts',
|
|
312
|
-
description: 'Search for prompts directly on-chain from LibraryRegistry and
|
|
365
|
+
description: 'Search for prompts directly on-chain from LibraryRegistry and DAO registries',
|
|
313
366
|
inputSchema: {
|
|
314
367
|
type: 'object',
|
|
315
368
|
properties: {
|
|
@@ -319,7 +372,7 @@ Next steps:
|
|
|
319
372
|
},
|
|
320
373
|
subdao: {
|
|
321
374
|
type: 'string',
|
|
322
|
-
description: 'Filter by specific
|
|
375
|
+
description: 'Filter by specific DAO address (optional)'
|
|
323
376
|
},
|
|
324
377
|
tags: {
|
|
325
378
|
type: 'array',
|
|
@@ -374,6 +427,42 @@ Next steps:
|
|
|
374
427
|
required: []
|
|
375
428
|
}
|
|
376
429
|
},
|
|
430
|
+
{
|
|
431
|
+
name: 'list_persona_templates',
|
|
432
|
+
description: 'List available persona templates for the Metaprompt Engine',
|
|
433
|
+
inputSchema: {
|
|
434
|
+
type: 'object',
|
|
435
|
+
properties: {},
|
|
436
|
+
required: []
|
|
437
|
+
}
|
|
438
|
+
},
|
|
439
|
+
{
|
|
440
|
+
name: 'run_persona_interview',
|
|
441
|
+
description: 'Generate a persona system prompt from structured answers (One-Shot, no human loop)',
|
|
442
|
+
inputSchema: {
|
|
443
|
+
type: 'object',
|
|
444
|
+
properties: {
|
|
445
|
+
template: { type: 'string', description: 'Template key (e.g., coding-assistant, governance-helper)' },
|
|
446
|
+
answers: { type: 'object', description: 'Key-value map of slot answers' },
|
|
447
|
+
save: { type: 'boolean', description: 'Whether to save the artifact to disk (default: false)' },
|
|
448
|
+
saveKey: { type: 'string', description: 'Optional filename for saving' }
|
|
449
|
+
},
|
|
450
|
+
required: ['template', 'answers']
|
|
451
|
+
}
|
|
452
|
+
},
|
|
453
|
+
{
|
|
454
|
+
name: 'persona_interview_step',
|
|
455
|
+
description: 'Stateful persona interview. Returns slot metadata for the host agent to phrase questions. Pass answers back with the stateToken to progress through slots.',
|
|
456
|
+
inputSchema: {
|
|
457
|
+
type: 'object',
|
|
458
|
+
properties: {
|
|
459
|
+
template: { type: 'string', description: 'Template key (coding-assistant, governance-helper, research-analyst, custom). Required on first call.' },
|
|
460
|
+
stateToken: { type: 'string', description: 'Base64-encoded state from previous response. Omit on first call.' },
|
|
461
|
+
answer: { type: 'string', description: 'Answer for the slot identified by currentSlotKey in the previous response.' }
|
|
462
|
+
},
|
|
463
|
+
required: []
|
|
464
|
+
}
|
|
465
|
+
},
|
|
377
466
|
{
|
|
378
467
|
name: 'save_metaprompt',
|
|
379
468
|
description: 'Persist a metaprompt to the local Sage workspace and optionally append to agents.md',
|
|
@@ -427,13 +516,13 @@ Next steps:
|
|
|
427
516
|
},
|
|
428
517
|
{
|
|
429
518
|
name: 'list_subdaos',
|
|
430
|
-
description: 'List all available
|
|
519
|
+
description: 'List all available DAOs in the Sage Protocol',
|
|
431
520
|
inputSchema: {
|
|
432
521
|
type: 'object',
|
|
433
522
|
properties: {
|
|
434
523
|
limit: {
|
|
435
524
|
type: 'number',
|
|
436
|
-
description: 'Maximum number of
|
|
525
|
+
description: 'Maximum number of DAOs to return (default: 20)'
|
|
437
526
|
}
|
|
438
527
|
},
|
|
439
528
|
required: []
|
|
@@ -455,10 +544,10 @@ Next steps:
|
|
|
455
544
|
},
|
|
456
545
|
{
|
|
457
546
|
name: 'refresh_library_bindings',
|
|
458
|
-
description: 'Refresh cached per-library registry mappings (optional: target a specific
|
|
547
|
+
description: 'Refresh cached per-library registry mappings (optional: target a specific DAO)',
|
|
459
548
|
inputSchema: {
|
|
460
549
|
type: 'object',
|
|
461
|
-
properties: { subdao: { type: 'string', description: 'Optional
|
|
550
|
+
properties: { subdao: { type: 'string', description: 'Optional DAO address (0x...)' } },
|
|
462
551
|
required: []
|
|
463
552
|
}
|
|
464
553
|
},
|
|
@@ -482,10 +571,10 @@ Next steps:
|
|
|
482
571
|
},
|
|
483
572
|
{
|
|
484
573
|
name: 'list_subdao_libraries',
|
|
485
|
-
description: 'List per-library PromptRegistry mappings for a
|
|
574
|
+
description: 'List per-library PromptRegistry mappings for a DAO',
|
|
486
575
|
inputSchema: {
|
|
487
576
|
type: 'object',
|
|
488
|
-
properties: { subdao: { type: 'string', description: '
|
|
577
|
+
properties: { subdao: { type: 'string', description: 'DAO address (0x...)' } },
|
|
489
578
|
required: ['subdao']
|
|
490
579
|
}
|
|
491
580
|
},
|
|
@@ -513,13 +602,13 @@ Next steps:
|
|
|
513
602
|
},
|
|
514
603
|
{
|
|
515
604
|
name: 'propose_manifest',
|
|
516
|
-
description: `Generate the proposal payload and CLI commands for a
|
|
605
|
+
description: `Generate the proposal payload and CLI commands for a DAO governance update.
|
|
517
606
|
Note: This tool does NOT sign transactions. It generates the hex data and CLI commands for you to execute in your terminal.`,
|
|
518
607
|
inputSchema: {
|
|
519
608
|
type: 'object',
|
|
520
609
|
properties: {
|
|
521
610
|
cid: { type: 'string', description: 'IPFS CID of the manifest' },
|
|
522
|
-
subdao: { type: 'string', description: '
|
|
611
|
+
subdao: { type: 'string', description: 'DAO address' },
|
|
523
612
|
description: { type: 'string', description: 'Proposal description' },
|
|
524
613
|
promptCount: { type: 'number', description: 'Number of prompts (optional override)' },
|
|
525
614
|
manifest: { type: 'object', description: 'Optional manifest object for context' }
|
|
@@ -563,11 +652,11 @@ Note: This tool does NOT sign transactions. It generates the hex data and CLI co
|
|
|
563
652
|
},
|
|
564
653
|
{
|
|
565
654
|
name: 'list_proposals',
|
|
566
|
-
description: 'List active proposals for a
|
|
655
|
+
description: 'List active governance proposals for a DAO',
|
|
567
656
|
inputSchema: {
|
|
568
657
|
type: 'object',
|
|
569
658
|
properties: {
|
|
570
|
-
subdao: { type: 'string', description: '
|
|
659
|
+
subdao: { type: 'string', description: 'DAO address' },
|
|
571
660
|
state: { type: 'string', description: 'Filter by state (Active, Executed, etc)' },
|
|
572
661
|
limit: { type: 'number', description: 'Max items (default 10)' }
|
|
573
662
|
},
|
|
@@ -586,7 +675,7 @@ Note: This tool does NOT sign transactions. It prepares everything so you can ex
|
|
|
586
675
|
type: 'object',
|
|
587
676
|
properties: {
|
|
588
677
|
manifest: { type: 'object', description: 'Manifest JSON' },
|
|
589
|
-
subdao: { type: 'string', description: 'Optional
|
|
678
|
+
subdao: { type: 'string', description: 'Optional DAO address for hints' },
|
|
590
679
|
description: { type: 'string', description: 'Optional description override' },
|
|
591
680
|
dry_run: { type: 'boolean', description: 'Validate and build proposal payload without uploading to IPFS (no network calls)' }
|
|
592
681
|
},
|
|
@@ -737,7 +826,7 @@ Note: This tool does NOT sign transactions. It prepares everything so you can ex
|
|
|
737
826
|
},
|
|
738
827
|
{
|
|
739
828
|
name: 'suggest_subdaos_for_library',
|
|
740
|
-
description: 'Suggest
|
|
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.',
|
|
741
830
|
inputSchema: {
|
|
742
831
|
type: 'object',
|
|
743
832
|
properties: {
|
|
@@ -747,7 +836,7 @@ Note: This tool does NOT sign transactions. It prepares everything so you can ex
|
|
|
747
836
|
},
|
|
748
837
|
limit: {
|
|
749
838
|
type: 'number',
|
|
750
|
-
description: 'Max
|
|
839
|
+
description: 'Max DAOs to return (default 5)',
|
|
751
840
|
default: 5
|
|
752
841
|
},
|
|
753
842
|
mode_filter: {
|
|
@@ -772,36 +861,15 @@ Note: This tool does NOT sign transactions. It prepares everything so you can ex
|
|
|
772
861
|
},
|
|
773
862
|
target: {
|
|
774
863
|
type: 'string',
|
|
775
|
-
description: 'Target
|
|
864
|
+
description: 'Target DAO address or "auto" to pick a likely candidate',
|
|
776
865
|
default: 'auto'
|
|
777
866
|
}
|
|
778
867
|
},
|
|
779
868
|
required: ['library']
|
|
780
869
|
}
|
|
781
870
|
},
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
description: 'List local workspace skills from prompts/ (e.g. prompts/skills/*.md)',
|
|
785
|
-
inputSchema: {
|
|
786
|
-
type: 'object',
|
|
787
|
-
properties: {},
|
|
788
|
-
required: []
|
|
789
|
-
}
|
|
790
|
-
},
|
|
791
|
-
{
|
|
792
|
-
name: 'get_workspace_skill',
|
|
793
|
-
description: 'Load a workspace skill by key and return its content',
|
|
794
|
-
inputSchema: {
|
|
795
|
-
type: 'object',
|
|
796
|
-
properties: {
|
|
797
|
-
key: {
|
|
798
|
-
type: 'string',
|
|
799
|
-
description: 'Skill key relative to prompts dir (e.g. skills/backend-dev-guidelines)'
|
|
800
|
-
}
|
|
801
|
-
},
|
|
802
|
-
required: ['key']
|
|
803
|
-
}
|
|
804
|
-
}
|
|
871
|
+
// NOTE: list_workspace_skills and get_workspace_skill have been removed.
|
|
872
|
+
// Use list_prompts({ kind: 'skill' }) and get_prompt({ key }) instead.
|
|
805
873
|
];
|
|
806
874
|
|
|
807
875
|
// Structured logger to stderr (stdout must remain JSON-RPC only)
|
|
@@ -835,6 +903,9 @@ Note: This tool does NOT sign transactions. It prepares everything so you can ex
|
|
|
835
903
|
// Local library helper for unified search
|
|
836
904
|
this.libraryManager = new LibraryManager();
|
|
837
905
|
|
|
906
|
+
// Project context service for get_project_context tool
|
|
907
|
+
this.projectContextService = new ProjectContextService();
|
|
908
|
+
|
|
838
909
|
// Simple rate limiter (token bucket per tool)
|
|
839
910
|
this.rateConfig = { capacity: 20, refillPerSec: 0.33 }; // ~20/min
|
|
840
911
|
this.rateLimiter = createRateLimiter(this.rateConfig);
|
|
@@ -1013,6 +1084,9 @@ Note: This tool does NOT sign transactions. It prepares everything so you can ex
|
|
|
1013
1084
|
'tool:get_prompts_from_manifest': (params) => this.getPromptsFromManifest(params),
|
|
1014
1085
|
'tool:list_metaprompts': () => this.listMetaprompts(),
|
|
1015
1086
|
'tool:start_metaprompt_interview': (params) => this.startMetapromptInterview(params),
|
|
1087
|
+
'tool:list_persona_templates': () => this.listPersonaTemplates(),
|
|
1088
|
+
'tool:run_persona_interview': (params) => this.runPersonaInterview(params),
|
|
1089
|
+
'tool:persona_interview_step': (params) => this.personaInterviewStep(params),
|
|
1016
1090
|
'tool:save_metaprompt': (params) => this.saveMetaprompt(params),
|
|
1017
1091
|
'tool:get_metaprompt': (params) => this.getMetaprompt(params),
|
|
1018
1092
|
'tool:generate_metaprompt_link': (params) => this.generateMetapromptLink(params),
|
|
@@ -1025,8 +1099,6 @@ Note: This tool does NOT sign transactions. It prepares everything so you can ex
|
|
|
1025
1099
|
'tool:list_proposals': (params) => this.listProposals(params),
|
|
1026
1100
|
'tool:publish_manifest_flow': (params) => this.publishManifestFlow(params),
|
|
1027
1101
|
'tool:refresh_library_bindings': (params) => this.refreshLibraryBindings(params),
|
|
1028
|
-
'tool:list_workspace_skills': (params) => this.listWorkspaceSkills(params),
|
|
1029
|
-
'tool:get_workspace_skill': (params) => this.getWorkspaceSkill(params),
|
|
1030
1102
|
'tool:update_library_metadata': (params) => this.updateLibraryMetadata(params),
|
|
1031
1103
|
'tool:bulk_update_prompts': (params) => this.bulkUpdatePrompts(params),
|
|
1032
1104
|
'tool:list_templates': (params) => this.listTemplates(params),
|
|
@@ -1035,6 +1107,8 @@ Note: This tool does NOT sign transactions. It prepares everything so you can ex
|
|
|
1035
1107
|
'tool:analyze_dependencies': (params) => this.analyzeDependencies(params),
|
|
1036
1108
|
'tool:suggest_subdaos_for_library': (params) => this.suggestSubdaosForLibrary(params),
|
|
1037
1109
|
'tool:generate_publishing_commands': (params) => this.generatePublishingCommands(params),
|
|
1110
|
+
'tool:suggest_sage_tools': (params) => this.suggestSageTools(params),
|
|
1111
|
+
'tool:get_project_context': (params) => this.getProjectContext(params),
|
|
1038
1112
|
});
|
|
1039
1113
|
|
|
1040
1114
|
const toolHandlers = {
|
|
@@ -1202,93 +1276,9 @@ Note: This tool does NOT sign transactions. It prepares everything so you can ex
|
|
|
1202
1276
|
}
|
|
1203
1277
|
|
|
1204
1278
|
// ───────────────────────── Workspace skills (repo-tied skills) ─────────────────────────
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
* Convention:
|
|
1209
|
-
* - Workspace file: .sage/workspace.json (optional)
|
|
1210
|
-
* - Prompts dir: workspace.promptsDir or "prompts"
|
|
1211
|
-
* - Skill files live under promptsDir/skills/*.md
|
|
1212
|
-
*/
|
|
1213
|
-
listWorkspaceSkills() {
|
|
1214
|
-
try {
|
|
1215
|
-
const { readWorkspace, DEFAULT_DIR } = require('./services/prompts/workspace');
|
|
1216
|
-
const { findWorkspaceSkills } = require('./services/skills/discovery');
|
|
1217
|
-
const ws = readWorkspace() || {};
|
|
1218
|
-
const promptsDir = ws.promptsDir || DEFAULT_DIR || 'prompts';
|
|
1219
|
-
const results = findWorkspaceSkills({ promptsDir });
|
|
1220
|
-
if (!results.length) {
|
|
1221
|
-
return {
|
|
1222
|
-
content: [
|
|
1223
|
-
{
|
|
1224
|
-
type: 'text',
|
|
1225
|
-
text: 'No skills found. Create prompts/skills/<name>.md or prompts/skills/<name>/SKILL.md to define skills for this repo.',
|
|
1226
|
-
},
|
|
1227
|
-
{ type: 'text', text: '```json\n' + JSON.stringify({ skills: [] }, null, 2) + '\n```' },
|
|
1228
|
-
],
|
|
1229
|
-
};
|
|
1230
|
-
}
|
|
1231
|
-
const textLines = results
|
|
1232
|
-
.map(
|
|
1233
|
-
(s, idx) =>
|
|
1234
|
-
`${idx + 1}. **${s.name}** (${s.key})\n 📁 ${path.relative(process.cwd(), s.path)}${s.summary ? `\n 📝 ${s.summary}` : ''
|
|
1235
|
-
}${s.tags && s.tags.length ? `\n 🔖 ${s.tags.join(', ')}` : ''}`,
|
|
1236
|
-
)
|
|
1237
|
-
.join('\n\n');
|
|
1238
|
-
return {
|
|
1239
|
-
content: [
|
|
1240
|
-
{ type: 'text', text: `Workspace skills (${results.length})\n\n${textLines}` },
|
|
1241
|
-
{ type: 'text', text: '```json\n' + JSON.stringify({ skills: results }, null, 2) + '\n```' },
|
|
1242
|
-
],
|
|
1243
|
-
};
|
|
1244
|
-
} catch (error) {
|
|
1245
|
-
return {
|
|
1246
|
-
content: [{ type: 'text', text: `Error listing workspace skills: ${error.message}` }],
|
|
1247
|
-
};
|
|
1248
|
-
}
|
|
1249
|
-
}
|
|
1250
|
-
|
|
1251
|
-
/**
|
|
1252
|
-
* Load a workspace skill by key and return its content.
|
|
1253
|
-
* Key is relative to prompts dir, e.g. "skills/backend-dev-guidelines".
|
|
1254
|
-
*/
|
|
1255
|
-
getWorkspaceSkill({ key }) {
|
|
1256
|
-
try {
|
|
1257
|
-
if (!key || !String(key).trim()) {
|
|
1258
|
-
return { content: [{ type: 'text', text: 'get_workspace_skill: key is required' }] };
|
|
1259
|
-
}
|
|
1260
|
-
const { readWorkspace, DEFAULT_DIR } = require('./services/prompts/workspace');
|
|
1261
|
-
const { resolveSkillFileByKey } = require('./services/skills/discovery');
|
|
1262
|
-
const ws = readWorkspace() || {};
|
|
1263
|
-
const promptsDir = ws.promptsDir || DEFAULT_DIR || 'prompts';
|
|
1264
|
-
const safeKey = String(key).trim().replace(/^\/+/, '').replace(/\.md$/i, '');
|
|
1265
|
-
const resolved = resolveSkillFileByKey({ promptsDir, key: safeKey });
|
|
1266
|
-
if (!resolved || !fs.existsSync(resolved.path)) {
|
|
1267
|
-
const expectedFlat = path.join(process.cwd(), promptsDir, `${safeKey}.md`);
|
|
1268
|
-
const expectedDir = path.join(process.cwd(), promptsDir, safeKey, 'SKILL.md');
|
|
1269
|
-
return {
|
|
1270
|
-
content: [
|
|
1271
|
-
{
|
|
1272
|
-
type: 'text',
|
|
1273
|
-
text: `Workspace skill not found for key '${safeKey}'. Expected at ${path.relative(process.cwd(), expectedFlat)} or ${path.relative(process.cwd(), expectedDir)}`,
|
|
1274
|
-
},
|
|
1275
|
-
],
|
|
1276
|
-
};
|
|
1277
|
-
}
|
|
1278
|
-
const body = fs.readFileSync(resolved.path, 'utf8');
|
|
1279
|
-
return {
|
|
1280
|
-
content: [
|
|
1281
|
-
{
|
|
1282
|
-
type: 'text',
|
|
1283
|
-
text: `Loaded workspace skill '${safeKey}' from ${path.relative(process.cwd(), resolved.path)}.\n\n${body}`,
|
|
1284
|
-
},
|
|
1285
|
-
{ type: 'text', text: '```json\n' + JSON.stringify({ key: safeKey, path: resolved.path, baseDir: resolved.baseDir, body }, null, 2) + '\n```' },
|
|
1286
|
-
],
|
|
1287
|
-
};
|
|
1288
|
-
} catch (error) {
|
|
1289
|
-
return { content: [{ type: 'text', text: `Error loading workspace skill: ${error.message}` }] };
|
|
1290
|
-
}
|
|
1291
|
-
}
|
|
1279
|
+
// NOTE: listWorkspaceSkills and getWorkspaceSkill have been removed.
|
|
1280
|
+
// Use list_prompts({ kind: 'skill' }) and get_prompt({ key }) instead.
|
|
1281
|
+
// ─────────────────────────────────────────────────────────────────────────────────────────
|
|
1292
1282
|
|
|
1293
1283
|
async getPromptByName(params = {}) {
|
|
1294
1284
|
const name = String(params.name || '').trim();
|
|
@@ -1381,38 +1371,87 @@ Note: This tool does NOT sign transactions. It prepares everything so you can ex
|
|
|
1381
1371
|
return this.searchPromptsUnifiedHandler(options);
|
|
1382
1372
|
}
|
|
1383
1373
|
|
|
1384
|
-
async listPrompts({ source = 'local', library = '', limit = 20 } = {}) {
|
|
1374
|
+
async listPrompts({ source = 'local', library = '', limit = 20, kind, publishable } = {}) {
|
|
1385
1375
|
try {
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
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
|
+
}
|
|
1393
1404
|
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
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
|
+
});
|
|
1404
1415
|
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
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
|
+
}
|
|
1412
1427
|
}
|
|
1413
1428
|
}
|
|
1414
1429
|
|
|
1415
|
-
|
|
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
|
+
};
|
|
1416
1455
|
} catch (error) {
|
|
1417
1456
|
return { content: [{ type: 'text', text: `Error listing prompts: ${error.message}` }] };
|
|
1418
1457
|
}
|
|
@@ -1424,6 +1463,41 @@ Note: This tool does NOT sign transactions. It prepares everything so you can ex
|
|
|
1424
1463
|
return { content: [{ type: 'text', text: 'Error: key parameter is required' }] };
|
|
1425
1464
|
}
|
|
1426
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
|
|
1427
1501
|
const results = await this.searchPromptsUnifiedHandler({
|
|
1428
1502
|
query: key,
|
|
1429
1503
|
source: 'local',
|
|
@@ -1452,7 +1526,12 @@ Note: This tool does NOT sign transactions. It prepares everything so you can ex
|
|
|
1452
1526
|
return { content: [{ type: 'text', text: `No prompt found with key "${key}"` }] };
|
|
1453
1527
|
}
|
|
1454
1528
|
|
|
1455
|
-
|
|
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\`\`\``;
|
|
1456
1535
|
|
|
1457
1536
|
return {
|
|
1458
1537
|
content: [
|
|
@@ -1585,6 +1664,61 @@ Note: This tool does NOT sign transactions. It prepares everything so you can ex
|
|
|
1585
1664
|
}
|
|
1586
1665
|
}
|
|
1587
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
|
+
|
|
1588
1722
|
async listTemplates(params = {}) {
|
|
1589
1723
|
try {
|
|
1590
1724
|
const result = this.templateManager.listTemplates(params || {});
|
|
@@ -2453,6 +2587,274 @@ Use \`help(topic="create")\` for more details.
|
|
|
2453
2587
|
return this.listLibrariesHandler(options);
|
|
2454
2588
|
}
|
|
2455
2589
|
|
|
2590
|
+
// ───────────────────────── Tool suggestion meta-router ─────────────────────────
|
|
2591
|
+
|
|
2592
|
+
tokenizeForMatching(text) {
|
|
2593
|
+
if (!text) return [];
|
|
2594
|
+
const lower = String(text).toLowerCase();
|
|
2595
|
+
const tokens = lower.split(/[^a-z0-9]+/g).filter(Boolean);
|
|
2596
|
+
// Small stopword list; just enough to ignore generic phrasing.
|
|
2597
|
+
const stop = new Set([
|
|
2598
|
+
'the', 'a', 'an', 'to', 'for', 'and', 'or', 'of', 'in', 'on', 'with', 'this', 'that',
|
|
2599
|
+
'i', 'me', 'my', 'we', 'our', 'you', 'your',
|
|
2600
|
+
'want', 'need', 'help', 'can', 'could', 'would', 'should',
|
|
2601
|
+
'how', 'do', 'does', 'did', 'what', 'is', 'are', 'it', 'be',
|
|
2602
|
+
]);
|
|
2603
|
+
return tokens.filter((t) => !stop.has(t));
|
|
2604
|
+
}
|
|
2605
|
+
|
|
2606
|
+
inferStage(goal, explicitStage) {
|
|
2607
|
+
if (explicitStage) return explicitStage;
|
|
2608
|
+
if (!goal) return null;
|
|
2609
|
+
|
|
2610
|
+
const patterns = {
|
|
2611
|
+
prompt_workspace: /\b(prompt|skill|workspace|create|edit|draft|write|local)\b/gi,
|
|
2612
|
+
persona: /\b(persona|metaprompt|interview|agent|character|assistant|system prompt)\b/gi,
|
|
2613
|
+
libraries: /\b(library|manifest|template|collection|import|export)\b/gi,
|
|
2614
|
+
governance: /\b(publish|proposal|vote|subdao|dao|govern|approve|execute)\b/gi,
|
|
2615
|
+
treasury: /\b(treasury|bond|boost|withdraw|sxxx|token|stake)\b/gi,
|
|
2616
|
+
discovery: /\b(search|find|list|browse|explore|trending|discover)\b/gi,
|
|
2617
|
+
};
|
|
2618
|
+
|
|
2619
|
+
const scores = {};
|
|
2620
|
+
for (const [stage, pattern] of Object.entries(patterns)) {
|
|
2621
|
+
const matches = goal.match(pattern) || [];
|
|
2622
|
+
if (matches.length > 0) scores[stage] = matches.length;
|
|
2623
|
+
}
|
|
2624
|
+
|
|
2625
|
+
const entries = Object.entries(scores);
|
|
2626
|
+
if (!entries.length) return null;
|
|
2627
|
+
entries.sort((a, b) => b[1] - a[1]);
|
|
2628
|
+
return entries[0][0];
|
|
2629
|
+
}
|
|
2630
|
+
|
|
2631
|
+
detectWorkflow(goal) {
|
|
2632
|
+
if (!goal) return null;
|
|
2633
|
+
const lower = goal.toLowerCase();
|
|
2634
|
+
|
|
2635
|
+
const workflows = [
|
|
2636
|
+
{
|
|
2637
|
+
// Create & publish a prompt/library
|
|
2638
|
+
pattern: /create.*prompt.*publish|publish.*prompt|create.*library.*publish|publish.*library/i,
|
|
2639
|
+
tools: ['quick_create_prompt', 'improve_prompt', 'publish_manifest_flow'],
|
|
2640
|
+
},
|
|
2641
|
+
{
|
|
2642
|
+
// Design & (optionally) publish a persona
|
|
2643
|
+
pattern: /create.*persona.*publish|design.*persona.*publish|persona.*publish/i,
|
|
2644
|
+
tools: ['list_persona_templates', 'persona_interview_step', 'save_metaprompt', 'publish_manifest_flow'],
|
|
2645
|
+
},
|
|
2646
|
+
{
|
|
2647
|
+
// Design a persona without explicit publishing intent
|
|
2648
|
+
pattern: /design.*persona|create.*persona|persona.*workflow/i,
|
|
2649
|
+
tools: ['list_persona_templates', 'persona_interview_step', 'save_metaprompt'],
|
|
2650
|
+
},
|
|
2651
|
+
{
|
|
2652
|
+
// Find & iterate on prompts
|
|
2653
|
+
pattern: /find.*prompt.*iterate|iterate.*prompt|improve.*prompt/i,
|
|
2654
|
+
tools: ['search_prompts', 'get_prompt', 'quick_iterate_prompt'],
|
|
2655
|
+
},
|
|
2656
|
+
{
|
|
2657
|
+
// Publish to a DAO/SubDAO
|
|
2658
|
+
pattern: /publish.*dao|dao.*publish|publish.*subdao|subdao.*publish/i,
|
|
2659
|
+
tools: ['suggest_subdaos_for_library', 'generate_publishing_commands', 'publish_manifest_flow'],
|
|
2660
|
+
},
|
|
2661
|
+
];
|
|
2662
|
+
|
|
2663
|
+
for (const wf of workflows) {
|
|
2664
|
+
if (wf.pattern.test(lower)) {
|
|
2665
|
+
return wf.tools;
|
|
2666
|
+
}
|
|
2667
|
+
}
|
|
2668
|
+
return null;
|
|
2669
|
+
}
|
|
2670
|
+
|
|
2671
|
+
scoreToolForGoal(toolMeta, goalTokens, stage) {
|
|
2672
|
+
// BM25-inspired scoring with simple term frequency and length normalization.
|
|
2673
|
+
const baseTerms = [
|
|
2674
|
+
...(toolMeta.keywords || []),
|
|
2675
|
+
...this.tokenizeForMatching(toolMeta.description || ''),
|
|
2676
|
+
...this.tokenizeForMatching(toolMeta.whenToUse || ''),
|
|
2677
|
+
];
|
|
2678
|
+
|
|
2679
|
+
const docLen = baseTerms.length || 1;
|
|
2680
|
+
const avgLen = 15;
|
|
2681
|
+
const k1 = 1.2;
|
|
2682
|
+
const b = 0.75;
|
|
2683
|
+
|
|
2684
|
+
let score = 0;
|
|
2685
|
+
|
|
2686
|
+
for (const token of goalTokens) {
|
|
2687
|
+
const tf = baseTerms.filter((t) => t === token).length;
|
|
2688
|
+
if (tf > 0) {
|
|
2689
|
+
const tfScore = (tf * (k1 + 1)) / (tf + k1 * (1 - b + b * (docLen / avgLen)));
|
|
2690
|
+
score += tfScore;
|
|
2691
|
+
}
|
|
2692
|
+
}
|
|
2693
|
+
|
|
2694
|
+
// Negative keyword penalty.
|
|
2695
|
+
if (toolMeta.negativeKeywords && toolMeta.negativeKeywords.length) {
|
|
2696
|
+
let penalties = 0;
|
|
2697
|
+
for (const neg of toolMeta.negativeKeywords) {
|
|
2698
|
+
if (goalTokens.includes(neg)) penalties += 1;
|
|
2699
|
+
}
|
|
2700
|
+
if (penalties > 0) {
|
|
2701
|
+
score *= (0.5 ** penalties);
|
|
2702
|
+
}
|
|
2703
|
+
}
|
|
2704
|
+
|
|
2705
|
+
// Stage/category boost.
|
|
2706
|
+
if (stage && toolMeta.category === stage) {
|
|
2707
|
+
score *= 1.5;
|
|
2708
|
+
}
|
|
2709
|
+
|
|
2710
|
+
// Base weight.
|
|
2711
|
+
const weight = typeof toolMeta.weight === 'number' ? toolMeta.weight : 1;
|
|
2712
|
+
score *= weight;
|
|
2713
|
+
|
|
2714
|
+
// Normalize to 0–1 range.
|
|
2715
|
+
return Math.min(score / 5, 1);
|
|
2716
|
+
}
|
|
2717
|
+
|
|
2718
|
+
orderSuggestedTools(scored, limit) {
|
|
2719
|
+
if (!Array.isArray(scored) || !scored.length) return [];
|
|
2720
|
+
const sorted = [...scored].sort((a, b) => b.confidence - a.confidence).slice(0, limit);
|
|
2721
|
+
if (sorted.length <= 3) {
|
|
2722
|
+
return sorted.map((item, idx) => ({ ...item, priority: idx + 1 }));
|
|
2723
|
+
}
|
|
2724
|
+
|
|
2725
|
+
// Simple U-shaped ordering: higher-ranked items gravitate to the ends.
|
|
2726
|
+
const result = [];
|
|
2727
|
+
for (let i = 0; i < sorted.length; i += 1) {
|
|
2728
|
+
if (i % 2 === 0) {
|
|
2729
|
+
// Even indices append to the end
|
|
2730
|
+
result.push(sorted[i]);
|
|
2731
|
+
} else {
|
|
2732
|
+
// Odd indices unshift to the front
|
|
2733
|
+
result.unshift(sorted[i]);
|
|
2734
|
+
}
|
|
2735
|
+
}
|
|
2736
|
+
|
|
2737
|
+
return result.map((item, idx) => ({ ...item, priority: idx + 1 }));
|
|
2738
|
+
}
|
|
2739
|
+
|
|
2740
|
+
async suggestSageTools(params) {
|
|
2741
|
+
// Arguments are already validated by createToolArgsValidator.
|
|
2742
|
+
const { goal, stage, context, limit = 5, includeAlternatives = false } = params || {};
|
|
2743
|
+
|
|
2744
|
+
const effectiveStage = this.inferStage(goal, stage);
|
|
2745
|
+
const goalTokens = this.tokenizeForMatching(goal);
|
|
2746
|
+
|
|
2747
|
+
// Choose candidate tools from registry.
|
|
2748
|
+
let candidates = [];
|
|
2749
|
+
if (effectiveStage === 'discovery') {
|
|
2750
|
+
// Discovery is intentionally cross-cutting; surface the most browse/search oriented tools.
|
|
2751
|
+
const discoveryTools = ['search_prompts', 'list_prompts', 'trending_prompts', 'list_persona_templates', 'list_libraries'];
|
|
2752
|
+
candidates = discoveryTools
|
|
2753
|
+
.map((name) => getToolMeta(name))
|
|
2754
|
+
.filter(Boolean);
|
|
2755
|
+
} else if (effectiveStage) {
|
|
2756
|
+
candidates = getToolsForCategory(effectiveStage);
|
|
2757
|
+
} else {
|
|
2758
|
+
// If no clear stage, fall back to a small set of discovery-friendly tools.
|
|
2759
|
+
const discoveryTools = ['search_prompts', 'list_prompts', 'trending_prompts', 'list_persona_templates', 'list_libraries'];
|
|
2760
|
+
candidates = discoveryTools
|
|
2761
|
+
.map((name) => getToolMeta(name))
|
|
2762
|
+
.filter(Boolean);
|
|
2763
|
+
}
|
|
2764
|
+
|
|
2765
|
+
// Cross-category fallback: if we have a clear stage, allow strongly-matching tools
|
|
2766
|
+
// from other categories to join the candidate set.
|
|
2767
|
+
if (effectiveStage) {
|
|
2768
|
+
const allTools = Object.entries(TOOL_REGISTRY).map(([name, meta]) => ({ name, ...meta }));
|
|
2769
|
+
const existingNames = new Set(candidates.map((c) => c.name));
|
|
2770
|
+
const crossCategory = allTools
|
|
2771
|
+
.filter((t) => t.category !== effectiveStage && !existingNames.has(t.name))
|
|
2772
|
+
.map((t) => ({
|
|
2773
|
+
...t,
|
|
2774
|
+
confidence: this.scoreToolForGoal(t, goalTokens, null),
|
|
2775
|
+
}))
|
|
2776
|
+
.filter((t) => t.confidence > 0.6);
|
|
2777
|
+
|
|
2778
|
+
if (crossCategory.length) {
|
|
2779
|
+
candidates = candidates.concat(crossCategory);
|
|
2780
|
+
}
|
|
2781
|
+
}
|
|
2782
|
+
|
|
2783
|
+
// Apply simple context-based filtering/hints.
|
|
2784
|
+
if (context && context.hasWorkspace === false) {
|
|
2785
|
+
// If there is no workspace, de-prioritize workspace-only tools.
|
|
2786
|
+
candidates = candidates.map((c) => {
|
|
2787
|
+
if (c.category === 'prompt_workspace') {
|
|
2788
|
+
return { ...c, weight: (c.weight || 1) * 0.7 };
|
|
2789
|
+
}
|
|
2790
|
+
return c;
|
|
2791
|
+
});
|
|
2792
|
+
}
|
|
2793
|
+
|
|
2794
|
+
// Score tools.
|
|
2795
|
+
const scored = candidates.map((meta) => ({
|
|
2796
|
+
...meta,
|
|
2797
|
+
confidence: this.scoreToolForGoal(meta, goalTokens, effectiveStage),
|
|
2798
|
+
})).filter((item) => item.confidence >= 0.15);
|
|
2799
|
+
|
|
2800
|
+
if (!scored.length) {
|
|
2801
|
+
const fallback = {
|
|
2802
|
+
recommendations: [],
|
|
2803
|
+
alternatives: [],
|
|
2804
|
+
inferredStage: effectiveStage || null,
|
|
2805
|
+
suggestedWorkflow: null,
|
|
2806
|
+
};
|
|
2807
|
+
return {
|
|
2808
|
+
content: [{
|
|
2809
|
+
type: 'text',
|
|
2810
|
+
text: JSON.stringify(fallback, null, 2),
|
|
2811
|
+
}],
|
|
2812
|
+
};
|
|
2813
|
+
}
|
|
2814
|
+
|
|
2815
|
+
const ordered = this.orderSuggestedTools(scored, limit);
|
|
2816
|
+
const workflow = this.detectWorkflow(goal);
|
|
2817
|
+
|
|
2818
|
+
const recommendations = ordered.map((item) => ({
|
|
2819
|
+
toolName: item.name,
|
|
2820
|
+
category: item.category,
|
|
2821
|
+
confidence: item.confidence,
|
|
2822
|
+
priority: item.priority,
|
|
2823
|
+
rationale: item.whenToUse,
|
|
2824
|
+
requiredParams: item.requiredParams || [],
|
|
2825
|
+
}));
|
|
2826
|
+
|
|
2827
|
+
let alternatives = [];
|
|
2828
|
+
if (includeAlternatives) {
|
|
2829
|
+
const remaining = scored
|
|
2830
|
+
.filter((s) => !ordered.find((r) => r.name === s.name))
|
|
2831
|
+
.sort((a, b) => b.confidence - a.confidence)
|
|
2832
|
+
.slice(0, limit);
|
|
2833
|
+
alternatives = remaining.map((item, idx) => ({
|
|
2834
|
+
toolName: item.name,
|
|
2835
|
+
category: item.category,
|
|
2836
|
+
confidence: item.confidence,
|
|
2837
|
+
priority: idx + 1,
|
|
2838
|
+
rationale: item.whenToUse,
|
|
2839
|
+
requiredParams: item.requiredParams || [],
|
|
2840
|
+
}));
|
|
2841
|
+
}
|
|
2842
|
+
|
|
2843
|
+
const result = {
|
|
2844
|
+
recommendations,
|
|
2845
|
+
alternatives,
|
|
2846
|
+
inferredStage: effectiveStage || null,
|
|
2847
|
+
suggestedWorkflow: workflow || null,
|
|
2848
|
+
};
|
|
2849
|
+
|
|
2850
|
+
return {
|
|
2851
|
+
content: [{
|
|
2852
|
+
type: 'text',
|
|
2853
|
+
text: JSON.stringify(result, null, 2),
|
|
2854
|
+
}],
|
|
2855
|
+
};
|
|
2856
|
+
}
|
|
2857
|
+
|
|
2456
2858
|
listMetaprompts() {
|
|
2457
2859
|
const rows = metapromptDesigner.listMetaprompts();
|
|
2458
2860
|
if (!rows.length) {
|
|
@@ -2848,6 +3250,207 @@ Use \`help(topic="create")\` for more details.
|
|
|
2848
3250
|
}
|
|
2849
3251
|
}
|
|
2850
3252
|
|
|
3253
|
+
// ───────────────────────────── Metaprompt Engine Tools ─────────────────────────────
|
|
3254
|
+
|
|
3255
|
+
async listPersonaTemplates() {
|
|
3256
|
+
try {
|
|
3257
|
+
const templates = require('./services/metaprompt/templates.json');
|
|
3258
|
+
const list = Object.values(templates).map(t => ({
|
|
3259
|
+
key: t.key,
|
|
3260
|
+
name: t.name,
|
|
3261
|
+
description: t.description,
|
|
3262
|
+
recommended_slots: t.recommended_slots?.map(s => s.label) || []
|
|
3263
|
+
}));
|
|
3264
|
+
|
|
3265
|
+
return {
|
|
3266
|
+
content: [
|
|
3267
|
+
{
|
|
3268
|
+
type: 'text',
|
|
3269
|
+
text: JSON.stringify(list, null, 2)
|
|
3270
|
+
}
|
|
3271
|
+
]
|
|
3272
|
+
};
|
|
3273
|
+
} catch (error) {
|
|
3274
|
+
return {
|
|
3275
|
+
isError: true,
|
|
3276
|
+
content: [{ type: 'text', text: `Failed to list templates: ${error.message}` }]
|
|
3277
|
+
};
|
|
3278
|
+
}
|
|
3279
|
+
}
|
|
3280
|
+
|
|
3281
|
+
async runPersonaInterview(params) {
|
|
3282
|
+
const { template, answers, save, saveKey } = params;
|
|
3283
|
+
try {
|
|
3284
|
+
const config = require('./config');
|
|
3285
|
+
const PromptBuilder = require('./services/metaprompt/prompt-builder');
|
|
3286
|
+
const MetapromptPersistence = require('./services/metaprompt/persistence');
|
|
3287
|
+
const templates = require('./services/metaprompt/templates.json');
|
|
3288
|
+
|
|
3289
|
+
// 1. Validate Template
|
|
3290
|
+
const templateDef = templates[template] || templates['custom'];
|
|
3291
|
+
|
|
3292
|
+
// 2. Build Prompt (One-Shot)
|
|
3293
|
+
// Note: run_persona_interview assumes the *agent* has already done the planning/filling.
|
|
3294
|
+
// It bypasses the SlotPlanner and just builds the artifact.
|
|
3295
|
+
const builder = new PromptBuilder();
|
|
3296
|
+
// We need the full slot definitions to build dynamic sections.
|
|
3297
|
+
// Since we skipped the planner, we'll use the template's recommended slots as the definition.
|
|
3298
|
+
const slots = templateDef.recommended_slots || [];
|
|
3299
|
+
|
|
3300
|
+
const systemPrompt = builder.buildSystemPrompt(template, slots, answers || {});
|
|
3301
|
+
|
|
3302
|
+
let result = {
|
|
3303
|
+
systemPrompt,
|
|
3304
|
+
slug: '',
|
|
3305
|
+
paths: {}
|
|
3306
|
+
};
|
|
3307
|
+
|
|
3308
|
+
// 3. Save (Optional)
|
|
3309
|
+
if (save) {
|
|
3310
|
+
const persistence = new MetapromptPersistence(config);
|
|
3311
|
+
const slug = saveKey || `${template}-${Date.now().toString().slice(-6)}`;
|
|
3312
|
+
|
|
3313
|
+
// Save dummy history so the artifact exists
|
|
3314
|
+
const historyPaths = persistence.saveMetaprompt(slug, {
|
|
3315
|
+
templateKey: template,
|
|
3316
|
+
transcript: [{ role: 'system', content: 'Generated via MCP run_persona_interview (one-shot).' }],
|
|
3317
|
+
answers: answers || {}
|
|
3318
|
+
});
|
|
3319
|
+
|
|
3320
|
+
// Save Skill
|
|
3321
|
+
const skillPath = persistence.saveSkill(slug, systemPrompt);
|
|
3322
|
+
|
|
3323
|
+
result.slug = slug;
|
|
3324
|
+
result.paths = {
|
|
3325
|
+
metaprompt: historyPaths.metaprompt,
|
|
3326
|
+
skill: skillPath
|
|
3327
|
+
};
|
|
3328
|
+
}
|
|
3329
|
+
|
|
3330
|
+
return {
|
|
3331
|
+
content: [
|
|
3332
|
+
{
|
|
3333
|
+
type: 'text',
|
|
3334
|
+
text: JSON.stringify(result, null, 2)
|
|
3335
|
+
}
|
|
3336
|
+
]
|
|
3337
|
+
};
|
|
3338
|
+
|
|
3339
|
+
} catch (error) {
|
|
3340
|
+
return {
|
|
3341
|
+
isError: true,
|
|
3342
|
+
content: [{ type: 'text', text: `Failed to run persona interview: ${error.message}` }]
|
|
3343
|
+
};
|
|
3344
|
+
}
|
|
3345
|
+
}
|
|
3346
|
+
|
|
3347
|
+
/**
|
|
3348
|
+
* Stateful persona interview that walks through template slots.
|
|
3349
|
+
*
|
|
3350
|
+
* Returns slot metadata (label, description, group) for the host agent to phrase questions.
|
|
3351
|
+
* The agent passes answers back with the stateToken to progress through slots.
|
|
3352
|
+
* When all required slots are filled, returns the generated system prompt.
|
|
3353
|
+
*
|
|
3354
|
+
* @param {object} params
|
|
3355
|
+
* @param {string} params.template - Template key (coding-assistant, governance-helper, etc.)
|
|
3356
|
+
* @param {string} [params.stateToken] - Base64-encoded state from previous call
|
|
3357
|
+
* @param {string} [params.answer] - Answer for the current slot (currentSlotKey from previous response)
|
|
3358
|
+
*/
|
|
3359
|
+
async personaInterviewStep(params) {
|
|
3360
|
+
const { template, stateToken, answer } = params;
|
|
3361
|
+
try {
|
|
3362
|
+
const templates = require('./services/metaprompt/templates.json');
|
|
3363
|
+
const PromptBuilder = require('./services/metaprompt/prompt-builder');
|
|
3364
|
+
|
|
3365
|
+
// 1. Restore or initialize state
|
|
3366
|
+
let state;
|
|
3367
|
+
if (stateToken) {
|
|
3368
|
+
try {
|
|
3369
|
+
state = JSON.parse(Buffer.from(stateToken, 'base64').toString('utf8'));
|
|
3370
|
+
} catch {
|
|
3371
|
+
return { isError: true, content: [{ type: 'text', text: 'Invalid stateToken' }] };
|
|
3372
|
+
}
|
|
3373
|
+
} else {
|
|
3374
|
+
// Initialize new interview state
|
|
3375
|
+
const templateKey = template || 'custom';
|
|
3376
|
+
const templateDef = templates[templateKey] || templates.custom;
|
|
3377
|
+
state = {
|
|
3378
|
+
templateKey,
|
|
3379
|
+
slots: templateDef.recommended_slots || [],
|
|
3380
|
+
answers: {},
|
|
3381
|
+
currentSlotKey: null,
|
|
3382
|
+
};
|
|
3383
|
+
}
|
|
3384
|
+
|
|
3385
|
+
// 2. Record answer for the current slot (if provided)
|
|
3386
|
+
if (answer && state.currentSlotKey) {
|
|
3387
|
+
state.answers[state.currentSlotKey] = answer;
|
|
3388
|
+
}
|
|
3389
|
+
|
|
3390
|
+
// 3. Find next unanswered slot (required first, then optional by priority)
|
|
3391
|
+
const unanswered = state.slots.filter(s => !state.answers[s.key]);
|
|
3392
|
+
const requiredUnanswered = unanswered.filter(s => s.required);
|
|
3393
|
+
const nextSlot = requiredUnanswered[0] || unanswered.find(s => s.priority <= 2) || null;
|
|
3394
|
+
|
|
3395
|
+
// 4. Update state with current slot key
|
|
3396
|
+
state.currentSlotKey = nextSlot?.key || null;
|
|
3397
|
+
|
|
3398
|
+
// 5. Serialize state for next call
|
|
3399
|
+
const newStateToken = Buffer.from(JSON.stringify(state)).toString('base64');
|
|
3400
|
+
|
|
3401
|
+
// 6. Check if interview is complete
|
|
3402
|
+
if (!nextSlot) {
|
|
3403
|
+
// All required slots filled - build the system prompt (no LLM needed)
|
|
3404
|
+
const builder = new PromptBuilder();
|
|
3405
|
+
const systemPrompt = builder.buildSystemPrompt(state.templateKey, state.slots, state.answers);
|
|
3406
|
+
|
|
3407
|
+
return {
|
|
3408
|
+
content: [{
|
|
3409
|
+
type: 'text',
|
|
3410
|
+
text: JSON.stringify({
|
|
3411
|
+
done: true,
|
|
3412
|
+
systemPrompt,
|
|
3413
|
+
template: state.templateKey,
|
|
3414
|
+
answers: state.answers,
|
|
3415
|
+
filledSlots: Object.keys(state.answers).length,
|
|
3416
|
+
totalSlots: state.slots.length,
|
|
3417
|
+
stateToken: newStateToken,
|
|
3418
|
+
}, null, 2)
|
|
3419
|
+
}]
|
|
3420
|
+
};
|
|
3421
|
+
}
|
|
3422
|
+
|
|
3423
|
+
// 7. Return next slot info for the host LLM to phrase the question
|
|
3424
|
+
// The host LLM should use slotLabel/slotDescription to ask the user
|
|
3425
|
+
const suggestedQuestion = `Please provide: ${nextSlot.label}. ${nextSlot.description || ''}`.trim();
|
|
3426
|
+
|
|
3427
|
+
return {
|
|
3428
|
+
content: [{
|
|
3429
|
+
type: 'text',
|
|
3430
|
+
text: JSON.stringify({
|
|
3431
|
+
done: false,
|
|
3432
|
+
currentSlotKey: nextSlot.key,
|
|
3433
|
+
slotLabel: nextSlot.label,
|
|
3434
|
+
slotDescription: nextSlot.description || '',
|
|
3435
|
+
slotGroup: nextSlot.group || 'general',
|
|
3436
|
+
slotRequired: nextSlot.required || false,
|
|
3437
|
+
suggestedQuestion,
|
|
3438
|
+
filledSlots: Object.keys(state.answers).length,
|
|
3439
|
+
totalSlots: state.slots.length,
|
|
3440
|
+
remainingRequired: requiredUnanswered.length,
|
|
3441
|
+
stateToken: newStateToken,
|
|
3442
|
+
}, null, 2)
|
|
3443
|
+
}]
|
|
3444
|
+
};
|
|
3445
|
+
|
|
3446
|
+
} catch (error) {
|
|
3447
|
+
return {
|
|
3448
|
+
isError: true,
|
|
3449
|
+
content: [{ type: 'text', text: `Failed to execute interview step: ${error.message}` }]
|
|
3450
|
+
};
|
|
3451
|
+
}
|
|
3452
|
+
}
|
|
3453
|
+
|
|
2851
3454
|
async getSubDAOList(options = {}) {
|
|
2852
3455
|
try {
|
|
2853
3456
|
return await this.subdaoDiscovery.listSubDaos(options);
|