trapic-mcp 0.1.3 → 0.1.4

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.
@@ -31,8 +31,8 @@ When the trapic-mcp MCP server is connected, automatically call \`trace_create\`
31
31
  - Important user preferences or project constraints learned
32
32
 
33
33
  **How to create traces:**
34
- - \`claim\`: One clear sentence describing what happened or was decided
35
- - \`reason\`: Why this decision was made or what caused it
34
+ - \`content\`: One clear sentence describing what happened or was decided
35
+ - \`context\`: Why this decision was made or what caused it
36
36
  - \`tags\`: **Always include the current project directory name as the first tag**, then add relevant tags (technology, category)
37
37
  - \`confidence\`: "high" for verified facts, "medium" for likely conclusions, "low" for hypotheses
38
38
  - \`source\`: "session" for conversation-derived traces
@@ -760,7 +760,7 @@ async function cmdStatus() {
760
760
 
761
761
  console.log("\nMost recent:");
762
762
  for (const t of traceList.slice(0, 5)) {
763
- console.log(` ${fmtDate(t.created_at)} ${truncate(t.claim || t.title || "(no claim)", 70)}`);
763
+ console.log(` ${fmtDate(t.created_at)} ${truncate(t.content || "(no content)", 70)}`);
764
764
  }
765
765
  console.log();
766
766
  } catch (err) {
@@ -796,10 +796,10 @@ async function cmdSearch(query) {
796
796
  const tags = t.tags?.length ? `[${t.tags.join(", ")}]` : "";
797
797
  const conf = t.confidence || "";
798
798
 
799
- console.log(` ${sim.padEnd(7)} ${truncate(t.claim || "(no claim)", 65)}`);
799
+ console.log(` ${sim.padEnd(7)} ${truncate(t.content || "(no content)", 65)}`);
800
800
  console.log(` ${tags} ${conf} | ${fmtDate(t.created_at)}`);
801
- if (t.reason) {
802
- console.log(` ${truncate(t.reason, 70)}`);
801
+ if (t.context) {
802
+ console.log(` ${truncate(t.context, 70)}`);
803
803
  }
804
804
  console.log();
805
805
  }
@@ -838,10 +838,10 @@ async function cmdList() {
838
838
  for (const t of traces) {
839
839
  const tags = t.tags?.length ? `[${t.tags.join(", ")}]` : "";
840
840
  const conf = t.confidence ? t.confidence : "";
841
- console.log(` ${fmtDate(t.created_at)} ${truncate(t.claim || "(no claim)", 60)}`);
841
+ console.log(` ${fmtDate(t.created_at)} ${truncate(t.content || "(no content)", 60)}`);
842
842
  console.log(` ${tags} ${conf}`);
843
- if (t.reason) {
844
- console.log(` ${truncate(t.reason, 65)}`);
843
+ if (t.context) {
844
+ console.log(` ${truncate(t.context, 65)}`);
845
845
  }
846
846
  console.log();
847
847
  }
package/dist/conflict.js CHANGED
@@ -13,6 +13,8 @@ A contradiction means the new trace says the OPPOSITE of an existing trace about
13
13
  - New: "Added pagination" vs Existing: "Added search" → NOT a conflict (different topics)
14
14
  - New: "Password min 8 chars" vs Existing: "Password min 6 chars" → CONFLICT (supersedes)
15
15
 
16
+ Each trace has a "content" field (what was decided/discovered) and an optional "context" field (why).
17
+
16
18
  Return JSON:
17
19
  {
18
20
  "conflicts": [
@@ -53,13 +55,13 @@ export async function detectConflicts(traceId, userId) {
53
55
  const userMessage = JSON.stringify({
54
56
  new_trace: {
55
57
  id: trace.id,
56
- claim: trace.content || trace.claim,
57
- reason: trace.context || trace.reason,
58
+ content: trace.content || trace.claim,
59
+ context: trace.context || trace.reason,
58
60
  },
59
61
  existing_traces: candidates.slice(0, 5).map((t) => ({
60
62
  id: t.id,
61
- claim: t.claim,
62
- reason: t.reason,
63
+ content: t.content || t.claim,
64
+ context: t.context || t.reason,
63
65
  tags: t.tags,
64
66
  })),
65
67
  }, null, 2);
@@ -32,8 +32,8 @@ export function registerActive(server, userId) {
32
32
  : (data ?? []);
33
33
  const traces = filtered.map((row) => ({
34
34
  id: row.id,
35
- claim: row.claim,
36
- reason: row.reason,
35
+ content: row.content || row.claim,
36
+ context: row.context || row.reason,
37
37
  tags: row.tags,
38
38
  caused_by: row.caused_by,
39
39
  confidence: row.confidence,
@@ -31,8 +31,8 @@ export function registerChain(server, userId) {
31
31
  : (data ?? []);
32
32
  const chain = filtered.map((row) => ({
33
33
  id: row.id,
34
- claim: row.claim,
35
- reason: row.reason,
34
+ content: row.content || row.claim,
35
+ context: row.context || row.reason,
36
36
  status: row.status,
37
37
  tags: row.tags,
38
38
  caused_by: row.caused_by,
@@ -88,8 +88,8 @@ export function registerChain(server, userId) {
88
88
  : (data ?? []);
89
89
  const effects = filteredEffects.map((row) => ({
90
90
  id: row.id,
91
- claim: row.claim,
92
- reason: row.reason,
91
+ content: row.content || row.claim,
92
+ context: row.context || row.reason,
93
93
  status: row.status,
94
94
  tags: row.tags,
95
95
  caused_by: row.caused_by,
@@ -89,8 +89,8 @@ export async function contextualizeTrace(traceId) {
89
89
  const userMessage = JSON.stringify({
90
90
  new_trace: {
91
91
  id: trace.id,
92
- claim: trace.claim,
93
- reason: trace.reason,
92
+ content: trace.content || trace.claim,
93
+ context: trace.context || trace.reason,
94
94
  tags: trace.tags,
95
95
  author: trace.author,
96
96
  },
@@ -100,8 +100,8 @@ export async function contextualizeTrace(traceId) {
100
100
  resolution: c.resolution,
101
101
  vector_similarity: c.similarity,
102
102
  traces: c.traces.map((t) => ({
103
- claim: t.claim,
104
- reason: t.reason,
103
+ content: t.content,
104
+ context: t.context,
105
105
  author: t.author,
106
106
  role: t.role,
107
107
  })),
@@ -154,8 +154,6 @@ export async function contextualizeTrace(traceId) {
154
154
  resolution: "fragmented",
155
155
  embedding: summaryEmbedding,
156
156
  scope: trace.scope,
157
- org_id: trace.org_id,
158
- team_id: trace.team_id,
159
157
  })
160
158
  .select()
161
159
  .single();
@@ -55,15 +55,13 @@ export function registerCreate(server, userId) {
55
55
  }],
56
56
  };
57
57
  }
58
- // Generate embedding from claim + reason
58
+ // Generate embedding from content + context
59
59
  const embeddingText = params.reason
60
60
  ? `${params.claim} ${params.reason}`
61
61
  : params.claim;
62
62
  const embedding = await generateEmbedding(embeddingText);
63
63
  // Use encrypted insert RPC
64
64
  const { data, error } = await supabase.rpc("insert_encrypted_trace", {
65
- p_claim: params.claim,
66
- p_reason: params.reason ?? null,
67
65
  p_content: params.claim,
68
66
  p_context: params.reason ?? null,
69
67
  p_scope: params.scope,
@@ -73,7 +71,6 @@ export function registerCreate(server, userId) {
73
71
  p_source: params.source,
74
72
  p_source_id: params.source_id ?? null,
75
73
  p_confidence: params.confidence,
76
- p_references: params.references,
77
74
  p_embedding: embedding,
78
75
  });
79
76
  if (error || !data?.id) {
@@ -2,7 +2,7 @@ import { z } from "zod";
2
2
  import { llmChat } from "../llm.js";
3
3
  const EXTRACTION_SYSTEM_PROMPT = `You are a knowledge extraction engine for an organizational knowledge system called "Traces."
