wyrm-mcp 3.2.1 → 3.3.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/database.d.ts +0 -1
- package/dist/database.d.ts.map +1 -1
- package/dist/database.js +7 -203
- package/dist/database.js.map +1 -1
- package/dist/index.js +827 -39
- package/dist/index.js.map +1 -1
- package/dist/indexer.d.ts +84 -0
- package/dist/indexer.d.ts.map +1 -0
- package/dist/indexer.js +160 -0
- package/dist/indexer.js.map +1 -0
- package/dist/intelligence.d.ts +116 -0
- package/dist/intelligence.d.ts.map +1 -0
- package/dist/intelligence.js +278 -0
- package/dist/intelligence.js.map +1 -0
- package/dist/knowledge-graph.d.ts +113 -0
- package/dist/knowledge-graph.d.ts.map +1 -0
- package/dist/knowledge-graph.js +235 -0
- package/dist/knowledge-graph.js.map +1 -0
- package/dist/memory-artifacts.d.ts +106 -0
- package/dist/memory-artifacts.d.ts.map +1 -0
- package/dist/memory-artifacts.js +291 -0
- package/dist/memory-artifacts.js.map +1 -0
- package/dist/migrations.d.ts +31 -0
- package/dist/migrations.d.ts.map +1 -0
- package/dist/migrations.js +525 -0
- package/dist/migrations.js.map +1 -0
- package/dist/providers/embedding-provider.d.ts +81 -0
- package/dist/providers/embedding-provider.d.ts.map +1 -0
- package/dist/providers/embedding-provider.js +149 -0
- package/dist/providers/embedding-provider.js.map +1 -0
- package/dist/vectors.d.ts +32 -48
- package/dist/vectors.d.ts.map +1 -1
- package/dist/vectors.js +119 -225
- package/dist/vectors.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -28,11 +28,19 @@ import { initializeOrchestrator, getDefaultConfig } from "./auto-orchestrator.js
|
|
|
28
28
|
import { sanitizeFtsQuery, sanitizeString, validateBatchSize } from "./security.js";
|
|
29
29
|
import { getCrypto, initializeCrypto } from "./crypto.js";
|
|
30
30
|
import { createVectorStore } from "./vectors.js";
|
|
31
|
+
import { IndexingPipeline } from "./indexer.js";
|
|
32
|
+
import { KnowledgeGraph } from "./knowledge-graph.js";
|
|
33
|
+
import { MemoryArtifacts } from "./memory-artifacts.js";
|
|
34
|
+
import { GroundTruths, ReasoningScaffolds } from "./intelligence.js";
|
|
31
35
|
import { initializeLicense, getLicenseInfo, hasFeature, getTier, activateLicense } from "./license.js";
|
|
32
36
|
import { WyrmAnalytics } from "./analytics.js";
|
|
33
37
|
import { WyrmCloudBackup } from "./cloud-backup.js";
|
|
34
38
|
const db = new WyrmDB();
|
|
35
39
|
const sync = new WyrmSync(db);
|
|
40
|
+
const graph = new KnowledgeGraph(db.getDatabase());
|
|
41
|
+
const memory = new MemoryArtifacts(db.getDatabase());
|
|
42
|
+
const groundTruths = new GroundTruths(db.getDatabase());
|
|
43
|
+
const scaffoldLib = new ReasoningScaffolds(db.getDatabase());
|
|
36
44
|
// ==================== OPTIONAL ENCRYPTION ====================
|
|
37
45
|
if (process.env.WYRM_ENCRYPTION_KEY) {
|
|
38
46
|
try {
|
|
@@ -45,10 +53,46 @@ if (process.env.WYRM_ENCRYPTION_KEY) {
|
|
|
45
53
|
const wyrmCrypto = getCrypto();
|
|
46
54
|
// ==================== OPTIONAL VECTOR SEARCH ====================
|
|
47
55
|
let vectorStore = null;
|
|
56
|
+
let indexingPipeline = null;
|
|
48
57
|
try {
|
|
49
58
|
const provider = (process.env.WYRM_VECTOR_PROVIDER || 'none');
|
|
50
59
|
if (provider !== 'none') {
|
|
51
|
-
vectorStore = createVectorStore({ provider });
|
|
60
|
+
vectorStore = createVectorStore({ provider }, db.getDatabase());
|
|
61
|
+
// Content resolver for the indexing pipeline
|
|
62
|
+
const resolveContent = (type, id) => {
|
|
63
|
+
try {
|
|
64
|
+
switch (type) {
|
|
65
|
+
case 'session': {
|
|
66
|
+
const s = db.getSession(id);
|
|
67
|
+
if (!s)
|
|
68
|
+
return null;
|
|
69
|
+
const parts = [s.objectives, s.completed, s.notes, s.summary].filter(Boolean);
|
|
70
|
+
return parts.join(' ') || null;
|
|
71
|
+
}
|
|
72
|
+
case 'quest': {
|
|
73
|
+
const q = db.getDatabase().prepare('SELECT * FROM quests WHERE id = ?').get(id);
|
|
74
|
+
if (!q)
|
|
75
|
+
return null;
|
|
76
|
+
return [q.title, q.description].filter(Boolean).join(' ') || null;
|
|
77
|
+
}
|
|
78
|
+
case 'note':
|
|
79
|
+
case 'context': {
|
|
80
|
+
const rows = db.getDatabase().prepare('SELECT value FROM data_lake WHERE id = ?').all(id);
|
|
81
|
+
if (!rows.length)
|
|
82
|
+
return null;
|
|
83
|
+
const val = wyrmCrypto.maybeDecrypt(rows[0].value);
|
|
84
|
+
return val.startsWith('ENCRYPTED:') ? null : val; // skip encrypted content
|
|
85
|
+
}
|
|
86
|
+
default:
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
indexingPipeline = new IndexingPipeline(db.getDatabase(), vectorStore, resolveContent, { batchSize: 20, maxRetries: 3, intervalMs: 10000 });
|
|
95
|
+
indexingPipeline.start();
|
|
52
96
|
}
|
|
53
97
|
}
|
|
54
98
|
catch (e) {
|
|
@@ -172,6 +216,13 @@ const READ_ONLY_TOOLS = new Set([
|
|
|
172
216
|
"wyrm_license",
|
|
173
217
|
"wyrm_analytics_dashboard",
|
|
174
218
|
"wyrm_cost_report",
|
|
219
|
+
"wyrm_entity_search",
|
|
220
|
+
"wyrm_entity_graph",
|
|
221
|
+
"wyrm_entity_path",
|
|
222
|
+
"wyrm_recall",
|
|
223
|
+
"wyrm_context_build",
|
|
224
|
+
"wyrm_truth_get",
|
|
225
|
+
"wyrm_scaffold_get",
|
|
175
226
|
]);
|
|
176
227
|
/** Tools that mutate data — invalidate relevant caches */
|
|
177
228
|
const WRITE_TOOLS = new Set([
|
|
@@ -192,6 +243,15 @@ const WRITE_TOOLS = new Set([
|
|
|
192
243
|
"wyrm_activate",
|
|
193
244
|
"wyrm_cloud_backup",
|
|
194
245
|
"wyrm_encrypt_setup",
|
|
246
|
+
"wyrm_entity_add",
|
|
247
|
+
"wyrm_entity_link",
|
|
248
|
+
"wyrm_entity_merge",
|
|
249
|
+
"wyrm_remember",
|
|
250
|
+
"wyrm_feedback",
|
|
251
|
+
"wyrm_truth_set",
|
|
252
|
+
"wyrm_scaffold_save",
|
|
253
|
+
"wyrm_distill",
|
|
254
|
+
"wyrm_review",
|
|
195
255
|
]);
|
|
196
256
|
const server = new Server({
|
|
197
257
|
name: "wyrm",
|
|
@@ -493,12 +553,13 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
493
553
|
// Search
|
|
494
554
|
{
|
|
495
555
|
name: "wyrm_search",
|
|
496
|
-
description: "Search across all projects, sessions, quests, and
|
|
556
|
+
description: "Search across all projects, sessions, quests, data, and entities. Supports lexical (FTS), semantic (vector), and hybrid (RRF fusion) search modes.",
|
|
497
557
|
inputSchema: {
|
|
498
558
|
type: "object",
|
|
499
559
|
properties: {
|
|
500
560
|
query: { type: "string", description: "Search query" },
|
|
501
|
-
type: { type: "string", enum: ["all", "sessions", "quests", "data"], description: "What to search" },
|
|
561
|
+
type: { type: "string", enum: ["all", "sessions", "quests", "data", "entities"], description: "What to search" },
|
|
562
|
+
mode: { type: "string", enum: ["lexical", "semantic", "hybrid"], description: "Search mode: lexical (FTS), semantic (vector similarity), or hybrid (RRF fusion). Default: hybrid if vectors available, lexical otherwise." },
|
|
502
563
|
projectPath: { type: "string", description: "Limit to specific project" },
|
|
503
564
|
},
|
|
504
565
|
required: ["query"],
|
|
@@ -660,6 +721,268 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
660
721
|
required: ["action"],
|
|
661
722
|
},
|
|
662
723
|
},
|
|
724
|
+
// Knowledge Graph
|
|
725
|
+
{
|
|
726
|
+
name: "wyrm_entity_add",
|
|
727
|
+
description: "Add an entity to the knowledge graph (person, concept, tool, etc.)",
|
|
728
|
+
inputSchema: {
|
|
729
|
+
type: "object",
|
|
730
|
+
properties: {
|
|
731
|
+
projectPath: { type: "string", description: "Project to add entity to" },
|
|
732
|
+
name: { type: "string", description: "Entity name" },
|
|
733
|
+
type: { type: "string", description: "Entity type (e.g., person, concept, tool, framework, service)" },
|
|
734
|
+
metadata: { type: "string", description: "JSON metadata about the entity" },
|
|
735
|
+
aliases: { type: "array", items: { type: "string" }, description: "Alternative names for the entity" },
|
|
736
|
+
},
|
|
737
|
+
required: ["projectPath", "name", "type"],
|
|
738
|
+
},
|
|
739
|
+
},
|
|
740
|
+
{
|
|
741
|
+
name: "wyrm_entity_link",
|
|
742
|
+
description: "Create a relationship between two entities in the knowledge graph",
|
|
743
|
+
inputSchema: {
|
|
744
|
+
type: "object",
|
|
745
|
+
properties: {
|
|
746
|
+
projectPath: { type: "string", description: "Project containing the entities" },
|
|
747
|
+
source: { type: "string", description: "Source entity name" },
|
|
748
|
+
target: { type: "string", description: "Target entity name" },
|
|
749
|
+
relationship: { type: "string", description: "Relationship type (e.g., uses, depends_on, created_by, extends)" },
|
|
750
|
+
weight: { type: "number", description: "Relationship weight 0-1 (default: 1.0)" },
|
|
751
|
+
confidence: { type: "number", description: "Confidence score 0-1 (default: 1.0)" },
|
|
752
|
+
sourceMemory: { type: "string", description: "Source of this knowledge (e.g., session:42, quest:7)" },
|
|
753
|
+
},
|
|
754
|
+
required: ["projectPath", "source", "target", "relationship"],
|
|
755
|
+
},
|
|
756
|
+
},
|
|
757
|
+
{
|
|
758
|
+
name: "wyrm_entity_search",
|
|
759
|
+
description: "Search for entities in the knowledge graph",
|
|
760
|
+
inputSchema: {
|
|
761
|
+
type: "object",
|
|
762
|
+
properties: {
|
|
763
|
+
projectPath: { type: "string", description: "Project to search in" },
|
|
764
|
+
query: { type: "string", description: "Search query for entity names" },
|
|
765
|
+
type: { type: "string", description: "Filter by entity type" },
|
|
766
|
+
limit: { type: "number", description: "Max results (default: 20)" },
|
|
767
|
+
},
|
|
768
|
+
required: ["projectPath", "query"],
|
|
769
|
+
},
|
|
770
|
+
},
|
|
771
|
+
{
|
|
772
|
+
name: "wyrm_entity_graph",
|
|
773
|
+
description: "Get the neighborhood graph around an entity — all connected nodes within N hops",
|
|
774
|
+
inputSchema: {
|
|
775
|
+
type: "object",
|
|
776
|
+
properties: {
|
|
777
|
+
projectPath: { type: "string", description: "Project containing the entity" },
|
|
778
|
+
entity: { type: "string", description: "Entity name to center on" },
|
|
779
|
+
depth: { type: "number", description: "Max hops from center (1-5, default: 2)" },
|
|
780
|
+
},
|
|
781
|
+
required: ["projectPath", "entity"],
|
|
782
|
+
},
|
|
783
|
+
},
|
|
784
|
+
{
|
|
785
|
+
name: "wyrm_entity_path",
|
|
786
|
+
description: "Find the shortest path between two entities in the knowledge graph",
|
|
787
|
+
inputSchema: {
|
|
788
|
+
type: "object",
|
|
789
|
+
properties: {
|
|
790
|
+
projectPath: { type: "string", description: "Project containing the entities" },
|
|
791
|
+
source: { type: "string", description: "Source entity name" },
|
|
792
|
+
target: { type: "string", description: "Target entity name" },
|
|
793
|
+
maxDepth: { type: "number", description: "Max path length (1-5, default: 5)" },
|
|
794
|
+
},
|
|
795
|
+
required: ["projectPath", "source", "target"],
|
|
796
|
+
},
|
|
797
|
+
},
|
|
798
|
+
{
|
|
799
|
+
name: "wyrm_entity_merge",
|
|
800
|
+
description: "Merge two entities — combines aliases, redirects relationships, deletes the source",
|
|
801
|
+
inputSchema: {
|
|
802
|
+
type: "object",
|
|
803
|
+
properties: {
|
|
804
|
+
projectPath: { type: "string", description: "Project containing the entities" },
|
|
805
|
+
source: { type: "string", description: "Entity name to merge FROM (will be deleted)" },
|
|
806
|
+
target: { type: "string", description: "Entity name to merge INTO (will be kept)" },
|
|
807
|
+
},
|
|
808
|
+
required: ["projectPath", "source", "target"],
|
|
809
|
+
},
|
|
810
|
+
},
|
|
811
|
+
// ==================== INTELLIGENCE AMPLIFICATION ====================
|
|
812
|
+
{
|
|
813
|
+
name: "wyrm_remember",
|
|
814
|
+
description: "Store a distilled knowledge artifact — proven patterns, lessons learned, anti-patterns, and reasoning traces. These are recalled automatically by wyrm_context_build to help AI models apply past knowledge to new tasks.",
|
|
815
|
+
inputSchema: {
|
|
816
|
+
type: "object",
|
|
817
|
+
properties: {
|
|
818
|
+
projectPath: { type: "string", description: "Project this knowledge belongs to" },
|
|
819
|
+
kind: {
|
|
820
|
+
type: "string",
|
|
821
|
+
enum: ["reasoning_trace", "lesson", "pattern", "anti_pattern", "heuristic"],
|
|
822
|
+
description: "reasoning_trace: a solved problem with steps; lesson: a general insight; pattern: a proven approach; anti_pattern: what NOT to do; heuristic: a rule of thumb",
|
|
823
|
+
},
|
|
824
|
+
problem: { type: "string", description: "What was being solved or observed (be specific and searchable)" },
|
|
825
|
+
constraints: { type: "string", description: "What conditions or constraints applied" },
|
|
826
|
+
validatedFix: { type: "string", description: "What actually worked — the validated solution or approach" },
|
|
827
|
+
whyItWorked: { type: "string", description: "The insight behind WHY this worked (key for transfer learning)" },
|
|
828
|
+
outcome: { type: "string", enum: ["positive", "negative", "neutral"], description: "Was this approach successful?" },
|
|
829
|
+
tags: { type: "array", items: { type: "string" }, description: "Searchable tags (e.g. ['auth', 'rate-limit', 'typescript'])" },
|
|
830
|
+
confidence: { type: "number", description: "Confidence level 0.0–1.0 (default: 1.0)" },
|
|
831
|
+
sourceSessionId: { type: "number", description: "Session ID where this was discovered (optional)" },
|
|
832
|
+
},
|
|
833
|
+
required: ["projectPath", "kind", "problem"],
|
|
834
|
+
},
|
|
835
|
+
},
|
|
836
|
+
{
|
|
837
|
+
name: "wyrm_recall",
|
|
838
|
+
description: "Retrieve relevant knowledge artifacts for a given task or problem. Uses 2-stage retrieval (FTS + tag matching) with freshness and confidence weighting. Returns distilled knowledge from past sessions.",
|
|
839
|
+
inputSchema: {
|
|
840
|
+
type: "object",
|
|
841
|
+
properties: {
|
|
842
|
+
projectPath: { type: "string", description: "Project to search within" },
|
|
843
|
+
query: { type: "string", description: "Describe the current task or problem" },
|
|
844
|
+
kind: {
|
|
845
|
+
type: "string",
|
|
846
|
+
enum: ["reasoning_trace", "lesson", "pattern", "anti_pattern", "heuristic"],
|
|
847
|
+
description: "Filter by artifact kind (optional — searches all kinds if omitted)",
|
|
848
|
+
},
|
|
849
|
+
limit: { type: "number", description: "Max results (default: 10)" },
|
|
850
|
+
minConfidence: { type: "number", description: "Minimum confidence threshold 0.0–1.0 (default: 0.0)" },
|
|
851
|
+
},
|
|
852
|
+
required: ["projectPath", "query"],
|
|
853
|
+
},
|
|
854
|
+
},
|
|
855
|
+
{
|
|
856
|
+
name: "wyrm_feedback",
|
|
857
|
+
description: "Record whether a recalled knowledge artifact was useful. Adjusts confidence over time — successful reuse boosts it, failure lowers it. Call after applying a recalled pattern or lesson.",
|
|
858
|
+
inputSchema: {
|
|
859
|
+
type: "object",
|
|
860
|
+
properties: {
|
|
861
|
+
artifactId: { type: "number", description: "ID of the memory artifact (from wyrm_recall results)" },
|
|
862
|
+
success: { type: "boolean", description: "Was this artifact helpful for the task?" },
|
|
863
|
+
},
|
|
864
|
+
required: ["artifactId", "success"],
|
|
865
|
+
},
|
|
866
|
+
},
|
|
867
|
+
{
|
|
868
|
+
name: "wyrm_context_build",
|
|
869
|
+
description: "Assemble an optimized memory brief for the current task — a formatted block of relevant patterns, lessons, reasoning traces, and anti-patterns from past sessions. Inject this at the start of complex tasks to give AI models the right context without bloating the whole conversation.",
|
|
870
|
+
inputSchema: {
|
|
871
|
+
type: "object",
|
|
872
|
+
properties: {
|
|
873
|
+
projectPath: { type: "string", description: "Project to build context from" },
|
|
874
|
+
task: { type: "string", description: "Describe the current task in detail — the more specific, the better the recall" },
|
|
875
|
+
maxItems: { type: "number", description: "Max knowledge items to include (default: 10, max: 20)" },
|
|
876
|
+
kinds: {
|
|
877
|
+
type: "array",
|
|
878
|
+
items: { type: "string", enum: ["reasoning_trace", "lesson", "pattern", "anti_pattern", "heuristic"] },
|
|
879
|
+
description: "Limit to specific artifact kinds (default: all)",
|
|
880
|
+
},
|
|
881
|
+
minConfidence: { type: "number", description: "Minimum confidence threshold (default: 0.3)" },
|
|
882
|
+
},
|
|
883
|
+
required: ["projectPath", "task"],
|
|
884
|
+
},
|
|
885
|
+
},
|
|
886
|
+
{
|
|
887
|
+
name: "wyrm_truth_set",
|
|
888
|
+
description: "Store a ground truth for the project — validated facts about architecture, constraints, conventions, or key decisions. Ground truths are injected first into every wyrm_context_build response so the AI always works from a foundation of confirmed facts.",
|
|
889
|
+
inputSchema: {
|
|
890
|
+
type: "object",
|
|
891
|
+
properties: {
|
|
892
|
+
projectPath: { type: "string", description: "Project root path" },
|
|
893
|
+
category: { type: "string", description: "Category grouping (e.g. 'architecture', 'conventions', 'constraints', 'decisions')" },
|
|
894
|
+
key: { type: "string", description: "Short unique key within the category (snake_case)" },
|
|
895
|
+
value: { type: "string", description: "The ground truth statement" },
|
|
896
|
+
rationale: { type: "string", description: "Why this is true / how it was determined" },
|
|
897
|
+
source: { type: "string", description: "Origin: 'user', 'derived', 'observed', 'confirmed'" },
|
|
898
|
+
confidence: { type: "number", description: "Confidence 0-1 (default: 1.0 for user-asserted truths)" },
|
|
899
|
+
},
|
|
900
|
+
required: ["projectPath", "category", "key", "value"],
|
|
901
|
+
},
|
|
902
|
+
},
|
|
903
|
+
{
|
|
904
|
+
name: "wyrm_truth_get",
|
|
905
|
+
description: "Retrieve current ground truths for a project. Returns validated facts the AI should treat as baseline. Optionally filter by category.",
|
|
906
|
+
inputSchema: {
|
|
907
|
+
type: "object",
|
|
908
|
+
properties: {
|
|
909
|
+
projectPath: { type: "string", description: "Project root path" },
|
|
910
|
+
category: { type: "string", description: "Filter to specific category (optional)" },
|
|
911
|
+
},
|
|
912
|
+
required: ["projectPath"],
|
|
913
|
+
},
|
|
914
|
+
},
|
|
915
|
+
{
|
|
916
|
+
name: "wyrm_scaffold_save",
|
|
917
|
+
description: "Save a reasoning scaffold — a structured checklist that guides step-by-step thinking for a specific problem type. Scaffolds are automatically surfaced by wyrm_context_build when the task matches, helping AI models apply proven reasoning patterns.",
|
|
918
|
+
inputSchema: {
|
|
919
|
+
type: "object",
|
|
920
|
+
properties: {
|
|
921
|
+
projectPath: { type: "string", description: "Project path (optional, null for global scaffolds)" },
|
|
922
|
+
problemType: { type: "string", description: "Short slug for the problem type (e.g. 'api-design', 'security-review', 'db-migration')" },
|
|
923
|
+
description: { type: "string", description: "What kind of task this scaffold helps with" },
|
|
924
|
+
whenToUse: { type: "string", description: "Conditions that indicate this scaffold should be applied" },
|
|
925
|
+
steps: { type: "array", items: { type: "string" }, description: "Ordered checklist steps to follow" },
|
|
926
|
+
verificationSteps: { type: "array", items: { type: "string" }, description: "Steps to verify the work is correct" },
|
|
927
|
+
doNotApplyIf: { type: "string", description: "Conditions where this scaffold does NOT apply" },
|
|
928
|
+
tags: { type: "array", items: { type: "string" }, description: "Searchable tags" },
|
|
929
|
+
},
|
|
930
|
+
required: ["problemType", "description", "whenToUse", "steps"],
|
|
931
|
+
},
|
|
932
|
+
},
|
|
933
|
+
{
|
|
934
|
+
name: "wyrm_scaffold_get",
|
|
935
|
+
description: "Find the best reasoning scaffold for a task description. Returns the most relevant structured checklist, or null if no match meets the confidence threshold.",
|
|
936
|
+
inputSchema: {
|
|
937
|
+
type: "object",
|
|
938
|
+
properties: {
|
|
939
|
+
projectPath: { type: "string", description: "Project path (searches project + global)" },
|
|
940
|
+
task: { type: "string", description: "Task description to match against scaffolds" },
|
|
941
|
+
minConfidence: { type: "number", description: "Minimum match confidence 0-1 (default: 0.3)" },
|
|
942
|
+
},
|
|
943
|
+
required: ["task"],
|
|
944
|
+
},
|
|
945
|
+
},
|
|
946
|
+
{
|
|
947
|
+
name: "wyrm_distill",
|
|
948
|
+
description: "Distill a work session into candidate memory artifacts for review. Parses the session's objectives, decisions, and outcomes into structured artifacts (lessons, patterns, anti-patterns) and queues them for approval via wyrm_review. Use at session end to extract institutional knowledge.",
|
|
949
|
+
inputSchema: {
|
|
950
|
+
type: "object",
|
|
951
|
+
properties: {
|
|
952
|
+
projectPath: { type: "string", description: "Project root path" },
|
|
953
|
+
sessionId: { type: "string", description: "Session ID to distill" },
|
|
954
|
+
candidates: {
|
|
955
|
+
type: "array",
|
|
956
|
+
description: "Pre-parsed candidate artifacts to store for review",
|
|
957
|
+
items: {
|
|
958
|
+
type: "object",
|
|
959
|
+
properties: {
|
|
960
|
+
kind: { type: "string", enum: ["lesson", "pattern", "anti_pattern", "heuristic", "reasoning_trace"] },
|
|
961
|
+
title: { type: "string" },
|
|
962
|
+
content: { type: "string" },
|
|
963
|
+
tags: { type: "array", items: { type: "string" } },
|
|
964
|
+
confidence: { type: "number" },
|
|
965
|
+
},
|
|
966
|
+
required: ["kind", "title", "content"],
|
|
967
|
+
},
|
|
968
|
+
},
|
|
969
|
+
},
|
|
970
|
+
required: ["projectPath", "candidates"],
|
|
971
|
+
},
|
|
972
|
+
},
|
|
973
|
+
{
|
|
974
|
+
name: "wyrm_review",
|
|
975
|
+
description: "Approve or reject a pending memory artifact from the distillation queue. Approved artifacts enter the active recall pool; rejected artifacts are permanently discarded.",
|
|
976
|
+
inputSchema: {
|
|
977
|
+
type: "object",
|
|
978
|
+
properties: {
|
|
979
|
+
artifactId: { type: "number", description: "ID of the artifact to review" },
|
|
980
|
+
approved: { type: "boolean", description: "true to approve (activate), false to reject (delete)" },
|
|
981
|
+
notes: { type: "string", description: "Optional reviewer notes" },
|
|
982
|
+
},
|
|
983
|
+
required: ["artifactId", "approved"],
|
|
984
|
+
},
|
|
985
|
+
},
|
|
663
986
|
],
|
|
664
987
|
}));
|
|
665
988
|
// Tool implementations
|
|
@@ -1169,74 +1492,539 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1169
1492
|
}
|
|
1170
1493
|
// ==================== SEARCH ====================
|
|
1171
1494
|
case "wyrm_search": {
|
|
1172
|
-
const { query, type, projectPath } = args;
|
|
1495
|
+
const { query, type, mode, projectPath } = args;
|
|
1173
1496
|
const sanitizedQuery = sanitizeFtsQuery(query);
|
|
1174
1497
|
const project = projectPath ? db.getProject(projectPath) : undefined;
|
|
1175
1498
|
const projectId = project?.id;
|
|
1176
1499
|
const searchType = type || 'all';
|
|
1177
|
-
|
|
1500
|
+
const searchMode = mode || (vectorStore ? 'hybrid' : 'lexical');
|
|
1501
|
+
let text = `🐉 **Search Results for "${query}"** _(mode: ${searchMode})_\n\n`;
|
|
1502
|
+
const ftsResultsByType = {};
|
|
1178
1503
|
if (searchType === 'all' || searchType === 'sessions') {
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1504
|
+
if (searchMode !== 'semantic') {
|
|
1505
|
+
const sessions = db.searchSessions(sanitizedQuery, projectId);
|
|
1506
|
+
ftsResultsByType['session'] = sessions.map((s, i) => ({
|
|
1507
|
+
id: s.id,
|
|
1508
|
+
snippet: `${s.date}: ${(s.objectives || s.completed || 'No info').slice(0, 80)}`,
|
|
1509
|
+
rank: i + 1,
|
|
1510
|
+
}));
|
|
1186
1511
|
}
|
|
1187
1512
|
}
|
|
1188
1513
|
if (searchType === 'all' || searchType === 'quests') {
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1514
|
+
if (searchMode !== 'semantic') {
|
|
1515
|
+
const quests = db.searchQuests(sanitizedQuery);
|
|
1516
|
+
ftsResultsByType['quest'] = quests.map((q, i) => ({
|
|
1517
|
+
id: q.id,
|
|
1518
|
+
snippet: `#${q.id}: ${q.title}`,
|
|
1519
|
+
rank: i + 1,
|
|
1520
|
+
}));
|
|
1196
1521
|
}
|
|
1197
1522
|
}
|
|
1198
1523
|
if (searchType === 'all' || searchType === 'data') {
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1524
|
+
if (searchMode !== 'semantic') {
|
|
1525
|
+
const data = db.searchData(sanitizedQuery, projectId);
|
|
1526
|
+
ftsResultsByType['note'] = data.map((d, i) => ({
|
|
1527
|
+
id: d.id,
|
|
1528
|
+
snippet: `${d.category}/${d.key}: ${wyrmCrypto.maybeDecrypt(d.value).slice(0, 60)}`,
|
|
1529
|
+
rank: i + 1,
|
|
1530
|
+
}));
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
let vectorResults = [];
|
|
1534
|
+
if (vectorStore && searchMode !== 'lexical' && (searchType === 'all' || searchType === 'data' || searchType === 'sessions' || searchType === 'quests')) {
|
|
1535
|
+
try {
|
|
1536
|
+
vectorResults = await vectorStore.search(query, 20, projectId);
|
|
1537
|
+
}
|
|
1538
|
+
catch {
|
|
1539
|
+
// Vector search failed gracefully — continue with FTS
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
// ── Reciprocal Rank Fusion (RRF) ──────────────────────────────────────
|
|
1543
|
+
// Score formula: Σ 1/(k + rank) where k=60
|
|
1544
|
+
const RRF_K = 60;
|
|
1545
|
+
const rrfScores = new Map();
|
|
1546
|
+
const addFtsToRrf = (contentType, results) => {
|
|
1547
|
+
results.forEach((r, idx) => {
|
|
1548
|
+
const key = `${contentType}:${r.id}`;
|
|
1549
|
+
const existing = rrfScores.get(key);
|
|
1550
|
+
const score = 1 / (RRF_K + (idx + 1));
|
|
1551
|
+
if (existing) {
|
|
1552
|
+
existing.score += score;
|
|
1553
|
+
}
|
|
1554
|
+
else {
|
|
1555
|
+
rrfScores.set(key, { score, snippet: r.snippet, type: contentType, id: r.id });
|
|
1556
|
+
}
|
|
1557
|
+
});
|
|
1558
|
+
};
|
|
1559
|
+
for (const [contentType, results] of Object.entries(ftsResultsByType)) {
|
|
1560
|
+
addFtsToRrf(contentType, results);
|
|
1561
|
+
}
|
|
1562
|
+
// Add vector results to RRF (sorted by similarity desc → rank 1=most similar)
|
|
1563
|
+
const vecByType = {};
|
|
1564
|
+
for (const vr of vectorResults) {
|
|
1565
|
+
if (!vecByType[vr.content_type])
|
|
1566
|
+
vecByType[vr.content_type] = [];
|
|
1567
|
+
vecByType[vr.content_type].push(vr);
|
|
1568
|
+
}
|
|
1569
|
+
for (const [contentType, results] of Object.entries(vecByType)) {
|
|
1570
|
+
results.forEach((vr, idx) => {
|
|
1571
|
+
const key = `${contentType}:${vr.content_id}`;
|
|
1572
|
+
const existing = rrfScores.get(key);
|
|
1573
|
+
const score = 1 / (RRF_K + (idx + 1));
|
|
1574
|
+
if (existing) {
|
|
1575
|
+
existing.score += score;
|
|
1576
|
+
existing.snippet += ` (semantic: ${(vr.similarity * 100).toFixed(0)}%)`;
|
|
1577
|
+
}
|
|
1578
|
+
else {
|
|
1579
|
+
rrfScores.set(key, { score, snippet: `${contentType} #${vr.content_id} (semantic: ${(vr.similarity * 100).toFixed(0)}%)`, type: contentType, id: vr.content_id });
|
|
1580
|
+
}
|
|
1581
|
+
});
|
|
1582
|
+
}
|
|
1583
|
+
// Sort by RRF score and render fused results
|
|
1584
|
+
const fused = Array.from(rrfScores.values()).sort((a, b) => b.score - a.score);
|
|
1585
|
+
if (fused.length > 0) {
|
|
1586
|
+
const byType = {};
|
|
1587
|
+
for (const r of fused) {
|
|
1588
|
+
if (!byType[r.type])
|
|
1589
|
+
byType[r.type] = [];
|
|
1590
|
+
byType[r.type].push(r);
|
|
1591
|
+
}
|
|
1592
|
+
for (const [contentType, results] of Object.entries(byType)) {
|
|
1593
|
+
const label = contentType === 'note' ? 'Data' : contentType.charAt(0).toUpperCase() + contentType.slice(1) + 's';
|
|
1594
|
+
text += `## ${label} (${results.length})\n`;
|
|
1595
|
+
for (const r of results.slice(0, 10)) {
|
|
1596
|
+
text += `- ${r.snippet}\n`;
|
|
1205
1597
|
}
|
|
1206
1598
|
text += '\n';
|
|
1207
1599
|
}
|
|
1208
1600
|
}
|
|
1601
|
+
// Skills search (FTS only — no vector indexing for skills)
|
|
1209
1602
|
if (searchType === 'all' || searchType === 'skills') {
|
|
1210
1603
|
const skills = db.searchSkills(sanitizedQuery, 10);
|
|
1211
1604
|
if (skills.length > 0) {
|
|
1212
1605
|
text += `## Skills (${skills.length})\n`;
|
|
1213
1606
|
for (const s of skills) {
|
|
1214
|
-
text += `- **${s.name}** (${s.category || 'uncategorized'}) ${s.is_active ? '
|
|
1607
|
+
text += `- **${s.name}** (${s.category || 'uncategorized'}) ${s.is_active ? '✅' : '❌'}\n`;
|
|
1215
1608
|
text += ` ${s.description}\n`;
|
|
1216
1609
|
}
|
|
1610
|
+
text += '\n';
|
|
1217
1611
|
}
|
|
1218
1612
|
}
|
|
1219
|
-
//
|
|
1220
|
-
if (
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
}
|
|
1613
|
+
// Entity graph search
|
|
1614
|
+
if ((searchType === 'all' || searchType === 'entities') && projectId !== undefined) {
|
|
1615
|
+
const entities = graph.searchEntities(projectId, query, 10);
|
|
1616
|
+
if (entities.length > 0) {
|
|
1617
|
+
text += `## Entities (${entities.length})\n`;
|
|
1618
|
+
for (const e of entities) {
|
|
1619
|
+
const rels = graph.getRelationships(e.id, 'both');
|
|
1620
|
+
text += `- **${e.name}** _(${e.type})_ — ${rels.length} relationship${rels.length !== 1 ? 's' : ''}\n`;
|
|
1228
1621
|
}
|
|
1229
|
-
|
|
1230
|
-
catch {
|
|
1231
|
-
// Vector search failed gracefully — FTS results still returned
|
|
1622
|
+
text += '\n';
|
|
1232
1623
|
}
|
|
1233
1624
|
}
|
|
1625
|
+
if (fused.length === 0 && !text.includes('##')) {
|
|
1626
|
+
text += '_No results found._\n';
|
|
1627
|
+
}
|
|
1234
1628
|
const response = cachedResponse(text);
|
|
1235
1629
|
if (cacheKey)
|
|
1236
1630
|
cache.set(cacheKey, response, 20000);
|
|
1237
1631
|
return response;
|
|
1238
1632
|
}
|
|
1239
|
-
// ====================
|
|
1633
|
+
// ==================== KNOWLEDGE GRAPH ====================
|
|
1634
|
+
case "wyrm_entity_add": {
|
|
1635
|
+
const { projectPath: ePath, name: eName, type: eType, metadata: eMeta, aliases: eAliases } = args;
|
|
1636
|
+
const eProject = db.getProject(ePath);
|
|
1637
|
+
if (!eProject)
|
|
1638
|
+
return { content: [{ type: "text", text: `Project not found: ${ePath}` }], isError: true };
|
|
1639
|
+
const existingEntity = graph.findEntity(eProject.id, eName, eType);
|
|
1640
|
+
if (existingEntity) {
|
|
1641
|
+
return { content: [{ type: "text", text: `🐉 Entity already exists: **${eName}** (${eType}) — id ${existingEntity.id}` }] };
|
|
1642
|
+
}
|
|
1643
|
+
const entity = graph.addEntity(eProject.id, eName, eType, eMeta);
|
|
1644
|
+
if (eAliases?.length) {
|
|
1645
|
+
for (const alias of eAliases)
|
|
1646
|
+
graph.addAlias(entity.id, alias);
|
|
1647
|
+
}
|
|
1648
|
+
cache.invalidate('wyrm_entity_search');
|
|
1649
|
+
cache.invalidate('wyrm_entity_graph');
|
|
1650
|
+
cache.invalidate('wyrm_stats');
|
|
1651
|
+
let text = `🐉 **Entity Added**\n\n- **${entity.name}** _(${entity.type})_\n- ID: ${entity.id}\n`;
|
|
1652
|
+
if (eAliases?.length)
|
|
1653
|
+
text += `- Aliases: ${eAliases.join(', ')}\n`;
|
|
1654
|
+
return { content: [{ type: "text", text }] };
|
|
1655
|
+
}
|
|
1656
|
+
case "wyrm_entity_link": {
|
|
1657
|
+
const { projectPath: lPath, source, target, relationship, weight, confidence, sourceMemory } = args;
|
|
1658
|
+
const lProject = db.getProject(lPath);
|
|
1659
|
+
if (!lProject)
|
|
1660
|
+
return { content: [{ type: "text", text: `Project not found: ${lPath}` }], isError: true };
|
|
1661
|
+
const srcEntity = graph.findEntity(lProject.id, source) ?? graph.findByAlias(lProject.id, source);
|
|
1662
|
+
const tgtEntity = graph.findEntity(lProject.id, target) ?? graph.findByAlias(lProject.id, target);
|
|
1663
|
+
if (!srcEntity)
|
|
1664
|
+
return { content: [{ type: "text", text: `Entity not found: "${source}". Use wyrm_entity_add first.` }], isError: true };
|
|
1665
|
+
if (!tgtEntity)
|
|
1666
|
+
return { content: [{ type: "text", text: `Entity not found: "${target}". Use wyrm_entity_add first.` }], isError: true };
|
|
1667
|
+
const rel = graph.addRelationship(lProject.id, srcEntity.id, tgtEntity.id, relationship, {
|
|
1668
|
+
weight: weight ?? 1.0,
|
|
1669
|
+
confidence: confidence ?? 1.0,
|
|
1670
|
+
sourceMemory,
|
|
1671
|
+
});
|
|
1672
|
+
cache.invalidate('wyrm_entity_search');
|
|
1673
|
+
cache.invalidate('wyrm_entity_graph');
|
|
1674
|
+
cache.invalidate('wyrm_stats');
|
|
1675
|
+
return { content: [{ type: "text", text: `🐉 **Relationship Created**\n\n**${source}** —[${relationship}]→ **${target}**\n- ID: ${rel.id}${sourceMemory ? `\n- Source: ${sourceMemory}` : ''}` }] };
|
|
1676
|
+
}
|
|
1677
|
+
case "wyrm_entity_search": {
|
|
1678
|
+
const { projectPath: sePath, query: seQuery, type: seType, limit: seLimit } = args;
|
|
1679
|
+
const seProject = db.getProject(sePath);
|
|
1680
|
+
if (!seProject)
|
|
1681
|
+
return { content: [{ type: "text", text: `Project not found: ${sePath}` }], isError: true };
|
|
1682
|
+
const entities = graph.searchEntities(seProject.id, seQuery, seLimit ?? 20);
|
|
1683
|
+
const filtered = seType ? entities.filter(e => e.type === seType) : entities;
|
|
1684
|
+
if (filtered.length === 0) {
|
|
1685
|
+
return { content: [{ type: "text", text: `🐉 No entities found matching "${seQuery}"` }] };
|
|
1686
|
+
}
|
|
1687
|
+
let text = `🐉 **Entities matching "${seQuery}"** (${filtered.length})\n\n`;
|
|
1688
|
+
for (const e of filtered) {
|
|
1689
|
+
const rels = graph.getRelationships(e.id, 'both');
|
|
1690
|
+
const aliases = graph.getAliases(e.id);
|
|
1691
|
+
text += `- **${e.name}** _(${e.type})_ — ${rels.length} connection${rels.length !== 1 ? 's' : ''}`;
|
|
1692
|
+
if (aliases.length)
|
|
1693
|
+
text += ` | also: ${aliases.join(', ')}`;
|
|
1694
|
+
text += '\n';
|
|
1695
|
+
}
|
|
1696
|
+
const response = cachedResponse(text);
|
|
1697
|
+
if (cacheKey)
|
|
1698
|
+
cache.set(cacheKey, response, 15000);
|
|
1699
|
+
return response;
|
|
1700
|
+
}
|
|
1701
|
+
case "wyrm_entity_graph": {
|
|
1702
|
+
const { projectPath: gPath, entity: gEntity, depth: gDepth } = args;
|
|
1703
|
+
const gProject = db.getProject(gPath);
|
|
1704
|
+
if (!gProject)
|
|
1705
|
+
return { content: [{ type: "text", text: `Project not found: ${gPath}` }], isError: true };
|
|
1706
|
+
const centerEntity = graph.findEntity(gProject.id, gEntity) ?? graph.findByAlias(gProject.id, gEntity);
|
|
1707
|
+
if (!centerEntity)
|
|
1708
|
+
return { content: [{ type: "text", text: `Entity not found: "${gEntity}". Use wyrm_entity_search to find entities.` }], isError: true };
|
|
1709
|
+
const hood = graph.getNeighborhood(centerEntity.id, Math.min(gDepth ?? 2, 5));
|
|
1710
|
+
let text = `🐉 **Graph: ${centerEntity.name}** _(depth: ${gDepth ?? 2})_\n\n`;
|
|
1711
|
+
text += `**${hood.nodes.length} nodes, ${hood.edges.length} edges**\n\n`;
|
|
1712
|
+
// Group nodes by depth
|
|
1713
|
+
const byDepth = {};
|
|
1714
|
+
for (const n of hood.nodes) {
|
|
1715
|
+
if (!byDepth[n.depth])
|
|
1716
|
+
byDepth[n.depth] = [];
|
|
1717
|
+
byDepth[n.depth].push(n);
|
|
1718
|
+
}
|
|
1719
|
+
for (const [depth, nodes] of Object.entries(byDepth).sort((a, b) => Number(a[0]) - Number(b[0]))) {
|
|
1720
|
+
const label = depth === '0' ? '🎯 Center' : `Hop ${depth}`;
|
|
1721
|
+
text += `**${label}:** ${nodes.map(n => `${n.name} (${n.type})`).join(', ')}\n`;
|
|
1722
|
+
}
|
|
1723
|
+
if (hood.edges.length > 0) {
|
|
1724
|
+
text += '\n**Connections:**\n';
|
|
1725
|
+
for (const e of hood.edges.slice(0, 20)) {
|
|
1726
|
+
text += `- ${e.source_name} —[${e.relationship_type}]→ ${e.target_name}\n`;
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1729
|
+
const response = cachedResponse(text);
|
|
1730
|
+
if (cacheKey)
|
|
1731
|
+
cache.set(cacheKey, response, 15000);
|
|
1732
|
+
return response;
|
|
1733
|
+
}
|
|
1734
|
+
case "wyrm_entity_path": {
|
|
1735
|
+
const { projectPath: pPath, source: pSource, target: pTarget, maxDepth: pDepth } = args;
|
|
1736
|
+
const pProject = db.getProject(pPath);
|
|
1737
|
+
if (!pProject)
|
|
1738
|
+
return { content: [{ type: "text", text: `Project not found: ${pPath}` }], isError: true };
|
|
1739
|
+
const pSrc = graph.findEntity(pProject.id, pSource) ?? graph.findByAlias(pProject.id, pSource);
|
|
1740
|
+
const pTgt = graph.findEntity(pProject.id, pTarget) ?? graph.findByAlias(pProject.id, pTarget);
|
|
1741
|
+
if (!pSrc)
|
|
1742
|
+
return { content: [{ type: "text", text: `Entity not found: "${pSource}"` }], isError: true };
|
|
1743
|
+
if (!pTgt)
|
|
1744
|
+
return { content: [{ type: "text", text: `Entity not found: "${pTarget}"` }], isError: true };
|
|
1745
|
+
const path = graph.findPath(pSrc.id, pTgt.id, Math.min(pDepth ?? 5, 5));
|
|
1746
|
+
if (!path) {
|
|
1747
|
+
return { content: [{ type: "text", text: `🐉 No path found between **${pSource}** and **${pTarget}** within ${pDepth ?? 5} hops.` }] };
|
|
1748
|
+
}
|
|
1749
|
+
// Resolve entity names for path display
|
|
1750
|
+
const pathEntities = path.map(id => graph.getEntity(id)).filter(Boolean);
|
|
1751
|
+
const pathStr = pathEntities.map(e => e.name).join(' → ');
|
|
1752
|
+
return { content: [{ type: "text", text: `🐉 **Path Found** (${path.length - 1} hops)\n\n${pathStr}` }] };
|
|
1753
|
+
}
|
|
1754
|
+
case "wyrm_entity_merge": {
|
|
1755
|
+
const { projectPath: mPath, source: mSource, target: mTarget } = args;
|
|
1756
|
+
const mProject = db.getProject(mPath);
|
|
1757
|
+
if (!mProject)
|
|
1758
|
+
return { content: [{ type: "text", text: `Project not found: ${mPath}` }], isError: true };
|
|
1759
|
+
const mSrc = graph.findEntity(mProject.id, mSource) ?? graph.findByAlias(mProject.id, mSource);
|
|
1760
|
+
const mTgt = graph.findEntity(mProject.id, mTarget) ?? graph.findByAlias(mProject.id, mTarget);
|
|
1761
|
+
if (!mSrc)
|
|
1762
|
+
return { content: [{ type: "text", text: `Entity not found: "${mSource}"` }], isError: true };
|
|
1763
|
+
if (!mTgt)
|
|
1764
|
+
return { content: [{ type: "text", text: `Entity not found: "${mTarget}"` }], isError: true };
|
|
1765
|
+
if (mSrc.id === mTgt.id)
|
|
1766
|
+
return { content: [{ type: "text", text: `Cannot merge entity with itself.` }], isError: true };
|
|
1767
|
+
graph.mergeEntities(mSrc.id, mTgt.id);
|
|
1768
|
+
cache.invalidate('wyrm_entity_search');
|
|
1769
|
+
cache.invalidate('wyrm_entity_graph');
|
|
1770
|
+
cache.invalidate('wyrm_stats');
|
|
1771
|
+
return { content: [{ type: "text", text: `🐉 **Merged** "${mSource}" → "${mTarget}"\n\n"${mSource}" is now an alias of "${mTarget}". All relationships redirected.` }] };
|
|
1772
|
+
}
|
|
1773
|
+
// ==================== INTELLIGENCE AMPLIFICATION ====================
|
|
1774
|
+
case "wyrm_remember": {
|
|
1775
|
+
const { projectPath: rPath, kind: rKind, problem: rProblem, constraints: rConst, validatedFix: rFix, whyItWorked: rWhy, outcome: rOutcome, tags: rTags, confidence: rConf, sourceSessionId: rSessId } = args;
|
|
1776
|
+
const rProject = db.getProject(rPath);
|
|
1777
|
+
if (!rProject)
|
|
1778
|
+
return { content: [{ type: "text", text: `Project not found: ${rPath}` }], isError: true };
|
|
1779
|
+
const artifact = memory.add(rProject.id, {
|
|
1780
|
+
kind: rKind,
|
|
1781
|
+
problem: rProblem,
|
|
1782
|
+
constraints: rConst,
|
|
1783
|
+
validatedFix: rFix,
|
|
1784
|
+
whyItWorked: rWhy,
|
|
1785
|
+
outcome: rOutcome,
|
|
1786
|
+
tags: rTags,
|
|
1787
|
+
confidence: rConf,
|
|
1788
|
+
sourceSessionId: rSessId,
|
|
1789
|
+
});
|
|
1790
|
+
const kindEmoji = {
|
|
1791
|
+
pattern: '✅', lesson: '📚', reasoning_trace: '🧠', anti_pattern: '⚠️', heuristic: '💡',
|
|
1792
|
+
};
|
|
1793
|
+
const emoji = kindEmoji[artifact.kind] ?? '🐉';
|
|
1794
|
+
let text = `🐉 **Memory Stored** ${emoji}\n\n`;
|
|
1795
|
+
text += `- **Kind:** ${artifact.kind}\n`;
|
|
1796
|
+
text += `- **ID:** ${artifact.id}\n`;
|
|
1797
|
+
text += `- **Problem:** ${artifact.problem}\n`;
|
|
1798
|
+
if (artifact.validated_fix)
|
|
1799
|
+
text += `- **Solution:** ${artifact.validated_fix}\n`;
|
|
1800
|
+
if (artifact.why_it_worked)
|
|
1801
|
+
text += `- **Why it worked:** ${artifact.why_it_worked}\n`;
|
|
1802
|
+
text += `- **Confidence:** ${(artifact.confidence * 100).toFixed(0)}%\n`;
|
|
1803
|
+
if (artifact.tags)
|
|
1804
|
+
text += `- **Tags:** ${artifact.tags}\n`;
|
|
1805
|
+
text += `\n_Use \`wyrm_recall\` to retrieve similar memories, or \`wyrm_context_build\` to assemble a task brief._`;
|
|
1806
|
+
return { content: [{ type: "text", text }] };
|
|
1807
|
+
}
|
|
1808
|
+
case "wyrm_recall": {
|
|
1809
|
+
const { projectPath: rcPath, query: rcQuery, kind: rcKind, limit: rcLimit, minConfidence: rcMinConf } = args;
|
|
1810
|
+
const rcProject = db.getProject(rcPath);
|
|
1811
|
+
if (!rcProject)
|
|
1812
|
+
return { content: [{ type: "text", text: `Project not found: ${rcPath}` }], isError: true };
|
|
1813
|
+
const results = memory.recall(rcProject.id, rcQuery, {
|
|
1814
|
+
kind: rcKind,
|
|
1815
|
+
limit: rcLimit ?? 10,
|
|
1816
|
+
minConfidence: rcMinConf,
|
|
1817
|
+
});
|
|
1818
|
+
if (results.length === 0) {
|
|
1819
|
+
return { content: [{ type: "text", text: `🐉 No relevant memory found for: "${rcQuery}"\n\nUse \`wyrm_remember\` to store knowledge as you work.` }] };
|
|
1820
|
+
}
|
|
1821
|
+
const kindEmoji = {
|
|
1822
|
+
pattern: '✅', lesson: '📚', reasoning_trace: '🧠', anti_pattern: '⚠️', heuristic: '💡',
|
|
1823
|
+
};
|
|
1824
|
+
let text = `🐉 **Memory Recall** — "${rcQuery}" (${results.length} found)\n\n`;
|
|
1825
|
+
for (const r of results) {
|
|
1826
|
+
const a = r.artifact;
|
|
1827
|
+
const emoji = kindEmoji[a.kind] ?? '📌';
|
|
1828
|
+
text += `### ${emoji} ${a.kind} (ID: ${a.id}) — relevance ${(r.relevance_score * 100).toFixed(0)}%\n`;
|
|
1829
|
+
text += `**Problem:** ${a.problem}\n`;
|
|
1830
|
+
if (a.constraints)
|
|
1831
|
+
text += `**Constraints:** ${a.constraints}\n`;
|
|
1832
|
+
if (a.validated_fix)
|
|
1833
|
+
text += `**Solution:** ${a.validated_fix}\n`;
|
|
1834
|
+
if (a.why_it_worked)
|
|
1835
|
+
text += `**Why:** ${a.why_it_worked}\n`;
|
|
1836
|
+
if (a.outcome === 'negative')
|
|
1837
|
+
text += `⚠️ _This approach failed — see solution for what to do instead_\n`;
|
|
1838
|
+
text += `_Confidence: ${(a.confidence * 100).toFixed(0)}% | Used ${a.reuse_count}× | Success rate: ${a.reuse_count > 0 ? ((a.reuse_success_count / a.reuse_count) * 100).toFixed(0) + '%' : 'not yet used'}_\n\n`;
|
|
1839
|
+
}
|
|
1840
|
+
text += `_Use \`wyrm_feedback\` to record whether these were helpful._`;
|
|
1841
|
+
const response = cachedResponse(text);
|
|
1842
|
+
if (cacheKey)
|
|
1843
|
+
cache.set(cacheKey, response, 15000);
|
|
1844
|
+
return response;
|
|
1845
|
+
}
|
|
1846
|
+
case "wyrm_feedback": {
|
|
1847
|
+
const { artifactId, success } = args;
|
|
1848
|
+
const artifact = memory.get(artifactId);
|
|
1849
|
+
if (!artifact)
|
|
1850
|
+
return { content: [{ type: "text", text: `Artifact #${artifactId} not found.` }], isError: true };
|
|
1851
|
+
memory.recordFeedback(artifactId, success);
|
|
1852
|
+
const updated = memory.get(artifactId);
|
|
1853
|
+
const icon = success ? '✅' : '❌';
|
|
1854
|
+
return { content: [{ type: "text", text: `🐉 Feedback recorded ${icon}\n\n- Artifact #${artifactId}: "${artifact.problem.slice(0, 60)}"\n- New confidence: ${(updated.confidence * 100).toFixed(0)}%\n- Total uses: ${updated.reuse_count} (✅ ${updated.reuse_success_count} / ❌ ${updated.reuse_failure_count})` }] };
|
|
1855
|
+
}
|
|
1856
|
+
case "wyrm_context_build": {
|
|
1857
|
+
const { projectPath: cbPath, task: cbTask, maxItems: cbMax, kinds: cbKinds, minConfidence: cbMinConf } = args;
|
|
1858
|
+
const cbProject = db.getProject(cbPath);
|
|
1859
|
+
if (!cbProject)
|
|
1860
|
+
return { content: [{ type: "text", text: `Project not found: ${cbPath}` }], isError: true };
|
|
1861
|
+
const sections = [];
|
|
1862
|
+
// Section 1: Ground truths (~1200 char budget)
|
|
1863
|
+
const truthsText = groundTruths.formatForContext(cbProject.id);
|
|
1864
|
+
if (truthsText) {
|
|
1865
|
+
sections.push(truthsText.slice(0, 1200));
|
|
1866
|
+
}
|
|
1867
|
+
// Section 2: Best reasoning scaffold (~1500 char budget)
|
|
1868
|
+
const scaffoldMatch = scaffoldLib.findBest(cbTask, cbProject.id);
|
|
1869
|
+
if (scaffoldMatch) {
|
|
1870
|
+
const scaffoldText = scaffoldLib.formatForContext(scaffoldMatch);
|
|
1871
|
+
sections.push(scaffoldText.slice(0, 1500));
|
|
1872
|
+
}
|
|
1873
|
+
// Section 3: Memory artifacts brief (~2000 char budget)
|
|
1874
|
+
const brief = memory.buildContextBrief(cbProject.id, cbTask, {
|
|
1875
|
+
maxItems: Math.min(cbMax ?? 10, 20),
|
|
1876
|
+
kinds: cbKinds,
|
|
1877
|
+
minConfidence: cbMinConf,
|
|
1878
|
+
});
|
|
1879
|
+
if (brief.sections.length > 0) {
|
|
1880
|
+
const truncated = brief.text.slice(0, 2000);
|
|
1881
|
+
sections.push(truncated);
|
|
1882
|
+
}
|
|
1883
|
+
if (sections.length === 0) {
|
|
1884
|
+
return { content: [{ type: "text", text: `🐉 **Context Brief**\n\nNo relevant memory found for this task yet.\n\nAs you work, use \`wyrm_remember\` to store:\n- ✅ Patterns that work\n- ⚠️ Anti-patterns to avoid\n- 💡 Heuristics and shortcuts\n- 🧠 Reasoning traces from complex problems\n\nFuture context briefs will become richer over time.` }] };
|
|
1885
|
+
}
|
|
1886
|
+
const stats = memory.getStats(cbProject.id);
|
|
1887
|
+
const truthStats = groundTruths.getStats(cbProject.id);
|
|
1888
|
+
let text = `🐉 **Context Brief** — "${cbTask}"\n\n`;
|
|
1889
|
+
text += sections.join('\n\n---\n\n');
|
|
1890
|
+
text += `\n\n_Brief: ${truthStats.current} ground truth${truthStats.current !== 1 ? 's' : ''}, scaffold: ${scaffoldMatch ? scaffoldMatch.scaffold.problem_type : 'none'}, ${stats.total} memory artifact${stats.total !== 1 ? 's' : ''}.`;
|
|
1891
|
+
if (brief.sourceIds.length > 0) {
|
|
1892
|
+
text += ` Memory IDs: [${brief.sourceIds.join(', ')}] — use \`wyrm_feedback\` to rate._`;
|
|
1893
|
+
}
|
|
1894
|
+
else {
|
|
1895
|
+
text += `_`;
|
|
1896
|
+
}
|
|
1897
|
+
const response = cachedResponse(text);
|
|
1898
|
+
if (cacheKey)
|
|
1899
|
+
cache.set(cacheKey, response, 10000);
|
|
1900
|
+
return response;
|
|
1901
|
+
}
|
|
1902
|
+
case "wyrm_truth_set": {
|
|
1903
|
+
const { projectPath: tPath, category: tCat, key: tKey, value: tVal, rationale: tRat, source: tSrc, confidence: tConf } = args;
|
|
1904
|
+
const tProject = db.getProject(tPath);
|
|
1905
|
+
if (!tProject)
|
|
1906
|
+
return { content: [{ type: "text", text: `Project not found: ${tPath}` }], isError: true };
|
|
1907
|
+
const truth = groundTruths.set(tProject.id, {
|
|
1908
|
+
category: tCat,
|
|
1909
|
+
key: tKey,
|
|
1910
|
+
value: tVal,
|
|
1911
|
+
rationale: tRat,
|
|
1912
|
+
source: tSrc,
|
|
1913
|
+
confidence: tConf ?? 1.0,
|
|
1914
|
+
});
|
|
1915
|
+
cache.invalidate('wyrm_truth_get');
|
|
1916
|
+
cache.invalidate('wyrm_context_build');
|
|
1917
|
+
return { content: [{ type: "text", text: `🐉 **Ground Truth Set** ✅\n\n- **Category:** ${tCat}\n- **Key:** ${tKey}\n- **Value:** ${tVal}${tRat ? `\n- **Rationale:** ${tRat}` : ''}\n- **ID:** ${truth.id}\n\nThis truth will be injected at the top of every \`wyrm_context_build\` response for this project.` }] };
|
|
1918
|
+
}
|
|
1919
|
+
case "wyrm_truth_get": {
|
|
1920
|
+
const { projectPath: tgPath, category: tgCat } = args;
|
|
1921
|
+
const tgProject = db.getProject(tgPath);
|
|
1922
|
+
if (!tgProject)
|
|
1923
|
+
return { content: [{ type: "text", text: `Project not found: ${tgPath}` }], isError: true };
|
|
1924
|
+
const truths = groundTruths.getCurrent(tgProject.id, tgCat);
|
|
1925
|
+
if (truths.length === 0) {
|
|
1926
|
+
const msg = tgCat
|
|
1927
|
+
? `No ground truths in category "${tgCat}" for this project.`
|
|
1928
|
+
: `No ground truths stored yet. Use \`wyrm_truth_set\` to add validated facts.`;
|
|
1929
|
+
return { content: [{ type: "text", text: `🐉 **Ground Truths**\n\n${msg}` }] };
|
|
1930
|
+
}
|
|
1931
|
+
const byCategory = new Map();
|
|
1932
|
+
for (const t of truths) {
|
|
1933
|
+
if (!byCategory.has(t.category))
|
|
1934
|
+
byCategory.set(t.category, []);
|
|
1935
|
+
byCategory.get(t.category).push(t);
|
|
1936
|
+
}
|
|
1937
|
+
let text = `🐉 **Ground Truths** — ${truths.length} active\n\n`;
|
|
1938
|
+
for (const [cat, items] of byCategory) {
|
|
1939
|
+
text += `### ${cat}\n`;
|
|
1940
|
+
for (const t of items) {
|
|
1941
|
+
text += `- **${t.key}:** ${t.value}`;
|
|
1942
|
+
if (t.confidence < 1)
|
|
1943
|
+
text += ` _(confidence: ${(t.confidence * 100).toFixed(0)}%)_`;
|
|
1944
|
+
if (t.rationale)
|
|
1945
|
+
text += `\n _${t.rationale}_`;
|
|
1946
|
+
text += '\n';
|
|
1947
|
+
}
|
|
1948
|
+
text += '\n';
|
|
1949
|
+
}
|
|
1950
|
+
const response = cachedResponse(text);
|
|
1951
|
+
if (cacheKey)
|
|
1952
|
+
cache.set(cacheKey, response, 15000);
|
|
1953
|
+
return response;
|
|
1954
|
+
}
|
|
1955
|
+
case "wyrm_scaffold_save": {
|
|
1956
|
+
const { projectPath: spPath, problemType, description: spDesc, whenToUse, steps, verificationSteps, doNotApplyIf, tags: spTags } = args;
|
|
1957
|
+
const spProjectId = spPath ? db.getProject(spPath)?.id ?? undefined : undefined;
|
|
1958
|
+
const scaffold = scaffoldLib.save({
|
|
1959
|
+
projectId: spProjectId,
|
|
1960
|
+
problemType: problemType,
|
|
1961
|
+
description: spDesc,
|
|
1962
|
+
whenToUse: whenToUse,
|
|
1963
|
+
steps,
|
|
1964
|
+
verificationSteps: verificationSteps ?? undefined,
|
|
1965
|
+
doNotApplyIf: doNotApplyIf ? [doNotApplyIf] : undefined,
|
|
1966
|
+
tags: spTags ?? [],
|
|
1967
|
+
});
|
|
1968
|
+
cache.invalidate('wyrm_scaffold_get');
|
|
1969
|
+
cache.invalidate('wyrm_context_build');
|
|
1970
|
+
return { content: [{ type: "text", text: `🐉 **Reasoning Scaffold Saved** ✅\n\n- **Problem Type:** ${problemType}\n- **Description:** ${spDesc}\n- **Steps:** ${steps.length} checklist items\n- **ID:** ${scaffold.id}\n\nThis scaffold will be surfaced by \`wyrm_context_build\` when tasks match "${problemType}".` }] };
|
|
1971
|
+
}
|
|
1972
|
+
case "wyrm_scaffold_get": {
|
|
1973
|
+
const { projectPath: sgPath, task: sgTask, minConfidence: sgConf } = args;
|
|
1974
|
+
const sgProjectId = sgPath ? db.getProject(sgPath)?.id ?? undefined : undefined;
|
|
1975
|
+
const match = scaffoldLib.findBest(sgTask, sgProjectId, sgConf);
|
|
1976
|
+
if (!match) {
|
|
1977
|
+
return { content: [{ type: "text", text: `🐉 **Reasoning Scaffold**\n\nNo scaffold matched for: "${sgTask}"\n\nUse \`wyrm_scaffold_save\` to add scaffolds for this problem type.` }] };
|
|
1978
|
+
}
|
|
1979
|
+
const text = scaffoldLib.formatForContext(match);
|
|
1980
|
+
const response = cachedResponse(`🐉 **Reasoning Scaffold Match** (${(match.matchScore * 100).toFixed(0)}% confidence)\n\n${text}`);
|
|
1981
|
+
if (cacheKey)
|
|
1982
|
+
cache.set(cacheKey, response, 15000);
|
|
1983
|
+
return response;
|
|
1984
|
+
}
|
|
1985
|
+
case "wyrm_distill": {
|
|
1986
|
+
const { projectPath: dPath, candidates } = args;
|
|
1987
|
+
const dProject = db.getProject(dPath);
|
|
1988
|
+
if (!dProject)
|
|
1989
|
+
return { content: [{ type: "text", text: `Project not found: ${dPath}` }], isError: true };
|
|
1990
|
+
if (!candidates || candidates.length === 0) {
|
|
1991
|
+
return { content: [{ type: "text", text: `🐉 **Distill**\n\nNo candidates provided. Provide an array of candidate artifacts to distill.` }], isError: true };
|
|
1992
|
+
}
|
|
1993
|
+
const ids = [];
|
|
1994
|
+
for (const c of candidates) {
|
|
1995
|
+
const artifact = memory.add(dProject.id, {
|
|
1996
|
+
kind: c.kind,
|
|
1997
|
+
problem: c.title,
|
|
1998
|
+
validatedFix: c.content,
|
|
1999
|
+
tags: c.tags ?? [],
|
|
2000
|
+
confidence: c.confidence ?? 0.7,
|
|
2001
|
+
needsReview: 1,
|
|
2002
|
+
});
|
|
2003
|
+
ids.push(artifact.id);
|
|
2004
|
+
}
|
|
2005
|
+
let text = `🐉 **Distillation Complete** — ${ids.length} artifact${ids.length !== 1 ? 's' : ''} queued for review\n\n`;
|
|
2006
|
+
candidates.forEach((c, i) => {
|
|
2007
|
+
text += `- [#${ids[i]}] **${c.kind}**: ${c.title}\n`;
|
|
2008
|
+
});
|
|
2009
|
+
text += `\nUse \`wyrm_review\` with each ID to approve or reject.`;
|
|
2010
|
+
return { content: [{ type: "text", text }] };
|
|
2011
|
+
}
|
|
2012
|
+
case "wyrm_review": {
|
|
2013
|
+
const { artifactId: rvId, approved, notes: rvNotes } = args;
|
|
2014
|
+
const artifact = memory.get(rvId);
|
|
2015
|
+
if (!artifact)
|
|
2016
|
+
return { content: [{ type: "text", text: `Artifact #${rvId} not found.` }], isError: true };
|
|
2017
|
+
if (approved) {
|
|
2018
|
+
memory.update(rvId, { needs_review: 0 });
|
|
2019
|
+
const icon = '✅';
|
|
2020
|
+
return { content: [{ type: "text", text: `🐉 ${icon} **Approved** — Artifact #${rvId} is now active.\n\n"${artifact.problem.slice(0, 80)}"\n\nIt will appear in future \`wyrm_recall\` and \`wyrm_context_build\` results.${rvNotes ? `\n\nNotes: ${rvNotes}` : ''}` }] };
|
|
2021
|
+
}
|
|
2022
|
+
else {
|
|
2023
|
+
const rawDb = db.getDatabase();
|
|
2024
|
+
rawDb.prepare('DELETE FROM memory_artifacts WHERE id = ?').run(rvId);
|
|
2025
|
+
return { content: [{ type: "text", text: `🐉 ❌ **Rejected** — Artifact #${rvId} permanently deleted.\n\n"${artifact.problem.slice(0, 80)}"${rvNotes ? `\n\nNotes: ${rvNotes}` : ''}` }] };
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
1240
2028
|
case "wyrm_sync": {
|
|
1241
2029
|
const { projectPath, direction } = args;
|
|
1242
2030
|
const dir = direction || 'both';
|