context-vault 3.5.1 → 3.6.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/register-tools.d.ts.map +1 -1
- package/dist/register-tools.js +2 -0
- package/dist/register-tools.js.map +1 -1
- package/dist/tools/context-status.d.ts.map +1 -1
- package/dist/tools/context-status.js +42 -0
- package/dist/tools/context-status.js.map +1 -1
- package/dist/tools/get-context.d.ts.map +1 -1
- package/dist/tools/get-context.js +16 -0
- package/dist/tools/get-context.js.map +1 -1
- package/dist/tools/save-context.d.ts.map +1 -1
- package/dist/tools/save-context.js +20 -0
- package/dist/tools/save-context.js.map +1 -1
- package/dist/tools/session-end.d.ts +20 -0
- package/dist/tools/session-end.d.ts.map +1 -0
- package/dist/tools/session-end.js +288 -0
- package/dist/tools/session-end.js.map +1 -0
- package/node_modules/@context-vault/core/package.json +1 -1
- package/package.json +2 -2
- package/src/register-tools.ts +2 -0
- package/src/tools/context-status.ts +65 -0
- package/src/tools/get-context.ts +22 -0
- package/src/tools/save-context.ts +25 -0
- package/src/tools/session-end.ts +338 -0
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { captureAndIndex } from '@context-vault/core/capture';
|
|
3
|
+
import { execSync } from 'node:child_process';
|
|
4
|
+
import { ok, ensureVaultExists } from '../helpers.js';
|
|
5
|
+
const DEFAULT_SIMILARITY_THRESHOLD = 0.85;
|
|
6
|
+
const MAX_ENTRIES_PER_SESSION = 5;
|
|
7
|
+
const PROMPT_TEMPLATE = `## Session End: Knowledge Capture
|
|
8
|
+
|
|
9
|
+
Before this session ends, take a moment to capture what was learned.
|
|
10
|
+
|
|
11
|
+
**Answer these questions (skip any that don't apply):**
|
|
12
|
+
|
|
13
|
+
1. **What was accomplished?**
|
|
14
|
+
List the key outcomes of this session.
|
|
15
|
+
|
|
16
|
+
2. **What was learned?** (most important)
|
|
17
|
+
Non-obvious findings, gotchas, or discoveries that would save time in a future session.
|
|
18
|
+
Skip anything derivable from reading the code or git history.
|
|
19
|
+
|
|
20
|
+
3. **What decisions were made and why?**
|
|
21
|
+
Architectural choices, trade-offs, or scope decisions with their rationale.
|
|
22
|
+
|
|
23
|
+
4. **What would save time for the next session?**
|
|
24
|
+
Blockers, context that took a while to build, or shortcuts discovered.
|
|
25
|
+
|
|
26
|
+
**Then call \`session_end\` again with your summary and any discrete discoveries:**
|
|
27
|
+
|
|
28
|
+
\`\`\`
|
|
29
|
+
session_end({
|
|
30
|
+
summary: "your summary text",
|
|
31
|
+
discoveries: [
|
|
32
|
+
{ title: "Short descriptive title", body: "What was learned and why it matters", kind: "insight" },
|
|
33
|
+
{ title: "Decision: chose X over Y", body: "Rationale and trade-offs", kind: "decision" }
|
|
34
|
+
]
|
|
35
|
+
})
|
|
36
|
+
\`\`\``;
|
|
37
|
+
function detectProject() {
|
|
38
|
+
try {
|
|
39
|
+
const remote = execSync('git remote get-url origin 2>/dev/null', {
|
|
40
|
+
encoding: 'utf-8',
|
|
41
|
+
timeout: 3000,
|
|
42
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
43
|
+
}).trim();
|
|
44
|
+
if (remote) {
|
|
45
|
+
const match = remote.match(/\/([^/]+?)(?:\.git)?$/);
|
|
46
|
+
if (match)
|
|
47
|
+
return match[1];
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
catch { }
|
|
51
|
+
try {
|
|
52
|
+
const cwd = process.cwd();
|
|
53
|
+
const parts = cwd.split(/[/\\]/);
|
|
54
|
+
return parts[parts.length - 1];
|
|
55
|
+
}
|
|
56
|
+
catch { }
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
function classifyDiscovery(body) {
|
|
60
|
+
const lower = body.toLowerCase();
|
|
61
|
+
if (/chose|decided|picked|went with|trade.?off|alternative/i.test(lower))
|
|
62
|
+
return 'architectural';
|
|
63
|
+
if (/bug|fix|error|crash|broke|regression/i.test(lower))
|
|
64
|
+
return 'bugfix';
|
|
65
|
+
if (/pattern|convention|approach|technique/i.test(lower))
|
|
66
|
+
return 'pattern';
|
|
67
|
+
if (/api|endpoint|library|framework|dependency/i.test(lower))
|
|
68
|
+
return 'integration';
|
|
69
|
+
return 'general';
|
|
70
|
+
}
|
|
71
|
+
function extractInsightsFromSummary(summary) {
|
|
72
|
+
const insights = [];
|
|
73
|
+
const sections = summary.split(/\n(?=#+\s|(?:\d+\.|\*|-)\s+\*\*)/);
|
|
74
|
+
for (const section of sections) {
|
|
75
|
+
const lower = section.toLowerCase();
|
|
76
|
+
if (/\blearned\b|\bdiscover|\bgotcha|\bnon.?obvious|\bsurpris|\bunexpect|\bworkaround/.test(lower)) {
|
|
77
|
+
const lines = section.split('\n').filter(l => l.trim());
|
|
78
|
+
const bullets = lines.filter(l => /^\s*[-*]\s/.test(l));
|
|
79
|
+
if (bullets.length > 0) {
|
|
80
|
+
for (const bullet of bullets) {
|
|
81
|
+
const text = bullet.replace(/^\s*[-*]\s+/, '').trim();
|
|
82
|
+
if (text.length > 20) {
|
|
83
|
+
insights.push({
|
|
84
|
+
title: text.length > 120 ? text.slice(0, 117) + '...' : text,
|
|
85
|
+
body: text,
|
|
86
|
+
kind: 'insight',
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
else if (section.trim().length > 30) {
|
|
92
|
+
const firstLine = lines[0]?.replace(/^#+\s*/, '').trim() || 'Session insight';
|
|
93
|
+
insights.push({
|
|
94
|
+
title: firstLine.length > 120 ? firstLine.slice(0, 117) + '...' : firstLine,
|
|
95
|
+
body: section.trim(),
|
|
96
|
+
kind: 'insight',
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (/\bdecision|\bdecided|\bchose|\btrade.?off/.test(lower)) {
|
|
101
|
+
const lines = section.split('\n').filter(l => l.trim());
|
|
102
|
+
const bullets = lines.filter(l => /^\s*[-*]\s/.test(l));
|
|
103
|
+
if (bullets.length > 0) {
|
|
104
|
+
for (const bullet of bullets) {
|
|
105
|
+
const text = bullet.replace(/^\s*[-*]\s+/, '').trim();
|
|
106
|
+
if (text.length > 20) {
|
|
107
|
+
insights.push({
|
|
108
|
+
title: text.length > 120 ? text.slice(0, 117) + '...' : text,
|
|
109
|
+
body: text,
|
|
110
|
+
kind: 'decision',
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return insights.slice(0, MAX_ENTRIES_PER_SESSION);
|
|
118
|
+
}
|
|
119
|
+
async function deduplicateAndSave(entries, ctx) {
|
|
120
|
+
const saved = [];
|
|
121
|
+
const skipped = [];
|
|
122
|
+
for (const entry of entries) {
|
|
123
|
+
const embeddingText = [entry.title, entry.body].filter(Boolean).join(' ');
|
|
124
|
+
let isDuplicate = false;
|
|
125
|
+
try {
|
|
126
|
+
const embedding = await ctx.embed(embeddingText);
|
|
127
|
+
if (embedding) {
|
|
128
|
+
const vecCount = ctx.db.prepare('SELECT COUNT(*) as c FROM vault_vec').get()?.c ?? 0;
|
|
129
|
+
if (vecCount > 0) {
|
|
130
|
+
const vecRows = ctx.db
|
|
131
|
+
.prepare('SELECT v.rowid, v.distance FROM vault_vec v WHERE embedding MATCH ? ORDER BY distance LIMIT 3')
|
|
132
|
+
.all(embedding, 3);
|
|
133
|
+
for (const vr of vecRows) {
|
|
134
|
+
const similarity = Math.max(0, 1 - vr.distance / 2);
|
|
135
|
+
if (similarity >= DEFAULT_SIMILARITY_THRESHOLD) {
|
|
136
|
+
const row = ctx.db
|
|
137
|
+
.prepare('SELECT id, title FROM vault WHERE rowid = ?')
|
|
138
|
+
.get(vr.rowid);
|
|
139
|
+
if (row) {
|
|
140
|
+
isDuplicate = true;
|
|
141
|
+
skipped.push({
|
|
142
|
+
title: entry.title,
|
|
143
|
+
reason: `similar to "${row.title || row.id}" (${(similarity * 100).toFixed(0)}%)`,
|
|
144
|
+
});
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
catch { }
|
|
153
|
+
if (isDuplicate)
|
|
154
|
+
continue;
|
|
155
|
+
try {
|
|
156
|
+
const result = await captureAndIndex(ctx, {
|
|
157
|
+
kind: entry.kind,
|
|
158
|
+
title: entry.title,
|
|
159
|
+
body: entry.body,
|
|
160
|
+
tags: entry.tags,
|
|
161
|
+
tier: entry.tier,
|
|
162
|
+
meta: entry.meta,
|
|
163
|
+
source: 'session-end',
|
|
164
|
+
});
|
|
165
|
+
saved.push({ id: result.id, title: entry.title, kind: entry.kind });
|
|
166
|
+
}
|
|
167
|
+
catch (e) {
|
|
168
|
+
skipped.push({
|
|
169
|
+
title: entry.title,
|
|
170
|
+
reason: `save failed: ${e.message}`,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return { saved, skipped };
|
|
175
|
+
}
|
|
176
|
+
export const name = 'session_end';
|
|
177
|
+
export const description = 'End-of-session knowledge capture. Call when a session ends to extract and save insights, decisions, and learnings. If called without a summary, returns a prompt template to guide the agent through knowledge capture.';
|
|
178
|
+
export const inputSchema = {
|
|
179
|
+
summary: z
|
|
180
|
+
.string()
|
|
181
|
+
.optional()
|
|
182
|
+
.describe('Session summary text covering what was accomplished, learned, decided, and what would help next time.'),
|
|
183
|
+
discoveries: z
|
|
184
|
+
.array(z.object({
|
|
185
|
+
title: z.string().describe('Short descriptive title for this discovery'),
|
|
186
|
+
body: z.string().describe('What was learned and why it matters'),
|
|
187
|
+
kind: z
|
|
188
|
+
.enum(['insight', 'decision', 'pattern', 'reference'])
|
|
189
|
+
.optional()
|
|
190
|
+
.describe('Entry kind (default: insight)'),
|
|
191
|
+
}))
|
|
192
|
+
.optional()
|
|
193
|
+
.describe('Explicit discoveries to save. Each becomes a vault entry with deduplication.'),
|
|
194
|
+
project: z
|
|
195
|
+
.string()
|
|
196
|
+
.optional()
|
|
197
|
+
.describe('Project name for bucket tagging. Auto-detected from git remote if not provided.'),
|
|
198
|
+
};
|
|
199
|
+
export async function handler({ summary, discoveries, project }, ctx, { ensureIndexed }) {
|
|
200
|
+
const { config } = ctx;
|
|
201
|
+
const vaultErr = ensureVaultExists(config);
|
|
202
|
+
if (vaultErr)
|
|
203
|
+
return vaultErr;
|
|
204
|
+
if (!summary && (!discoveries || discoveries.length === 0)) {
|
|
205
|
+
return ok(PROMPT_TEMPLATE);
|
|
206
|
+
}
|
|
207
|
+
await ensureIndexed({ blocking: false });
|
|
208
|
+
const effectiveProject = project?.trim() || detectProject();
|
|
209
|
+
const bucketTag = effectiveProject ? `bucket:${effectiveProject}` : null;
|
|
210
|
+
const baseTags = ['auto-session', ...(bucketTag ? [bucketTag] : [])];
|
|
211
|
+
const entriesToSave = [];
|
|
212
|
+
if (discoveries?.length) {
|
|
213
|
+
for (const d of discoveries.slice(0, MAX_ENTRIES_PER_SESSION)) {
|
|
214
|
+
const kind = d.kind || 'insight';
|
|
215
|
+
entriesToSave.push({
|
|
216
|
+
title: d.title,
|
|
217
|
+
body: d.body,
|
|
218
|
+
kind,
|
|
219
|
+
tags: [...baseTags],
|
|
220
|
+
tier: kind === 'decision' ? 'durable' : 'working',
|
|
221
|
+
meta: {
|
|
222
|
+
discovery_type: classifyDiscovery(d.body),
|
|
223
|
+
source: 'session-end-explicit',
|
|
224
|
+
},
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
if (summary) {
|
|
229
|
+
const extracted = extractInsightsFromSummary(summary);
|
|
230
|
+
const remainingSlots = MAX_ENTRIES_PER_SESSION - entriesToSave.length;
|
|
231
|
+
for (const e of extracted.slice(0, remainingSlots)) {
|
|
232
|
+
entriesToSave.push({
|
|
233
|
+
title: e.title,
|
|
234
|
+
body: e.body,
|
|
235
|
+
kind: e.kind,
|
|
236
|
+
tags: [...baseTags],
|
|
237
|
+
tier: e.kind === 'decision' ? 'durable' : 'working',
|
|
238
|
+
meta: {
|
|
239
|
+
discovery_type: classifyDiscovery(e.body),
|
|
240
|
+
source: 'session-end-extracted',
|
|
241
|
+
},
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
entriesToSave.push({
|
|
245
|
+
title: `Session: ${effectiveProject || 'unknown'} ${new Date().toISOString().slice(0, 10)}`,
|
|
246
|
+
body: summary,
|
|
247
|
+
kind: 'session',
|
|
248
|
+
tags: [...baseTags],
|
|
249
|
+
tier: 'ephemeral',
|
|
250
|
+
meta: {
|
|
251
|
+
source: 'session-end',
|
|
252
|
+
discoveries_explicit: discoveries?.length ?? 0,
|
|
253
|
+
discoveries_extracted: extractInsightsFromSummary(summary).length,
|
|
254
|
+
},
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
if (entriesToSave.length === 0) {
|
|
258
|
+
return ok('No save-worthy content found in the provided summary. Session recorded without vault entries.');
|
|
259
|
+
}
|
|
260
|
+
const { saved, skipped } = await deduplicateAndSave(entriesToSave, ctx);
|
|
261
|
+
const lines = ['## Session End Summary\n'];
|
|
262
|
+
if (saved.length > 0) {
|
|
263
|
+
lines.push(`### Saved (${saved.length})\n`);
|
|
264
|
+
for (const s of saved) {
|
|
265
|
+
lines.push(`- **${s.title}** (\`${s.kind}\`) \`${s.id}\``);
|
|
266
|
+
}
|
|
267
|
+
lines.push('');
|
|
268
|
+
}
|
|
269
|
+
if (skipped.length > 0) {
|
|
270
|
+
lines.push(`### Skipped (${skipped.length})\n`);
|
|
271
|
+
for (const s of skipped) {
|
|
272
|
+
lines.push(`- **${s.title}**: ${s.reason}`);
|
|
273
|
+
}
|
|
274
|
+
lines.push('');
|
|
275
|
+
}
|
|
276
|
+
if (saved.length === 0 && skipped.length > 0) {
|
|
277
|
+
lines.push('_All discoveries matched existing entries. No new entries created._');
|
|
278
|
+
}
|
|
279
|
+
const result = ok(lines.join('\n'));
|
|
280
|
+
result._meta = {
|
|
281
|
+
project: effectiveProject,
|
|
282
|
+
saved_count: saved.length,
|
|
283
|
+
skipped_count: skipped.length,
|
|
284
|
+
saved_ids: saved.map(s => s.id),
|
|
285
|
+
};
|
|
286
|
+
return result;
|
|
287
|
+
}
|
|
288
|
+
//# sourceMappingURL=session-end.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-end.js","sourceRoot":"","sources":["../../src/tools/session-end.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "context-vault",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.6.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Persistent memory for AI agents — saves and searches knowledge across sessions",
|
|
6
6
|
"bin": {
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
"@context-vault/core"
|
|
64
64
|
],
|
|
65
65
|
"dependencies": {
|
|
66
|
-
"@context-vault/core": "^3.
|
|
66
|
+
"@context-vault/core": "^3.6.0",
|
|
67
67
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
68
68
|
"adm-zip": "^0.5.16",
|
|
69
69
|
"sqlite-vec": "^0.1.0"
|
package/src/register-tools.ts
CHANGED
|
@@ -21,6 +21,7 @@ import * as createSnapshot from './tools/create-snapshot.js';
|
|
|
21
21
|
import * as sessionStart from './tools/session-start.js';
|
|
22
22
|
import * as listBuckets from './tools/list-buckets.js';
|
|
23
23
|
import * as ingestProject from './tools/ingest-project.js';
|
|
24
|
+
import * as sessionEnd from './tools/session-end.js';
|
|
24
25
|
|
|
25
26
|
const toolModules = [
|
|
26
27
|
getContext,
|
|
@@ -33,6 +34,7 @@ const toolModules = [
|
|
|
33
34
|
clearContext,
|
|
34
35
|
createSnapshot,
|
|
35
36
|
sessionStart,
|
|
37
|
+
sessionEnd,
|
|
36
38
|
listBuckets,
|
|
37
39
|
];
|
|
38
40
|
|
|
@@ -14,6 +14,47 @@ function relativeTime(ts: number): string {
|
|
|
14
14
|
return `${hrs} hour${hrs === 1 ? '' : 's'} ago`;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
interface LearningRate {
|
|
18
|
+
saved7d: number;
|
|
19
|
+
saved30d: number;
|
|
20
|
+
sessions30d: number;
|
|
21
|
+
savesPerSession: string;
|
|
22
|
+
recalls30d: number;
|
|
23
|
+
recallToSaveRatio: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function computeLearningRate(ctx: LocalCtx): LearningRate | null {
|
|
27
|
+
try {
|
|
28
|
+
const saved7d = (ctx.db.prepare(
|
|
29
|
+
`SELECT COUNT(*) as c FROM vault WHERE created_at >= datetime('now', '-7 days') AND kind NOT IN ('bucket', 'session', 'brief')`
|
|
30
|
+
).get() as any)?.c ?? 0;
|
|
31
|
+
|
|
32
|
+
const saved30d = (ctx.db.prepare(
|
|
33
|
+
`SELECT COUNT(*) as c FROM vault WHERE created_at >= datetime('now', '-30 days') AND kind NOT IN ('bucket', 'session', 'brief')`
|
|
34
|
+
).get() as any)?.c ?? 0;
|
|
35
|
+
|
|
36
|
+
const sessions30d = (ctx.db.prepare(
|
|
37
|
+
`SELECT COUNT(*) as c FROM vault WHERE created_at >= datetime('now', '-30 days') AND kind = 'session'`
|
|
38
|
+
).get() as any)?.c ?? 0;
|
|
39
|
+
|
|
40
|
+
const savesPerSession = sessions30d > 0
|
|
41
|
+
? (saved30d / sessions30d).toFixed(1)
|
|
42
|
+
: '0.0';
|
|
43
|
+
|
|
44
|
+
const recalls30d = (ctx.db.prepare(
|
|
45
|
+
`SELECT SUM(recall_count) as c FROM vault WHERE last_recalled_at >= datetime('now', '-30 days')`
|
|
46
|
+
).get() as any)?.c ?? 0;
|
|
47
|
+
|
|
48
|
+
const recallToSaveRatio = saved30d > 0
|
|
49
|
+
? `${(recalls30d / saved30d).toFixed(1)}:1`
|
|
50
|
+
: recalls30d > 0 ? `${recalls30d}:0 (all recall, no save)` : 'n/a';
|
|
51
|
+
|
|
52
|
+
return { saved7d, saved30d, sessions30d, savesPerSession, recalls30d, recallToSaveRatio };
|
|
53
|
+
} catch {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
17
58
|
export const name = 'context_status';
|
|
18
59
|
|
|
19
60
|
export const description =
|
|
@@ -114,6 +155,30 @@ export function handler(_args: Record<string, any>, ctx: LocalCtx): ToolResult {
|
|
|
114
155
|
}
|
|
115
156
|
}
|
|
116
157
|
|
|
158
|
+
const learningRate = computeLearningRate(ctx);
|
|
159
|
+
if (learningRate) {
|
|
160
|
+
lines.push(``, `### Learning Rate`);
|
|
161
|
+
lines.push(`| Metric | Value |`);
|
|
162
|
+
lines.push(`|---|---|`);
|
|
163
|
+
lines.push(`| **Saved (7 days)** | ${learningRate.saved7d} |`);
|
|
164
|
+
lines.push(`| **Saved (30 days)** | ${learningRate.saved30d} |`);
|
|
165
|
+
lines.push(`| **Sessions (30 days)** | ${learningRate.sessions30d} |`);
|
|
166
|
+
if (learningRate.sessions30d > 0) {
|
|
167
|
+
lines.push(`| **Saves per session** | ${learningRate.savesPerSession} |`);
|
|
168
|
+
}
|
|
169
|
+
if (learningRate.recalls30d > 0 || learningRate.saved30d > 0) {
|
|
170
|
+
lines.push(`| **Recalls (30 days)** | ${learningRate.recalls30d} |`);
|
|
171
|
+
lines.push(`| **Recall:save ratio** | ${learningRate.recallToSaveRatio} |`);
|
|
172
|
+
}
|
|
173
|
+
if (learningRate.saved30d === 0) {
|
|
174
|
+
lines.push('');
|
|
175
|
+
lines.push('_No entries saved in 30 days. Knowledge may be getting lost between sessions._');
|
|
176
|
+
} else if (learningRate.sessions30d > 0 && learningRate.savesPerSession === '0.0') {
|
|
177
|
+
lines.push('');
|
|
178
|
+
lines.push('_Very low save rate. Consider using `session_end` to capture learnings._');
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
117
182
|
if (status.stalePaths) {
|
|
118
183
|
lines.push(``);
|
|
119
184
|
lines.push(`### ⚠ Stale Paths`);
|
package/src/tools/get-context.ts
CHANGED
|
@@ -243,6 +243,22 @@ function checkStaleness(entry: any): { stale: boolean; stale_reason: string } |
|
|
|
243
243
|
return null;
|
|
244
244
|
}
|
|
245
245
|
|
|
246
|
+
const ERROR_TERMS = /\b(error|bug|fix|debug|failing|broken|crash|exception|issue|wrong|unexpected|stacktrace|traceback)\b/i;
|
|
247
|
+
|
|
248
|
+
function generateSaveHint(query: string | undefined, resultCount: number): string | null {
|
|
249
|
+
if (!query) return null;
|
|
250
|
+
|
|
251
|
+
if (ERROR_TERMS.test(query)) {
|
|
252
|
+
return 'If you solve this, save the root cause and fix as an insight.';
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (resultCount === 0) {
|
|
256
|
+
return 'No existing entries for this topic. If you learn something useful, save it for future sessions.';
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
|
|
246
262
|
export const name = 'get_context';
|
|
247
263
|
|
|
248
264
|
export const description =
|
|
@@ -743,6 +759,12 @@ export async function handler(
|
|
|
743
759
|
if (consolidationSuggestions.length > 0) {
|
|
744
760
|
meta.consolidation_suggestions = consolidationSuggestions;
|
|
745
761
|
}
|
|
762
|
+
|
|
763
|
+
const saveHint = generateSaveHint(query, filtered.length);
|
|
764
|
+
if (saveHint) {
|
|
765
|
+
meta.save_hint = saveHint;
|
|
766
|
+
}
|
|
767
|
+
|
|
746
768
|
result._meta = meta;
|
|
747
769
|
return result;
|
|
748
770
|
}
|
|
@@ -249,6 +249,27 @@ function validateSaveInput({
|
|
|
249
249
|
return null;
|
|
250
250
|
}
|
|
251
251
|
|
|
252
|
+
function enrichDecisionMeta(
|
|
253
|
+
mergedMeta: Record<string, any>,
|
|
254
|
+
title: string | undefined,
|
|
255
|
+
body: string | undefined
|
|
256
|
+
): void {
|
|
257
|
+
const combined = [title, body].filter(Boolean).join(' ').toLowerCase();
|
|
258
|
+
|
|
259
|
+
if (/\b(architect|infra|schema|database|api|stack|deploy|migration)\b/.test(combined)) {
|
|
260
|
+
mergedMeta.decision_type = 'architectural';
|
|
261
|
+
} else if (/\b(scope|feature|requirement|priority|roadmap|milestone)\b/.test(combined)) {
|
|
262
|
+
mergedMeta.decision_type = 'product';
|
|
263
|
+
} else if (/\b(convention|style|naming|lint|format|pattern)\b/.test(combined)) {
|
|
264
|
+
mergedMeta.decision_type = 'convention';
|
|
265
|
+
} else {
|
|
266
|
+
mergedMeta.decision_type = 'general';
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
mergedMeta.alternatives_noted = /\b(alternative|instead of|over|rather than|compared to|vs\.?|versus|option|considered)\b/i.test(combined);
|
|
270
|
+
mergedMeta.has_rationale = /\b(because|reason|rationale|why|trade.?off|benefit|downside|pro|con)\b/i.test(combined);
|
|
271
|
+
}
|
|
272
|
+
|
|
252
273
|
export const name = 'save_context';
|
|
253
274
|
|
|
254
275
|
export const description =
|
|
@@ -587,6 +608,10 @@ export async function handler(
|
|
|
587
608
|
mergedMeta.encoding_context = parsedCtx.text;
|
|
588
609
|
}
|
|
589
610
|
|
|
611
|
+
if (normalizedKind === 'decision') {
|
|
612
|
+
enrichDecisionMeta(mergedMeta, title, body);
|
|
613
|
+
}
|
|
614
|
+
|
|
590
615
|
const finalMeta = Object.keys(mergedMeta).length ? mergedMeta : undefined;
|
|
591
616
|
|
|
592
617
|
const effectiveTier = tier ?? defaultTierFor(normalizedKind);
|