kratos-memory 1.6.0 → 1.7.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/AGENTS.md +4 -0
- package/README.md +27 -5
- package/dist/cli/capture-handler.d.ts +5 -0
- package/dist/cli/capture-handler.d.ts.map +1 -1
- package/dist/cli/capture-handler.js +39 -0
- package/dist/cli/capture-handler.js.map +1 -1
- package/dist/cli/commands/ask.d.ts.map +1 -1
- package/dist/cli/commands/ask.js +126 -121
- package/dist/cli/commands/ask.js.map +1 -1
- package/dist/cli/commands/capture.d.ts.map +1 -1
- package/dist/cli/commands/capture.js +3 -0
- package/dist/cli/commands/capture.js.map +1 -1
- package/dist/cli/commands/context.d.ts +11 -0
- package/dist/cli/commands/context.d.ts.map +1 -0
- package/dist/cli/commands/context.js +119 -0
- package/dist/cli/commands/context.js.map +1 -0
- package/dist/cli/commands/hooks.d.ts.map +1 -1
- package/dist/cli/commands/hooks.js +149 -62
- package/dist/cli/commands/hooks.js.map +1 -1
- package/dist/cli/commands/migrate.d.ts.map +1 -1
- package/dist/cli/commands/migrate.js +111 -27
- package/dist/cli/commands/migrate.js.map +1 -1
- package/dist/cli/commands/summary.d.ts.map +1 -1
- package/dist/cli/commands/summary.js +44 -0
- package/dist/cli/commands/summary.js.map +1 -1
- package/dist/cli/index.js +12 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/memory-server/database.d.ts +0 -1
- package/dist/memory-server/database.d.ts.map +1 -1
- package/dist/memory-server/database.js +11 -35
- package/dist/memory-server/database.js.map +1 -1
- package/dist/project-manager.d.ts.map +1 -1
- package/dist/project-manager.js +6 -2
- package/dist/project-manager.js.map +1 -1
- package/package.json +1 -1
- package/dist/memory-server/concept-store-enhanced.d.ts +0 -88
- package/dist/memory-server/concept-store-enhanced.d.ts.map +0 -1
- package/dist/memory-server/concept-store-enhanced.js +0 -392
- package/dist/memory-server/concept-store-enhanced.js.map +0 -1
- package/dist/memory-server/concept-store.d.ts +0 -58
- package/dist/memory-server/concept-store.d.ts.map +0 -1
- package/dist/memory-server/concept-store.js +0 -329
- package/dist/memory-server/concept-store.js.map +0 -1
- package/dist/memory-server/context-broker.d.ts +0 -63
- package/dist/memory-server/context-broker.d.ts.map +0 -1
- package/dist/memory-server/context-broker.js +0 -340
- package/dist/memory-server/context-broker.js.map +0 -1
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { Output } from '../output.js';
|
|
2
|
+
const DEFAULT_BUDGET_TOKENS = 2000;
|
|
3
|
+
const CHARS_PER_TOKEN = 4;
|
|
4
|
+
/**
|
|
5
|
+
* Emit a compact, token-budgeted context block built from memories.
|
|
6
|
+
* Designed for SessionStart hook injection: stdout becomes session context.
|
|
7
|
+
* Plain text — chalk is bypassed so hook output stays clean.
|
|
8
|
+
*/
|
|
9
|
+
export async function contextCommand(ctx, opts) {
|
|
10
|
+
const budgetTokens = opts.budget ? parseInt(opts.budget, 10) : DEFAULT_BUDGET_TOKENS;
|
|
11
|
+
const budgetChars = budgetTokens * CHARS_PER_TOKEN;
|
|
12
|
+
const projectMemories = ctx.projectMemoryDb.getRecent({ k: 100 });
|
|
13
|
+
const globalMemories = ctx.globalMemoryDb.getRecent({ k: 50 });
|
|
14
|
+
const entries = [
|
|
15
|
+
...projectMemories.map(m => ({ memory: m, scope: 'project' })),
|
|
16
|
+
...globalMemories.map(m => ({ memory: m, scope: 'global' })),
|
|
17
|
+
];
|
|
18
|
+
if (entries.length === 0) {
|
|
19
|
+
if (opts.json) {
|
|
20
|
+
Output.json({ project: ctx.project.name, count: 0, sections: {} });
|
|
21
|
+
}
|
|
22
|
+
// No memories — emit nothing so hooks inject nothing
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const isPinned = (m) => m.tags.includes('__pinned');
|
|
26
|
+
const isAutoCapture = (m) => m.tags.includes('auto-capture');
|
|
27
|
+
const isSessionSummary = (m) => m.tags.includes('session-summary');
|
|
28
|
+
const pinned = entries.filter(e => isPinned(e.memory));
|
|
29
|
+
const important = entries.filter(e => !isPinned(e.memory) && !isAutoCapture(e.memory) && e.memory.importance >= 4).sort((a, b) => b.memory.importance - a.memory.importance || b.memory.created_at - a.memory.created_at);
|
|
30
|
+
const recent = entries.filter(e => !isPinned(e.memory) && !isAutoCapture(e.memory) && e.memory.importance < 4).sort((a, b) => b.memory.created_at - a.memory.created_at);
|
|
31
|
+
// Latest auto-captured session summary gets one line of its own
|
|
32
|
+
const lastSession = entries
|
|
33
|
+
.filter(e => isSessionSummary(e.memory))
|
|
34
|
+
.sort((a, b) => b.memory.created_at - a.memory.created_at)[0];
|
|
35
|
+
if (opts.json) {
|
|
36
|
+
Output.json({
|
|
37
|
+
project: ctx.project.name,
|
|
38
|
+
count: entries.length,
|
|
39
|
+
sections: {
|
|
40
|
+
pinned: pinned.map(serializeEntry),
|
|
41
|
+
important: important.map(serializeEntry),
|
|
42
|
+
recent: recent.slice(0, 15).map(serializeEntry),
|
|
43
|
+
last_session: lastSession ? serializeEntry(lastSession) : null,
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const lines = [];
|
|
49
|
+
lines.push(`# Kratos memory — ${ctx.project.name}`);
|
|
50
|
+
lines.push('Prior knowledge about this project, loaded from local memory. ' +
|
|
51
|
+
'Before solving a problem, check it here first: `kratos search "<topic>"`. ' +
|
|
52
|
+
'After fixing a bug or making a decision, save it: `kratos save "<what + why>" --tags <tags>`.');
|
|
53
|
+
lines.push('');
|
|
54
|
+
let used = lines.join('\n').length;
|
|
55
|
+
const sections = [
|
|
56
|
+
{ title: 'Pinned', items: pinned, max: pinned.length },
|
|
57
|
+
{ title: 'Decisions & fixes', items: important, max: 20 },
|
|
58
|
+
{ title: 'Recent', items: recent, max: 10 },
|
|
59
|
+
];
|
|
60
|
+
for (const section of sections) {
|
|
61
|
+
if (section.items.length === 0)
|
|
62
|
+
continue;
|
|
63
|
+
const sectionLines = [`## ${section.title}`];
|
|
64
|
+
let added = 0;
|
|
65
|
+
for (const entry of section.items.slice(0, section.max)) {
|
|
66
|
+
const line = formatEntry(entry);
|
|
67
|
+
if (used + sectionLines.join('\n').length + line.length > budgetChars)
|
|
68
|
+
break;
|
|
69
|
+
sectionLines.push(line);
|
|
70
|
+
added++;
|
|
71
|
+
}
|
|
72
|
+
if (added > 0) {
|
|
73
|
+
lines.push(...sectionLines, '');
|
|
74
|
+
used = lines.join('\n').length;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (lastSession && used + 200 < budgetChars) {
|
|
78
|
+
lines.push(`Last session: ${truncate(lastSession.memory.summary, 160)} (${relativeTime(lastSession.memory.created_at)})`);
|
|
79
|
+
}
|
|
80
|
+
console.log(lines.join('\n').trimEnd());
|
|
81
|
+
}
|
|
82
|
+
function formatEntry(entry) {
|
|
83
|
+
const m = entry.memory;
|
|
84
|
+
const scopeTag = entry.scope === 'global' ? ' [global]' : '';
|
|
85
|
+
const body = m.text && m.text !== m.summary
|
|
86
|
+
? `${m.summary}: ${truncate(m.text, 200)}`
|
|
87
|
+
: truncate(m.summary, 200);
|
|
88
|
+
return `- ${body}${scopeTag} (${relativeTime(m.created_at)})`;
|
|
89
|
+
}
|
|
90
|
+
function serializeEntry(entry) {
|
|
91
|
+
return {
|
|
92
|
+
id: entry.memory.id,
|
|
93
|
+
scope: entry.scope,
|
|
94
|
+
summary: entry.memory.summary,
|
|
95
|
+
text: entry.memory.text,
|
|
96
|
+
tags: entry.memory.tags,
|
|
97
|
+
importance: entry.memory.importance,
|
|
98
|
+
created_at: entry.memory.created_at,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
function truncate(text, max) {
|
|
102
|
+
const oneLine = text.replace(/\s+/g, ' ').trim();
|
|
103
|
+
return oneLine.length > max ? oneLine.substring(0, max - 1) + '…' : oneLine;
|
|
104
|
+
}
|
|
105
|
+
function relativeTime(timestamp) {
|
|
106
|
+
const diffMs = Date.now() - timestamp;
|
|
107
|
+
const minutes = Math.floor(diffMs / 60000);
|
|
108
|
+
if (minutes < 60)
|
|
109
|
+
return `${Math.max(minutes, 1)}m ago`;
|
|
110
|
+
const hours = Math.floor(minutes / 60);
|
|
111
|
+
if (hours < 24)
|
|
112
|
+
return `${hours}h ago`;
|
|
113
|
+
const days = Math.floor(hours / 24);
|
|
114
|
+
if (days < 30)
|
|
115
|
+
return `${days}d ago`;
|
|
116
|
+
const months = Math.floor(days / 30);
|
|
117
|
+
return `${months}mo ago`;
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../../../src/cli/commands/context.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAQtC,MAAM,qBAAqB,GAAG,IAAI,CAAC;AACnC,MAAM,eAAe,GAAG,CAAC,CAAC;AAE1B;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,GAAe,EAAE,IAGrD;IACC,MAAM,YAAY,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,qBAAqB,CAAC;IACrF,MAAM,WAAW,GAAG,YAAY,GAAG,eAAe,CAAC;IAEnD,MAAM,eAAe,GAAG,GAAG,CAAC,eAAe,CAAC,SAAS,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC;IAClE,MAAM,cAAc,GAAG,GAAG,CAAC,cAAc,CAAC,SAAS,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;IAE/D,MAAM,OAAO,GAAmB;QAC9B,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,KAAK,EAAE,SAAkB,EAAE,CAAC,CAAC;QACvE,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,KAAK,EAAE,QAAiB,EAAE,CAAC,CAAC;KACtE,CAAC;IAEF,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;YACd,MAAM,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,CAAC;QACrE,CAAC;QACD,qDAAqD;QACrD,OAAO;IACT,CAAC;IAED,MAAM,QAAQ,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;IAC5D,MAAM,aAAa,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC;IACrE,MAAM,gBAAgB,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,iBAAiB,CAAC,CAAC;IAE3E,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;IACvD,MAAM,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CACnC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,UAAU,IAAI,CAAC,CAC5E,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,GAAG,CAAC,CAAC,MAAM,CAAC,UAAU,IAAI,CAAC,CAAC,MAAM,CAAC,UAAU,GAAG,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IACzG,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAChC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,UAAU,GAAG,CAAC,CAC3E,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,GAAG,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IAE5D,gEAAgE;IAChE,MAAM,WAAW,GAAG,OAAO;SACxB,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,gBAAgB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;SACvC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,GAAG,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;IAEhE,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;QACd,MAAM,CAAC,IAAI,CAAC;YACV,OAAO,EAAE,GAAG,CAAC,OAAO,CAAC,IAAI;YACzB,KAAK,EAAE,OAAO,CAAC,MAAM;YACrB,QAAQ,EAAE;gBACR,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,cAAc,CAAC;gBAClC,SAAS,EAAE,SAAS,CAAC,GAAG,CAAC,cAAc,CAAC;gBACxC,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,cAAc,CAAC;gBAC/C,YAAY,EAAE,WAAW,CAAC,CAAC,CAAC,cAAc,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,IAAI;aAC/D;SACF,CAAC,CAAC;QACH,OAAO;IACT,CAAC;IAED,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,CAAC,IAAI,CAAC,qBAAqB,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;IACpD,KAAK,CAAC,IAAI,CAAC,gEAAgE;QACzE,4EAA4E;QAC5E,+FAA+F,CAAC,CAAC;IACnG,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAEf,IAAI,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC;IAEnC,MAAM,QAAQ,GAAiE;QAC7E,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE;QACtD,EAAE,KAAK,EAAE,mBAAmB,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,EAAE,EAAE;QACzD,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE,EAAE;KAC5C,CAAC;IAEF,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,IAAI,OAAO,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,SAAS;QACzC,MAAM,YAAY,GAAa,CAAC,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;QACvD,IAAI,KAAK,GAAG,CAAC,CAAC;QAEd,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;YACxD,MAAM,IAAI,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;YAChC,IAAI,IAAI,GAAG,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,GAAG,WAAW;gBAAE,MAAM;YAC7E,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACxB,KAAK,EAAE,CAAC;QACV,CAAC;QAED,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;YACd,KAAK,CAAC,IAAI,CAAC,GAAG,YAAY,EAAE,EAAE,CAAC,CAAC;YAChC,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC;QACjC,CAAC;IACH,CAAC;IAED,IAAI,WAAW,IAAI,IAAI,GAAG,GAAG,GAAG,WAAW,EAAE,CAAC;QAC5C,KAAK,CAAC,IAAI,CAAC,iBAAiB,QAAQ,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,EAAE,GAAG,CAAC,KAAK,YAAY,CAAC,WAAW,CAAC,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;IAC5H,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;AAC1C,CAAC;AAED,SAAS,WAAW,CAAC,KAAmB;IACtC,MAAM,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC;IACvB,MAAM,QAAQ,GAAG,KAAK,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC;IAC7D,MAAM,IAAI,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,OAAO;QACzC,CAAC,CAAC,GAAG,CAAC,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE;QAC1C,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;IAC7B,OAAO,KAAK,IAAI,GAAG,QAAQ,KAAK,YAAY,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC;AAChE,CAAC;AAED,SAAS,cAAc,CAAC,KAAmB;IACzC,OAAO;QACL,EAAE,EAAE,KAAK,CAAC,MAAM,CAAC,EAAE;QACnB,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC,OAAO;QAC7B,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,IAAI;QACvB,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,IAAI;QACvB,UAAU,EAAE,KAAK,CAAC,MAAM,CAAC,UAAU;QACnC,UAAU,EAAE,KAAK,CAAC,MAAM,CAAC,UAAU;KACpC,CAAC;AACJ,CAAC;AAED,SAAS,QAAQ,CAAC,IAAY,EAAE,GAAW;IACzC,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IACjD,OAAO,OAAO,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,EAAE,GAAG,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC;AAC9E,CAAC;AAED,SAAS,YAAY,CAAC,SAAiB;IACrC,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;IACtC,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,KAAK,CAAC,CAAC;IAC3C,IAAI,OAAO,GAAG,EAAE;QAAE,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,OAAO,CAAC;IACxD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,EAAE,CAAC,CAAC;IACvC,IAAI,KAAK,GAAG,EAAE;QAAE,OAAO,GAAG,KAAK,OAAO,CAAC;IACvC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,EAAE,CAAC,CAAC;IACpC,IAAI,IAAI,GAAG,EAAE;QAAE,OAAO,GAAG,IAAI,OAAO,CAAC;IACrC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,EAAE,CAAC,CAAC;IACrC,OAAO,GAAG,MAAM,QAAQ,CAAC;AAC3B,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/hooks.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/hooks.ts"],"names":[],"mappings":"AAqCA,wBAAsB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAehE"}
|
|
@@ -1,21 +1,36 @@
|
|
|
1
1
|
import { Output } from '../output.js';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import fs from 'fs-extra';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
4
|
+
// Claude Code hook schema: each event holds matcher groups, each group holds
|
|
5
|
+
// hooks of { type: 'command', command }. Flat { matcher, command } entries
|
|
6
|
+
// (written by kratos <= 1.6.x) are silently ignored by Claude Code — install
|
|
7
|
+
// migrates them to this format.
|
|
8
|
+
const KRATOS_HOOKS = {
|
|
9
|
+
SessionStart: [
|
|
10
|
+
{
|
|
11
|
+
hooks: [{ type: 'command', command: 'kratos context' }],
|
|
12
|
+
},
|
|
13
|
+
],
|
|
14
|
+
PostToolUse: [
|
|
15
|
+
{
|
|
16
|
+
matcher: 'Edit|Write|MultiEdit',
|
|
17
|
+
hooks: [{ type: 'command', command: 'kratos capture --event post-tool-use' }],
|
|
18
|
+
},
|
|
19
|
+
],
|
|
20
|
+
Stop: [
|
|
21
|
+
{
|
|
22
|
+
hooks: [{ type: 'command', command: 'kratos capture --event session-end' }],
|
|
23
|
+
},
|
|
24
|
+
],
|
|
18
25
|
};
|
|
26
|
+
const HOOK_EVENTS = Object.keys(KRATOS_HOOKS);
|
|
27
|
+
const GIT_HOOK_START = '# >>> kratos-memory post-commit >>>';
|
|
28
|
+
const GIT_HOOK_END = '# <<< kratos-memory post-commit <<<';
|
|
29
|
+
const GIT_HOOK_BLOCK = [
|
|
30
|
+
GIT_HOOK_START,
|
|
31
|
+
'command -v kratos >/dev/null 2>&1 && kratos capture --event git-commit >/dev/null 2>&1 || true',
|
|
32
|
+
GIT_HOOK_END,
|
|
33
|
+
].join('\n');
|
|
19
34
|
export async function hooksCommand(action) {
|
|
20
35
|
switch (action) {
|
|
21
36
|
case 'install':
|
|
@@ -32,12 +47,26 @@ export async function hooksCommand(action) {
|
|
|
32
47
|
process.exit(1);
|
|
33
48
|
}
|
|
34
49
|
}
|
|
50
|
+
/** Matches both current-format and legacy flat-format kratos entries. */
|
|
51
|
+
function isKratosEntry(entry) {
|
|
52
|
+
if (typeof entry?.command === 'string' && entry.command.includes('kratos '))
|
|
53
|
+
return true;
|
|
54
|
+
if (Array.isArray(entry?.hooks)) {
|
|
55
|
+
return entry.hooks.some((h) => typeof h?.command === 'string' && h.command.includes('kratos '));
|
|
56
|
+
}
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
/** Legacy flat entries ({ matcher, command }) that Claude Code never executed. */
|
|
60
|
+
function isLegacyEntry(entry) {
|
|
61
|
+
return typeof entry?.command === 'string' && !Array.isArray(entry?.hooks);
|
|
62
|
+
}
|
|
63
|
+
function settingsFilePath() {
|
|
64
|
+
return path.join(process.cwd(), '.claude', 'settings.local.json');
|
|
65
|
+
}
|
|
35
66
|
async function installHooks() {
|
|
36
|
-
const settingsPath =
|
|
37
|
-
|
|
38
|
-
await fs.ensureDir(settingsDir);
|
|
67
|
+
const settingsPath = settingsFilePath();
|
|
68
|
+
await fs.ensureDir(path.dirname(settingsPath));
|
|
39
69
|
let settings = {};
|
|
40
|
-
// Load existing settings if present
|
|
41
70
|
if (await fs.pathExists(settingsPath)) {
|
|
42
71
|
try {
|
|
43
72
|
settings = await fs.readJson(settingsPath);
|
|
@@ -46,35 +75,75 @@ async function installHooks() {
|
|
|
46
75
|
settings = {};
|
|
47
76
|
}
|
|
48
77
|
}
|
|
49
|
-
// Check if hooks already installed
|
|
50
|
-
if (settings.hooks?.PostToolUse || settings.hooks?.Stop) {
|
|
51
|
-
// Check if our hooks are already there
|
|
52
|
-
const existingPostTool = settings.hooks.PostToolUse || [];
|
|
53
|
-
const hasKratosHook = existingPostTool.some((h) => h.command?.includes('kratos capture'));
|
|
54
|
-
if (hasKratosHook) {
|
|
55
|
-
Output.warn('Kratos auto-capture hooks are already installed');
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
// Merge hooks into existing settings (don't overwrite other hooks)
|
|
60
78
|
if (!settings.hooks)
|
|
61
79
|
settings.hooks = {};
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
settings.hooks
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
settings.hooks
|
|
69
|
-
|
|
80
|
+
let migrated = 0;
|
|
81
|
+
for (const event of HOOK_EVENTS) {
|
|
82
|
+
const existing = settings.hooks[event] || [];
|
|
83
|
+
migrated += existing.filter((e) => isKratosEntry(e) && isLegacyEntry(e)).length;
|
|
84
|
+
// Drop any prior kratos entries (legacy or current), keep everything else
|
|
85
|
+
const others = existing.filter((e) => !isKratosEntry(e));
|
|
86
|
+
settings.hooks[event] = [...others, ...KRATOS_HOOKS[event]];
|
|
87
|
+
}
|
|
70
88
|
await fs.writeJson(settingsPath, settings, { spaces: 2 });
|
|
71
|
-
Output.success('
|
|
89
|
+
Output.success('Kratos hooks installed');
|
|
90
|
+
if (migrated > 0) {
|
|
91
|
+
Output.warn(`Migrated ${migrated} legacy hook entr${migrated === 1 ? 'y' : 'ies'} that Claude Code was ignoring`);
|
|
92
|
+
}
|
|
72
93
|
Output.dim(`Config written to: ${settingsPath}`);
|
|
73
|
-
Output.dim('
|
|
74
|
-
Output.dim('
|
|
94
|
+
Output.dim('SessionStart: injects project memory into every session (kratos context)');
|
|
95
|
+
Output.dim('PostToolUse: captures file edits (Edit/Write/MultiEdit)');
|
|
96
|
+
Output.dim('Stop: saves a session summary');
|
|
97
|
+
await installGitHook();
|
|
98
|
+
}
|
|
99
|
+
async function installGitHook() {
|
|
100
|
+
const hooksDir = path.join(process.cwd(), '.git', 'hooks');
|
|
101
|
+
if (!await fs.pathExists(path.join(process.cwd(), '.git'))) {
|
|
102
|
+
Output.dim('No .git directory — skipped git post-commit capture');
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
await fs.ensureDir(hooksDir);
|
|
106
|
+
const hookPath = path.join(hooksDir, 'post-commit');
|
|
107
|
+
if (await fs.pathExists(hookPath)) {
|
|
108
|
+
const existing = await fs.readFile(hookPath, 'utf8');
|
|
109
|
+
if (existing.includes(GIT_HOOK_START)) {
|
|
110
|
+
Output.dim('Git post-commit capture already installed');
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
// Preserve the user's existing hook — append our block
|
|
114
|
+
await fs.writeFile(hookPath, existing.trimEnd() + '\n\n' + GIT_HOOK_BLOCK + '\n');
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
await fs.writeFile(hookPath, '#!/bin/sh\n' + GIT_HOOK_BLOCK + '\n');
|
|
118
|
+
}
|
|
119
|
+
await fs.chmod(hookPath, 0o755);
|
|
120
|
+
Output.dim('Git: post-commit saves each commit as a memory');
|
|
121
|
+
}
|
|
122
|
+
async function uninstallGitHook() {
|
|
123
|
+
const hookPath = path.join(process.cwd(), '.git', 'hooks', 'post-commit');
|
|
124
|
+
if (!await fs.pathExists(hookPath))
|
|
125
|
+
return;
|
|
126
|
+
const existing = await fs.readFile(hookPath, 'utf8');
|
|
127
|
+
if (!existing.includes(GIT_HOOK_START))
|
|
128
|
+
return;
|
|
129
|
+
const startIdx = existing.indexOf(GIT_HOOK_START);
|
|
130
|
+
const endIdx = existing.indexOf(GIT_HOOK_END);
|
|
131
|
+
if (endIdx === -1)
|
|
132
|
+
return;
|
|
133
|
+
const cleaned = (existing.slice(0, startIdx) + existing.slice(endIdx + GIT_HOOK_END.length))
|
|
134
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
135
|
+
.trimEnd();
|
|
136
|
+
// Nothing left but the shebang — remove the file entirely
|
|
137
|
+
if (cleaned.replace(/^#!.*$/m, '').trim() === '') {
|
|
138
|
+
await fs.remove(hookPath);
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
await fs.writeFile(hookPath, cleaned + '\n');
|
|
142
|
+
}
|
|
143
|
+
Output.dim('Git post-commit capture removed');
|
|
75
144
|
}
|
|
76
145
|
async function uninstallHooks() {
|
|
77
|
-
const settingsPath =
|
|
146
|
+
const settingsPath = settingsFilePath();
|
|
78
147
|
if (!await fs.pathExists(settingsPath)) {
|
|
79
148
|
Output.warn('No hooks configuration found');
|
|
80
149
|
return;
|
|
@@ -85,48 +154,66 @@ async function uninstallHooks() {
|
|
|
85
154
|
Output.warn('No hooks found in settings');
|
|
86
155
|
return;
|
|
87
156
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
settings.hooks.
|
|
91
|
-
|
|
92
|
-
delete settings.hooks.PostToolUse;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
if (settings.hooks.Stop) {
|
|
96
|
-
settings.hooks.Stop = settings.hooks.Stop.filter((h) => !h.command?.includes('kratos capture'));
|
|
97
|
-
if (settings.hooks.Stop.length === 0) {
|
|
98
|
-
delete settings.hooks.Stop;
|
|
157
|
+
for (const event of Object.keys(settings.hooks)) {
|
|
158
|
+
settings.hooks[event] = (settings.hooks[event] || []).filter((e) => !isKratosEntry(e));
|
|
159
|
+
if (settings.hooks[event].length === 0) {
|
|
160
|
+
delete settings.hooks[event];
|
|
99
161
|
}
|
|
100
162
|
}
|
|
101
|
-
// Clean up empty hooks object
|
|
102
163
|
if (Object.keys(settings.hooks).length === 0) {
|
|
103
164
|
delete settings.hooks;
|
|
104
165
|
}
|
|
105
166
|
await fs.writeJson(settingsPath, settings, { spaces: 2 });
|
|
106
|
-
Output.success('Kratos
|
|
167
|
+
Output.success('Kratos hooks removed');
|
|
107
168
|
}
|
|
108
169
|
catch {
|
|
109
170
|
Output.error('Failed to read settings file');
|
|
110
171
|
process.exit(1);
|
|
111
172
|
}
|
|
173
|
+
await uninstallGitHook();
|
|
112
174
|
}
|
|
113
175
|
async function checkHooksStatus() {
|
|
114
|
-
const settingsPath =
|
|
176
|
+
const settingsPath = settingsFilePath();
|
|
115
177
|
if (!await fs.pathExists(settingsPath)) {
|
|
116
178
|
Output.info('No hooks installed (no settings file found)');
|
|
117
179
|
return;
|
|
118
180
|
}
|
|
119
181
|
try {
|
|
120
182
|
const settings = await fs.readJson(settingsPath);
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
183
|
+
let installed = 0;
|
|
184
|
+
let legacy = 0;
|
|
185
|
+
for (const event of HOOK_EVENTS) {
|
|
186
|
+
const entries = settings.hooks?.[event] || [];
|
|
187
|
+
for (const entry of entries) {
|
|
188
|
+
if (!isKratosEntry(entry))
|
|
189
|
+
continue;
|
|
190
|
+
if (isLegacyEntry(entry))
|
|
191
|
+
legacy++;
|
|
192
|
+
else
|
|
193
|
+
installed++;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
if (installed === 0 && legacy === 0) {
|
|
124
197
|
Output.info('Kratos hooks are NOT installed');
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
if (legacy > 0) {
|
|
201
|
+
Output.warn(`${legacy} kratos hook entr${legacy === 1 ? 'y is' : 'ies are'} in a legacy format Claude Code ignores — run: kratos hooks install`);
|
|
125
202
|
}
|
|
126
|
-
|
|
127
|
-
Output.success(
|
|
128
|
-
|
|
129
|
-
|
|
203
|
+
if (installed > 0) {
|
|
204
|
+
Output.success(`Kratos hooks are installed (${installed} active entr${installed === 1 ? 'y' : 'ies'})`);
|
|
205
|
+
for (const event of HOOK_EVENTS) {
|
|
206
|
+
const count = (settings.hooks?.[event] || []).filter((e) => isKratosEntry(e) && !isLegacyEntry(e)).length;
|
|
207
|
+
if (count > 0)
|
|
208
|
+
Output.dim(`${event}: ${count}`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
const gitHookPath = path.join(process.cwd(), '.git', 'hooks', 'post-commit');
|
|
212
|
+
if (await fs.pathExists(gitHookPath)) {
|
|
213
|
+
const content = await fs.readFile(gitHookPath, 'utf8');
|
|
214
|
+
if (content.includes(GIT_HOOK_START)) {
|
|
215
|
+
Output.dim('Git post-commit: installed');
|
|
216
|
+
}
|
|
130
217
|
}
|
|
131
218
|
}
|
|
132
219
|
catch {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hooks.js","sourceRoot":"","sources":["../../../src/cli/commands/hooks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACtC,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,MAAM,UAAU,CAAC;AAE1B,MAAM,
|
|
1
|
+
{"version":3,"file":"hooks.js","sourceRoot":"","sources":["../../../src/cli/commands/hooks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACtC,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,MAAM,UAAU,CAAC;AAE1B,6EAA6E;AAC7E,2EAA2E;AAC3E,6EAA6E;AAC7E,gCAAgC;AAChC,MAAM,YAAY,GAA0B;IAC1C,YAAY,EAAE;QACZ;YACE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,gBAAgB,EAAE,CAAC;SACxD;KACF;IACD,WAAW,EAAE;QACX;YACE,OAAO,EAAE,sBAAsB;YAC/B,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,sCAAsC,EAAE,CAAC;SAC9E;KACF;IACD,IAAI,EAAE;QACJ;YACE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,oCAAoC,EAAE,CAAC;SAC5E;KACF;CACF,CAAC;AAEF,MAAM,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;AAE9C,MAAM,cAAc,GAAG,qCAAqC,CAAC;AAC7D,MAAM,YAAY,GAAG,qCAAqC,CAAC;AAC3D,MAAM,cAAc,GAAG;IACrB,cAAc;IACd,gGAAgG;IAChG,YAAY;CACb,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAEb,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,MAAc;IAC/C,QAAQ,MAAM,EAAE,CAAC;QACf,KAAK,SAAS;YACZ,MAAM,YAAY,EAAE,CAAC;YACrB,MAAM;QACR,KAAK,WAAW;YACd,MAAM,cAAc,EAAE,CAAC;YACvB,MAAM;QACR,KAAK,QAAQ;YACX,MAAM,gBAAgB,EAAE,CAAC;YACzB,MAAM;QACR;YACE,MAAM,CAAC,KAAK,CAAC,mBAAmB,MAAM,mCAAmC,CAAC,CAAC;YAC3E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;AACH,CAAC;AAED,yEAAyE;AACzE,SAAS,aAAa,CAAC,KAAU;IAC/B,IAAI,OAAO,KAAK,EAAE,OAAO,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC;QAAE,OAAO,IAAI,CAAC;IACzF,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,CAAC;QAChC,OAAO,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,OAAO,CAAC,EAAE,OAAO,KAAK,QAAQ,IAAI,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC;IACvG,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,kFAAkF;AAClF,SAAS,aAAa,CAAC,KAAU;IAC/B,OAAO,OAAO,KAAK,EAAE,OAAO,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;AAC5E,CAAC;AAED,SAAS,gBAAgB;IACvB,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,qBAAqB,CAAC,CAAC;AACpE,CAAC;AAED,KAAK,UAAU,YAAY;IACzB,MAAM,YAAY,GAAG,gBAAgB,EAAE,CAAC;IACxC,MAAM,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC;IAE/C,IAAI,QAAQ,GAAQ,EAAE,CAAC;IACvB,IAAI,MAAM,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;QACtC,IAAI,CAAC;YACH,QAAQ,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;QAC7C,CAAC;QAAC,MAAM,CAAC;YACP,QAAQ,GAAG,EAAE,CAAC;QAChB,CAAC;IACH,CAAC;IAED,IAAI,CAAC,QAAQ,CAAC,KAAK;QAAE,QAAQ,CAAC,KAAK,GAAG,EAAE,CAAC;IAEzC,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,KAAK,MAAM,KAAK,IAAI,WAAW,EAAE,CAAC;QAChC,MAAM,QAAQ,GAAU,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;QACpD,QAAQ,IAAI,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;QAChF,0EAA0E;QAC1E,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC;QACzD,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,MAAM,EAAE,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC;IAC9D,CAAC;IAED,MAAM,EAAE,CAAC,SAAS,CAAC,YAAY,EAAE,QAAQ,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;IAE1D,MAAM,CAAC,OAAO,CAAC,wBAAwB,CAAC,CAAC;IACzC,IAAI,QAAQ,GAAG,CAAC,EAAE,CAAC;QACjB,MAAM,CAAC,IAAI,CAAC,YAAY,QAAQ,oBAAoB,QAAQ,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,gCAAgC,CAAC,CAAC;IACpH,CAAC;IACD,MAAM,CAAC,GAAG,CAAC,sBAAsB,YAAY,EAAE,CAAC,CAAC;IACjD,MAAM,CAAC,GAAG,CAAC,0EAA0E,CAAC,CAAC;IACvF,MAAM,CAAC,GAAG,CAAC,0DAA0D,CAAC,CAAC;IACvE,MAAM,CAAC,GAAG,CAAC,uCAAuC,CAAC,CAAC;IAEpD,MAAM,cAAc,EAAE,CAAC;AACzB,CAAC;AAED,KAAK,UAAU,cAAc;IAC3B,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;IAC3D,IAAI,CAAC,MAAM,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC;QAC3D,MAAM,CAAC,GAAG,CAAC,qDAAqD,CAAC,CAAC;QAClE,OAAO;IACT,CAAC;IAED,MAAM,EAAE,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;IAC7B,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,aAAa,CAAC,CAAC;IAEpD,IAAI,MAAM,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAClC,MAAM,QAAQ,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QACrD,IAAI,QAAQ,CAAC,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC;YACtC,MAAM,CAAC,GAAG,CAAC,2CAA2C,CAAC,CAAC;YACxD,OAAO;QACT,CAAC;QACD,uDAAuD;QACvD,MAAM,EAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,QAAQ,CAAC,OAAO,EAAE,GAAG,MAAM,GAAG,cAAc,GAAG,IAAI,CAAC,CAAC;IACpF,CAAC;SAAM,CAAC;QACN,MAAM,EAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,aAAa,GAAG,cAAc,GAAG,IAAI,CAAC,CAAC;IACtE,CAAC;IACD,MAAM,EAAE,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IAChC,MAAM,CAAC,GAAG,CAAC,yDAAyD,CAAC,CAAC;AACxE,CAAC;AAED,KAAK,UAAU,gBAAgB;IAC7B,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,CAAC,CAAC;IAC1E,IAAI,CAAC,MAAM,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO;IAE3C,MAAM,QAAQ,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IACrD,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,cAAc,CAAC;QAAE,OAAO;IAE/C,MAAM,QAAQ,GAAG,QAAQ,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;IAClD,MAAM,MAAM,GAAG,QAAQ,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;IAC9C,IAAI,MAAM,KAAK,CAAC,CAAC;QAAE,OAAO;IAE1B,MAAM,OAAO,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,MAAM,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;SACzF,OAAO,CAAC,SAAS,EAAE,MAAM,CAAC;SAC1B,OAAO,EAAE,CAAC;IAEb,0DAA0D;IAC1D,IAAI,OAAO,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QACjD,MAAM,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IAC5B,CAAC;SAAM,CAAC;QACN,MAAM,EAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI,CAAC,CAAC;IAC/C,CAAC;IACD,MAAM,CAAC,GAAG,CAAC,iCAAiC,CAAC,CAAC;AAChD,CAAC;AAED,KAAK,UAAU,cAAc;IAC3B,MAAM,YAAY,GAAG,gBAAgB,EAAE,CAAC;IAExC,IAAI,CAAC,MAAM,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;QACvC,MAAM,CAAC,IAAI,CAAC,8BAA8B,CAAC,CAAC;QAC5C,OAAO;IACT,CAAC;IAED,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;QAEjD,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;YACpB,MAAM,CAAC,IAAI,CAAC,4BAA4B,CAAC,CAAC;YAC1C,OAAO;QACT,CAAC;QAED,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;YAChD,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC;YAC5F,IAAI,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACvC,OAAO,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YAC/B,CAAC;QACH,CAAC;QAED,IAAI,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC7C,OAAO,QAAQ,CAAC,KAAK,CAAC;QACxB,CAAC;QAED,MAAM,EAAE,CAAC,SAAS,CAAC,YAAY,EAAE,QAAQ,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;QAE1D,MAAM,CAAC,OAAO,CAAC,sBAAsB,CAAC,CAAC;IACzC,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,CAAC,KAAK,CAAC,8BAA8B,CAAC,CAAC;QAC7C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,gBAAgB,EAAE,CAAC;AAC3B,CAAC;AAED,KAAK,UAAU,gBAAgB;IAC7B,MAAM,YAAY,GAAG,gBAAgB,EAAE,CAAC;IAExC,IAAI,CAAC,MAAM,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;QACvC,MAAM,CAAC,IAAI,CAAC,6CAA6C,CAAC,CAAC;QAC3D,OAAO;IACT,CAAC;IAED,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;QACjD,IAAI,SAAS,GAAG,CAAC,CAAC;QAClB,IAAI,MAAM,GAAG,CAAC,CAAC;QAEf,KAAK,MAAM,KAAK,IAAI,WAAW,EAAE,CAAC;YAChC,MAAM,OAAO,GAAU,QAAQ,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;YACrD,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;gBAC5B,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC;oBAAE,SAAS;gBACpC,IAAI,aAAa,CAAC,KAAK,CAAC;oBAAE,MAAM,EAAE,CAAC;;oBAC9B,SAAS,EAAE,CAAC;YACnB,CAAC;QACH,CAAC;QAED,IAAI,SAAS,KAAK,CAAC,IAAI,MAAM,KAAK,CAAC,EAAE,CAAC;YACpC,MAAM,CAAC,IAAI,CAAC,gCAAgC,CAAC,CAAC;YAC9C,OAAO;QACT,CAAC;QAED,IAAI,MAAM,GAAG,CAAC,EAAE,CAAC;YACf,MAAM,CAAC,IAAI,CAAC,GAAG,MAAM,oBAAoB,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,qEAAqE,CAAC,CAAC;QACnJ,CAAC;QACD,IAAI,SAAS,GAAG,CAAC,EAAE,CAAC;YAClB,MAAM,CAAC,OAAO,CAAC,+BAA+B,SAAS,eAAe,SAAS,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC;YACxG,KAAK,MAAM,KAAK,IAAI,WAAW,EAAE,CAAC;gBAChC,MAAM,KAAK,GAAG,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAClD,CAAC,CAAM,EAAE,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,CAClD,CAAC,MAAM,CAAC;gBACT,IAAI,KAAK,GAAG,CAAC;oBAAE,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,KAAK,KAAK,EAAE,CAAC,CAAC;YAClD,CAAC;QACH,CAAC;QAED,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,CAAC,CAAC;QAC7E,IAAI,MAAM,EAAE,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;YACrC,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;YACvD,IAAI,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC;gBACrC,MAAM,CAAC,GAAG,CAAC,4BAA4B,CAAC,CAAC;YAC3C,CAAC;QACH,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,CAAC,KAAK,CAAC,8BAA8B,CAAC,CAAC;IAC/C,CAAC;AACH,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migrate.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/migrate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"migrate.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/migrate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAM7C,wBAAsB,cAAc,CAAC,GAAG,EAAE,UAAU,EAAE,IAAI,EAAE;IAC1D,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,GAAG,OAAO,CAAC,IAAI,CAAC,CA6JhB"}
|
|
@@ -1,56 +1,140 @@
|
|
|
1
1
|
import { Output } from '../output.js';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import fs from 'fs
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import crypto from 'crypto';
|
|
4
5
|
export async function migrateCommand(ctx, opts) {
|
|
5
6
|
const kratosHome = opts.from || path.join(process.env.HOME || process.env.USERPROFILE || '', '.kratos');
|
|
6
7
|
const projectsDir = path.join(kratosHome, 'projects');
|
|
7
8
|
Output.header('Kratos Data Migration');
|
|
8
|
-
if (!
|
|
9
|
+
if (!fs.existsSync(projectsDir)) {
|
|
9
10
|
Output.warn('No existing data found at: ' + projectsDir);
|
|
10
11
|
Output.dim('If your data is elsewhere, use --from <path>');
|
|
11
12
|
return;
|
|
12
13
|
}
|
|
13
|
-
const entries =
|
|
14
|
+
const entries = fs.readdirSync(projectsDir);
|
|
14
15
|
let totalMemories = 0;
|
|
15
16
|
let totalProjects = 0;
|
|
17
|
+
let mergedCount = 0;
|
|
18
|
+
// Build map: canonical (lowercase) project ID → project root
|
|
19
|
+
// Then find orphans: projects whose ID doesn't match the canonical hash of their root
|
|
20
|
+
const Database = (await import('better-sqlite3')).default;
|
|
21
|
+
const orphans = [];
|
|
16
22
|
for (const entry of entries) {
|
|
17
|
-
const dbPath = path.join(projectsDir, entry, 'databases', 'memories.db');
|
|
18
23
|
const projectJsonPath = path.join(projectsDir, entry, 'project.json');
|
|
19
|
-
|
|
24
|
+
const dbPath = path.join(projectsDir, entry, 'databases', 'memories.db');
|
|
25
|
+
if (!fs.existsSync(projectJsonPath))
|
|
20
26
|
continue;
|
|
21
|
-
|
|
22
|
-
// Read project metadata
|
|
23
|
-
let projectName = entry;
|
|
27
|
+
let meta;
|
|
24
28
|
try {
|
|
25
|
-
|
|
26
|
-
const meta = await fs.readJson(projectJsonPath);
|
|
27
|
-
projectName = meta.name || entry;
|
|
28
|
-
}
|
|
29
|
+
meta = JSON.parse(fs.readFileSync(projectJsonPath, 'utf-8'));
|
|
29
30
|
}
|
|
30
31
|
catch {
|
|
31
|
-
|
|
32
|
+
continue;
|
|
32
33
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
34
|
+
const root = meta.root;
|
|
35
|
+
if (!root)
|
|
36
|
+
continue;
|
|
37
|
+
// Compute canonical ID (lowercase path hash — the correct one)
|
|
38
|
+
const canonicalHash = crypto.createHash('sha256')
|
|
39
|
+
.update(root.toLowerCase())
|
|
40
|
+
.digest('hex');
|
|
41
|
+
const canonicalId = `proj_${canonicalHash.substring(0, 12)}`;
|
|
42
|
+
totalProjects++;
|
|
43
|
+
// Count memories
|
|
44
|
+
if (fs.existsSync(dbPath)) {
|
|
45
|
+
try {
|
|
46
|
+
const db = new Database(dbPath, { readonly: true });
|
|
47
|
+
const row = db.prepare('SELECT COUNT(*) as count FROM memories').get();
|
|
48
|
+
totalMemories += row.count;
|
|
49
|
+
db.close();
|
|
50
|
+
}
|
|
51
|
+
catch { /* skip */ }
|
|
52
|
+
}
|
|
53
|
+
// If this project's folder name doesn't match canonical ID, it's an orphan
|
|
54
|
+
if (entry !== canonicalId && fs.existsSync(dbPath)) {
|
|
55
|
+
orphans.push({ dir: entry, name: meta.name || entry, root, canonicalId });
|
|
41
56
|
}
|
|
42
|
-
|
|
43
|
-
|
|
57
|
+
}
|
|
58
|
+
if (orphans.length > 0) {
|
|
59
|
+
Output.header('Merging Orphaned Projects');
|
|
60
|
+
Output.dim(`Found ${orphans.length} orphaned project(s) from case-sensitive ID change`);
|
|
61
|
+
console.log('');
|
|
62
|
+
for (const orphan of orphans) {
|
|
63
|
+
const orphanDbPath = path.join(projectsDir, orphan.dir, 'databases', 'memories.db');
|
|
64
|
+
const canonicalDir = path.join(projectsDir, orphan.canonicalId);
|
|
65
|
+
const canonicalDbDir = path.join(canonicalDir, 'databases');
|
|
66
|
+
const canonicalDbPath = path.join(canonicalDbDir, 'memories.db');
|
|
67
|
+
try {
|
|
68
|
+
const srcDb = new Database(orphanDbPath, { readonly: true });
|
|
69
|
+
const memories = srcDb.prepare('SELECT * FROM memories').all();
|
|
70
|
+
srcDb.close();
|
|
71
|
+
if (memories.length === 0) {
|
|
72
|
+
Output.dim(` ${orphan.name} (${orphan.dir}): empty, skipping`);
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
// Ensure canonical project directory exists
|
|
76
|
+
fs.mkdirSync(canonicalDbDir, { recursive: true });
|
|
77
|
+
// Write project.json if missing
|
|
78
|
+
const canonicalPjPath = path.join(canonicalDir, 'project.json');
|
|
79
|
+
if (!fs.existsSync(canonicalPjPath)) {
|
|
80
|
+
fs.writeFileSync(canonicalPjPath, JSON.stringify({
|
|
81
|
+
id: orphan.canonicalId,
|
|
82
|
+
name: orphan.name,
|
|
83
|
+
root: orphan.root,
|
|
84
|
+
createdAt: new Date(),
|
|
85
|
+
lastAccessed: new Date(),
|
|
86
|
+
}, null, 2));
|
|
87
|
+
}
|
|
88
|
+
// Open or create canonical DB and copy memories
|
|
89
|
+
const dstDb = new Database(canonicalDbPath);
|
|
90
|
+
dstDb.pragma('journal_mode = WAL');
|
|
91
|
+
// Ensure schema exists
|
|
92
|
+
dstDb.exec(`
|
|
93
|
+
CREATE TABLE IF NOT EXISTS memories (
|
|
94
|
+
id TEXT PRIMARY KEY,
|
|
95
|
+
project_id TEXT NOT NULL,
|
|
96
|
+
summary TEXT NOT NULL,
|
|
97
|
+
text TEXT NOT NULL,
|
|
98
|
+
tags TEXT DEFAULT '[]',
|
|
99
|
+
paths TEXT DEFAULT '[]',
|
|
100
|
+
importance INTEGER DEFAULT 3,
|
|
101
|
+
created_at INTEGER NOT NULL,
|
|
102
|
+
updated_at INTEGER NOT NULL,
|
|
103
|
+
ttl INTEGER,
|
|
104
|
+
expires_at INTEGER,
|
|
105
|
+
dedupe_hash TEXT
|
|
106
|
+
)
|
|
107
|
+
`);
|
|
108
|
+
const insert = dstDb.prepare(`
|
|
109
|
+
INSERT OR IGNORE INTO memories (id, project_id, summary, text, tags, paths, importance, created_at, updated_at, ttl, expires_at, dedupe_hash)
|
|
110
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
111
|
+
`);
|
|
112
|
+
let copied = 0;
|
|
113
|
+
for (const mem of memories) {
|
|
114
|
+
const result = insert.run(mem.id, orphan.canonicalId, mem.summary, mem.text, mem.tags, mem.paths, mem.importance, mem.created_at, mem.updated_at, mem.ttl, mem.expires_at, mem.dedupe_hash);
|
|
115
|
+
if (result.changes > 0)
|
|
116
|
+
copied++;
|
|
117
|
+
}
|
|
118
|
+
dstDb.close();
|
|
119
|
+
mergedCount += copied;
|
|
120
|
+
Output.success(` ${orphan.name}: merged ${copied}/${memories.length} memories → ${orphan.canonicalId}`);
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
Output.warn(` ${orphan.name}: merge failed — ${error instanceof Error ? error.message : 'unknown error'}`);
|
|
124
|
+
}
|
|
44
125
|
}
|
|
45
126
|
}
|
|
46
127
|
console.log('');
|
|
47
128
|
Output.header('Migration Summary');
|
|
48
129
|
Output.info(`Projects found: ${totalProjects}`);
|
|
49
130
|
Output.info(`Total memories: ${totalMemories}`);
|
|
131
|
+
if (mergedCount > 0) {
|
|
132
|
+
Output.success(`Merged memories: ${mergedCount}`);
|
|
133
|
+
}
|
|
50
134
|
Output.info(`Data location: ${projectsDir}`);
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
135
|
+
if (orphans.length === 0) {
|
|
136
|
+
console.log('');
|
|
137
|
+
Output.success('All projects are using canonical IDs. No migration needed.');
|
|
138
|
+
}
|
|
55
139
|
}
|
|
56
140
|
//# sourceMappingURL=migrate.js.map
|