@splicr/mcp-server 0.10.4 → 0.11.1
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.js +144 -93
- package/dist/index.js +18 -12
- package/dist/lib/api-client.d.ts +13 -0
- package/dist/lib/api-client.js +8 -0
- package/dist/tools/get-decisions.d.ts +27 -0
- package/dist/tools/get-decisions.js +90 -0
- package/dist/tools/get-project-context.js +8 -2
- package/dist/tools/get-team-status.d.ts +14 -0
- package/dist/tools/get-team-status.js +61 -0
- package/dist/tools/save-from-agent.d.ts +5 -0
- package/dist/tools/save-from-agent.js +21 -15
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -404,30 +404,50 @@ async function runHook() {
|
|
|
404
404
|
// Gather env signals + fuse with user's prompt
|
|
405
405
|
const { gatherSignals } = await import('./lib/signal-gatherer.js');
|
|
406
406
|
const { fuseSignals } = await import('./lib/signal-fusion.js');
|
|
407
|
-
const { getRelevantContext } = await import('./lib/api-client.js');
|
|
407
|
+
const { getRelevantContext, getProjectContext } = await import('./lib/api-client.js');
|
|
408
|
+
const { detectProject } = await import('./lib/project-detector.js');
|
|
408
409
|
const envSignals = gatherSignals(process.cwd());
|
|
409
410
|
const fused = fuseSignals({ task: userPrompt }, envSignals);
|
|
410
411
|
if (fused.queries.length === 0) {
|
|
411
412
|
process.exit(0);
|
|
412
413
|
return;
|
|
413
414
|
}
|
|
414
|
-
//
|
|
415
|
-
const
|
|
415
|
+
// Check if this is the first message of the session (patterns not yet injected)
|
|
416
|
+
const sessionState = loadSessionState(sessionId);
|
|
417
|
+
const isFirstMessage = !loadSessionMeta(sessionId).patterns_injected;
|
|
418
|
+
// Fetch relevant context + patterns in parallel (patterns only on first message)
|
|
419
|
+
const contextPromise = getRelevantContext({
|
|
416
420
|
queries: fused.queries,
|
|
417
421
|
tech_stack: fused.tech_stack,
|
|
418
|
-
limit: 5,
|
|
422
|
+
limit: 5,
|
|
419
423
|
});
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
424
|
+
let patternsPromise = Promise.resolve({});
|
|
425
|
+
if (isFirstMessage) {
|
|
426
|
+
const detected = await detectProject(process.cwd()).catch(() => null);
|
|
427
|
+
if (detected?.name) {
|
|
428
|
+
patternsPromise = getProjectContext({
|
|
429
|
+
project_name: detected.name,
|
|
430
|
+
project_id: detected.id,
|
|
431
|
+
limit: 1, // we only need patterns, not captures
|
|
432
|
+
}).catch(() => ({}));
|
|
433
|
+
}
|
|
423
434
|
}
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
if (
|
|
435
|
+
const [{ results }, patternData] = await Promise.all([contextPromise, patternsPromise]);
|
|
436
|
+
// Build patterns section (first message only, deterministic enforcement)
|
|
437
|
+
let patternsSection = '';
|
|
438
|
+
if (isFirstMessage && patternData.patterns && patternData.patterns.length > 0) {
|
|
439
|
+
const patternLines = patternData.patterns.map((p, i) => `${i + 1}. **${p.name}** — ${p.description}`).join('\n');
|
|
440
|
+
patternsSection = `ACTIVE TEAM PATTERNS for ${patternData.project_name || 'this project'}:\n${patternLines}\n\nThese patterns are established by your team. Follow them unless explicitly asked to deviate.\n\n---\n\n`;
|
|
441
|
+
// Mark patterns as injected for this session
|
|
442
|
+
saveSessionMeta(sessionId, { patterns_injected: true });
|
|
443
|
+
}
|
|
444
|
+
if ((!results || results.length === 0) && !patternsSection) {
|
|
428
445
|
process.exit(0);
|
|
429
446
|
return;
|
|
430
447
|
}
|
|
448
|
+
// Session-aware dedup: don't inject same captures twice in one session
|
|
449
|
+
const injectedIds = sessionState;
|
|
450
|
+
const freshResults = (results || []).filter((r) => !injectedIds.has(r.id));
|
|
431
451
|
// Take top 3 fresh results
|
|
432
452
|
const topResults = freshResults.slice(0, 3);
|
|
433
453
|
// Track what we injected (IDs + titles for context-aware gate)
|
|
@@ -435,14 +455,22 @@ async function runHook() {
|
|
|
435
455
|
for (const r of topResults)
|
|
436
456
|
injectedIds.add(r.id);
|
|
437
457
|
saveSessionState(sessionId, injectedIds, titles);
|
|
438
|
-
// Format
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
const
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
458
|
+
// Format knowledge context
|
|
459
|
+
let contextSection = '';
|
|
460
|
+
if (topResults.length > 0) {
|
|
461
|
+
const contextLines = topResults.map((r, i) => {
|
|
462
|
+
const why = r.why_relevant || 'Semantically similar to your work context';
|
|
463
|
+
const tags = r.tags?.length ? r.tags.slice(0, 5).join(', ') : '';
|
|
464
|
+
const source = r.source_type || 'unknown';
|
|
465
|
+
return `${i + 1}. "${r.title}" [${source}]${tags ? ` (${tags})` : ''}\n → ${r.insight}\n WHY RELEVANT: ${why}\n DEEP DIVE: get_full_content("${r.id}")`;
|
|
466
|
+
}).join('\n\n');
|
|
467
|
+
contextSection = `SPLICR CONTEXT — The user's saved research matched this task. Use these findings to inform your response:\n\n${contextLines}\n\nACTION: Review above before answering. Call get_full_content(id) for complete articles. Fall back to web search only if these don't cover the question.`;
|
|
468
|
+
}
|
|
469
|
+
const context = patternsSection + contextSection;
|
|
470
|
+
if (!context.trim()) {
|
|
471
|
+
process.exit(0);
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
446
474
|
// Output JSON that Claude Code's hook system understands
|
|
447
475
|
const output = JSON.stringify({
|
|
448
476
|
hookSpecificOutput: {
|
|
@@ -713,31 +741,23 @@ async function runSessionEnd() {
|
|
|
713
741
|
// Take the last ~5000 chars of assistant messages
|
|
714
742
|
const combined = messages.join('\n\n');
|
|
715
743
|
const tail = combined.length > 5000 ? combined.slice(-5000) : combined;
|
|
716
|
-
//
|
|
717
|
-
if (tail.includes('Saved to Splicr')) {
|
|
718
|
-
process.exit(0);
|
|
719
|
-
return;
|
|
720
|
-
}
|
|
721
|
-
// Send to capture endpoint — let the AI pipeline distill and decide significance
|
|
744
|
+
// Send to structured learning extraction endpoint
|
|
722
745
|
const authMod = await import('./auth.js');
|
|
723
746
|
const auth = await authMod.loadAuth();
|
|
724
747
|
const API_URL = process.env.SPLICR_API_URL || 'https://api-production-d889.up.railway.app';
|
|
725
|
-
// Fire-and-forget POST to
|
|
726
|
-
|
|
748
|
+
// Fire-and-forget POST to session/complete-with-transcript
|
|
749
|
+
// This replaces the old raw transcript dump with structured AI extraction
|
|
750
|
+
fetch(`${API_URL}/mcp/session/complete-with-transcript`, {
|
|
727
751
|
method: 'POST',
|
|
728
752
|
headers: {
|
|
729
753
|
'Authorization': `Bearer ${auth.accessToken}`,
|
|
730
754
|
'Content-Type': 'application/json',
|
|
731
755
|
},
|
|
732
756
|
body: JSON.stringify({
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
captured_from: 'ide_agent',
|
|
736
|
-
title: `Session learning — ${new Date().toISOString().split('T')[0]}`,
|
|
737
|
-
tags: ['auto-save', 'session-learning'],
|
|
738
|
-
extracted_content: tail,
|
|
757
|
+
session_id: sessionId || undefined,
|
|
758
|
+
transcript_tail: tail,
|
|
739
759
|
}),
|
|
740
|
-
signal: AbortSignal.timeout(
|
|
760
|
+
signal: AbortSignal.timeout(10000),
|
|
741
761
|
}).catch(() => { }); // fire-and-forget
|
|
742
762
|
process.exit(0);
|
|
743
763
|
}
|
|
@@ -781,6 +801,37 @@ function saveSessionState(sessionId, injectedIds, titles) {
|
|
|
781
801
|
}
|
|
782
802
|
catch { /* non-critical */ }
|
|
783
803
|
}
|
|
804
|
+
function loadSessionMeta(sessionId) {
|
|
805
|
+
if (!sessionId)
|
|
806
|
+
return {};
|
|
807
|
+
try {
|
|
808
|
+
const file = join(SESSION_STATE_DIR, `${sessionId}.json`);
|
|
809
|
+
if (existsSync(file)) {
|
|
810
|
+
const data = JSON.parse(readFileSync(file, 'utf-8'));
|
|
811
|
+
return { patterns_injected: data.patterns_injected || false };
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
catch { /* ignore */ }
|
|
815
|
+
return {};
|
|
816
|
+
}
|
|
817
|
+
function saveSessionMeta(sessionId, meta) {
|
|
818
|
+
if (!sessionId)
|
|
819
|
+
return;
|
|
820
|
+
try {
|
|
821
|
+
if (!existsSync(SESSION_STATE_DIR))
|
|
822
|
+
mkdirSync(SESSION_STATE_DIR, { recursive: true });
|
|
823
|
+
const file = join(SESSION_STATE_DIR, `${sessionId}.json`);
|
|
824
|
+
let existing = {};
|
|
825
|
+
if (existsSync(file)) {
|
|
826
|
+
try {
|
|
827
|
+
existing = JSON.parse(readFileSync(file, 'utf-8'));
|
|
828
|
+
}
|
|
829
|
+
catch { }
|
|
830
|
+
}
|
|
831
|
+
writeFileSync(file, JSON.stringify({ ...existing, ...meta, updated: Date.now() }));
|
|
832
|
+
}
|
|
833
|
+
catch { /* non-critical */ }
|
|
834
|
+
}
|
|
784
835
|
function readStdin() {
|
|
785
836
|
return new Promise((resolve) => {
|
|
786
837
|
let data = '';
|
|
@@ -791,26 +842,26 @@ function readStdin() {
|
|
|
791
842
|
setTimeout(() => resolve(data), 500);
|
|
792
843
|
});
|
|
793
844
|
}
|
|
794
|
-
const SPLICR_INSTRUCTION = `Call get_relevant_context (Splicr MCP) at the START of any task. Pass your understanding of the task (the user's message), any files you're reading/modifying, and any errors you've encountered. It automatically searches the user's knowledge base using full context awareness — your task, git branch, recent commits, changed files, and more.
|
|
795
|
-
|
|
796
|
-
For follow-up or narrow searches during the same task, use search_knowledge with a specific query.
|
|
797
|
-
|
|
798
|
-
When results have high relevance (>50%), call get_full_content(id) to get the complete saved content before responding.
|
|
799
|
-
|
|
800
|
-
Splicr is the user's personal knowledge base. They save articles, tweets, code snippets, and research. Your job is to use it — not ignore it and rely on training data alone.
|
|
801
|
-
|
|
802
|
-
## Auto-save learnings
|
|
803
|
-
At the END of significant sessions (not trivial ones), review what you learned and save decisions/discoveries to Splicr using save_from_agent. Include the context field for attribution.
|
|
804
|
-
|
|
805
|
-
Save ONLY: decisions (chose X over Y because...), discoveries (undocumented behavior), workarounds (non-obvious fixes), synthesis (combined multiple insights into a new approach).
|
|
806
|
-
Do NOT save: file edits, routine commands, anything derivable from git log or reading the current code.
|
|
807
|
-
|
|
808
|
-
Ask yourself: "Would a developer starting a new session benefit from knowing this?" If yes, save it.
|
|
809
|
-
|
|
810
|
-
## Failure modes
|
|
811
|
-
- Splicr returns no results → fall back to web search or training data
|
|
812
|
-
- Splicr returns results but they don't answer the question → supplement with web search
|
|
813
|
-
- Splicr API is unreachable → proceed without it, don't block the session
|
|
845
|
+
const SPLICR_INSTRUCTION = `Call get_relevant_context (Splicr MCP) at the START of any task. Pass your understanding of the task (the user's message), any files you're reading/modifying, and any errors you've encountered. It automatically searches the user's knowledge base using full context awareness — your task, git branch, recent commits, changed files, and more.
|
|
846
|
+
|
|
847
|
+
For follow-up or narrow searches during the same task, use search_knowledge with a specific query.
|
|
848
|
+
|
|
849
|
+
When results have high relevance (>50%), call get_full_content(id) to get the complete saved content before responding.
|
|
850
|
+
|
|
851
|
+
Splicr is the user's personal knowledge base. They save articles, tweets, code snippets, and research. Your job is to use it — not ignore it and rely on training data alone.
|
|
852
|
+
|
|
853
|
+
## Auto-save learnings
|
|
854
|
+
At the END of significant sessions (not trivial ones), review what you learned and save decisions/discoveries to Splicr using save_from_agent. Include the context field for attribution.
|
|
855
|
+
|
|
856
|
+
Save ONLY: decisions (chose X over Y because...), discoveries (undocumented behavior), workarounds (non-obvious fixes), synthesis (combined multiple insights into a new approach).
|
|
857
|
+
Do NOT save: file edits, routine commands, anything derivable from git log or reading the current code.
|
|
858
|
+
|
|
859
|
+
Ask yourself: "Would a developer starting a new session benefit from knowing this?" If yes, save it.
|
|
860
|
+
|
|
861
|
+
## Failure modes
|
|
862
|
+
- Splicr returns no results → fall back to web search or training data
|
|
863
|
+
- Splicr returns results but they don't answer the question → supplement with web search
|
|
864
|
+
- Splicr API is unreachable → proceed without it, don't block the session
|
|
814
865
|
- You forgot to save learnings → the Stop hook will remind you`;
|
|
815
866
|
const SPLICR_MARKER = '# Splicr';
|
|
816
867
|
function getInstructionFiles() {
|
|
@@ -1060,17 +1111,17 @@ async function teamCommand() {
|
|
|
1060
1111
|
break;
|
|
1061
1112
|
}
|
|
1062
1113
|
default:
|
|
1063
|
-
console.error(`
|
|
1064
|
-
Splicr Teams
|
|
1065
|
-
|
|
1066
|
-
Commands:
|
|
1067
|
-
team create "Name" Create a new team
|
|
1068
|
-
team list List your teams
|
|
1069
|
-
team invite Show invite link for your team
|
|
1070
|
-
team join <code> Join a team by invite code
|
|
1071
|
-
|
|
1072
|
-
Or use setup --join:
|
|
1073
|
-
setup --join <code> Sign up + join team + configure agents (one command)
|
|
1114
|
+
console.error(`
|
|
1115
|
+
Splicr Teams
|
|
1116
|
+
|
|
1117
|
+
Commands:
|
|
1118
|
+
team create "Name" Create a new team
|
|
1119
|
+
team list List your teams
|
|
1120
|
+
team invite Show invite link for your team
|
|
1121
|
+
team join <code> Join a team by invite code
|
|
1122
|
+
|
|
1123
|
+
Or use setup --join:
|
|
1124
|
+
setup --join <code> Sign up + join team + configure agents (one command)
|
|
1074
1125
|
`);
|
|
1075
1126
|
break;
|
|
1076
1127
|
}
|
|
@@ -1087,34 +1138,34 @@ function printManualConfig() {
|
|
|
1087
1138
|
console.error(' }\n');
|
|
1088
1139
|
}
|
|
1089
1140
|
function printHelp() {
|
|
1090
|
-
console.error(`
|
|
1091
|
-
Splicr — route what you read to what you're building
|
|
1092
|
-
|
|
1093
|
-
Getting started:
|
|
1094
|
-
npx @splicr/mcp-server setup Sign up + configure all agents (one command)
|
|
1095
|
-
npx @splicr/mcp-server setup --join <code> Sign up + join team + configure agents
|
|
1096
|
-
|
|
1097
|
-
Commands:
|
|
1098
|
-
setup One-time setup: authenticate + configure agents + hooks
|
|
1099
|
-
login Re-authenticate
|
|
1100
|
-
team create "Name" Create a new team
|
|
1101
|
-
team list List your teams
|
|
1102
|
-
team invite Show invite link for your team
|
|
1103
|
-
team join <code> Join a team by invite code
|
|
1104
|
-
dashboard Open knowledge dashboard in browser
|
|
1105
|
-
uninstall Remove Splicr from all coding agents
|
|
1106
|
-
|
|
1107
|
-
Supported agents (auto-detected):
|
|
1108
|
-
Claude Code, Codex, Cursor, Cline, Antigravity
|
|
1109
|
-
|
|
1110
|
-
Quick start:
|
|
1111
|
-
1. Run: npx @splicr/mcp-server setup
|
|
1112
|
-
2. Save knowledge from anywhere:
|
|
1113
|
-
- Telegram: t.me/SplicrBot (send links from your phone)
|
|
1114
|
-
- Extension: chromewebstore.google.com/detail/dllhofjfmkmbdadilbdgojapfbmlhnla
|
|
1115
|
-
- Agent: save_from_agent tool (agents save learnings)
|
|
1116
|
-
3. Open any coding agent — your saves show up when relevant
|
|
1117
|
-
4. Dashboard: splicr.dev/dashboard
|
|
1141
|
+
console.error(`
|
|
1142
|
+
Splicr — route what you read to what you're building
|
|
1143
|
+
|
|
1144
|
+
Getting started:
|
|
1145
|
+
npx @splicr/mcp-server setup Sign up + configure all agents (one command)
|
|
1146
|
+
npx @splicr/mcp-server setup --join <code> Sign up + join team + configure agents
|
|
1147
|
+
|
|
1148
|
+
Commands:
|
|
1149
|
+
setup One-time setup: authenticate + configure agents + hooks
|
|
1150
|
+
login Re-authenticate
|
|
1151
|
+
team create "Name" Create a new team
|
|
1152
|
+
team list List your teams
|
|
1153
|
+
team invite Show invite link for your team
|
|
1154
|
+
team join <code> Join a team by invite code
|
|
1155
|
+
dashboard Open knowledge dashboard in browser
|
|
1156
|
+
uninstall Remove Splicr from all coding agents
|
|
1157
|
+
|
|
1158
|
+
Supported agents (auto-detected):
|
|
1159
|
+
Claude Code, Codex, Cursor, Cline, Antigravity
|
|
1160
|
+
|
|
1161
|
+
Quick start:
|
|
1162
|
+
1. Run: npx @splicr/mcp-server setup
|
|
1163
|
+
2. Save knowledge from anywhere:
|
|
1164
|
+
- Telegram: t.me/SplicrBot (send links from your phone)
|
|
1165
|
+
- Extension: chromewebstore.google.com/detail/dllhofjfmkmbdadilbdgojapfbmlhnla
|
|
1166
|
+
- Agent: save_from_agent tool (agents save learnings)
|
|
1167
|
+
3. Open any coding agent — your saves show up when relevant
|
|
1168
|
+
4. Dashboard: splicr.dev/dashboard
|
|
1118
1169
|
`);
|
|
1119
1170
|
}
|
|
1120
1171
|
main().catch(err => {
|
package/dist/index.js
CHANGED
|
@@ -13,6 +13,8 @@ import { browseProjectSchema, handleBrowseProject } from './tools/browse-project
|
|
|
13
13
|
import { getCompiledPageSchema, handleGetCompiledPage } from './tools/get-compiled-page.js';
|
|
14
14
|
import { grepKnowledgeSchema, handleGrepKnowledge } from './tools/grep-knowledge.js';
|
|
15
15
|
import { exploreKnowledgeSchema, handleExploreKnowledge } from './tools/explore-knowledge.js';
|
|
16
|
+
import { getDecisionsSchema, handleGetDecisions } from './tools/get-decisions.js';
|
|
17
|
+
import { getTeamStatusSchema, handleGetTeamStatus } from './tools/get-team-status.js';
|
|
16
18
|
import { completeSession } from './lib/api-client.js';
|
|
17
19
|
// Prevent unhandled errors from crashing the MCP server
|
|
18
20
|
process.on('uncaughtException', (err) => {
|
|
@@ -33,18 +35,18 @@ for (const signal of ['SIGTERM', 'SIGINT']) {
|
|
|
33
35
|
}
|
|
34
36
|
const server = new Server({ name: 'splicr', version: '0.1.0' }, {
|
|
35
37
|
capabilities: { tools: {} },
|
|
36
|
-
instructions: `Splicr is the user's personal knowledge base — articles, tweets, code snippets, GitHub repos, and agent learnings saved from across the web. It is your PRIMARY source of context. Using it is not optional.
|
|
37
|
-
|
|
38
|
-
CONTRACTS (you must follow these):
|
|
39
|
-
1. BEFORE answering any question, BEFORE web searching, BEFORE relying on training data — call get_relevant_context with the user's task. This is not a suggestion. The user saved knowledge specifically for moments like this.
|
|
40
|
-
2. When results have relevance >50%, call get_full_content(id) to get the complete saved content. Use it to ground your response.
|
|
41
|
-
2b. When results include a compiled_page field, prefer get_compiled_page(id) — it's a synthesized wiki page merging multiple related sources into one structured document. More useful than reading individual captures.
|
|
42
|
-
3. Only fall back to web search or training data if Splicr returns no results or results don't cover the question.
|
|
43
|
-
4. At the END of significant sessions, call save_from_agent with decisions, discoveries, or workarounds you made. Include the context field for attribution.
|
|
44
|
-
|
|
45
|
-
FAILURE MODES:
|
|
46
|
-
- Splicr returns no results → proceed with web search or training data
|
|
47
|
-
- Splicr API is unreachable → proceed without it, don't block the session
|
|
38
|
+
instructions: `Splicr is the user's personal knowledge base — articles, tweets, code snippets, GitHub repos, and agent learnings saved from across the web. It is your PRIMARY source of context. Using it is not optional.
|
|
39
|
+
|
|
40
|
+
CONTRACTS (you must follow these):
|
|
41
|
+
1. BEFORE answering any question, BEFORE web searching, BEFORE relying on training data — call get_relevant_context with the user's task. This is not a suggestion. The user saved knowledge specifically for moments like this.
|
|
42
|
+
2. When results have relevance >50%, call get_full_content(id) to get the complete saved content. Use it to ground your response.
|
|
43
|
+
2b. When results include a compiled_page field, prefer get_compiled_page(id) — it's a synthesized wiki page merging multiple related sources into one structured document. More useful than reading individual captures.
|
|
44
|
+
3. Only fall back to web search or training data if Splicr returns no results or results don't cover the question.
|
|
45
|
+
4. At the END of significant sessions, call save_from_agent with decisions, discoveries, or workarounds you made. Include the context field for attribution.
|
|
46
|
+
|
|
47
|
+
FAILURE MODES:
|
|
48
|
+
- Splicr returns no results → proceed with web search or training data
|
|
49
|
+
- Splicr API is unreachable → proceed without it, don't block the session
|
|
48
50
|
- Results don't match the question → supplement with other sources`,
|
|
49
51
|
});
|
|
50
52
|
// List tools
|
|
@@ -61,6 +63,8 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
61
63
|
getCompiledPageSchema,
|
|
62
64
|
grepKnowledgeSchema,
|
|
63
65
|
exploreKnowledgeSchema,
|
|
66
|
+
getDecisionsSchema,
|
|
67
|
+
getTeamStatusSchema,
|
|
64
68
|
],
|
|
65
69
|
}));
|
|
66
70
|
// Handle tool calls with per-tool timeout
|
|
@@ -79,6 +83,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
79
83
|
get_compiled_page: handleGetCompiledPage,
|
|
80
84
|
grep_knowledge: handleGrepKnowledge,
|
|
81
85
|
explore_knowledge: handleExploreKnowledge,
|
|
86
|
+
get_decisions: handleGetDecisions,
|
|
87
|
+
get_team_status: handleGetTeamStatus,
|
|
82
88
|
}[name];
|
|
83
89
|
if (!handler) {
|
|
84
90
|
return { content: [{ type: 'text', text: `Unknown tool: ${name}` }], isError: true };
|
package/dist/lib/api-client.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ export declare function getProjectContext(params: {
|
|
|
11
11
|
limit?: number;
|
|
12
12
|
}): Promise<{
|
|
13
13
|
results: any[];
|
|
14
|
+
patterns?: any[];
|
|
14
15
|
project_name?: string;
|
|
15
16
|
}>;
|
|
16
17
|
export declare function getRecentInsights(params: {
|
|
@@ -27,6 +28,7 @@ export declare function saveFromAgent(params: {
|
|
|
27
28
|
tags?: string[];
|
|
28
29
|
memory_type?: string;
|
|
29
30
|
context?: Record<string, unknown>;
|
|
31
|
+
register_as_pattern?: boolean;
|
|
30
32
|
}): Promise<{
|
|
31
33
|
id: string;
|
|
32
34
|
title: string;
|
|
@@ -136,4 +138,15 @@ export declare function getRelevantContext(params: {
|
|
|
136
138
|
results: any[];
|
|
137
139
|
search_strategy: string;
|
|
138
140
|
}>;
|
|
141
|
+
export declare function getDecisions(params: {
|
|
142
|
+
topic?: string;
|
|
143
|
+
project_name?: string;
|
|
144
|
+
type?: string;
|
|
145
|
+
limit?: number;
|
|
146
|
+
}): Promise<{
|
|
147
|
+
results: any[];
|
|
148
|
+
}>;
|
|
149
|
+
export declare function getTeamStatus(params: {
|
|
150
|
+
project?: string;
|
|
151
|
+
}): Promise<any>;
|
|
139
152
|
export { API_URL };
|
package/dist/lib/api-client.js
CHANGED
|
@@ -100,4 +100,12 @@ export async function getRelevantContext(params) {
|
|
|
100
100
|
const data = await apiRequest('POST', '/mcp/relevant-context', params);
|
|
101
101
|
return { results: data.results ?? [], search_strategy: data.search_strategy ?? '' };
|
|
102
102
|
}
|
|
103
|
+
export async function getDecisions(params) {
|
|
104
|
+
const data = await apiRequest('POST', '/mcp/decisions', params);
|
|
105
|
+
return { results: data.results ?? [] };
|
|
106
|
+
}
|
|
107
|
+
export async function getTeamStatus(params) {
|
|
108
|
+
const query = params.project ? `?project=${encodeURIComponent(params.project)}` : '';
|
|
109
|
+
return await apiRequest('GET', `/mcp/team-status${query}`);
|
|
110
|
+
}
|
|
103
111
|
export { API_URL };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export declare const getDecisionsSchema: {
|
|
2
|
+
name: "get_decisions";
|
|
3
|
+
description: string;
|
|
4
|
+
inputSchema: {
|
|
5
|
+
type: "object";
|
|
6
|
+
properties: {
|
|
7
|
+
topic: {
|
|
8
|
+
type: "string";
|
|
9
|
+
description: string;
|
|
10
|
+
};
|
|
11
|
+
project: {
|
|
12
|
+
type: "string";
|
|
13
|
+
description: string;
|
|
14
|
+
};
|
|
15
|
+
type: {
|
|
16
|
+
type: "string";
|
|
17
|
+
enum: string[];
|
|
18
|
+
description: string;
|
|
19
|
+
};
|
|
20
|
+
limit: {
|
|
21
|
+
type: "number";
|
|
22
|
+
description: string;
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
export declare function handleGetDecisions(args: Record<string, unknown>): Promise<string>;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { getDecisions } from '../lib/api-client.js';
|
|
2
|
+
import { detectProject } from '../lib/project-detector.js';
|
|
3
|
+
import * as session from '../lib/session-state.js';
|
|
4
|
+
export const getDecisionsSchema = {
|
|
5
|
+
name: 'get_decisions',
|
|
6
|
+
description: `Query the team's decision log - technical decisions, patterns, discoveries, and failures recorded during agent sessions.
|
|
7
|
+
|
|
8
|
+
Use when:
|
|
9
|
+
- Before making an architecture or technology choice (see what the team decided before)
|
|
10
|
+
- When someone asks "why did we do X this way?"
|
|
11
|
+
- When evaluating alternatives for a new approach
|
|
12
|
+
- To check if something was tried before and failed
|
|
13
|
+
|
|
14
|
+
Returns structured decisions with rationale, alternatives considered, and anti-pattern warnings.`,
|
|
15
|
+
inputSchema: {
|
|
16
|
+
type: 'object',
|
|
17
|
+
properties: {
|
|
18
|
+
topic: { type: 'string', description: 'Topic to search decisions about (e.g., "auth", "caching", "error handling")' },
|
|
19
|
+
project: { type: 'string', description: 'Project name or "auto" (default: auto)' },
|
|
20
|
+
type: {
|
|
21
|
+
type: 'string',
|
|
22
|
+
enum: ['decision', 'discovery', 'pattern', 'failure', 'workaround', 'all'],
|
|
23
|
+
description: 'Filter by decision type (default: all)',
|
|
24
|
+
},
|
|
25
|
+
limit: { type: 'number', description: 'Max results (default: 10)' },
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
export async function handleGetDecisions(args) {
|
|
30
|
+
const topic = args.topic;
|
|
31
|
+
const typeFilter = args.type || 'all';
|
|
32
|
+
const limit = Math.min(Math.max(1, Number(args.limit) || 10), 50);
|
|
33
|
+
// Resolve project
|
|
34
|
+
let projectName;
|
|
35
|
+
const projectArg = args.project || 'auto';
|
|
36
|
+
if (projectArg === 'auto') {
|
|
37
|
+
const detected = await detectProject(process.cwd());
|
|
38
|
+
projectName = detected?.name;
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
projectName = projectArg;
|
|
42
|
+
}
|
|
43
|
+
const data = await getDecisions({
|
|
44
|
+
topic,
|
|
45
|
+
project_name: projectName,
|
|
46
|
+
type: typeFilter,
|
|
47
|
+
limit,
|
|
48
|
+
});
|
|
49
|
+
session.recordToolCall();
|
|
50
|
+
if (!data.results || data.results.length === 0) {
|
|
51
|
+
const scope = projectName ? ` for ${projectName}` : '';
|
|
52
|
+
const topicNote = topic ? ` about "${topic}"` : '';
|
|
53
|
+
return `No decisions found${scope}${topicNote}. The decision log builds over time as agents auto-extract learnings from sessions.`;
|
|
54
|
+
}
|
|
55
|
+
const items = data.results.map((r, i) => {
|
|
56
|
+
const meta = r.metadata || {};
|
|
57
|
+
const typeLabel = meta.decision_type || meta.learning_type || 'unknown';
|
|
58
|
+
const confidence = meta.confidence ? ` (${meta.confidence} confidence)` : '';
|
|
59
|
+
// Special formatting for failures/anti-patterns
|
|
60
|
+
let prefix = '';
|
|
61
|
+
if (typeLabel === 'failure')
|
|
62
|
+
prefix = '[ANTI-PATTERN] ';
|
|
63
|
+
else if (typeLabel === 'architecture')
|
|
64
|
+
prefix = '[DECISION] ';
|
|
65
|
+
else if (typeLabel === 'pattern')
|
|
66
|
+
prefix = '[PATTERN] ';
|
|
67
|
+
else if (typeLabel === 'discovery')
|
|
68
|
+
prefix = '[DISCOVERY] ';
|
|
69
|
+
else if (typeLabel === 'workaround')
|
|
70
|
+
prefix = '[WORKAROUND] ';
|
|
71
|
+
let entry = `${i + 1}. ${prefix}**${r.title || 'Untitled'}**${confidence}\n`;
|
|
72
|
+
entry += ` ${r.insight || r.raw_content || '(no content)'}\n`;
|
|
73
|
+
if (meta.alternatives_considered) {
|
|
74
|
+
entry += ` *Alternatives considered:* ${meta.alternatives_considered}\n`;
|
|
75
|
+
}
|
|
76
|
+
if (meta.failed_reason) {
|
|
77
|
+
entry += ` *Why it failed:* ${meta.failed_reason}\n`;
|
|
78
|
+
}
|
|
79
|
+
if (meta.better_approach) {
|
|
80
|
+
entry += ` *Better approach:* ${meta.better_approach}\n`;
|
|
81
|
+
}
|
|
82
|
+
const tags = r.tags?.filter((t) => !t.startsWith('learning:') && t !== 'auto-extracted');
|
|
83
|
+
if (tags?.length > 0) {
|
|
84
|
+
entry += ` tags: ${tags.join(', ')}`;
|
|
85
|
+
}
|
|
86
|
+
return entry;
|
|
87
|
+
}).join('\n\n');
|
|
88
|
+
const scope = projectName ? ` for ${projectName}` : '';
|
|
89
|
+
return `*Decision log${scope} — ${data.results.length} result(s):*\n\n${items}`;
|
|
90
|
+
}
|
|
@@ -4,8 +4,8 @@ import { gatherSignals } from '../lib/signal-gatherer.js';
|
|
|
4
4
|
import { fuseSignals } from '../lib/signal-fusion.js';
|
|
5
5
|
export const getProjectContextSchema = {
|
|
6
6
|
name: 'get_project_context',
|
|
7
|
-
description: `Show what's new in a project's knowledge feed from Splicr. Only call this when the user explicitly asks to see new saves, recent context, or "what's new." Do NOT call this proactively at session start.
|
|
8
|
-
|
|
7
|
+
description: `Show what's new in a project's knowledge feed from Splicr. Only call this when the user explicitly asks to see new saves, recent context, or "what's new." Do NOT call this proactively at session start.
|
|
8
|
+
|
|
9
9
|
Uses a watermark system — only returns captures saved since the last time context was served for this project. Safe to call repeatedly; it will return empty if nothing is new.`,
|
|
10
10
|
inputSchema: {
|
|
11
11
|
type: 'object',
|
|
@@ -40,6 +40,12 @@ export async function handleGetProjectContext(args) {
|
|
|
40
40
|
const results = data.results;
|
|
41
41
|
const name = data.project_name || projectName;
|
|
42
42
|
let output = '';
|
|
43
|
+
// --- SECTION 0: Active project patterns (cross-agent consistency) ---
|
|
44
|
+
const patterns = data.patterns;
|
|
45
|
+
if (patterns && patterns.length > 0) {
|
|
46
|
+
const patternItems = patterns.map((p, i) => `${i + 1}. **${p.name}** — ${p.description}`).join('\n');
|
|
47
|
+
output += `*Active patterns for ${name}:*\n${patternItems}\n\n---\n\n`;
|
|
48
|
+
}
|
|
43
49
|
if (results && results.length > 0) {
|
|
44
50
|
const items = results.map((r, i) => `${i + 1}. **${r.title || 'Untitled'}** · splicr [${r.relevance}]\n` +
|
|
45
51
|
` ${r.insight}\n` +
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare const getTeamStatusSchema: {
|
|
2
|
+
name: "get_team_status";
|
|
3
|
+
description: string;
|
|
4
|
+
inputSchema: {
|
|
5
|
+
type: "object";
|
|
6
|
+
properties: {
|
|
7
|
+
project: {
|
|
8
|
+
type: "string";
|
|
9
|
+
description: string;
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
export declare function handleGetTeamStatus(args: Record<string, unknown>): Promise<string>;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { getTeamStatus } from '../lib/api-client.js';
|
|
2
|
+
import { detectProject } from '../lib/project-detector.js';
|
|
3
|
+
import * as session from '../lib/session-state.js';
|
|
4
|
+
export const getTeamStatusSchema = {
|
|
5
|
+
name: 'get_team_status',
|
|
6
|
+
description: `Get a brief on what the team is working on right now. Shows open PRs, active branches, and recent merges from GitHub.
|
|
7
|
+
|
|
8
|
+
Use when:
|
|
9
|
+
- Starting a session and want to know what's in flight
|
|
10
|
+
- Before starting work on a feature (check if someone else is already on it)
|
|
11
|
+
- To understand recent changes to the codebase
|
|
12
|
+
|
|
13
|
+
Requires GitHub integration (connected via Splicr dashboard).`,
|
|
14
|
+
inputSchema: {
|
|
15
|
+
type: 'object',
|
|
16
|
+
properties: {
|
|
17
|
+
project: { type: 'string', description: 'Project name or "auto" (default: auto)' },
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
export async function handleGetTeamStatus(args) {
|
|
22
|
+
const projectArg = args.project || 'auto';
|
|
23
|
+
let projectName;
|
|
24
|
+
if (projectArg === 'auto') {
|
|
25
|
+
const detected = await detectProject(process.cwd());
|
|
26
|
+
projectName = detected?.name;
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
projectName = projectArg;
|
|
30
|
+
}
|
|
31
|
+
const data = await getTeamStatus({ project: projectName });
|
|
32
|
+
session.recordToolCall();
|
|
33
|
+
if (data.error) {
|
|
34
|
+
return data.error;
|
|
35
|
+
}
|
|
36
|
+
let output = `*Team status for ${data.repo}:*\n\n`;
|
|
37
|
+
// Open PRs
|
|
38
|
+
if (data.open_prs && data.open_prs.length > 0) {
|
|
39
|
+
output += `**Open PRs (${data.open_prs.length}):**\n`;
|
|
40
|
+
output += data.open_prs.map((pr) => `- #${pr.number} ${pr.title} (${pr.author}, branch: \`${pr.branch}\`)${pr.draft ? ' [DRAFT]' : ''}`).join('\n');
|
|
41
|
+
output += '\n\n';
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
output += 'No open PRs.\n\n';
|
|
45
|
+
}
|
|
46
|
+
// Recent merges
|
|
47
|
+
if (data.recent_merges && data.recent_merges.length > 0) {
|
|
48
|
+
output += `**Recently merged (last 7 days):**\n`;
|
|
49
|
+
output += data.recent_merges.map((m) => {
|
|
50
|
+
const daysAgo = Math.floor((Date.now() - new Date(m.merged_at).getTime()) / (1000 * 60 * 60 * 24));
|
|
51
|
+
const when = daysAgo === 0 ? 'today' : daysAgo === 1 ? 'yesterday' : `${daysAgo}d ago`;
|
|
52
|
+
return `- ${m.title} (${m.author}, ${when})`;
|
|
53
|
+
}).join('\n');
|
|
54
|
+
output += '\n\n';
|
|
55
|
+
}
|
|
56
|
+
// Active branches
|
|
57
|
+
if (data.active_branches && data.active_branches.length > 0) {
|
|
58
|
+
output += `**Active branches:** ${data.active_branches.map((b) => `\`${b.name}\``).join(', ')}`;
|
|
59
|
+
}
|
|
60
|
+
return output.trim();
|
|
61
|
+
}
|
|
@@ -52,6 +52,7 @@ export declare const saveFromAgentSchema: {
|
|
|
52
52
|
};
|
|
53
53
|
decision_type: {
|
|
54
54
|
type: "string";
|
|
55
|
+
enum: string[];
|
|
55
56
|
description: string;
|
|
56
57
|
};
|
|
57
58
|
durability: {
|
|
@@ -60,6 +61,10 @@ export declare const saveFromAgentSchema: {
|
|
|
60
61
|
};
|
|
61
62
|
};
|
|
62
63
|
};
|
|
64
|
+
register_as_pattern: {
|
|
65
|
+
type: "boolean";
|
|
66
|
+
description: string;
|
|
67
|
+
};
|
|
63
68
|
};
|
|
64
69
|
required: ("title" | "content")[];
|
|
65
70
|
};
|
|
@@ -3,20 +3,20 @@ import { detectProject } from '../lib/project-detector.js';
|
|
|
3
3
|
import * as session from '../lib/session-state.js';
|
|
4
4
|
export const saveFromAgentSchema = {
|
|
5
5
|
name: 'save_from_agent',
|
|
6
|
-
description: `Save knowledge to Splicr. Call this in two scenarios:
|
|
7
|
-
|
|
8
|
-
1. When the user explicitly asks to save/remember something
|
|
9
|
-
2. At the end of significant sessions — save decisions, discoveries, and workarounds (NOT routine actions)
|
|
10
|
-
|
|
11
|
-
For auto-save at session end, include the "context" field with attribution metadata.
|
|
12
|
-
|
|
13
|
-
**Memory types** (use memory_type to categorize):
|
|
14
|
-
- "fact" — permanent knowledge (API uses X pattern, team decided Y). Default if omitted.
|
|
15
|
-
- "procedural" — reusable how-to (deploy steps, workarounds, setup instructions)
|
|
16
|
-
- "episodic" — session-level context (what happened this session, investigation results)
|
|
17
|
-
- "scratchpad" — short-lived working memory (temp notes, in-progress thinking)
|
|
18
|
-
|
|
19
|
-
What to save: decisions (chose X over Y because...), discoveries (found undocumented behavior), workarounds (non-obvious fix), synthesis (combined multiple insights).
|
|
6
|
+
description: `Save knowledge to Splicr. Call this in two scenarios:
|
|
7
|
+
|
|
8
|
+
1. When the user explicitly asks to save/remember something
|
|
9
|
+
2. At the end of significant sessions — save decisions, discoveries, and workarounds (NOT routine actions)
|
|
10
|
+
|
|
11
|
+
For auto-save at session end, include the "context" field with attribution metadata.
|
|
12
|
+
|
|
13
|
+
**Memory types** (use memory_type to categorize):
|
|
14
|
+
- "fact" — permanent knowledge (API uses X pattern, team decided Y). Default if omitted.
|
|
15
|
+
- "procedural" — reusable how-to (deploy steps, workarounds, setup instructions)
|
|
16
|
+
- "episodic" — session-level context (what happened this session, investigation results)
|
|
17
|
+
- "scratchpad" — short-lived working memory (temp notes, in-progress thinking)
|
|
18
|
+
|
|
19
|
+
What to save: decisions (chose X over Y because...), discoveries (found undocumented behavior), workarounds (non-obvious fix), synthesis (combined multiple insights).
|
|
20
20
|
What NOT to save: file edits (git has those), routine commands, anything derivable from reading the code.`,
|
|
21
21
|
inputSchema: {
|
|
22
22
|
type: 'object',
|
|
@@ -37,10 +37,14 @@ What NOT to save: file edits (git has those), routine commands, anything derivab
|
|
|
37
37
|
agent: { type: 'string', description: 'Agent/model name (e.g. "claude-opus-4-6")' },
|
|
38
38
|
files: { type: 'array', items: { type: 'string' }, description: 'Files involved in this learning' },
|
|
39
39
|
splicr_refs: { type: 'array', items: { type: 'string' }, description: 'IDs of Splicr captures referenced during this session' },
|
|
40
|
-
decision_type: { type: 'string', description: 'Type: architecture, workaround, discovery, synthesis, or
|
|
40
|
+
decision_type: { type: 'string', enum: ['architecture', 'workaround', 'discovery', 'synthesis', 'pattern', 'failure'], description: 'Type: architecture, workaround, discovery, synthesis, pattern, or failure' },
|
|
41
41
|
durability: { type: 'string', description: 'How long this knowledge stays relevant: permanent, versioned, or ephemeral' },
|
|
42
42
|
},
|
|
43
43
|
},
|
|
44
|
+
register_as_pattern: {
|
|
45
|
+
type: 'boolean',
|
|
46
|
+
description: 'Set true to also register this as an active project pattern (enforced across future agent sessions)',
|
|
47
|
+
},
|
|
44
48
|
},
|
|
45
49
|
required: ['content', 'title'],
|
|
46
50
|
},
|
|
@@ -52,6 +56,7 @@ export async function handleSaveFromAgent(args) {
|
|
|
52
56
|
const tags = args.tags || [];
|
|
53
57
|
const memoryType = args.memory_type || 'fact';
|
|
54
58
|
const context = args.context;
|
|
59
|
+
const registerAsPattern = args.register_as_pattern;
|
|
55
60
|
// Resolve project name
|
|
56
61
|
let projectName;
|
|
57
62
|
if (projectArg === 'auto') {
|
|
@@ -68,6 +73,7 @@ export async function handleSaveFromAgent(args) {
|
|
|
68
73
|
tags,
|
|
69
74
|
memory_type: memoryType,
|
|
70
75
|
context,
|
|
76
|
+
register_as_pattern: registerAsPattern,
|
|
71
77
|
});
|
|
72
78
|
if (result.duplicate) {
|
|
73
79
|
return `*Already saved in Splicr:* "${title}"`;
|