wyrm-mcp 3.3.0 → 3.4.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.
package/dist/index.js CHANGED
@@ -32,6 +32,9 @@ import { IndexingPipeline } from "./indexer.js";
32
32
  import { KnowledgeGraph } from "./knowledge-graph.js";
33
33
  import { MemoryArtifacts } from "./memory-artifacts.js";
34
34
  import { GroundTruths, ReasoningScaffolds } from "./intelligence.js";
35
+ import { classifyCapture } from "./capture.js";
36
+ import { injectSystemPrompt } from "./autoconfig.js";
37
+ export { classifyCapture } from "./capture.js";
35
38
  import { initializeLicense, getLicenseInfo, hasFeature, getTier, activateLicense } from "./license.js";
36
39
  import { WyrmAnalytics } from "./analytics.js";
37
40
  import { WyrmCloudBackup } from "./cloud-backup.js";
@@ -223,6 +226,7 @@ const READ_ONLY_TOOLS = new Set([
223
226
  "wyrm_context_build",
224
227
  "wyrm_truth_get",
225
228
  "wyrm_scaffold_get",
229
+ "wyrm_session_prime",
226
230
  ]);
227
231
  /** Tools that mutate data — invalidate relevant caches */
228
232
  const WRITE_TOOLS = new Set([
@@ -252,10 +256,15 @@ const WRITE_TOOLS = new Set([
252
256
  "wyrm_scaffold_save",
253
257
  "wyrm_distill",
254
258
  "wyrm_review",
259
+ "wyrm_capture",
260
+ "wyrm_import_git",
261
+ "wyrm_import_pr",
262
+ "wyrm_import_rules",
263
+ "wyrm_inject_prompt",
255
264
  ]);
256
265
  const server = new Server({
257
266
  name: "wyrm",
258
- version: "3.2.0",
267
+ version: "3.4.0",
259
268
  }, {
260
269
  capabilities: {
261
270
  tools: {},
@@ -983,6 +992,101 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
983
992
  required: ["artifactId", "approved"],
984
993
  },
985
994
  },
995
+ // ==================== v3.4.0 TOOLS ====================
996
+ {
997
+ name: "wyrm_capture",
998
+ description: "Unified capture with auto-classification. Detects whether content is a quest, ground truth, or memory artifact and stores it automatically. Use this for any quick thought, decision, task, or lesson you want to preserve.",
999
+ inputSchema: {
1000
+ type: "object",
1001
+ properties: {
1002
+ content: { type: "string", description: "The text to classify and store" },
1003
+ project_id: { type: "number", description: "Project ID to associate with (optional)" },
1004
+ tags: { type: "array", items: { type: "string" }, description: "Optional tags" },
1005
+ mode: { type: "string", enum: ["auto", "quest", "truth", "memory"], description: "Override auto-classification mode" },
1006
+ },
1007
+ required: ["content"],
1008
+ },
1009
+ },
1010
+ {
1011
+ name: "wyrm_session_prime",
1012
+ description: "Assemble a rich session context brief — ground truths, reasoning scaffold, memory artifacts, and active quests — in a single call. Use at the start of any complex conversation to load all relevant project knowledge.",
1013
+ inputSchema: {
1014
+ type: "object",
1015
+ properties: {
1016
+ project_id: { type: "number", description: "Project ID to prime for" },
1017
+ project_name: { type: "string", description: "Project name (used if project_id not given)" },
1018
+ task: { type: "string", description: "Current task — used to find best scaffold and relevant memories" },
1019
+ log_session: { type: "boolean", description: "If true, also create a session log entry (default: false)" },
1020
+ },
1021
+ },
1022
+ },
1023
+ {
1024
+ name: "wyrm_import_git",
1025
+ description: "Import a list of commits into the Wyrm memory review queue. Useful for onboarding a project's history or capturing recent work.",
1026
+ inputSchema: {
1027
+ type: "object",
1028
+ properties: {
1029
+ project_id: { type: "number", description: "Project ID" },
1030
+ commits: {
1031
+ type: "array",
1032
+ description: "Array of commit objects",
1033
+ items: {
1034
+ type: "object",
1035
+ properties: {
1036
+ message: { type: "string" },
1037
+ diff_summary: { type: "string" },
1038
+ author: { type: "string" },
1039
+ date: { type: "string" },
1040
+ },
1041
+ required: ["message"],
1042
+ },
1043
+ },
1044
+ auto_approve: { type: "boolean", description: "Skip review queue and activate immediately (default: false)" },
1045
+ },
1046
+ required: ["project_id", "commits"],
1047
+ },
1048
+ },
1049
+ {
1050
+ name: "wyrm_import_pr",
1051
+ description: "Import a pull request (title, body, review comments) into the Wyrm memory review queue to extract patterns and heuristics.",
1052
+ inputSchema: {
1053
+ type: "object",
1054
+ properties: {
1055
+ project_id: { type: "number", description: "Project ID" },
1056
+ title: { type: "string", description: "PR title" },
1057
+ body: { type: "string", description: "PR description / body" },
1058
+ review_comments: { type: "array", items: { type: "string" }, description: "Review comment strings" },
1059
+ auto_approve: { type: "boolean", description: "Skip review queue (default: false)" },
1060
+ },
1061
+ required: ["project_id", "title", "body"],
1062
+ },
1063
+ },
1064
+ {
1065
+ name: "wyrm_import_rules",
1066
+ description: "Import a .cursorrules / copilot-instructions file into Wyrm. Parses by section and creates ground truths (for constraint/rule language) or memory artifacts (for general guidelines).",
1067
+ inputSchema: {
1068
+ type: "object",
1069
+ properties: {
1070
+ project_id: { type: "number", description: "Project ID" },
1071
+ content: { type: "string", description: "Full text of the rules file" },
1072
+ format: { type: "string", enum: ["cursorrules", "copilot", "plain"], description: "Source format (default: plain)" },
1073
+ source_file: { type: "string", description: "Filename for provenance (optional)" },
1074
+ },
1075
+ required: ["project_id", "content"],
1076
+ },
1077
+ },
1078
+ {
1079
+ name: "wyrm_inject_prompt",
1080
+ description: "Opt-in: inject a Wyrm session-prime instruction block into .github/copilot-instructions.md and/or .cursor/rules for a project. Uses managed markers so re-runs are safe.",
1081
+ inputSchema: {
1082
+ type: "object",
1083
+ properties: {
1084
+ project_path: { type: "string", description: "Absolute path to the project root" },
1085
+ clients: { type: "array", items: { type: "string" }, description: "Clients to inject into: 'copilot', 'cursor' (default: both detected)" },
1086
+ },
1087
+ required: ["project_path"],
1088
+ },
1089
+ },
986
1090
  ],
987
1091
  }));