4
4
 
5
- A Trace is a minimal causal proposition — one clear statement (claim) with an optional reason (why).
5
+ A Trace is a minimal causal proposition — one clear statement (content) with an optional context (why).
6
6
 
7
7
  Your job: given a text block (session transcript, meeting notes, discussion log), extract all meaningful propositions as candidate Traces.
8
8
 
@@ -17,8 +17,8 @@ Your job: given a text block (session transcript, meeting notes, discussion log)
17
17
  ## Extraction rules
18
18
 
19
19
  - Each trace = ONE proposition. Do not combine multiple ideas.
20
- - claim = what happened / what was concluded (one clear sentence)
21
- - reason = why (optional — some facts have no reason)
20
+ - content = what happened / what was concluded (one clear sentence)
21
+ - context = why (optional — some facts have no context/reason)
22
22
  - Auto-assign tags based on content (e.g., "architecture", "design", "api", "performance", "security", "ux", "database", "deployment", "process", "decision")
23
23
  - All extracted traces get confidence: "low" (they need human review)
24
24
  - Identify the type: conclusion, decision, rejection, discovery, or agreement
@@ -31,7 +31,7 @@ Your job: given a text block (session transcript, meeting notes, discussion log)
31
31
 
32
32
  ## Output format
33
33
 
34
- Return JSON: { "traces": [ { "claim": "...", "reason": "..." or null, "tags": [...], "type": "conclusion|decision|rejection|discovery|agreement" } ] }`;
34
+ Return JSON: { "traces": [ { "content": "...", "context": "..." or null, "tags": [...], "type": "conclusion|decision|rejection|discovery|agreement" } ] }`;
35
35
  export function registerExtract(server) {
36
36
  server.tool("trapic_extract", "Extract candidate traces from a text block (session transcript, meeting notes, discussion). " +
37
37
  "Uses local LLM to identify conclusions, decisions, rejections, discoveries, and agreements. " +
@@ -55,8 +55,8 @@ export function registerExtract(server) {
55
55
  const parsed = JSON.parse(content);
56
56
  const rawCandidates = Array.isArray(parsed) ? parsed : (parsed.traces ?? parsed.results ?? []);
57
57
  candidates = rawCandidates.map((c) => ({
58
- claim: String(c.claim ?? ""),
59
- reason: c.reason ? String(c.reason) : null,
58
+ content: String(c.content || c.claim || ""),
59
+ context: c.context || c.reason ? String(c.context || c.reason) : null,
60
60
  tags: Array.isArray(c.tags) ? c.tags.map(String) : [],
61
61
  source: params.source,
62
62
  confidence: "low",
@@ -71,7 +71,7 @@ export function registerExtract(server) {
71
71
  }],
72
72
  };
73
73
  }
74
- candidates = candidates.filter((c) => c.claim.trim().length > 0);
74
+ candidates = candidates.filter((c) => c.content.trim().length > 0);
75
75
  return {
76
76
  content: [{
77
77
  type: "text",
@@ -49,8 +49,8 @@ export function registerSearch(server, userId) {
49
49
  : (data ?? []);
50
50
  const results = filtered.map((row) => ({
51
51
  id: row.id,
52
- claim: row.claim,
53
- reason: row.reason,
52
+ content: row.content || row.claim,
53
+ context: row.context || row.reason,
54
54
  status: row.status,
55
55
  tags: row.tags,
56
56
  caused_by: row.caused_by,
@@ -6,7 +6,7 @@ import { llmChat } from "../llm.js";
6
6
  import { checkMonthlyQuota } from "../quota.js";
7
7
  const SUMMARY_EXTRACTION_PROMPT = `You are a knowledge extraction engine. Given a conversation summary, extract all key decisions, discoveries, and conclusions as Traces.
8
8
 
9
- A Trace is a minimal causal proposition — one clear statement (claim) with an optional reason (why).
9
+ A Trace is a minimal causal proposition — one clear statement (content) with an optional context (why).
10
10
 
11
11
  ## What to extract
12
12
 
@@ -17,14 +17,14 @@ A Trace is a minimal causal proposition — one clear statement (claim) with an
17
17
  ## Rules
18
18
 
19
19
  - Each trace = ONE proposition. Do not combine multiple ideas.
20
- - claim = what happened / what was concluded (one clear sentence)
21
- - reason = why (optional — some facts have no reason)
20
+ - content = what happened / what was concluded (one clear sentence)
21
+ - context = why (optional — some facts have no context/reason)
22
22
  - Auto-assign tags based on content (e.g., "architecture", "design", "api", "performance", "security", "ux", "database", "deployment", "process", "decision")
23
23
  - Be thorough but avoid extracting trivial or obvious statements
24
24
 
25
25
  ## Output format
26
26
 
27
- Return JSON: { "traces": [{ "claim": "...", "reason": "..." or null, "tags": ["..."] }] }`;
27
+ Return JSON: { "traces": [{ "content": "...", "context": "..." or null, "tags": ["..."] }] }`;
28
28
  export function registerSummary(server, userId) {
29
29
  server.tool("trapic_auto_summary", "Extract and create multiple traces from a conversation summary in one call. " +
30
30
  "Uses LLM to identify key decisions, discoveries, and conclusions, then writes them all to DB. " +
@@ -61,11 +61,11 @@ export function registerSummary(server, userId) {
61
61
  const rawTraces = Array.isArray(parsed) ? parsed : (parsed.traces ?? parsed.results ?? []);
62
62
  extracted = rawTraces
63
63
  .map((t) => ({
64
- claim: String(t.claim ?? ""),
65
- reason: t.reason ? String(t.reason) : null,
64
+ content: String(t.content || t.claim || ""),
65
+ context: t.context || t.reason ? String(t.context || t.reason) : null,
66
66
  tags: Array.isArray(t.tags) ? t.tags.map(String) : [],
67
67
  }))
68
- .filter((t) => t.claim.trim().length > 0);
68
+ .filter((t) => t.content.trim().length > 0);
69
69
  }
70
70
  catch {
71
71
  return {
@@ -95,7 +95,7 @@ export function registerSummary(server, userId) {
95
95
  const created = [];
96
96
  const errors = [];
97
97
  // Batch embedding generation
98
- const embeddingTexts = extracted.map((trace) => trace.reason ? `${trace.claim} ${trace.reason}` : trace.claim);
98
+ const embeddingTexts = extracted.map((trace) => trace.context ? `${trace.content} ${trace.context}` : trace.content);
99
99
  let embeddings;
100
100
  try {
101
101
  embeddings = await generateEmbeddings(embeddingTexts);
@@ -118,8 +118,10 @@ export function registerSummary(server, userId) {
118
118
  const { data, error } = await supabase
119
119
  .from("traces")
120
120
  .insert({
121
- claim: trace.claim,
122
- reason: trace.reason,
121
+ content: trace.content,
122
+ context: trace.context,
123
+ claim: trace.content, // legacy column compat
124
+ reason: trace.context, // legacy column compat
123
125
  scope: "personal",
124
126
  author: effectiveUserId,
125
127
  tags: mergedTags,
@@ -133,14 +135,14 @@ export function registerSummary(server, userId) {
133
135
  .select()
134
136
  .single();
135
137
  if (error) {
136
- throw new Error(`Failed to create trace "${trace.claim}": ${error.message}`);
138
+ throw new Error(`Failed to create trace "${trace.content}": ${error.message}`);
137
139
  }
138
140
  // Contextualize (non-blocking failure)
139
141
  await contextualizeTrace(data.id).catch(() => null);
140
142
  return {
141
143
  id: data.id,
142
- claim: data.claim,
143
- reason: data.reason,
144
+ content: data.content || data.claim,
145
+ context: data.context || data.reason,
144
146
  tags: data.tags,
145
147
  };
146
148
  }));
@@ -3,8 +3,8 @@ import { getSupabase } from "../supabase.js";
3
3
  import { generateEmbedding } from "../embedding.js";
4
4
  import { audit } from "../audit.js";
5
5
  export function registerUpdate(server, userId) {
6
- server.tool("trapic_update", "Update an existing trace — change status, tags, claim/reason, or mark as superseded. " +
7
- "更新現有 trace — 可以變更狀態、標籤、claim/reason,或標記為被取代。", {
6
+ server.tool("trapic_update", "Update an existing trace — change status, tags, content/context, or mark as superseded. " +
7
+ "更新現有 trace — 可以變更狀態、標籤、content/context,或標記為被取代。", {
8
8
  trace_id: z.string().uuid().describe("ID of the trace to update. " +
9
9
  "要更新的 trace ID"),
10
10
  claim: z.string().optional().describe("Updated claim text. Will regenerate embedding if changed. " +
@@ -26,10 +26,14 @@ export function registerUpdate(server, userId) {
26
26
  const supabase = getSupabase();
27
27
  // Build update object with only provided fields
28
28
  const update = {};
29
- if (params.claim !== undefined)
30
- update.claim = params.claim;
31
- if (params.reason !== undefined)
32
- update.reason = params.reason;
29
+ if (params.claim !== undefined) {
30
+ update.content = params.claim;
31
+ update.claim = params.claim; // legacy column compat
32
+ }
33
+ if (params.reason !== undefined) {
34
+ update.context = params.reason;
35
+ update.reason = params.reason; // legacy column compat
36
+ }
33
37
  if (params.tags !== undefined)
34
38
  update.tags = params.tags;
35
39
  if (params.confidence !== undefined)
@@ -44,12 +48,12 @@ export function registerUpdate(server, userId) {
44
48
  else if (params.status !== undefined) {
45
49
  update.status = params.status;
46
50
  }
47
- // Regenerate embedding if claim or reason changed
51
+ // Regenerate embedding if content or context changed
48
52
  if (params.claim !== undefined || params.reason !== undefined) {
49
- // Fetch current trace to get existing claim/reason if only one changed
53
+ // Fetch current trace to get existing content/context if only one changed
50
54
  let query = supabase
51
55
  .from("traces")
52
- .select("claim, reason")
56
+ .select("content, context, claim, reason")
53
57
  .eq("id", params.trace_id);
54
58
  if (userId)
55
59
  query = query.eq("author", userId);
@@ -64,11 +68,11 @@ export function registerUpdate(server, userId) {
64
68
  ],
65
69
  };
66
70
  }
67
- const newClaim = params.claim ?? current.claim;
68
- const newReason = params.reason ?? current.reason;
69
- const embeddingText = newReason
70
- ? `${newClaim} ${newReason}`
71
- : newClaim;
71
+ const newContent = params.claim ?? current.content ?? current.claim;
72
+ const newContext = params.reason ?? current.context ?? current.reason;
73
+ const embeddingText = newContext
74
+ ? `${newContent} ${newContext}`
75
+ : newContent;
72
76
  update.embedding = await generateEmbedding(embeddingText);
73
77
  }
74
78
  if (Object.keys(update).length === 0) {
@@ -111,8 +115,8 @@ export function registerUpdate(server, userId) {
111
115
  message: "Trace updated successfully / Trace 更新成功",
112
116
  trace: {
113
117
  id: data.id,
114
- claim: data.claim,
115
- reason: data.reason,
118
+ content: data.content || data.claim,
119
+ context: data.context || data.reason,
116
120
  status: data.status,
117
121
  superseded_by: data.superseded_by,
118
122
  tags: data.tags,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trapic-mcp",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "AI knowledge management MCP server — automatically capture, search, and connect decisions, facts, and conventions across AI coding sessions",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",