988
1092
  // Tool implementations
@@ -2454,6 +2558,317 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
2454
2558
  return { content: [{ type: "text", text: `Unknown encrypt action: ${encAction}. Use: status, enable, test` }], isError: true };
2455
2559
  }
2456
2560
  }
2561
+ // ==================== v3.4.0 TOOLS ====================
2562
+ case "wyrm_capture": {
2563
+ const { content: capContent, project_id: capProjId, tags: capTags, mode: capMode } = args;
2564
+ // Determine classification
2565
+ let classified = classifyCapture(capContent);
2566
+ if (capMode && capMode !== 'auto') {
2567
+ const subtypeMap = { quest: 'quest', truth: 'decision', memory: 'pattern' };
2568
+ classified = {
2569
+ type: capMode,
2570
+ subtype: subtypeMap[capMode] ?? capMode,
2571
+ confidence: 100,
2572
+ reasoning: `Mode override: ${capMode}`,
2573
+ };
2574
+ }
2575
+ const { type: capType, subtype: capSubtype, confidence: capConf, reasoning: capReasoning } = classified;
2576
+ const effectiveProjId = capProjId ?? null;
2577
+ let storedId = 0;
2578
+ let typeShort = '';
2579
+ let needsReviewNote = '';
2580
+ if (capType === 'quest') {
2581
+ if (effectiveProjId === null) {
2582
+ return { content: [{ type: "text", text: `🧠 Cannot capture quest without project_id. Please provide project_id.` }], isError: true };
2583
+ }
2584
+ const quest = db.addQuest(effectiveProjId, capContent.slice(0, 200), '', 'medium', capTags?.join(','));
2585
+ storedId = quest.id;
2586
+ typeShort = 'quest';
2587
+ cache.invalidate('wyrm_all_quests');
2588
+ cache.invalidate('wyrm_stats');
2589
+ }
2590
+ else if (capType === 'truth') {
2591
+ if (effectiveProjId === null) {
2592
+ return { content: [{ type: "text", text: `🧠 Cannot capture truth without project_id. Please provide project_id.` }], isError: true };
2593
+ }
2594
+ if (capMode !== 'truth' && capConf < 100) {
2595
+ // Store as artifact pending review
2596
+ const artifact = memory.add(effectiveProjId, {
2597
+ kind: 'pattern',
2598
+ problem: capContent,
2599
+ validatedFix: '',
2600
+ whyItWorked: '',
2601
+ tags: capTags ?? [],
2602
+ confidence: capConf / 100,
2603
+ needsReview: 1,
2604
+ });
2605
+ storedId = artifact.id;
2606
+ typeShort = 'mem';
2607
+ needsReviewNote = `\n⚠️ Stored for review — run \`wyrm_review\` to activate`;
2608
+ }
2609
+ else {
2610
+ const truth = groundTruths.set(effectiveProjId, {
2611
+ category: 'decision',
2612
+ key: capContent.slice(0, 60),
2613
+ value: capContent,
2614
+ });
2615
+ storedId = truth.id;
2616
+ typeShort = 'truth';
2617
+ cache.invalidate('wyrm_truth_get');
2618
+ cache.invalidate('wyrm_context_build');
2619
+ }
2620
+ }
2621
+ else {
2622
+ // memory
2623
+ if (effectiveProjId === null) {
2624
+ return { content: [{ type: "text", text: `🧠 Cannot capture memory without project_id. Please provide project_id.` }], isError: true };
2625
+ }
2626
+ const shouldAutoCreate = capConf >= 75;
2627
+ const artifact = memory.add(effectiveProjId, {
2628
+ kind: capSubtype,
2629
+ problem: capContent,
2630
+ validatedFix: '',
2631
+ whyItWorked: '',
2632
+ tags: capTags ?? [],
2633
+ confidence: capConf / 100,
2634
+ needsReview: shouldAutoCreate ? 0 : 1,
2635
+ });
2636
+ storedId = artifact.id;
2637
+ typeShort = 'mem';
2638
+ if (!shouldAutoCreate) {
2639
+ needsReviewNote = `\n⚠️ Stored for review — run \`wyrm_review\` to activate`;
2640
+ }
2641
+ }
2642
+ return {
2643
+ content: [{
2644
+ type: "text",
2645
+ text: `🧠 Captured as ${capType}: ${capSubtype}\nConfidence: ${capConf}% | ${capReasoning}\nID: ${typeShort}:${storedId}${needsReviewNote}`,
2646
+ }],
2647
+ };
2648
+ }
2649
+ case "wyrm_session_prime": {
2650
+ const { project_id: spId, project_name: spName, task: spTask, log_session: spLog } = args;
2651
+ // Project resolution
2652
+ let spProject;
2653
+ if (spId) {
2654
+ spProject = db.getProjectById(spId);
2655
+ }
2656
+ else if (spName) {
2657
+ const rawDb = db.getDatabase();
2658
+ spProject = rawDb.prepare('SELECT * FROM projects WHERE LOWER(name) LIKE LOWER(?) LIMIT 1').get(`%${spName}%`);
2659
+ }
2660
+ else {
2661
+ const rawDb = db.getDatabase();
2662
+ spProject = rawDb.prepare(`
2663
+ SELECT p.* FROM projects p
2664
+ JOIN sessions s ON s.project_id = p.id
2665
+ ORDER BY s.created_at DESC LIMIT 1
2666
+ `).get();
2667
+ }
2668
+ if (!spProject) {
2669
+ const allProjects = db.getAllProjects(20);
2670
+ if (allProjects.length === 0) {
2671
+ return { content: [{ type: "text", text: `🐉 No projects found. Run \`wyrm_scan_projects\` first.` }] };
2672
+ }
2673
+ const candidates = allProjects.map(p => `- ${p.name} (ID: ${p.id})`).join('\n');
2674
+ return { content: [{ type: "text", text: `🐉 Project not found. Available projects:\n${candidates}` }] };
2675
+ }
2676
+ const sections = [];
2677
+ // Section 1 — Ground Truths (up to 1200 chars)
2678
+ const truthsText = groundTruths.formatForContext(spProject.id, 1200);
2679
+ if (truthsText)
2680
+ sections.push(truthsText);
2681
+ // Section 2 — Reasoning Scaffold (up to 1500 chars)
2682
+ if (spTask) {
2683
+ const scaffoldMatchSP = scaffoldLib.findBest(spTask, spProject.id, 0.3);
2684
+ if (scaffoldMatchSP) {
2685
+ sections.push(scaffoldLib.formatForContext(scaffoldMatchSP, 1500));
2686
+ }
2687
+ }
2688
+ // Section 3 — Memory Brief (up to 2000 chars)
2689
+ const spBrief = memory.buildContextBrief(spProject.id, spTask ?? 'general', { maxItems: 10, minConfidence: 0.3 });
2690
+ if (spBrief.text)
2691
+ sections.push(spBrief.text.slice(0, 2000));
2692
+ // Section 4 — Active Quests (up to 800 chars)
2693
+ const spRawDb = db.getDatabase();
2694
+ const spActiveQuests = spRawDb.prepare(`
2695
+ SELECT * FROM quests
2696
+ WHERE project_id = ? AND status = 'pending'
2697
+ ORDER BY
2698
+ CASE priority WHEN 'critical' THEN 1 WHEN 'high' THEN 2 WHEN 'medium' THEN 3 ELSE 4 END
2699
+ LIMIT 5
2700
+ `).all(spProject.id);
2701
+ if (spActiveQuests.length > 0) {
2702
+ const priorityEmoji = { critical: '🔴', high: '🟠', medium: '🟡', low: '🟢' };
2703
+ let questsBlock = '### 🎯 Active Quests\n';
2704
+ for (const q of spActiveQuests) {
2705
+ questsBlock += `- ${priorityEmoji[q.priority] ?? '•'} #${q.id}: ${q.title}\n`;
2706
+ }
2707
+ sections.push(questsBlock.slice(0, 800));
2708
+ }
2709
+ // Optionally log session
2710
+ let sessionNote = '';
2711
+ if (spLog) {
2712
+ const session = db.createSession(spProject.id, {
2713
+ objectives: spTask ?? 'Session started via wyrm_session_prime',
2714
+ summary: '',
2715
+ });
2716
+ sessionNote = `\nSession logged: #${session.id}`;
2717
+ }
2718
+ const spTruthStats = groundTruths.getStats(spProject.id);
2719
+ const spMemStats = memory.getStats(spProject.id);
2720
+ const summaryLine = `🐉 Wyrm primed for **${spProject.name}** | ${spTruthStats.current} truth${spTruthStats.current !== 1 ? 's' : ''} · ${spActiveQuests.length} quest${spActiveQuests.length !== 1 ? 's' : ''} · ${spMemStats.total} memor${spMemStats.total !== 1 ? 'ies' : 'y'}${sessionNote}`;
2721
+ const spContext = sections.length > 0
2722
+ ? sections.join('\n\n---\n\n') + '\n\n' + summaryLine
2723
+ : summaryLine + '\n\n_No memory stored yet — start working and Wyrm will learn._';
2724
+ const spResponse = cachedResponse(spContext);
2725
+ if (cacheKey)
2726
+ cache.set(cacheKey, spResponse, 30000);
2727
+ return spResponse;
2728
+ }
2729
+ case "wyrm_import_git": {
2730
+ const { project_id: gitProjId, commits: gitCommits, auto_approve: gitAutoApprove } = args;
2731
+ const gitProject = db.getProjectById(gitProjId);
2732
+ if (!gitProject)
2733
+ return { content: [{ type: "text", text: `Project ID ${gitProjId} not found.` }], isError: true };
2734
+ let captured = 0, skippedCount = 0;
2735
+ for (const commit of gitCommits) {
2736
+ const msg = commit.message ?? '';
2737
+ if (/^Merge /i.test(msg)) {
2738
+ skippedCount++;
2739
+ continue;
2740
+ }
2741
+ if (/^(chore|bump|release|version)/i.test(msg)) {
2742
+ skippedCount++;
2743
+ continue;
2744
+ }
2745
+ let artifactKind = 'pattern';
2746
+ if (/^fix(\(.+\))?:/i.test(msg))
2747
+ artifactKind = 'lesson';
2748
+ else if (/^feat(\(.+\))?:/i.test(msg))
2749
+ artifactKind = 'pattern';
2750
+ else if (/^refactor(\(.+\))?:/i.test(msg))
2751
+ artifactKind = 'heuristic';
2752
+ const typePrefix = msg.split(':')[0] ?? 'commit';
2753
+ memory.add(gitProjId, {
2754
+ kind: artifactKind,
2755
+ problem: msg,
2756
+ validatedFix: commit.diff_summary ?? '',
2757
+ whyItWorked: `Committed by ${commit.author ?? 'unknown'} on ${commit.date ?? 'unknown'}`,
2758
+ tags: ['git', 'commit', typePrefix.toLowerCase()],
2759
+ confidence: 0.6,
2760
+ needsReview: gitAutoApprove ? 0 : 1,
2761
+ });
2762
+ captured++;
2763
+ }
2764
+ return {
2765
+ content: [{
2766
+ type: "text",
2767
+ text: `🐉 **Git Import Complete**\n\n- **Captured:** ${captured}\n- **Skipped:** ${skippedCount} (merges/chores)\n- **Review needed:** ${gitAutoApprove ? 'No (auto-approved)' : `Yes — run \`wyrm_review\` to activate ${captured} artifact${captured !== 1 ? 's' : ''}`}`,
2768
+ }],
2769
+ };
2770
+ }
2771
+ case "wyrm_import_pr": {
2772
+ const { project_id: prProjId, title: prTitle, body: prBody, review_comments: prComments, auto_approve: prAutoApprove } = args;
2773
+ const prProject = db.getProjectById(prProjId);
2774
+ if (!prProject)
2775
+ return { content: [{ type: "text", text: `Project ID ${prProjId} not found.` }], isError: true };
2776
+ let prCreated = 0;
2777
+ const prNeedsReview = prAutoApprove ? 0 : 1;
2778
+ memory.add(prProjId, {
2779
+ kind: 'pattern',
2780
+ problem: `${prTitle}\n\n${prBody}`,
2781
+ validatedFix: '',
2782
+ whyItWorked: '',
2783
+ tags: ['git', 'pr'],
2784
+ confidence: 0.65,
2785
+ needsReview: prNeedsReview,
2786
+ });
2787
+ prCreated++;
2788
+ for (const comment of (prComments ?? [])) {
2789
+ if (/\b(should|must|always|never|prefer|avoid|use|don't)\b/i.test(comment)) {
2790
+ memory.add(prProjId, {
2791
+ kind: 'heuristic',
2792
+ problem: comment,
2793
+ validatedFix: '',
2794
+ whyItWorked: '',
2795
+ tags: ['git', 'pr', 'review'],
2796
+ confidence: 0.65,
2797
+ needsReview: prNeedsReview,
2798
+ });
2799
+ prCreated++;
2800
+ }
2801
+ }
2802
+ return {
2803
+ content: [{
2804
+ type: "text",
2805
+ text: `🐉 **PR Import Complete**\n\n- **Artifacts created:** ${prCreated}\n- **Review needed:** ${prAutoApprove ? 'No (auto-approved)' : `Yes — run \`wyrm_review\` to activate`}`,
2806
+ }],
2807
+ };
2808
+ }
2809
+ case "wyrm_import_rules": {
2810
+ const { project_id: ruleProjId, content: ruleContent, format: ruleFormat, source_file: ruleSrc } = args;
2811
+ const rulesProject = db.getProjectById(ruleProjId);
2812
+ if (!rulesProject)
2813
+ return { content: [{ type: "text", text: `Project ID ${ruleProjId} not found.` }], isError: true };
2814
+ const fmt = ruleFormat ?? 'plain';
2815
+ const src = ruleSrc ?? 'rules';
2816
+ const baseTags = ['imported', fmt, src];
2817
+ // Section-based parsing: prefer markdown headings, fall back to paragraph breaks
2818
+ const byHeadings = ruleContent.split(/\n(?=#)/);
2819
+ const byParagraphs = ruleContent.split(/\n\n+/);
2820
+ const chunks = (byHeadings.length >= byParagraphs.length ? byHeadings : byParagraphs)
2821
+ .map((c) => c.trim())
2822
+ .filter((c) => c.length >= 15);
2823
+ let ruleTruthsCreated = 0, ruleArtifactsCreated = 0;
2824
+ for (const chunk of chunks) {
2825
+ if (/\b(always|never|must|use|don't|avoid|prefer)\b/i.test(chunk)) {
2826
+ groundTruths.set(ruleProjId, {
2827
+ category: 'constraint',
2828
+ key: chunk.slice(0, 50).replace(/\n/g, ' '),
2829
+ value: chunk,
2830
+ source: src,
2831
+ });
2832
+ ruleTruthsCreated++;
2833
+ }
2834
+ else {
2835
+ memory.add(ruleProjId, {
2836
+ kind: 'heuristic',
2837
+ problem: chunk,
2838
+ validatedFix: '',
2839
+ whyItWorked: '',
2840
+ tags: baseTags,
2841
+ confidence: 0.7,
2842
+ needsReview: 1,
2843
+ });
2844
+ ruleArtifactsCreated++;
2845
+ }
2846
+ }
2847
+ return {
2848
+ content: [{
2849
+ type: "text",
2850
+ text: `🐉 **Rules Import Complete** (${fmt})\n\n- **Ground truths created:** ${ruleTruthsCreated} (active immediately)\n- **Memory artifacts created:** ${ruleArtifactsCreated} (pending review)\n- **Source:** ${src}\n\nRun \`wyrm_review\` to approve the memory artifacts.`,
2851
+ }],
2852
+ };
2853
+ }
2854
+ case "wyrm_inject_prompt": {
2855
+ const { project_path: injPath, clients: injClients } = args;
2856
+ const injectResult = injectSystemPrompt(injPath, injClients ?? []);
2857
+ let injText = `🐉 **System Prompt Injection**\n\n`;
2858
+ if (injectResult.injected.length > 0) {
2859
+ injText += `✅ Injected into:\n${injectResult.injected.map((f) => `- ${f}`).join('\n')}\n\n`;
2860
+ }
2861
+ if (injectResult.skipped.length > 0) {
2862
+ injText += `○ Skipped (unknown client): ${injectResult.skipped.join(', ')}\n\n`;
2863
+ }
2864
+ if (injectResult.errors.length > 0) {
2865
+ injText += `❌ Errors:\n${injectResult.errors.map((e) => `- ${e}`).join('\n')}\n\n`;
2866
+ }
2867
+ if (injectResult.injected.length > 0) {
2868
+ injText += `AI models in this project will now call \`wyrm_session_prime\` at the start of each conversation.`;
2869
+ }
2870
+ return { content: [{ type: "text", text: injText }] };
2871
+ }
2457
2872
  default:
2458
2873
  return {
2459
2874
  content: [{ type: "text", text: `Unknown tool: ${name}` }],